> ## Documentation Index
> Fetch the complete documentation index at: https://www.sapientpriors.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> The API key, how to name one of your own customers on a request, who owns what, and what happens when a request is not allowed.

This page explains how Priors knows who is calling. After you read it, you know what your key identifies, how to act for one of your customers, who owns the things you create, and what `400`, `401`, and `404` mean.

Every request carries an API key, and the key identifies your **organization**, not a person. Your organization owns the agents, holds the credit, and is billed.

```http theme={null}
Authorization: Bearer sp_7Kq2mN4pXr8wLz1c_a3f9…
X-SP-End-User: customer-4471-person-7
```

Both headers are literal strings. There is no signing, no timestamp, and no nonce, so there is nothing for a client library to compute and no clock skew to handle.

Every key your organization holds reaches the same things. That is what makes rotation uneventful: mint the new key, deploy it, then revoke the old one. Revocation takes effect on the very next request.

## Get a key

An administrator of your organization mints a key in the Priors application, under **Settings → API keys**. There is no endpoint for it. A key reaches your whole organization, so issuing one is an act of administration, and a key that could mint another key would turn one leak into an unbounded supply.

The key looks like this:

```
sp_7Kq2mN4pXr8wLz1c_a3f9…
└┬┘ └──────┬──────┘ └─┬─┘
 │         │          └─ the secret. Shown once, stored only as a hash, never recoverable.
 │         └──────────── the key id. Safe to log, and what you quote to support.
 └────────────────────── a fixed prefix, so a leaked key is easy to search for.
```

**The secret is shown exactly once.** Priors keeps only a hash of it, so nobody can read it back. Put it straight into your secret manager. A lost secret is replaced, never recovered.

Send the whole string as the bearer token. It is not a fragment to join with the key id.

Your organization can hold 5 live keys at a time.

## Keys expire

Every key is issued with a deadline, and today that is one year. A key stops verifying the moment it passes, and nothing reaches your backend to say so. An expired key returns the same `401` as an unknown one, so the first sign of a missed rotation is your own traffic failing.

**Settings → API keys** shows the expiry date of every key, and marks a key as expiring for the last month before its date. That page is the only warning there is. Read the date when you store the secret, and put the rotation in your calendar then.

## Name one of your customers

`X-SP-End-User` is your own identifier for the person a request acts for: an account number, a row id, anything stable on your side. Priors does not store the value. It hashes the value together with your organization id, so two clients that send the same string are still two different people.

| You send | You are | You reach |
| - | - | - |
| The header, with a value | That one person | The threads that person created, and no others |
| No header at all | Your organization | Every thread on every agent you own |
| The header, empty or whitespace | Refused | `400`. It is never read as "no header" |

The last row is deliberate. "No header" means your whole organization, which is the widest reach a key has. An unmapped customer that renders as `""` must not quietly be answered with more access than you asked for.

Learned memory is shared across an agent either way. Only the thread list narrows.

Send the header on **every** call for that person, not only when you create the thread. It also decides which rate-limit window the call spends from. See [Limits](/docs/limits).

## Keep the key on your server

**A key belongs on your backend. It must never reach a browser or a mobile app.** A key in front-end code is in your bundle, and anyone who loads the page can read it, whether or not the request itself succeeds.

Two things stop that happening by accident:

* Priors sends no CORS headers, so a browser blocks the call before it leaves the page.
* A request that carries a key and a `Sec-Fetch-Site` header is refused with `400`. Only a browser sets that header, and page JavaScript can neither remove it nor forge it.

Priors does **not** check `Origin` or `Referer`. Gateways add `Origin` to ordinary server-to-server calls, and HTTP libraries add `Referer` across redirects. If your own infrastructure adds either header, nothing here minds.

If you need your own front end to call Priors directly, contact us before you build around it.

## Ownership

Every agent, thread, observation, and file belongs to the organization that created it. No other organization can see it. When another caller asks for one of your ids, Priors returns `404`, not `403`. An id never reveals that something exists.

`X-SP-End-User` only ever narrows that. It never widens it.

## Errors

| Status | `code` | Cause | What to do |
| - | - | - | - |
| `400` | `invalid_request` | `X-SP-End-User` sent empty, or longer than 255 characters. A key sent on a request that carries `Sec-Fetch-Site`, which means the key reached a browser. | Read `error`. Correct the request. |
| `401` | `unauthorized` | The key is missing, malformed, unknown, revoked, or expired. All five answer alike on purpose. | Check the key. Quote the key id when you contact us: that is how we tell the five apart for you. |
| `404` | `not_found` | The id does not exist, or your key does not reach it. | Check the id, and check that the agent belongs to your organization. |

## Check what your key resolves to

`GET /v1/session` confirms which organization your key resolves to, and returns the upload limits your organization is held to.

```json theme={null}
{
  "authenticated": true,
  "user": {"id": "org_01J...", "email": "", "display_name": "partner key"},
  "organization_id": "org_01J...",
  "organization_name": "Weedu",
  "max_upload_bytes": 10485760,
  "max_image_bytes": 10485760,
  "max_images_per_turn": 10,
  "max_image_bytes_per_turn": 33554432
}
```

`user.id` is your organization when you send no `X-SP-End-User`, and the derived `eu_…` id for that person when you do. `email` is empty and `display_name` reads `partner key`, because a key names no person.

Read the `max_*` fields once when your client starts, and check a file against them before you upload it, rather than hardcoding the numbers.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.