Skip to main content

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.
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.

Permissions

Each token carries an array of permissions that control which API endpoints the agent can access:
PermissionGrants Access To
readGET /v1/balance, GET /v1/transactions, GET /v1/stablecoin/address
payPOST /v1/pay, POST /v1/payments, POST /v1/usdc/transfer
x402POST /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:
{
  "detail": "Token lacks required permission: pay"
}
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.

Security Model

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

Token created

The plaintext token is returned to the user exactly once at creation time.
2

Hash stored

The SHA-256 hash of the token is stored in the api_tokens table. The original plaintext is discarded.
3

Lookup on request

When an agent sends a request, the bearer token is hashed and matched against stored hashes.
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.
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 Requests with a Retry-After header.
  • If Redis is unavailable, the rate limiter degrades gracefully and allows requests through (fail-open).
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.
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

ActionHow
CreateVia dashboard: POST /dashboard/projects/:id/tokens
ListVia dashboard: GET /dashboard/projects/:id/tokens (returns metadata only, never the full token)
RevokeVia dashboard: DELETE /dashboard/projects/:id/tokens/:token_id

Token Fields

FieldTypeDescription
idstringUnique token identifier
token_prefixstringDisplay prefix (e.g., aw_live_a1b2)
namestringHuman-readable label
permissionsarrayList of permissions: read, pay, x402
is_activebooleanWhether the token is currently valid
expires_atdatetime or nullOptional expiration date
last_used_atdatetimeLast time the token was used (updated best-effort)
created_atdatetimeWhen the token was created