---
title: "API service reference"
description: "A Node API with signed webhooks, jobs with a dead-letter queue and an outbound partner call, on the SOC 2 baseline."
section: "guides"
status: "Planned for 0.7"
spec: "reference-architectures"
canonical: "https://mainsdk.dev/docs/guides/reference-api-service"
updated: "2026-10-28"
---

# API service reference

> A Node API with signed webhooks, jobs with a dead-letter queue and an outbound partner call, on the SOC 2 baseline.

Status: Planned for 0.7 · Canonical: https://mainsdk.dev/docs/guides/reference-api-service · Updated: 2026-10-28

<!-- generated by tools/build_reference.py from packages/cli/templates/mainsdk/api-service; edit the template, not this page -->

Start from it: `npx mainsdk init --template mainsdk/api-service`

The reference architecture for an HTTP API service: a verified inbound webhook, background jobs that retry and then land in a dead-letter queue, and sign-in for the routes people call. Pack: SOC 2 baseline.

The handler is a plain `fetch` function, so Hono or Express can sit in front of it. The interfaces (`webhooks`, `jobs`, `auth`, `http`) stay the same.

Runs with no account and no provider key: `npm run dev` serves it on local drivers. `npm test` runs the tests: a delivery with a bad signature is refused before the handler, a job that fails three times lands in the dead-letter queue.

Every change and every MainSDK release runs `.github/workflows/reference.yml`: `mainsdk check`, `mainsdk drill restore --to scratch` and `mainsdk assure --standard soc2`, `npm run bench` and `npm run chaos` (the partner down, slow or answering nonsense must never lose a delivery). `npm run verify-ci` fails when a job is removed.

Moving off Express with raw provider SDKs? `mainsdk migrate raw` ends here.

## Performance

The benchmark harness fails a change when p95 goes over 25 ms on local drivers (`npm run bench`).

## main.yaml

```yaml
schema: 1
project: prj_template_api_service
baseline: main27
auth:
  driver: local
  methods: [passkeys, link]
  admin: { require: phishing-resistant }
db:
  driver: local
jobs:
  driver: local
  dead-letter: true
webhooks:
  inbound:
    github: { preset: github, secret: secret:GITHUB_WEBHOOK_SECRET, path: /webhooks/github }
logging:
  audit: true
  retention: 12m
network:
  allow: [status.partner.dev]
  default: deny
  mode: observe
  http:
    timeout: 2s
    retries: { attempts: 1, backoff: 100ms, max: 1s }
# Evidence for `mainsdk assure --standard soc2`: change control is on (a pull request and a green gate before every merge),
# and the providers the app runs on are registered.
compliance:
  change-control: true
  providers: [local, ./backup.ts]
backup:
  rpo: 15m
  rto: 1h
  keep: 7d
  drill: { every: 30d, to: scratch }
  driver: ./backup.ts
```

## app.ts

```ts
// The API service reference architecture: an HTTP service with a verified inbound webhook, a background job that
// retries and then lands in the dead-letter queue, and sign-in for the routes people call. Hono or Express can
// replace the fetch handler; the interfaces (webhooks, jobs, auth) stay the same.
import { auth, http, jobs, MainError, webhooks } from 'mainsdk'

const synced: string[] = []

/**
 * The job the webhook queues: ask the partner whether it is ready, then record the sync. A partner that is down, slow
 * or answering nonsense fails the attempt; the queue retries it and then keeps it in the dead-letter queue, so no
 * delivery is lost. A repository named `broken` always fails, to show the retries.
 */
async function syncRepo(input: { repo: string }): Promise<void> {
  if (input.repo === 'broken')
    throw new MainError('MAIN-APP-0001', { detail: `Could not sync ${input.repo}`, status: 502 })
  const res = await http.fetch('https://status.partner.dev/v1/ready')
  const body = (await res.json()) as { ready?: boolean }
  if (!res.ok || body.ready !== true)
    throw new MainError('MAIN-APP-0002', { detail: 'The partner is not ready', status: 503 })
  synced.push(input.repo)
}

/** The inbound webhook: the signature is checked over the raw body before this runs; a bad one never gets here. */
async function onDelivery(body: unknown, meta: { id: string }): Promise<void> {
  const repo = String((body as { repository?: { name?: string } }).repository?.name ?? '')
  // One job per delivery: the provider's delivery id is the idempotency key, so a duplicate queues nothing.
  await jobs.run('repo.sync', { repo }, { idempotencyKey: meta.id, retries: 2, backoff: 'fixed' })
}

/** Register the handlers on the running app. It is cheap and safe to repeat, so every request does it. */
function wire(): ReturnType<typeof webhooks.receive> {
  jobs.on('repo.sync', syncRepo)
  return webhooks.receive('github', onDelivery)
}

const json = (status: number, body: unknown): Response => Response.json(body, { status })

/** The service: its routes, with every interface refusal answered as a problem and never a crash. */
export default async function fetch(req: Request): Promise<Response> {
  try {
    const receive = wire()
    const { pathname } = new URL(req.url)
    if (req.method === 'GET' && pathname === '/health') return json(200, { ok: true })
    if (req.method === 'POST' && pathname === '/webhooks/github') return await receive(req)
    await auth.require(req)
    if (req.method === 'GET' && pathname === '/repos') return json(200, { synced })
    if (req.method === 'GET' && pathname === '/jobs/dead') return json(200, await jobs.dead.list())
    return json(404, { error: 'not found' })
  } catch (e) {
    if (e instanceof MainError) return json(e.status, { code: e.code, title: e.title, fix: e.fix })
    throw e
  }
}
```
