> ## Documentation Index
> Fetch the complete documentation index at: https://paymanai.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Security rules

> What the SDK enforces and what stays yours.

```ts theme={null}
const problems = await payman.doctor();   // Diagnostic[]: level, code, message
if (problems.some((p) => p.level === "error")) process.exit(1);
```

Most of the rules below hold by construction inside the SDK. `doctor()` checks your registered redirect URI and the registration clock; the session cookie stays yours, with a reminder printed at mount. Run `doctor()` in CI.

## The rules

| Rule                                     | Detail                                                                                                                                | Held by  |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `state` is the CSRF check                | A CSPRNG value stored server-side against the signed-in session, single use, verified before `code` or `error` is read.               | SDK      |
| Neither secret reaches the browser       | The app key and the grant token are server-side only. The constructor refuses to run where `window` exists.                           | SDK      |
| Redirect URIs match exactly              | Same bytes, port included. Localhost, staging and production are three separate entries in the app's redirect-URI list.               | Platform |
| Codes are single use and live 60 seconds | A failed exchange burns the code too. Start the flow again, never retry.                                                              | Platform |
| The session cookie is `SameSite=Lax`     | `Strict` drops the cookie on the consent return, and only in production.                                                              | You      |
| No approval code field, ever             | The customer approves on Payman's page with an emailed one-time code. The SDK has no `approve()` and no type that can carry the code. | Platform |
| 401 is a state, not an outage            | A dead grant comes back as the `needs_connection` result, button included. Nothing to page anyone about.                              | SDK      |

<Warning>
  Never render an input for the approval code. It belongs on Payman's page, in the account owner's session; an app that collects it is phishing its own customers.
</Warning>

## What your app can see

A deployment id: the one deployment of the customer's own agent that they connected. That is the entire list.

No name or publisher for the customer's agent, no providers, no balances, no credentials. The grant is a pointer into Payman, not a copy of anything. Conversations file under the grant, so no other app connected to the same deployment can read yours.

## An approval ref is not a credential

A ref presented without your grant answers 404, and holding one buys nothing: approving takes the owner's own Payman session plus the code emailed to them. That is why a ref is safe to hand to a model, and a URL or token is not.
