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

# Authentication

> Exchange your Kira credentials for a bearer token and authenticate every API call.

Every Kira API call is authenticated with **two** pieces: your `x-api-key` header and a short-lived bearer token. You obtain the token by calling `POST /auth` with your `client_id` and `password`, then send it as `Authorization: Bearer <access_token>` on subsequent requests.

## Your credentials

Kira provisions three credentials for your account:

| Credential  | Used for                                                                     |
| ----------- | ---------------------------------------------------------------------------- |
| `client_id` | Identifies your account in the `POST /auth` request body.                    |
| `password`  | Authenticates the `POST /auth` request body.                                 |
| `api_key`   | Sent as the `x-api-key` header on **every** request, including `POST /auth`. |

<Tip>
  Keep these out of source control. Load them from your environment — for example `KIRA_CLIENT_ID`, `KIRA_PASSWORD`, and `KIRA_API_KEY`.
</Tip>

## Rotating credentials

There is no self-serve endpoint to rotate your `client_id`, `password`, or `api_key`. Plausible key-management paths (`/v1/api-keys`, `/v1/api-keys/rotate`, `/v1/keys`, `/v1/credentials`, and similar) all return a `403` with `code: "invalid_request"` and the message *"The request could not be matched to a valid route, or was malformed."* — a routing/request error, not a real endpoint with its own validation. To rotate any of your credentials, contact your assigned Customer Success Manager.

## Get an access token

Call `POST /auth` to exchange your `client_id` and `password` for a bearer token. This request carries the `x-api-key` header but does **not** require an `Authorization` header — it is the one endpoint that needs only `x-api-key`.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.balampay.com/sandbox/auth \
    -H "x-api-key: $KIRA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "client_id": "'"$KIRA_CLIENT_ID"'",
      "password": "'"$KIRA_PASSWORD"'"
    }'
  ```
</CodeGroup>

The request body is just the two credentials:

```json theme={null}
{
  "client_id": "your_client_id",
  "password": "your_password"
}
```

A successful response returns the token inside the standard `{ "message", "data" }` envelope:

```json theme={null}
{
  "message": "...",
  "data": {
    "access_token": "eyJhbGciOi...",
    "expires_in": 3600,
    "token_type": "Bearer"
  }
}
```

| Field               | Value                                              |
| ------------------- | -------------------------------------------------- |
| `data.access_token` | The bearer token to send on every subsequent call. |
| `data.expires_in`   | `3600` — the token's lifetime in seconds (1 hour). |
| `data.token_type`   | `Bearer`.                                          |

## Use the token

Send the token as a bearer token on every other request, alongside your `x-api-key` header. Both are required together:

```bash theme={null}
curl https://api.balampay.com/sandbox/v1/users \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-api-key: $KIRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ ... }'
```

## Required headers

These headers apply across the API. The `Authorization` token comes from the `POST /auth` step above; the rest come from your account configuration.

| Header            | Value                   | When                                                                                                                                        |
| ----------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `Authorization`   | `Bearer <access_token>` | Every call except `POST /auth`.                                                                                                             |
| `x-api-key`       | `<api_key>`             | Every call, including `POST /auth`.                                                                                                         |
| `X-Api-Version`   | `2026-04-14`            | Per request, until you pin your account. Can be dropped once `POST /v1/versioning/upgrade` succeeds — see [Versioning](/guides/versioning). |
| `Idempotency-Key` | `<UUID v4>`             | Required on the write endpoints listed below.                                                                                               |

`Idempotency-Key` is required on:

* `POST /v1/users`
* `POST /v1/recipients`
* `POST /v1/virtual-accounts`
* `POST /v1/virtual-accounts/{id}/payout`

Generate a fresh UUID v4 per request (for example, `uuidgen`).

## Token lifetime

The access token is valid for **3600 seconds (1 hour)**, as reported by `data.expires_in`. When it expires, call `POST /auth` again to obtain a new token. There is no refresh-token flow — re-authenticate with your `client_id` and `password`.

## Authentication errors

All authentication failures return `401 Unauthorized` with a JSON body of the form `{ "code": "unauthorized", "message": "<reason>" }`.

* **`POST /auth` rejects the credentials.** Verify your `client_id` and `password`, and confirm your `x-api-key` header is set and correct.
* **A subsequent call is missing or has an invalid bearer token, or the token has expired.** Re-run `POST /auth` to obtain a fresh token, then retry the request. There is no refresh-token flow.

<Tip>
  Cache the access token and re-use it until it is close to expiry. Re-authenticate (and retry once) whenever a call returns `401`.
</Tip>

<Note>
  For the full list of integration gotchas — status casing, field renames, and other quirks — see [Known limitations & quirks](/guides/known-limitations).
</Note>
