Base URL
All API endpoints are served from the PassAgent web application:
For local development:
http://localhost:3000/api
Authentication
Most endpoints require authentication via Supabase JWT. The token can be provided in two ways:
The web application uses HTTP-only cookies set by Supabase Auth. These are sent automatically with every request.// Server-side: create Supabase client from cookies
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
Browser extensions and the iOS app authenticate with a Bearer token in the Authorization header:curl -H "Authorization: Bearer <supabase-jwt>" \
https://passagent.ai/api/passwords
CSRF protection
All mutation endpoints (POST, PATCH, DELETE) require a valid CSRF token. Obtain a token from the CSRF endpoint and include it in subsequent requests:
// 1. Get CSRF token
const res = await fetch('/api/auth/csrf-token')
const { csrfToken } = await res.json()
// 2. Include in mutation requests
await fetch('/api/passwords', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-csrf-token': csrfToken,
},
body: JSON.stringify({ ... }),
})
Requests to mutation endpoints without a valid CSRF token will receive a 403 Forbidden response.
Rate limiting
Sensitive endpoints enforce rate limits to prevent abuse:
| Endpoint category | Limit | Window |
|---|
| Authentication | 10 requests | 15 minutes |
| Password reset tasks | 5 requests | 5 minutes |
| 2FA enrollment | 5 requests | 15 minutes |
| Password reveal | 30 requests | 1 minute |
| General API | 100 requests | 1 minute |
Rate-limited responses return 429 Too Many Requests with a Retry-After header.
Error responses
All error responses follow a consistent format:
{
"error": "Human-readable error message",
"code": "ERROR_CODE",
"status": 400
}
Common error codes
| Status | Code | Description |
|---|
400 | VALIDATION_ERROR | Request body failed validation |
401 | UNAUTHORIZED | Missing or invalid authentication |
403 | FORBIDDEN | Valid auth but insufficient permissions, or missing CSRF token |
404 | NOT_FOUND | Resource does not exist or is not accessible |
409 | CONFLICT | Resource already exists (e.g., duplicate entry) |
410 | GONE | Resource removed or feature deprecated (e.g., zero-trust TOTP code request) |
422 | UNPROCESSABLE | Request understood but cannot be processed |
429 | RATE_LIMITED | Too many requests |
500 | INTERNAL_ERROR | Server error |
Request validation
All request bodies are validated using Zod schemas. Invalid requests receive a 400 response with details about which fields failed validation:
{
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"details": [
{
"field": "password",
"message": "String must contain at least 8 characters"
}
]
}
List endpoints support cursor-based pagination:
GET /api/passwords?limit=50&cursor=<last-item-id>
| Parameter | Type | Default | Description |
|---|
limit | integer | 50 | Maximum items per page (max: 100) |
cursor | string | — | ID of the last item from the previous page |
Content type
All request and response bodies use JSON:
Content-Type: application/json
Streaming endpoints (reset progress, AI chat) use Server-Sent Events:
Content-Type: text/event-stream