API Reference
The Keystore API is served at https://api.keystore.io and follows REST conventions. All endpoints (except auth and status) require a JWT bearer token obtained via login or signup.
Base URL: https://api.keystore.io/v1
Authentication
All authenticated endpoints require the Authorization header:
Authorization: Bearer <jwt-token>JWTs are issued at login/signup with a 7-day expiry and can be refreshed before or shortly after expiration.
Auth
POST /v1/auth/signup
Create a new account, organization, and JWT.
curl -X POST https://api.keystore.io/v1/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"password": "securepassword",
"name": "Jane Doe",
"orgName": "Acme Inc"
}'{
"token": "eyJhbGci...",
"user": { "id": "uuid", "email": "you@example.com", "name": "Jane Doe" },
"org": { "id": "uuid", "name": "Acme Inc", "slug": "acme-inc" }
}POST /v1/auth/login
Authenticate and receive a JWT.
curl -X POST https://api.keystore.io/v1/auth/login \
-H "Content-Type: application/json" \
-d '{ "email": "you@example.com", "password": "securepassword" }'{
"token": "eyJhbGci...",
"user": { "id": "uuid", "email": "you@example.com", "name": "Jane Doe" },
"orgId": "uuid",
"orgs": [{ "id": "uuid", "name": "Acme Inc", "slug": "acme-inc", "role": "owner" }]
}POST /v1/auth/refresh
Refresh an expiring or recently expired JWT (within 7-day grace window).
curl -X POST https://api.keystore.io/v1/auth/refresh \
-H "Authorization: Bearer <old-token>"{ "token": "eyJhbGci..." }POST /v1/auth/switch-org
Switch the active organization context.
curl -X POST https://api.keystore.io/v1/auth/switch-org \
-H "Authorization: Bearer $TOKEN" \
-d '{ "orgId": "target-org-uuid" }'POST /v1/auth/forgot-password
Request a password reset email. Always returns 200 regardless of whether the email exists.
POST /v1/auth/reset-password
Reset password using a token from the reset email.
POST /v1/auth/verify-email
Verify email address using a token from the verification email.
Agents
GET /v1/agents?projectId={id}
List agents for a project. Requires projectId query parameter.
curl "https://api.keystore.io/v1/agents?projectId=<project-id>" \
-H "Authorization: Bearer $TOKEN"{
"agents": [
{
"id": "uuid",
"name": "my-agent",
"slug": "my-agent",
"status": "active",
"budgetCentsMonthly": 5000,
"rpmLimit": 120,
"rpdLimit": 50000,
"createdAt": "2026-01-15T10:00:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}POST /v1/agents
Create a new agent. Requires admin role.
curl -X POST https://api.keystore.io/v1/agents \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"projectId": "<project-id>",
"name": "my-agent",
"description": "Handles customer support",
"budgetCentsMonthly": 5000,
"rpmLimit": 120,
"rpdLimit": 50000
}'{
"agent": {
"id": "uuid",
"projectId": "uuid",
"name": "my-agent",
"slug": "my-agent",
"status": "active",
"budgetCentsMonthly": 5000,
"rpmLimit": 120,
"rpdLimit": 50000
}
}GET /v1/agents/:id
Get a single agent by ID.
PATCH /v1/agents/:id
Update agent settings (name, description, limits).
PATCH /v1/agents/:id/status
Change agent status. Requires admin role.
curl -X PATCH https://api.keystore.io/v1/agents/<id>/status \
-H "Authorization: Bearer $TOKEN" \
-d '{ "status": "paused" }'Valid statuses: active, paused, revoked.
DELETE /v1/agents/:id
Permanently delete an agent. Requires admin role.
API Keys
Agent tokens use the ks_ prefix and are SHA-256 hashed before storage. The full token is returned exactly once at creation time.
POST /v1/api-keys
Generate a new ks_ token. Requires admin role.
curl -X POST https://api.keystore.io/v1/api-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "production-key",
"projectId": "<project-id>",
"agentId": "<agent-id>",
"scope": "agent"
}'{
"key": {
"id": "uuid",
"name": "production-key",
"keyPrefix": "ks_a1b2c3d4",
"scope": "agent",
"createdAt": "2026-03-06T10:00:00.000Z"
},
"secretKey": "ks_a1b2c3d4e5f6...full64hexchars"
}The secretKey field is only returned in this response. Store it securely.
Scopes:
agent-- scoped to a specific agent (default)project-- scoped to all agents in a projectorg-- scoped to the entire organization
GET /v1/api-keys
List active (non-revoked) API keys. Full keys are never returned.
{
"keys": [
{
"id": "uuid",
"name": "production-key",
"keyPrefix": "ks_a1b2c3d4",
"scope": "agent",
"lastUsedAt": "2026-03-06T09:00:00.000Z",
"expiresAt": null,
"createdAt": "2026-01-15T10:00:00.000Z"
}
],
"total": 1
}DELETE /v1/api-keys/:id
Revoke an API key. Requires admin role. The key is soft-deleted (marked with revokedAt timestamp) and immediately stops working.
Providers
GET /v1/providers
List all built-in providers.
{
"providers": [
{ "slug": "openai", "displayName": "OpenAI" },
{ "slug": "anthropic", "displayName": "Anthropic" },
{ "slug": "neon", "displayName": "Neon" },
{ "slug": "vercel", "displayName": "Vercel" },
{ "slug": "resend", "displayName": "Resend" },
{ "slug": "s3", "displayName": "AWS S3" }
]
}GET /v1/providers/accounts?agentId={id}
List provider accounts assigned to an agent.
{
"accounts": [
{
"id": "uuid",
"providerSlug": "openai",
"status": "active",
"isByok": "true",
"mode": "byok",
"createdAt": "2026-01-15T10:00:00.000Z"
}
],
"total": 1
}POST /v1/providers/accounts
Assign a provider to an agent with credentials. Requires admin role.
curl -X POST https://api.keystore.io/v1/providers/accounts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"agentId": "<agent-id>",
"providerSlug": "openai",
"credentials": { "apiKey": "sk-real-openai-key" }
}'Credentials are encrypted with AES-256-GCM before storage.
POST /v1/providers/accounts/:id/rotate
Rotate credentials for a provider account. Requires admin role.
curl -X POST https://api.keystore.io/v1/providers/accounts/<id>/rotate \
-H "Authorization: Bearer $TOKEN" \
-d '{ "credentials": { "apiKey": "sk-new-openai-key" } }'DELETE /v1/providers/accounts/:id
Remove a provider from an agent. Requires admin role.
Custom Providers
GET /v1/custom-providers
List custom and built-in providers for your organization.
POST /v1/custom-providers
Register a new custom provider. Requires admin role.
curl -X POST https://api.keystore.io/v1/custom-providers \
-H "Authorization: Bearer $TOKEN" \
-d '{
"slug": "my-api",
"displayName": "My API",
"baseUrl": "https://api.example.com",
"authType": "bearer"
}'PUT /v1/custom-providers/:id
Update a custom provider. Requires admin role.
DELETE /v1/custom-providers/:id
Soft-delete a custom provider. Requires admin role.
Usage
GET /v1/usage/agents/:agentId
Get usage summary grouped by provider.
curl "https://api.keystore.io/v1/usage/agents/<agent-id>?start=2026-03-01&end=2026-03-06" \
-H "Authorization: Bearer $TOKEN"{
"usage": [
{
"providerSlug": "openai",
"totalRequests": 4250,
"totalTokensIn": 850000,
"totalTokensOut": 120000,
"totalCostCents": 1200,
"avgDurationMs": 450
}
]
}GET /v1/usage/agents/:agentId/summary
Get aggregated metrics with budget insights.
Query parameter: period -- one of 24h, 7d, 30d, 90d.
{
"totalRequests": 4250,
"errorRate": 1.2,
"p50Latency": 320,
"p95Latency": 890,
"costCents": 1200,
"budgetCents": 5000,
"burnRateCentsPerHour": 1.67,
"projectedMonthlyCents": 1202,
"daysUntilBudget": 76.2
}GET /v1/usage/agents/:agentId/timeseries
Get time-bucketed data for charts.
Query parameters: period (24h, 7d, 30d, 90d), interval (hour, day).
{
"timeseries": [
{
"bucket": "2026-03-06 10:00:00",
"requests": 120,
"errors": 2,
"avgLatency": 350,
"costCents": 45
}
]
}GET /v1/usage/agents/:agentId/providers
Get per-provider breakdown for an agent.
{
"providers": [
{
"providerSlug": "openai",
"totalRequests": 3000,
"errorCount": 15,
"avgLatency": 420,
"p95Latency": 890,
"totalCostCents": 900,
"totalTokensIn": 600000,
"totalTokensOut": 90000
}
]
}GET /v1/usage/agents/:agentId/logs
Get filtered proxy logs with pagination.
Query parameters: limit, offset, provider, statusMin, statusMax, start, end, search.
GET /v1/usage/agents/:agentId/export
Export usage data as JSON or CSV.
Query parameters: format (json, csv), period (24h, 7d, 30d, 90d).
GET /v1/usage/agents/compare
Compare metrics across multiple agents.
Query parameters: agentIds (comma-separated), period.
Webhooks
GET /v1/webhooks
List webhook endpoints for your organization.
POST /v1/webhooks
Create a webhook endpoint. Requires admin role. Returns the signing secret once.
PATCH /v1/webhooks/:id
Update endpoint URL, events, or status. Requires admin role.
DELETE /v1/webhooks/:id
Delete a webhook endpoint and its deliveries. Requires admin role.
GET /v1/webhooks/:endpointId/deliveries
List delivery logs for an endpoint. Filter by status query parameter.
POST /v1/webhooks/deliveries/:deliveryId/retry
Manually retry a failed delivery.
Status
GET /v1/status
Public endpoint (no auth required). Returns provider health status based on circuit breaker state.
{
"status": "operational",
"providers": [
{ "slug": "openai", "displayName": "OpenAI", "state": "CLOSED", "status": "operational" },
{ "slug": "anthropic", "displayName": "Anthropic", "state": "CLOSED", "status": "operational" }
],
"lastUpdated": "2026-03-06T12:00:00.000Z"
}Circuit breaker states: CLOSED (operational), HALF_OPEN (degraded), OPEN (outage).
Pagination
List endpoints support pagination via limit and offset query parameters:
limit-- maximum items per page (default 50, max 100)offset-- number of items to skip (default 0)
Paginated responses include a total field for the full count.
Error Responses
All error responses follow a consistent format:
{ "error": "Human-readable error message" }Common HTTP status codes:
| Status | Meaning |
|---|---|
| 400 | Bad request (validation error) |
| 401 | Missing or invalid authentication |
| 403 | Insufficient permissions |
| 404 | Resource not found |
| 409 | Conflict (e.g., duplicate slug) |
| 429 | Rate limited or budget exceeded |
| 500 | Internal server error |