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:- Prefix:
aw_live_identifies the token as an Unwall token. The prefix is stored in the database for display purposes (e.g., showingaw_live_a1b2...in the dashboard). - Body: A cryptographically random string generated at creation time.
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 |
403 Forbidden error:
Security Model
Tokens are never stored in plaintext. The system uses a one-way SHA-256 hash for storage and lookup:Hash stored
The SHA-256 hash of the token is stored in the
api_tokens table. The original plaintext is discarded.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_activeflag is set tofalse.
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.
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 Requestswith aRetry-Afterheader. - If Redis is unavailable, the rate limiter degrades gracefully and allows requests through (fail-open).
Redis Caching
To avoid hitting the database on every request, validated token contexts are cached in Redis for 5 minutes (300 seconds):- Agent sends a request with
Authorization: Bearer aw_live_.... - The token is SHA-256 hashed.
- Redis is checked for the cache key
token:<hash>. - Cache hit: The stored context (token ID, project ID, user ID, permissions) is returned immediately.
- Cache miss: The hash is looked up in the database, the project status is verified, and the result is cached.
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.
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 |