API Tokens
API tokens let you authenticate programmatic requests to izri without using cookies or browser sessions.
Creating a Token
Use the tokens.createToken tRPC mutation. You must be authenticated with a session first.
const result = await trpc.tokens.createToken.mutate({
name: 'CI pipeline token',
expiresIn: 7 * 24 * 3600, // 7 days in seconds (optional)
scopes: [], // reserved for future use
})
// Store result.rawToken immediately — it will NOT be shown again
console.log(result.rawToken) // izri_<64 hex chars>
The raw token is returned exactly once. If you lose it, revoke the old token and create a new one.
Using a Token in Requests
Pass the raw token as a Bearer header on any tRPC or REST request:
Authorization: Bearer izri_<your-token>
tRPC via HTTP
curl -X POST https://your-api/trpc/projects.listProjects \
-H "Authorization: Bearer izri_abc123..." \
-H "Content-Type: application/json" \
-d '{"json": {}}'
tRPC client (TypeScript)
import { createTRPCClient, httpBatchLink } from '@trpc/client'
import type { AppRouter } from '@izri/trpc/routers'
const trpc = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'https://your-api/trpc',
headers: () => ({
Authorization: `Bearer ${process.env.API_TOKEN}`,
}),
}),
],
})
Listing Tokens
const { tokens } = await trpc.tokens.listTokens.query()
// Returns metadata only — no raw token hashes
Each token object:
{
id: string
name: string
createdAt: Date
expiresAt: Date | null
lastUsedAt: Date | null
scopes: string[]
}
Revoking a Token
await trpc.tokens.revokeToken.mutate({ tokenId: 'TOKEN_ID' })
Revoked tokens are immediately rejected on all subsequent requests. There is no "soft delete" — the record is removed from the database.
Rotating a Token
Rotation atomically creates a new token and revokes the old one. Use this for credential rotation without downtime:
const result = await trpc.tokens.rotateToken.mutate({
tokenId: 'OLD_TOKEN_ID',
newName: 'Rotated token', // optional
expiresIn: 86400, // optional
})
// Update your secret store with result.rawToken before the old one stops working
Expiry Handling
- If
expiresInis omitted, the token never expires. - Expired tokens return HTTP 401 (UNAUTHORIZED) just like invalid tokens.
lastUsedAtis updated on every successful validation.- It is recommended to set an expiry for tokens used in CI or third-party integrations.
Security Best Practices
| Practice | Recommendation |
|---|---|
| Store tokens in secrets | Use environment variables or a secrets manager (e.g. Vault, GitHub Secrets). Never commit raw tokens to source control. |
| Set expiry | Prefer short-lived tokens (days/weeks) over non-expiring ones. |
| Rotate regularly | Use rotateToken on a schedule (e.g. every 90 days). |
| Least privilege | Future scopes field will allow restricting tokens to specific operations. |
| Revoke promptly | If a token is leaked, revoke it immediately via revokeToken. |
| One token per use case | Give each CI job, integration, or service its own named token so you can revoke individually. |
Token Format
Tokens have the form: izri_<64 lowercase hex chars> (66+ chars total).
The prefix izri_ allows automated secret scanners (e.g. GitHub secret scanning, truffleHog) to detect leaked tokens.
The database stores a SHA-256 hash of the token. The raw token is never logged or persisted after the initial creation response.
Endpoints Summary
| Procedure | Type | Auth required | Description |
|---|---|---|---|
tokens.createToken |
mutation | ✅ Session | Create a new token (raw shown once) |
tokens.listTokens |
query | ✅ Session | List your tokens (no raw values) |
tokens.revokeToken |
mutation | ✅ Session | Delete a token by ID |
tokens.rotateToken |
mutation | ✅ Session | Atomically rotate a token |