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

# Authentication

> Exchange your API key and API secret for an organization-scoped Bearer token, and keep it fresh with rotating refresh tokens.

**BrandWallet API uses a two-credential exchange: your API key (identifies your integration) plus an API secret (identifies the merchant organization) produce a Bearer token scoped to that organization. Every subsequent API call carries this token in the `Authorization` header.**

Two auth endpoints cover the whole lifecycle:

| Endpoint | Purpose |
| - | - |
| `POST /auth/token` | Exchange API key + secret for an access token |
| `POST /auth/refreshtoken` | Rotate the access token / refresh token pair |

## Credentials

| Credential | Purpose | Lifetime |
| - | - | - |
| **API Key** | Identifies your integration. Issued once and used with every organization you connect. | Permanent (until rotated) |
| **API Secret** | Identifies the merchant organization. One secret per organization, collected during onboarding. | Permanent (until rotated) |

> **Keep credentials server-side.** Your API key and secrets grant access to organization data. Never embed them in client-side code or expose them in a browser; only transmit them over HTTPS.

## Get a token

```http theme={null}
POST $BASE_URL/auth/token
```

Send your credentials as request headers — no request body. The pair is validated together: a valid API key with an API secret that belongs to another integration is rejected. The token is scoped to the secret's organization.

| Header | Type | Description |
| - | - | - |
| `x-api-key` | string | Required. The API key issued to your integration. |
| `x-api-secret` | string | Required. The API secret issued for the connected organization. |

### Example

```bash theme={null}
curl --request POST \
  --url "$BASE_URL/auth/token" \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'x-api-secret: ORGANIZATION_SECRET'
```

### Response

```json theme={null}
{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
  "tokenType": "Bearer",
  "expiresIn": 1754126400000,
  "refreshToken": "946d2689-c4e4-4ca2-a194-..."
}
```

| Field | Type | Description |
| - | - | - |
| `accessToken` | string | The JWT access token. Pass it as `Authorization: Bearer <accessToken>` on all API requests. |
| `tokenType` | string | Always `Bearer`. |
| `expiresIn` | number | Token expiry as a Unix epoch timestamp in **milliseconds**. Compare against the current time to decide when to refresh. |
| `refreshToken` | string | Renews the session without re-sending credentials — see below. |

## Call the API

Include `Authorization: Bearer <accessToken>` on every request. BrandWallet validates the token on each call and rejects missing, expired, or malformed tokens. When the token expires, refresh it or request a new one with your credentials.

```bash theme={null}
curl "$BASE_URL/v1/customers/find?q=5551234567" \
  --header 'Authorization: Bearer ACCESS_TOKEN'
```

## Refresh a token

```http theme={null}
POST $BASE_URL/auth/refreshtoken
```

Instead of re-sending credentials, send the current token pair. BrandWallet returns a new pair and immediately invalidates the old refresh token.

> **Refresh tokens are rotated** — each successful call invalidates the token you sent and returns a brand-new pair. Replace both stored tokens immediately after a successful refresh; resending an old refresh token fails with `_invalid_refresh_token`.

Payload schema:

```json theme={null}
{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",   // may be expired — accepted regardless
  "refreshToken": "946d2689-c4e4-4ca2-a194-..."       // must be the latest in the rotation
}
```

| Field | Type | Description |
| - | - | - |
| `accessToken` | string | Required. The access token issued together with the refresh token — accepted even when expired. |
| `refreshToken` | string | Required. The refresh token from the same token response. Previously used tokens are invalid. |

The response has the same shape as `POST /auth/token`: a new `accessToken`, `tokenType`, `expiresIn`, and `refreshToken`.

## Error codes

| Token | Status | Meaning |
| - | - | - |
| `_invalid_payload` | 400 | The `x-api-key` or `x-api-secret` header is missing from the `/auth/token` request. |
| `_invalid_credentials` | 401 | The API key and API secret pair is invalid or does not match a known organization. |
| `_invalid_refresh_token` | 400 | The refresh token is unknown, expired, or has already been rotated. |
| `_jwt_expired` | 401 | The access token has passed its expiry time. Refresh or re-request a token. |
| `_jwt_malformed` | 401 | The access token is not a valid JWT and cannot be parsed. |

For the complete list of error codes returned across all endpoints, see the [Error Reference](/api/errors).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.