> For the complete documentation index, see [llms.txt](https://docs.zebec.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zebec.io/developer-docs/partner-api/authentication.md).

# Authentication

Partner v1 uses two auth modes:

* **OTP endpoints** (`/partner/v1/auth/otp/*`) use HMAC-SHA256 with a partner credential issued by Zebec admin.
* **Cards and top-up endpoints** use `Authorization: Bearer <accessToken>`, where the token is returned by `POST /partner/v1/auth/otp/verify`.

## HMAC headers for OTP endpoints

| Header          | Value                                                       |
| --------------- | ----------------------------------------------------------- |
| `X-Partner-Key` | Public key ID, e.g. `pk_live_…`                             |
| `X-Timestamp`   | Request time in Unix milliseconds (server tolerates ±5 min) |
| `X-Nonce`       | UUID, unique per request — replay-protected for 10 min      |
| `X-Signature`   | Hex `HMAC_SHA256(secret, stringToSign)`                     |

### stringToSign

```
stringToSign = `${timestamp}\n${METHOD}\n${path}\n${sha256Hex(rawBody)}`
```

* `path` is the full request path including the query string, e.g. `/partner/v1/topup/quote?amount=100&sourceChain=SOLANA`.
* The body is hashed, not included raw, so signatures stay stable for any payload size.
* Empty body uses `sha256Hex('')`.

## Delegated bearer tokens

After verifying the OTP, the response contains an access token.

* Carry the token as `Authorization: Bearer <accessToken>`.
* Tokens expire after **48 hours**.
* Tokens carry the partner, API key, user, and `partner-delegated` scope claims.
* The user is always derived from the token, not supplied in partner requests.

## Scopes

API keys carry a scope set; endpoints declare what they need:

| Scope         | Allows                                                |
| ------------- | ----------------------------------------------------- |
| `auth:otp`    | Send / verify end-user OTP, mint delegation tokens    |
| `cards:read`  | Read card metadata + transactions, poll top-up status |
| `cards:topup` | Quote and submit a Carbon card top-up                 |

Missing scope → `403`. Bad signature / replay / stale timestamp → `401`. Cross-partner reads → `404` (anti-enumeration).

## Example: sending an OTP

```bash
curl -X POST https://dev-super.api.zebec.io/partner/v1/auth/otp/send \
  -H "X-Partner-Key: pk_live_..." \
  -H "X-Timestamp: 1717339200000" \
  -H "X-Nonce: 018f4b24-9f2c-7d2d-9c07-2d5d0f2f2a11" \
  -H "X-Signature: <hmac-signature>" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com"}'
```

For request/response schemas, see the [Swagger UI](https://dev-super.api.zebec.io/api/partner).
