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

# Authentication

> Authenticate API requests and operate keys safely.

Authenticated routes require an API key in the `X-Api-Key` header:

```http theme={null}
X-Api-Key: <api_key>
```

## Create a staging API key

Sign in to the [Stabyl staging app](https://app-staging.stabyl.com) to create
API keys for staging. Store the generated key when it is shown and use it only
with `https://api-staging.stabyl.com/v1`.

Keep API keys in server-side systems only. Do not embed keys in browser, mobile, desktop, or user-controlled code.

## Production API access

Production API access is not enabled by staging access. Your organization must
complete KYB and receive an explicit invitation from Stabyl before provisioning
production credentials. Follow the instructions in that invitation and use the
resulting credentials only with `https://api.stabyl.com/v1`.

Do not copy a staging key into production configuration or treat successful
staging integration as production approval.

New keys use immutable scopes. The default scope set excludes `wallets:withdraw`; select that scope explicitly during key creation and complete account authentication with an existing account passkey or withdrawal password. Changing scopes requires replacing the key. An IP or CIDR allowlist is optional.

## Key Handling Rules

| Rule                                                          | Reason                                       |
| ------------------------------------------------------------- | -------------------------------------------- |
| Store keys in a secret manager or encrypted environment store | Keys authorize account activity              |
| Use separate keys per service                                 | Limits blast radius and simplifies rotation  |
| Do not log full key values                                    | Logs are copied and retained in many systems |
| Keep staging and production keys separate                     | Prevents accidental live requests            |
| Use a dedicated key for withdrawals                           | Keeps signing material and scope narrow      |
| Store the signing secret returned at creation                 | The secret is displayed only once            |

## Signed Withdrawal Requests

Withdrawal quote and submit calls require three additional headers:

```http theme={null}
X-Stabyl-Timestamp: <unix_milliseconds>
X-Stabyl-Nonce: <uuid_v4_or_v7>
X-Stabyl-Signature: <base64url_ed25519_signature>
```

The signature covers the HTTP method, exact path and query, timestamp, nonce, and SHA-256 hash of the exact raw body bytes. See [Withdrawals](/docs/withdrawals) for canonicalization rules and the fixed test vector.

The signed request is the Partner withdrawal authorization. Quote and submit do
not accept or wait for an emailed verification code or client-supplied
withdrawal token.

## Public Endpoints

Only these Mintlify-documented routes do not require an API key:

* `GET /partner/wallets/chains`
* `GET /partner/wallets/fiat/banks`
* `POST /simulate/fiat/deposits`
* `POST /simulate/fiat/withdrawals`
* `POST /simulate/crypto/deposits`
* `POST /simulate/crypto/withdrawals`
* `POST /simulate/kyb/status`
* `POST /simulate/notifications/product-updates`

All other documented routes require `X-Api-Key`.

## Authentication Failures

| Status | What it usually means                                                                          | What to check                                                                 |
| ------ | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `401`  | The key or required signature is missing, malformed, invalid, or sent to the wrong environment | Header values, canonical path, raw body bytes, clock, key value, and base URL |
| `403`  | The key is valid but lacks the required immutable scope or fails its IP allowlist              | Key scopes, account readiness, and the caller's source IP                     |
| `409`  | A signed nonce was replayed or an idempotency key was reused for another intent                | Generate a new nonce for transport retries; keep the body unchanged           |
| `429`  | Requests are arriving too quickly                                                              | Backoff and reduce polling or retry loops                                     |
| `503`  | Signed replay protection is temporarily unavailable                                            | Fail closed and retry later with a new nonce                                  |

If the same key works for reads but not withdrawal writes, confirm `wallets:withdraw` is present and that the request was signed with the secret returned for that key.
