API reference

OverDue ships two developer primitives, intentionally generic so you can plug in whatever automation stack you already use (n8n, Zapier, OpenClaw, Hermes, a shell script): a token-authed REST surface at /api/v1/* and signed outbound webhooks for task and triage events. Generate a token and add a webhook in Settings → Developer.

Auth

Every /api/v1/* request needs an Authorization: Bearer <token> header. Tokens are scoped to a single user; the server identifies your account from the token. Tokens are shown once at creation and stored hashed (sha256). If you lose one, revoke it and generate a new one.

Each token is capped at 100 requests per minute. A 429 response includes a Retry-After header in seconds.

curl https://overdueapp.com/api/v1/me \
  -H "Authorization: Bearer ovd_live_..."

# 200 OK
{
  "user":  { "id": "uuid", "display_name": null, "timezone": "America/New_York" },
  "token": { "label": "n8n flow", "scopes": ["full_access"], ... }
}

Endpoints

GET /api/v1/me

Identify the calling token. Returns the owner user and the token metadata.

GET /api/v1/tasks

List tasks for the token’s user. Query params: status (comma-separated subset of inbox,now,next,waiting,later,scheduled,done), category_id, limit (1-200, default 100), cursor (ISO timestamp, paginate by next_cursor).

POST /api/v1/tasks

Create a task. Required: title. Everything else is optional and defaults match the in-app create flow.

curl https://overdueapp.com/api/v1/tasks \
  -H "Authorization: Bearer ovd_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Draft Q3 board update",
    "effort_min": 90,
    "priority": 2,
    "energy": "deep",
    "revenue_imp": "indirect",
    "due_at": "2026-05-26T17:00:00-04:00",
    "earliest_start": "2026-05-22T09:00:00-04:00",
    "source": "n8n",
    "source_url": "https://my-n8n/workflow/42"
  }'

# 201 Created
{ "task": { "id": "...", "title": "...", "status": "inbox", ... } }

GET /api/v1/tasks/{id}

Read one task.

PATCH /api/v1/tasks/{id}

Update one task. Send only the fields you want to change. Setting status: "done" stamps completed_at and fires task.completed.

POST /api/v1/tasks/{id}/notes

Append a work-log entry to a task without overwriting existing notes. Built for agent write-back: after an automation does work on a task, it records what it did here. Each call appends a timestamped, attributed line ([ISO · author] text), so human notes and agent logs coexist. Body: text (required), author(optional, defaults to "agent").

curl https://overdueapp.com/api/v1/tasks/<id>/notes \
  -H "Authorization: Bearer ovd_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "text": "Drafted the renewal email, waiting on your review.", "author": "Lisa" }'

POST /api/v1/tasks/bulk (Pro)

Insert up to 200 tasks in one call. Same per-item shape as POST /tasks; wrap under a tasks array. For migration scripts + LLM-emitted batches. Personal tier returns 403.

GET /api/v1/audit (Pro)

Read your account’s activity log: task creations, completions, voice ingests, scheduler runs. Default window is the last 30 days. Query params: since, until, kind, limit (1-500).

POST /api/v1/voice/items

Push an external action item into the triage queue. OverDue stores it as a one-item synthetic transcript (provider manual), so the in-app triage UI handles it the same way as a Fireflies/Fathom/Grain item.

curl https://overdueapp.com/api/v1/voice/items \
  -H "Authorization: Bearer ovd_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Send the renewal proposal to AcmeCo",
    "transcript": "Optional full transcript or context for triage.",
    "source_url": "https://my-recorder/...",
    "suggested_window": "lis"
  }'

GET /api/v1/events

Read auto-scheduled placements over a window. Query params: from / to ISO datetimes (default: last 24h to next 7d), limit (1-500, default 200). Google Calendar events are deliberately not exposed here — read those directly from Google.

AI features (Pro)

Two endpoints powered by the server-side LLM. Available when ANTHROPIC_API_KEY or OPENAI_API_KEY is configured on the server; 503 otherwise.

GET /api/v1/events/{placement_id}/brief

AI prep note for a single scheduled placement — 2–4 bullets, ~120 words: first concrete action, shape of done, the risk to dodge, optional handoff. Generated on first read, cached on the row. POST with {"regen": true} forces a re-generation.

GET /api/v1/briefing

Today’s morning briefing: highest-stakes calendar item, anything overdue / at-risk, the shape of the day, plus a specific first-60-minutes suggestion. Pre-generated by a 6 AM ET cron for active Pro users; on-demand on first read otherwise. Optional ?date=YYYY-MM-DD for a past day.

CLI

For terminal-first power users. Read commands work on any tier; write commands (add, done, note) require Pro.

$ export OVERDUE_TOKEN=ovd_live_...
$ npx overdue today                          # today's scheduled blocks
$ npx overdue list --status=now --limit=5    # tasks (filterable)
$ npx overdue add "Draft Q3 deck" --priority=2 --due=2026-06-15 --energy=deep
$ npx overdue done 1a2b3c4d                  # accepts 8-char id prefix
$ npx overdue note 1a2b3c4d "Drafted; awaiting review" --author=Lisa
$ npx overdue me                             # confirm token + scope

Source is in the OverDue repo at bin/overdue.mjs (single file, zero dependencies, runs on Node 18+).

MCP server (Pro)

OverDue exposes itself as a Model Context Protocol (MCP) server so Claude Desktop, Cursor, Zed, Codex, or any MCP-aware client can plug in and call OverDue tools directly — no glue code. Pro-tier tokens only.

Endpoint: https://overdueapp.com/api/mcp (POST, JSON-RPC 2.0 with Streamable HTTP transport). Auth header is the same Authorization: Bearer ovd_… you use for the rest of the API.

Tools exposed:

  • list_tasks — read recent tasks (filter by status / category)
  • create_task — add a task; the scheduler places it on the next tick
  • update_task — modify any task field, including status
  • complete_task — mark a task done + stamp completed_at
  • append_note — add a timestamped work-log entry to a task without clobbering existing notes
  • list_schedule — read scheduled placements over a window

Claude Desktop config example:

{
  "mcpServers": {
    "overdue": {
      "transport": {
        "type":    "streamable-http",
        "url":     "https://overdueapp.com/api/mcp",
        "headers": { "Authorization": "Bearer ovd_live_..." }
      }
    }
  }
}

Category-scoped tokens propagate through MCP automatically: an LIS-only token can only read/write LIS tasks even when called from Claude.

Webhooks

OverDue POSTs a JSON payload to each registered webhook URL when a subscribed event fires. Failed deliveries (5xx, timeout, or any non-2xx) retry up to two times with backoff (+30s, +5min). After 100 consecutive failures the webhook auto-disables; re-enable it from the Developer page.

Payload

POST https://your-endpoint.example.com/overdue
Content-Type: application/json
User-Agent: OverDue-Webhooks/1
X-OverDue-Event: task.created
X-OverDue-Delivery: <uuid; same id across retries>
X-OverDue-Signature: sha256=<hex hmac of the request body>

{
  "event":   "task.created",
  "user_id": "uuid",
  "at":      "2026-05-19T18:42:00.000Z",
  "data":    { "task": { "id": "...", "title": "...", ... } }
}

Verifying the signature

Compute sha256=<hex hmac> of the raw request body using your webhook secret, then compare against the X-OverDue-Signature header. Use a constant-time comparison (e.g. crypto.timingSafeEqual).

// Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret)
    .update(rawBody, 'utf8').digest('hex');
  const a = Buffer.from(expected, 'utf8');
  const b = Buffer.from(signatureHeader, 'utf8');
  return a.length === b.length && timingSafeEqual(a, b);
}

Event types

  • task.createdTask created
  • task.completedTask completed
  • task.scheduledTask scheduled
  • checkin.preCheck-in: pre
  • checkin.postCheck-in: post
  • checkin.slipCheck-in: slipped
  • briefing.readyDaily briefing ready
  • voice_item.queuedVoice triage item queued

Category scoping

Both tokens and webhooks can be restricted to one or more categories. An unscoped token has full access to every task; a scoped token can only see, create, and modify tasks whose category_id is in its allowed set. Pick the categories at creation time from Settings → Developer.

Tokens: GET /api/v1/tasks filters to allowed categories. POST without a category_id auto-fills the first allowed one; an explicit category outside the set returns 403. PATCH to a task outside the set returns 404 (no existence leak), and moving a task into a category outside the set returns 403. GET /api/v1/events only returns placements for in-scope tasks. POST /api/v1/voice/items is exempt — triage items are pre-category, so the user (or another token) is the one who eventually picks one on accept.

Webhooks: for a scoped webhook, task.created, task.completed, and task.scheduled only fire when the task's category_id is in the allowed set. Events without category attribution (voice_item.queued, briefing.ready, checkin.*) are skipped — run a second unscoped webhook for those.

Confirm a token's scope by calling GET /api/v1/me — the response includes a token.categories array (or null for unrestricted).

Versioning

Everything under /api/v1/* is a stable contract. Breaking changes ship under a new prefix and the old one keeps working for at least six months. Additive changes (new optional fields, new event types) can land in v1 without notice — your client should ignore unknown fields and unknown event types.