Skip to main content

Overview

API keys provide project-scoped authentication for programmatic access to Orquestra. Unlike JWTs, which are user-scoped and short-lived, API keys are permanent (or have custom expiration) and tied to a specific project.

Use Cases

CI/CD Pipelines

Automate transaction building and testing in your deployment workflows.

Backend Services

Server-to-server API calls without user authentication.

Bots & Automation

Automated scripts and tools that interact with your Solana program.

Third-Party Integrations

Share access with external services without exposing user credentials.

Authentication

API keys use the X-API-Key header for authentication:
curl https://api.orquestra.so/api/my-project/instructions/transfer/build \
  -H "X-API-Key: b58_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{ ... }'

Create API Key

Generate a new API key for a project.

Endpoint

POST /api/projects/:projectId/keys

Authentication

Requires JWT authentication. Only project owners can create API keys.

Request Body

expiresInDays
number
Optional expiration time in days. If not provided, the key never expires.Example: 90 for a 90-day expiration

Response

id
string
API key ID
key
string
The API key (only shown once)
createdAt
string
Creation timestamp (ISO 8601)
expiresAt
string | null
Expiration timestamp (ISO 8601) or null if never expires
message
string
Warning to store the key securely

Example

curl -X POST https://api.orquestra.so/api/my-project/keys \
  -H "Authorization: Bearer <your_jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{ "expiresInDays": 90 }'

Response Example

{
  "id": "key_abc123",
  "key": "b58_9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "createdAt": "2024-01-15T10:30:00Z",
  "expiresAt": "2024-04-15T10:30:00Z",
  "message": "Store this key securely. It will not be shown again."
}
The API key is only returned once during creation. Store it securely - you cannot retrieve it again.

List API Keys

Retrieve all API keys for a project.

Endpoint

GET /api/projects/:projectId/keys

Authentication

Requires JWT authentication. Only project owners can list API keys.

Response

keys
array
List of API keys

Example

cURL
curl https://api.orquestra.so/api/my-project/keys \
  -H "Authorization: Bearer <your_jwt_token>"

Response Example

{
  "keys": [
    {
      "id": "key_abc123",
      "key": "************************0f00a08",
      "last_used": "2024-01-20T15:45:00Z",
      "created_at": "2024-01-15T10:30:00Z",
      "expires_at": "2024-04-15T10:30:00Z"
    },
    {
      "id": "key_xyz789",
      "key": "************************a3d5e7f",
      "last_used": null,
      "created_at": "2024-01-18T08:20:00Z",
      "expires_at": null
    }
  ]
}

Delete API Key

Revoke an API key immediately.

Endpoint

DELETE /api/projects/:projectId/keys/:keyId

Authentication

Requires JWT authentication. Only project owners can delete API keys.

Example

cURL
curl -X DELETE https://api.orquestra.so/api/my-project/keys/key_abc123 \
  -H "Authorization: Bearer <your_jwt_token>"

Response Example

{
  "message": "API key deleted"
}

Rotate API Key

Generate a new key value for an existing key ID.

Endpoint

POST /api/projects/:projectId/keys/:keyId/rotate

Authentication

Requires JWT authentication. Only project owners can rotate API keys.

Response

id
string
API key ID (unchanged)
key
string
New API key value (only shown once)
message
string
Warning to store the new key

Example

cURL
curl -X POST https://api.orquestra.so/api/my-project/keys/key_abc123/rotate \
  -H "Authorization: Bearer <your_jwt_token>"

Response Example

{
  "id": "key_abc123",
  "key": "b58_7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730",
  "message": "API key rotated successfully. Store the new key — it will not be shown again."
}

Using API Keys

API keys are validated using the apiKeyMiddleware and provide project-scoped access.

Middleware Behavior

  1. Extracts the API key from the X-API-Key header
  2. Queries the database for a matching key
  3. Checks expiration (expires_at must be null or in the future)
  4. Updates last_used timestamp
  5. Sets context variables:
    • apiKeyProjectId: The project ID
    • apiKeyUserId: The user ID who owns the project

Supported Endpoints

Currently, API keys are primarily used for project management endpoints. Transaction building and PDA derivation endpoints are public and don’t require authentication.
API key authentication can be used on endpoints that support apiKeyMiddleware. Check individual endpoint documentation for authentication requirements.

Example: Building a Transaction with API Key

curl -X POST https://api.orquestra.so/api/my-project/instructions/transfer/build \
  -H "X-API-Key: b58_9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" \
  -H "Content-Type: application/json" \
  -d '{
    "accounts": {
      "from": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
      "to": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
      "systemProgram": "11111111111111111111111111111111"
    },
    "args": { "amount": 1000000 },
    "feePayer": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
  }'

Key Format

API keys are generated using cryptographically secure random bytes:
const keyBytes = new Uint8Array(32);
crypto.getRandomValues(keyBytes);
const apiKey = `b58_${Array.from(keyBytes).map(b => b.toString(16).padStart(2, '0')).join('')}`;
  • Prefix: b58_ (for identification)
  • Length: 68 characters total (4 prefix + 64 hex)
  • Entropy: 32 bytes (256 bits) of randomness

Security Best Practices

  • Use environment variables or secret management services
  • Never commit API keys to version control
  • Use .env files and add them to .gitignore
  • Rotate keys if accidentally exposed
  • Set expiresInDays when creating keys for temporary use
  • Short-lived keys reduce risk if compromised
  • Review and delete expired keys regularly
  • Create separate keys for different services
  • One key per project for easier tracking
  • Delete keys when no longer needed
  • Check last_used timestamps to detect unused keys
  • Track which services are using which keys
  • Investigate unexpected usage patterns
  • Rotate keys every 90 days as a best practice
  • Rotate immediately if you suspect compromise
  • Update all services using the old key

Error Responses

401 Unauthorized
Missing API Key
{
  "error": "Unauthorized",
  "message": "Missing X-API-Key header"
}
401 Unauthorized
Invalid or Expired API Key
{
  "error": "Unauthorized",
  "message": "Invalid or expired API key"
}
403 Forbidden
Insufficient Permissions
{
  "error": "Forbidden",
  "message": "This API key does not have access to this resource"
}
404 Not Found
Project or Key Not Found
{
  "error": "Project not found or access denied"
}

Comparison: API Keys vs JWT

FeatureAPI KeyJWT
ScopeProject-specificUser-wide
LifetimePermanent or custom7 days
RevocationImmediate (delete anytime)Cannot revoke
Use CaseProgrammatic accessUser authentication
HeaderX-API-Key: <key>Authorization: Bearer <token>
CreationOwner via APIOAuth flow
Best ForCI/CD, bots, backend servicesWeb apps, mobile apps
TrackingLast used timestampNo tracking

Implementation Reference

API key authentication is implemented in /packages/worker/src/middleware/auth.ts:
export async function apiKeyMiddleware(c: Context, next: Next) {
  const apiKey = c.req.header('X-API-Key');
  
  if (!apiKey) {
    return c.json({ error: 'Unauthorized', message: 'Missing X-API-Key header' }, 401);
  }
  
  const result = await db.prepare(
    `SELECT ak.*, p.user_id, p.name as project_name 
     FROM api_keys ak 
     JOIN projects p ON ak.project_id = p.id 
     WHERE ak.key = ? AND (ak.expires_at IS NULL OR ak.expires_at > datetime('now'))`
  ).bind(apiKey).first();
  
  if (!result) {
    return c.json({ error: 'Unauthorized', message: 'Invalid or expired API key' }, 401);
  }
  
  // Update last_used timestamp
  await db.prepare('UPDATE api_keys SET last_used = datetime(\'now\') WHERE key = ?')
    .bind(apiKey)
    .run();
  
  c.set('apiKeyProjectId', result.project_id);
  c.set('apiKeyUserId', result.user_id);
  
  await next();
}

Build docs developers (and LLMs) love