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

# API Tokens

> Create scoped tokens with granular permissions to control what your agents can do.

# API Tokens

API tokens are the credentials that AI agents use to authenticate with Unwall. Each token is scoped to a single project and carries a specific set of permissions that control what the agent can do.

## Token Format

Tokens follow this format:

```
aw_live_<random_string>
```

* **Prefix:** `aw_live_` identifies the token as an Unwall token. The prefix is stored in the database for display purposes (e.g., showing `aw_live_a1b2...` in the dashboard).
* **Body:** A cryptographically random string generated at creation time.

<Warning>
  The full token is displayed **only once** at creation time. It cannot be retrieved again. If lost, revoke the old token and create a new one.
</Warning>

## Permissions

Each token carries an array of permissions that control which API endpoints the agent can access:

| Permission | Grants Access To                                                        |
| ---------- | ----------------------------------------------------------------------- |
| `read`     | `GET /v1/balance`, `GET /v1/transactions`, `GET /v1/stablecoin/address` |
| `pay`      | `POST /v1/pay`, `POST /v1/payments`, `POST /v1/usdc/transfer`           |
| `x402`     | `POST /v1/pay` (x402 rail), `POST /v1/x402/pay`                         |

Permissions are checked at the endpoint level. If a token lacks the required permission, the request is rejected with a `403 Forbidden` error:

```json theme={null}
{
  "detail": "Token lacks required permission: pay"
}
```

<Tip>
  Follow the principle of least privilege. If an agent only needs to check balances and view transactions, issue a token with only the `read` permission. Create separate tokens for different agents or services with minimal permissions.
</Tip>

## Security Model

Tokens are never stored in plaintext. The system uses a one-way **SHA-256 hash** for storage and lookup:

<Steps>
  <Step title="Token created">
    The plaintext token is returned to the user exactly once at creation time.
  </Step>

  <Step title="Hash stored">
    The SHA-256 hash of the token is stored in the `api_tokens` table. The original plaintext is discarded.
  </Step>

  <Step title="Lookup on request">
    When an agent sends a request, the bearer token is hashed and matched against stored hashes.
  </Step>
</Steps>

This means that even if the database is compromised, an attacker cannot reconstruct valid tokens from the stored hashes.

## Expiry and Revocation

Tokens support two mechanisms for invalidation:

* **Optional expiration date**: Set an expiry when creating the token. After the expiry date, the token is automatically rejected.
* **Manual revocation**: Revoke any token at any time from the dashboard. The token's `is_active` flag is set to `false`.

<Note>
  There is a window of up to 5 minutes after revocation during which a cached token may still be accepted, due to Redis caching. For time-sensitive revocations, the cache entry expires naturally within this window.
</Note>

## Rate Limiting

Every token is rate-limited to **100 requests per minute** using a Redis-backed sliding window algorithm.

* The rate limiter uses Redis sorted sets to track request timestamps within a 60-second window.
* When the limit is exceeded, the API returns `429 Too Many Requests` with a `Retry-After` header.
* If Redis is unavailable, the rate limiter degrades gracefully and allows requests through (fail-open).

```json theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 12

{
  "detail": "Rate limit exceeded. Try again later."
}
```

## Redis Caching

To avoid hitting the database on every request, validated token contexts are cached in Redis for **5 minutes** (300 seconds):

1. Agent sends a request with `Authorization: Bearer aw_live_...`.
2. The token is SHA-256 hashed.
3. Redis is checked for the cache key `token:<hash>`.
4. **Cache hit:** The stored context (token ID, project ID, user ID, permissions) is returned immediately.
5. **Cache miss:** The hash is looked up in the database, the project status is verified, and the result is cached.

<Note>
  The cached context does **not** include the project balance. Balance is always fetched fresh from the ledger when needed for payment authorization. This prevents agents from exploiting stale cached balances to overdraw.
</Note>

## Token Lifecycle

| Action     | How                                                                                               |
| ---------- | ------------------------------------------------------------------------------------------------- |
| **Create** | Via dashboard: `POST /dashboard/projects/:id/tokens`                                              |
| **List**   | Via dashboard: `GET /dashboard/projects/:id/tokens` (returns metadata only, never the full token) |
| **Revoke** | Via dashboard: `DELETE /dashboard/projects/:id/tokens/:token_id`                                  |

## Token Fields

| Field          | Type             | Description                                        |
| -------------- | ---------------- | -------------------------------------------------- |
| `id`           | string           | Unique token identifier                            |
| `token_prefix` | string           | Display prefix (e.g., `aw_live_a1b2`)              |
| `name`         | string           | Human-readable label                               |
| `permissions`  | array            | List of permissions: `read`, `pay`, `x402`         |
| `is_active`    | boolean          | Whether the token is currently valid               |
| `expires_at`   | datetime or null | Optional expiration date                           |
| `last_used_at` | datetime         | Last time the token was used (updated best-effort) |
| `created_at`   | datetime         | When the token was created                         |
