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

# Authentication

> Secure your API integration with Bearer tokens and IP allowlisting

Every request to the Qustody API requires authentication via a Bearer API key. This guide covers credential format, lifecycle, and security best practices.

## API key format

Credentials consist of a **key ID** and a **secret**, separated by a colon:

```
Authorization: Bearer <key_id>:<secret>
```

The `key_id` is the public identifier for your credential. The `secret` is the private value. It is securely hashed server-side and **never stored in plaintext**.

<CodeGroup>
  ```python Python SDK theme={null}
  from quantumchain_custody import Client

  client = Client(api_key="ck_live_abc123:sk_live_xyz789")
  vaults = client.vaults.list()
  ```

  ```bash cURL theme={null}
  curl -X GET https://api.quantumchain.io/v1/vaults \
    -H "Authorization: Bearer ck_live_abc123:sk_live_xyz789"
  ```

  ```javascript JavaScript theme={null}
  const resp = await fetch("https://api.quantumchain.io/v1/vaults", {
    headers: {
      Authorization: "Bearer ck_live_abc123:sk_live_xyz789",
    },
  });
  ```
</CodeGroup>

## How it works

<Steps>
  <Step title="Client sends Bearer token">
    The middleware extracts the `key_id` and `secret` from the Authorization
    header.
  </Step>

  <Step title="Lookup credential">
    The `key_id` is looked up in the database. Only `ACTIVE` credentials are
    accepted.
  </Step>

  <Step title="Verify secret">
    The provided secret is compared against the stored **hash** using
    constant-time comparison.
  </Step>

  <Step title="Tenant resolution">
    The credential resolves to a tenant ID, which is injected into the request
    context. All subsequent operations are scoped to this tenant.
  </Step>

  <Step title="IP allowlist check">
    If the tenant has an IP allowlist configured, the request's source IP must
    match one of the allowed CIDRs.
  </Step>
</Steps>

## Credential lifecycle

| Stage           | Description                                               |
| --------------- | --------------------------------------------------------- |
| **Provisioned** | Key pair generated by the platform team or admin CLI      |
| **Active**      | Key is valid and accepts requests                         |
| **Rotated**     | New key issued; old key remains valid during grace period |
| **Revoked**     | Key permanently disabled; all requests return `401`       |

<Warning>
  Store your API secret securely. It is shown only once at creation time and cannot be retrieved later.
</Warning>

## IP allowlisting

For production deployments, restrict API access to known IP addresses. The custody API validates the source IP of each request against the tenant's allowlist.

When IP allowlisting is enabled:

* Requests from unlisted IPs receive a `403 Forbidden` response
* The allowlist is configured per tenant
* CIDR notation is supported for IP ranges

```bash theme={null}
curl -X POST https://api.quantumchain.io/v1/tenants \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "name": "Secure Corp",
    "ipAllowList": [
      "203.0.113.0/24",
      "198.51.100.42/32"
    ]
  }'
```

<Warning>
  When an IP allowlist is configured, requests from unlisted IPs receive a `403
      Forbidden` response. If the list is empty or null, all IPs are allowed.
</Warning>

## Idempotency

All `POST` and `PUT` requests should include an `Idempotency-Key` header:

```
Idempotency-Key: unique-request-identifier
```

* Keys are scoped per tenant, method, and path
* Keys are valid for **24 hours**
* Replaying a request with the same key returns the original response
* Using the same key with a different method or path returns a `422 IDEMPOTENCY_CONFLICT` error

<Tip>
  Use UUIDs or your own internal request IDs as idempotency keys. This makes
  retries safe across network failures.
</Tip>

## Error codes

Authentication failures return specific error codes:

| Status | Code                   | Meaning                                                          |
| ------ | ---------------------- | ---------------------------------------------------------------- |
| `401`  | `UNAUTHORIZED`         | Missing or invalid Bearer token                                  |
| `403`  | `FORBIDDEN`            | Valid token, but IP not in allowlist or insufficient permissions |
| `422`  | `IDEMPOTENCY_CONFLICT` | Idempotency key reused with different method/path                |

```json 401 Response theme={null}
{
  "code": "UNAUTHORIZED",
  "message": "authentication required",
  "requestId": "req_abc123"
}
```

## Security best practices

<AccordionGroup>
  <Accordion title="Rotate credentials regularly">
    Create a new credential, update your integrations, then revoke the old one.
    The API supports multiple active credentials per tenant. Rotate every 90 days.
  </Accordion>

  <Accordion title="Use IP allowlists in production">
    Restrict access to your known egress IPs. This provides defense-in-depth
    even if a credential is compromised. Never call the API from client-side code.
  </Accordion>

  <Accordion title="Never log secrets">
    The `secret` portion of your API key should be treated like a password.
    Never log it, commit it to version control, or expose it in client-side
    code.
  </Accordion>

  <Accordion title="Use separate credentials per environment">
    Maintain distinct credentials for development, staging, and production to
    limit blast radius.
  </Accordion>
</AccordionGroup>
