API Overview

How to authenticate with the Sendook Worker, request and response formats, and the route map.

The Sendook API is a single Cloudflare Worker. Every route is JSON-in / JSON-out, every request authenticates with a Bearer token, and there are exactly two kinds of token: the master key and the per-agent API key.

Base URL

The Worker is reached at whatever route is configured in wrangler.jsonc. For the hosted deployment that's:

https://api.sendook.com

For local development:

http://localhost:8787

Authentication

Every protected route reads Authorization: Bearer <token>. There are two acceptable token kinds:

Token kindWhere it comes fromWhat it can do
Master keyThe MASTER_KEY Worker secret.POST /agents, GET /agents, DELETE /agents/:id, plus everything the per-agent key can do (for any agent).
Per-agent API keyReturned exactly once by POST /agents.All /agents/:id/* routes for its own agent. Cannot create or list agents.

The agents directory is keyed on a SHA-256 HMAC of the per-agent key (peppered with API_KEY_HASH_PEPPER). The plain key is never stored.

Route map

MethodPathAuthDescription
GET/healthnone{ ok: true, version } for liveness probes.
GET/meper-agentReturns the agent identified by the bearer token.
POST/agentsmasterCreate a new agent. Returns the per-agent API key once.
GET/agentsmasterList every non-tombstoned agent.
GET/agents/:idmaster / agent's own keyGet a single agent.
DELETE/agents/:idmasterTombstone the agent and purge its DO storage.
POST/agents/:id/messages/sendmaster / agent's own keySend an email from the agent.
GET/agents/:id/messagesmaster / agent's own keyList the agent's messages (newest first).
GET/agents/:id/threads/:threadIdmaster / agent's own keyGet one thread plus its messages in order.
POST/agents/:id/webhooksmaster / agent's own keyCreate a webhook subscription. Returns the secret once.
GET/agents/:id/webhooksmaster / agent's own keyList the agent's webhooks (no secrets).
DELETE/agents/:id/webhooks/:webhookIdmaster / agent's own keyDelete one webhook.
GET/agents/:id/webhooks/:webhookId/attemptsmaster / agent's own keyList delivery attempts for one webhook (last 100).

Agent ids are 12 lowercase alphanumeric chars. UUIDs in webhook ids and thread ids use the standard 8-4-4-4-12 form.

Request format

  • All bodies are JSON; Content-Type: application/json is required for POST/DELETE with a body.
  • Body size caps are enforced in UTF-8 bytes, not UTF-16 chars: 4 KiB for POST /agents, 4 KiB for webhook create, 1 MiB for POST .../messages/send.
  • The POST .../messages/send body validates with Zod: addresses are real email addresses, subject ≤ 998 chars, text/html non-empty when present, recipients ≤ 50 total, attachments ≤ 10 with each data ≤ ~5 MiB raw and a strict base64 character set.

Response format

  • Success uses 200 OK (read), 201 Created (create), 202 Accepted (send queued / message persisted), 204 No Content (delete).
  • 502 Bad Gateway is returned for POST .../messages/send when every recipient was rejected (the per-recipient outcomes are still in the body).
  • Errors return application/json with { "error": "<message>" } and, for Zod failures, an additional details array of issues.
  • Error status codes follow HTTP conventions: 400 for validation, 401 for missing/bad bearer, 403 for cross-agent access, 404 for unknown id (or tombstoned agent), 500 for unexpected.

Example error

{
  "error": "invalid request body",
  "details": [
    { "code": "invalid_type", "expected": "string", "path": ["subject"] }
  ]
}

CORS

OPTIONS preflights and the Access-Control-Allow-Origin header are emitted only when the request Origin matches one of the comma-separated values in the ALLOWED_ORIGINS Worker var. Anything else is silently denied — the browser sees no CORS headers and rejects the request. Set ALLOWED_ORIGINS=* to allow any origin (only do this for self-hosted deployments behind another auth layer).

Resources

  • Agents — agent CRUD with the master key.
  • Messages — send and list per-agent.
  • Threads — fetch one conversation.
  • Webhooks — subscribe to events, inspect attempts.