Skip to main content

Overview

All /api endpoints (except public webhooks and /api/ping) require authentication via the x-api-key header.

API Key Header

Include your API key in every request:
curl -H "x-api-key: your-api-key-here" \
  https://your-domain.com/api/create-job
x-api-key
string
required
Your API key from the database. Managed through the admin UI, not environment variables.

How Authentication Works

  1. Request includes x-api-key header
  2. Server calls verifyApiKey(apiKey) from lib/db/api-keys.js
  3. Database lookup with timing-safe comparison
  4. Returns 401 Unauthorized if key is missing or invalid
function checkAuth(routePath, request) {
  if (PUBLIC_ROUTES.includes(routePath)) return null;

  const apiKey = request.headers.get('x-api-key');
  if (!apiKey) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const record = verifyApiKey(apiKey);
  if (!record) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  return null;
}

API Key Management

API keys are stored in the SQLite database (table: api_keys) and managed through the admin UI. Not environment variables. Unlike most framework secrets, API keys are database-backed for multi-key support and rotation.

Creating API Keys

Use the admin UI at /settings or insert directly into the database:
INSERT INTO api_keys (key_hash, name, created_at)
VALUES (?, ?, ?);
Keys are hashed using bcrypt before storage.

Revoking API Keys

Delete from the api_keys table:
DELETE FROM api_keys WHERE id = ?;

Public Routes

These routes do not require API keys:
  • /api/ping - Health check
  • /api/telegram/webhook - Authenticated via Telegram secret
  • /api/github/webhook - Authenticated via GitHub webhook secret
Webhook routes use their own authentication mechanisms (see Telegram and GitHub Webhook).

Timing-Safe Comparison

API key verification uses constant-time comparison to prevent timing attacks:
function safeCompare(a, b) {
  if (!a || !b) return false;
  const bufA = Buffer.from(a);
  const bufB = Buffer.from(b);
  if (bufA.length !== bufB.length) return false;
  return timingSafeEqual(bufA, bufB);
}

Error Responses

Missing API Key

curl https://your-domain.com/api/create-job
{
  "error": "Unauthorized"
}
HTTP Status: 401

Invalid API Key

curl -H "x-api-key: invalid-key" \
  https://your-domain.com/api/create-job
{
  "error": "Unauthorized"
}
HTTP Status: 401

Build docs developers (and LLMs) love