> ## 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 organization secret for a Bearer access token, then pass it in the Authorization header on every BrandWallet API request.

The BrandWallet API uses a two-credential exchange to produce access tokens. You send your API key — which identifies your integration — and an API secret — which identifies the merchant organization — to receive a Bearer token scoped to that organization. Every subsequent API call includes 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 merchant, collected during merchant onboarding. | Permanent (until rotated) |

<Warning>
  Your API key and secrets grant access to merchant data. Never embed them in client-side code or expose them in a browser. Only transmit them over HTTPS.
</Warning>

## Get a token

Call `POST /auth/token` with your credentials as request headers — no request body is required. Both credentials are validated as a pair: a valid key with a secret that belongs to another integration is rejected. The token is issued on behalf of the organization's service account and is scoped to that organization.

### Request headers

<ParamField header="x-api-key" type="string" required>
  The API key issued to your integration.
</ParamField>

<ParamField header="x-api-secret" type="string" required>
  The API secret issued for the connected organization.
</ParamField>

### Response fields

<ResponseField name="accessToken" type="string">
  The JWT access token. Pass this value as `Bearer` in the `Authorization` header on all API requests.
</ResponseField>

<ResponseField name="tokenType" type="string">
  The token type. Always `Bearer`.
</ResponseField>

<ResponseField name="expiresIn" type="number">
  Token expiry expressed as a Unix epoch timestamp in milliseconds. Compare this value against the current time to determine when to refresh.
</ResponseField>

<ResponseField name="refreshToken" type="string">
  A token you can use to renew the session without re-sending your credentials. See [Refresh a token](#refresh-a-token) below.
</ResponseField>

## Call the API

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

## Refresh a token

Instead of re-sending your credentials every time a token expires, call `POST /auth/refreshtoken` with the current `accessToken` and `refreshToken` pair. BrandWallet returns a new token pair and immediately invalidates the old refresh token.

<Warning>
  Refresh tokens are rotated — each successful call invalidates the token you send and returns a brand-new pair. Always replace both stored tokens immediately after a successful refresh. Replaying an old refresh token fails with `_invalid_refresh_token`.
</Warning>

### Body parameters

<ParamField body="accessToken" type="string" required>
  The access token issued together with the refresh token. This may be expired — the refresh endpoint accepts it regardless of expiry status.
</ParamField>

<ParamField body="refreshToken" type="string" required>
  The refresh token from the same token response. Must be the latest token in the rotation; previously used tokens are invalidated.
</ParamField>

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

## Error codes

| Code                     | HTTP 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 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).

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

  ```bash POST /auth/refreshtoken theme={null}
  curl --request POST \
    --url "$BASE_URL/auth/refreshtoken" \
    --header 'Content-Type: application/json' \
    --data '{
      "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
      "refreshToken": "946d2689-c4e4-4ca2-a194-..."
    }'
  ```

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

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

  ```json 401 Unauthorized theme={null}
  {
    "statuscode": 401,
    "errorcode": 401,
    "message": "_invalid_credentials",
    "description": "",
    "timestamp": 1754040000000,
    "path": "/auth/token",
    "method": "POST"
  }
  ```

  ```json 400 Bad Request theme={null}
  {
    "statuscode": 400,
    "errorcode": 400,
    "message": "_invalid_refresh_token",
    "description": "",
    "timestamp": 1754040000000,
    "path": "/auth/refreshtoken",
    "method": "POST"
  }
  ```
</ResponseExample>
