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

# Authentication

> Exchange your API keys for a bearer token and keep it fresh.

Tanda uses short-lived bearer tokens. You exchange two credentials for a token, then send the token on every request.

## Your credentials

| Credential | What it is | Where it can live |
| :- | :- | :- |
| **Public key** | Identifies your account. | Anywhere. It isn't secret. |
| **API key** | Secret used to mint tokens. | Your server only. |

Find both in the [dashboard](https://app.tandapay.io) under **Developers → API keys**. Sandbox and production have separate key pairs.

> [!WARNING]
>
> Anyone with your API key can move money from your wallet. Store it in an environment variable or secrets manager, never in client-side code or source control. If it leaks, rotate it in the dashboard straight away.

## Generate a token

Call [Generate bearer token](https://api.tandapay.io/api-reference/endpoint/generate-bearer-token.md) with the API key in the `api-key` header and the public key in the body.

```bash cURL theme={null}
curl -X POST https://api.tandapay.io/api/b2b/auth/generate-bearer-token \
  -H "api-key: $TANDA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "publicKey": "'"$TANDA_PUBLIC_KEY"'" }'
```

```javascript Node.js theme={null}
const res = await fetch("https://api.tandapay.io/api/b2b/auth/generate-bearer-token", {
  method: "POST",
  headers: { "api-key": process.env.TANDA_API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ publicKey: process.env.TANDA_PUBLIC_KEY }),
});
const { token, expiresAt } = await res.json();
```

```python Python theme={null}
import os, requests

res = requests.post(
    "https://api.tandapay.io/api/b2b/auth/generate-bearer-token",
    headers={"api-key": os.environ["TANDA_API_KEY"]},
    json={"publicKey": os.environ["TANDA_PUBLIC_KEY"]},
)
data = res.json()
token, expires_at = data["token"], data["expiresAt"]
```

```json Response theme={null}
{
  "success": true,
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresAt": 1767225600
}
```

## Use the token

Send it in the `Authorization` header on every other endpoint.

```bash theme={null}
curl https://api.tandapay.io/api/b2b/transactions \
  -H "Authorization: Bearer $TOKEN"
```

## Refresh before it expires

Tokens last one hour. `expiresAt` is the expiry as a Unix timestamp in seconds. Cache the token with its expiry, refresh a minute early, and if a request still returns `401`, mint a new token and retry once.

```javascript Token cache (Node.js) theme={null}
let cached = null; // { token, expiresAt }

export async function getToken() {
  const now = Math.floor(Date.now() / 1000);
  if (cached && cached.expiresAt - 60 > now) return cached.token;

  const res = await fetch("https://api.tandapay.io/api/b2b/auth/generate-bearer-token", {
    method: "POST",
    headers: { "api-key": process.env.TANDA_API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ publicKey: process.env.TANDA_PUBLIC_KEY }),
  });
  if (!res.ok) throw new Error(`Token request failed: ${res.status}`);

  const { token, expiresAt } = await res.json();
  cached = { token, expiresAt: expiresAt ?? now + 3600 };
  return token;
}
```

## Good practice

* Mint tokens on your backend only. Browsers and mobile apps should call your server, never Tanda directly.
* Keep token refresh in one place so every part of your system uses the same cache.
* Don't retry a `401` in a loop. One fresh token and one retry is enough; after that, alert someone.


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