Docs/Advanced/API Reference

API Reference

REST API endpoints for authentication, agents, API keys, providers, usage, and webhooks.


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:

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

bash
1
2
3
4
5
6
7
8
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"
  }'
json
1
2
3
4
5
{
  "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.

bash
1
2
3
curl -X POST https://api.keystore.io/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{ "email": "you@example.com", "password": "securepassword" }'
json
1
2
3
4
5
6
{
  "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).

bash
1
2
curl -X POST https://api.keystore.io/v1/auth/refresh \
  -H "Authorization: Bearer <old-token>"
json
1
{ "token": "eyJhbGci..." }

POST /v1/auth/switch-org

Switch the active organization context.

bash
1
2
3
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.

bash
1
2
curl "https://api.keystore.io/v1/agents?projectId=<project-id>" \
  -H "Authorization: Bearer $TOKEN"
json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "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.

bash
1
2
3
4
5
6
7
8
9
10
11
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
  }'
json
1
2
3
4
5
6
7
8
9
10
11
12
{
  "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.

bash
1
2
3
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.

bash
1
2
3
4
5
6
7
8
9
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"
  }'
json
1
2
3
4
5
6
7
8
9
10
{
  "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 project
  • org -- scoped to the entire organization

GET /v1/api-keys

List active (non-revoked) API keys. Full keys are never returned.

json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "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.

json
1
2
3
4
5
6
7
8
9
10
{
  "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.

json
1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "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.

bash
1
2
3
4
5
6
7
8
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.

bash
1
2
3
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.

bash
1
2
3
4
5
6
7
8
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.

bash
1
2
curl "https://api.keystore.io/v1/usage/agents/<agent-id>?start=2026-03-01&end=2026-03-06" \
  -H "Authorization: Bearer $TOKEN"
json
1
2
3
4
5
6
7
8
9
10
11
12
{
  "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.

json
1
2
3
4
5
6
7
8
9
10
11
{
  "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).

json
1
2
3
4
5
6
7
8
9
10
11
{
  "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.

json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "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.

json
1
2
3
4
5
6
7
8
{
  "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:

json
1
{ "error": "Human-readable error message" }

Common HTTP status codes:

StatusMeaning
400Bad request (validation error)
401Missing or invalid authentication
403Insufficient permissions
404Resource not found
409Conflict (e.g., duplicate slug)
429Rate limited or budget exceeded
500Internal server error