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

```ts
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

```bash
curl -X POST https://your-api/trpc/projects.listProjects \
  -H "Authorization: Bearer izri_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"json": {}}'
```

### tRPC client (TypeScript)

```ts
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

```ts
const { tokens } = await trpc.tokens.listTokens.query()
// Returns metadata only — no raw token hashes
```

Each token object:
```ts
{
  id: string
  name: string
  createdAt: Date
  expiresAt: Date | null
  lastUsedAt: Date | null
  scopes: string[]
}
```

---

## Revoking a Token

```ts
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:

```ts
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 `expiresIn` is omitted, the token never expires.
- Expired tokens return HTTP 401 (UNAUTHORIZED) just like invalid tokens.
- `lastUsedAt` is 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 |
