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

# Authentication

> Exchanging your API key and secret for a bearer token, and using it on every request.

The Partner API uses **bearer token authentication**. You exchange a long-lived API key/secret pair for a short-lived access token, then send that token on every authenticated request.

## 1. Exchange credentials for a token

```http theme={null}
POST /api/v1.1/auth/token
Content-Type: application/json
```

```json theme={null}
{
  "key": "your-api-key",
  "secret": "your-api-secret"
}
```

This endpoint itself requires no authentication — your key and secret *are* the credentials being verified. On success (`200`):

```json theme={null}
{
  "success": true,
  "data": {
    "access_token": "eyJhbGciOiJI…",
    "token_type": "Bearer",
    "expires_in": 3600,
    "expires_at": "2026-07-31T12:00:00.000Z"
  },
  "meta": { "timezone": "UTC" }
}
```

<Note>
  These response fields are **snake\_case** (`access_token`, not `accessToken`) — a deliberate exception so existing OAuth2 client libraries work against it. Every other endpoint in this API is camelCase.
</Note>

## 2. Send the token on every request

```http theme={null}
Authorization: Bearer <access_token>
Content-Type: application/json
```

```bash theme={null}
curl "https://api.spenza.com/api/v1.1/sims" \
  -H "Authorization: Bearer $SPENZA_TOKEN"
```

Two categories of endpoint don't need this header at all:

* The **async status poll** (`GET /api/v3/transactions/{transactionId}`) — the transaction ID itself is the credential.
* `POST /api/v1.1/port-in/eligibility` and `POST /api/v3/port-in` use `security: []`/role-based auth respectively rather than the standard bearer check — see the API Reference for each.

Every other endpoint requires the bearer token. Any valid token for your account can call any endpoint — there's no per-key permission scoping today. `POST /api/v1.1/sms` is the one exception: it additionally requires the account's `Admin` role tier.

## Token expiry

Tokens are short-lived (1 hour) — check `expires_at` on the token response and request a new one before it passes. The API doesn't document a separate refresh-token grant; re-running the key/secret exchange is how you obtain a new token. Build your client to catch a `401 UNAUTHORIZED` and transparently re-authenticate rather than hardcoding a refresh interval, since that's the behavior guaranteed by the contract.

## Credential security

* **The secret never appears in a response after the initial issuance** — treat it like a password, not a lookup value.
* **Server-side only.** Never put your API key/secret (or a long-lived bearer token) in a browser, mobile app, or any client an end user can inspect. Exchange for a token on your backend and proxy authenticated calls through it.
* **One secret manager, not `.env` in git.** Store credentials in your platform's secrets manager (or at minimum an untracked `.env`) — see **[Security & Best Practices](/security)**.
* **Rotate on suspicion.** If a key/secret may have leaked, contact Spenza support immediately to have it rotated; there is no self-service rotation endpoint in this API.

## Common authentication errors

| HTTP | `error.code`       | Cause                                                                                                              | Fix                                                                 |
| ---- | ------------------ | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
| 400  | `VALIDATION_ERROR` | `key` or `secret` omitted from the token request body.                                                             | Send both fields.                                                   |
| 401  | `UNAUTHORIZED`     | Key/secret pair is wrong at the token exchange, or a bearer token is missing/malformed/expired on a later request. | Double-check the credentials, or re-authenticate for a fresh token. |
| 403  | `FORBIDDEN`        | Your account's plan doesn't include API access, or the token lacks the role tier an endpoint requires.             | Upgrade your plan, or use an account/token with the required role.  |

See **[Errors](/errors)** for the complete error-code reference.

## Next steps

* **[Quickstart](/quickstart)** — authenticate and make your first real call end to end.
* **API Reference → Authentication** (sidebar) — the full `POST /api/v1.1/auth/token` operation spec.
