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

# Authentication

> API keys, scopes, and what happens when either is wrong.

Every request carries your secret key in the `X-API-Key` header. There is no other
authentication method for merchant traffic.

```bash theme={null}
curl "$SPENDIN_BASE_URL/v1/payouts" \
  -H "X-API-Key: sk_live_..."
```

Keys resolve to exactly one account. Everything you can read or write is scoped to
it — there is no way to reach another account's data with your key, and no account
identifier is ever passed in a request body. Another account's records read as
`404`, never `403`.

<Warning>
  Your key is shown once, when it is issued. We store only a hash and a short
  prefix used to look it up. A lost key cannot be recovered — it has to be revoked
  and replaced.
</Warning>

## Scopes

Keys carry a list of scopes, and every endpoint requires one. Request the narrowest
set that does the job.

| Scope                       | Grants                                                     |
| --------------------------- | ---------------------------------------------------------- |
| `payouts:write`             | Create payouts                                             |
| `payouts:read`              | Read and list payouts                                      |
| `payment_instructions:read` | Read the active payment instruction for a payout           |
| `rates:read`                | Indicative rates, and creating, reading, accepting quotes  |
| `identity:enquiry`          | List institutions and resolve account names                |
| `inflows:read`              | Read and list your settlements                             |
| `outflows:read`             | Read dispatch attempts against a payout                    |
| `refunds:manage`            | Register and replace refund destinations                   |
| `refunds:read`              | Read refund destinations and refund history                |
| `webhooks:manage`           | Configure the endpoint, rotate the secret, list deliveries |

Each operation in the [API Reference](/api-reference/introduction) declares its
required scope as `x-required-scopes`.

<Note>
  A minimum viable payout integration needs `payouts:write`, `payouts:read`,
  `payment_instructions:read`, `identity:enquiry`, and `refunds:manage` — the last
  one because a refund destination is a precondition of creating a payout at all.
</Note>

## Failure responses

All of these use the standard [error envelope](/guides/errors).

| Status | Code                      | Cause                                            |
| ------ | ------------------------- | ------------------------------------------------ |
| `401`  | `MISSING_API_KEY`         | No `X-API-Key` header on the request             |
| `401`  | `INVALID_API_KEY`         | The key does not match any active key            |
| `401`  | `API_KEY_INACTIVE`        | The key was deactivated                          |
| `401`  | `API_KEY_REVOKED`         | The key was revoked                              |
| `401`  | `API_KEY_EXPIRED`         | The key passed its expiry date                   |
| `401`  | `TENANT_INACTIVE`         | The account behind the key is disabled           |
| `403`  | `INSUFFICIENT_SCOPES`     | Valid key, missing the scope this endpoint needs |
| `403`  | `COMPLIANCE_NOT_APPROVED` | Valid, scoped key, but KYB is not approved       |

`INSUFFICIENT_SCOPES` names exactly what was missing, so you can tell a permissions
problem from an authentication one without guessing:

```json theme={null}
{
  "error": {
    "code": "INSUFFICIENT_SCOPES",
    "message": "This API key does not have the required permissions.",
    "details": [
      { "field": "scopes", "issue": "Missing required scope: payouts:write" }
    ],
    "request_id": "req_01HX..."
  }
}
```

<Warning>
  `COMPLIANCE_NOT_APPROVED` is not an authentication problem and no amount of
  retrying fixes it. Your key is valid and correctly scoped, but your compliance
  profile has not reached `APPROVED` — see [Get access](/get-access). Treat it as a
  configuration error and stop.
</Warning>

## Handling keys safely

<AccordionGroup>
  <Accordion title="Never put a key in client code" icon="browser">
    The key authorises payouts. It belongs on your server, in a secrets manager or
    environment variable — never in a browser bundle, mobile app, or repo.
  </Accordion>

  <Accordion title="Use separate keys per system" icon="layer-group">
    Issue distinct keys for your payout service, your reconciliation job, and your
    staging environment. When one leaks you revoke one key instead of rotating
    everything at once.
  </Accordion>

  <Accordion title="Rotate without downtime" icon="rotate">
    Several keys can be active at once. Issue the new key, deploy it, confirm
    traffic has moved by checking each key's last-used timestamp, then revoke the
    old one.
  </Accordion>

  <Accordion title="Do not reuse the key as a webhook secret" icon="signature">
    Webhook signatures use a separate `whsec_` secret with its own rotation. The two
    credentials move in opposite directions — your key authenticates you to us, the
    webhook secret authenticates us to you.
  </Accordion>
</AccordionGroup>
