Skip to content

Developers

Send to-dos in. Hear back when they’re done.

One HTTPS request with an sdk_ key adds a to-do to a SwitchDesk list. A signed webhook tells your tool when it’s checked off.

Plan: API & webhooks, Pro

Send your first to-do ↓See what the integrations do →

A support form sends one request to the SwitchDesk to-do API with a masked sdk_ key. It answers 201 Created, and “Call Marlow & Finch back about the renewal” appears at the top of the Incoming section, labeled “from Support form” with a Fernhill chip.

Call Marlow & Finch back about the renewal
from Support form
1.Prep pricing slides for ThursdayFernhill
2.Reply to Daniel about the Q4 hiring planFernhill
3.Book Theo's dentist appointmentFamily
On this page: Quickstart
  1. Quickstart
  2. Overview
  3. Authentication with sdk_ keys
  4. Create a to-do
  5. Update status
  6. Status webhooks
  7. Meeting exports
  8. Siri & Shortcuts
  9. API changelog

Quickstart

  1. Get a key. The list’s owner makes one key per tool in the app, under Settings → Incoming to-dos, and gives it to you. It starts with sdk_.
  2. Put it in SWITCHDESK_API_KEY where your code runs. Never hard-code it, log it or commit it.
  3. POST one to-do. Only text is required: the key’s own answers say where it goes and what it’s from. A 201 means it’s waiting in the owner’s Incoming.

Your first to-do

export SWITCHDESK_API_KEY=sdk_••••   # the key the list's owner gave you

curl -X POST "https://fyqpyruizhwicpfqevot.supabase.co/functions/v1/inbound-todos" \
  -H "x-api-key: $SWITCHDESK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Call Marlow & Finch back about the renewal"}'

201Created

Overview

The to-do API adds items to a SwitchDesk list. The list’s owner makes one key per tool in the app, under Settings → Incoming to-dos, and gives it to you. New items wait in Incoming, above “Today’s Todo”, or land at the bottom of a category.

RequestDoes
POST /inbound-todosSends one to-do. The body is one JSON object; arrays are rejected.
PATCH /inbound-todosChanges the status of a to-do you sent.
POST /inbound-todos/status/readReturns the current status of an item you sent.
GET /inbound-todosReturns the list’s current categories.

Each key’s Docs tab has these instructions, already set to that key’s answers: Hand this to your LLM or developer. It follows this key's options, and your key is never in it: the code reads it from SWITCHDESK_API_KEY.

Building an AI agent that reads and edits the whole list? That is AI Access: MCP, a REST API and the switchdesk CLI with per-permission keys, separate from these sdk_ keys. AI Access docs (MCP tools, REST, CLI).

Base URL

URL
https://fyqpyruizhwicpfqevot.supabase.co/functions/v1/inbound-todos

Authentication with sdk_ keys

Send the key in an x-api-key header, with Content-Type: application/json. Keys start with sdk_, and the app shows a new key once:

“Copy it now. For your security it won't be shown again. Store it as the SWITCHDESK_API_KEY environment variable where the code runs.”

Never hard-code the key, log it or commit it. A revoked key answers 403 key_revoked; a missing or wrong one, 401.

Try the key by listing categories. A request may name one of them, by name (not case-sensitive) or by id.

List categories

curl "https://fyqpyruizhwicpfqevot.supabase.co/functions/v1/inbound-todos" \
  -H "x-api-key: $SWITCHDESK_API_KEY"

200Response

{
  "ok": true,
  "categories": [
    { "id": "…", "name": "…" }
  ]
}

Create a to-do

POST one JSON object to the endpoint. Only text is required.

FieldWhat it is
text requiredThe to-do title, 1 to 1000 characters, one line. Write it as an action.
notesLonger detail, up to 4000 characters. Line breaks are kept.
sourceUp to 40 characters, shown as “from <source>”. Without it, the key’s name is shown.
destination"incoming" or "category". Omit it and the item goes to category when one is given, otherwise to Incoming.
categoryA category name. With "destination": "incoming" it tags the item instead of filing it.
category_idA category id from GET. Use it or category, not both.
external_idYour stable id for the item, up to 200 characters. Sending it again returns 200 with "duplicate": true and creates nothing. Ids are remembered for 90 days.

The key’s own answers fill in what a request leaves out: where items go, and the “from” label. “Today’s Todo” and This Week are the user’s to fill by hand. Naming either one, or a category that doesn’t exist, puts the item in Incoming with a warning. Nothing is rejected or created because of a category name.

Links: write [words](https://…) in text and the app shows the words, underlined and clickable. Only http, https and mailto links work.

StatuserrorMeaning
400invalid_json batch_not_supportedThe body isn’t one JSON object.
401missing_key invalid_keyNo key, or not a valid key.
403key_revokedThe key was revoked in the app.
413body_too_largeThe body is over 32 KB.
422text_required text_too_long invalid_destination …Fix the payload; retrying unchanged will fail again.
429rate_limited daily_limit incoming_fullOver a limit. Wait the Retry-After seconds, or until the user sorts Incoming.
500server_errorRetry with exponential backoff.

Retry only network failures, 500 and rate_limited, always with the same body and the same external_id.

Send a to-do

curl -X POST "https://fyqpyruizhwicpfqevot.supabase.co/functions/v1/inbound-todos" \
  -H "x-api-key: $SWITCHDESK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Call Marlow & Finch back about the renewal",
    "source": "Support form",
    "destination": "incoming",
    "category": "Fernhill"
  }'

201Response

{
  "ok": true,
  "id": "…",
  "duplicate": false,
  "destination": "incoming",
  "category": null,
  "preassigned_category": "Fernhill",
  "warnings": []
}

Update status

Mark an item you sent as "todo", "in_progress" or "done" with PATCH. If your tool can’t send PATCH, POST the same body to /inbound-todos/status. Send exactly one of external_id or id (from the create response).

  • Only items that came in through the API can be updated. To-dos the user typed can’t.
  • Update only after the create came back 201 or 200, never in parallel with it.
  • Sending the status you sent last does nothing, even if the user changed the item since. A repeat never overrides their change.
  • An update never moves, renames or re-files the item. There’s no delete.
  • 404 not_found means it was deleted or given to a teammate: stop updating it.

To read a status, POST /inbound-todos/status/read with {"external_id": "…"} or {"id": "…"}. Never put an external_id in a URL: URLs are logged.

Mark it done

curl -X PATCH "https://fyqpyruizhwicpfqevot.supabase.co/functions/v1/inbound-todos" \
  -H "x-api-key: $SWITCHDESK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "<id from the create response>", "status": "done"}'

200Response

{
  "ok": true,
  "id": "…",
  "external_id": null,
  "status": "done",
  "previous_status": "in_progress",
  "changed": true,
  "warnings": []
}

Read a status

curl
curl -X POST "https://fyqpyruizhwicpfqevot.supabase.co/functions/v1/inbound-todos/status/read" \
  -H "x-api-key: $SWITCHDESK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "<id from the create response>"}'

Status webhooks: signing, retries, ordering

Turn on Tell the sending tool when you finish an item? for a key and SwitchDesk sends your code an HTTPS POST when the user finishes, reopens, starts or removes an item that key sent.

  1. Build a receiver at a public https:// URL (port 443, a domain name, not an IP address).
  2. Read the signing secret from SWITCHDESK_WEBHOOK_SECRET. It starts with whsec_.
  3. The user pastes the URL into the app and presses “Send test event”. Verify it and answer 2xx.
typeMeaning
todo.status_changedThe status changed. data.status is now "todo", "in_progress" or "done".
todo.removedThe item left the list: deleted, or given to a teammate. If data.status is "done", it was finished first.
webhook.testThe app’s “Send test event” button. Verify it, answer 2xx, do nothing else.

Signing

Calls carry webhook-id, webhook-timestamp and webhook-signature: the Standard Webhooks format, so its libraries verify them too. Compute base64(HMAC-SHA256(key, {webhook-id}.{webhook-timestamp}.{raw body})) with the base64-decoded part of the secret after whsec_, over the raw body bytes. Reject timestamps more than 5 minutes off, and compare in constant time.

Retries

Answer 2xx within 10 seconds; the reply body is ignored. Anything else is retried with the same id and body over about 2 days. Redirects aren’t followed. Answer 410 Gone to turn calls off for the key. If every call fails for 3 days, calls turn off and the app shows why.

Ordering

Delivery is at least once: keep the ids you’ve handled for 3 days. Each item’s events carry data.sequence; apply an event only if it’s higher than the last one you applied. A "done" can be undone seconds later, and when your system changes because of a call, don’t send that change back.

If your tool can’t verify signatures (Zapier, Make), the URL itself is the secret: keep it private.

Receive and verify

{
  "id": "evt_4f1c2a9b0d3e4c5fa6b7c8d9e0f1a2b3",
  "type": "todo.status_changed",
  "occurred_at": "2026-09-23T18:04:11.482Z",
  "key_prefix": "sdk_••••",
  "data": {
    "id": "0d6f3c1e-8a2b-4c7d-9e1f-2a3b4c5d6e7f",
    "external_id": "slack:C0123ABCD:1712345.6789",
    "status": "done",
    "previous_status": "in_progress",
    "sequence": 3,
    "changed_by": "app"
  }
}

Meeting exports

An export sends a copy of each future call in one category to a webhook, set up in Settings → Meeting exports. Only calls that start after it’s saved are sent. SwitchDesk’s server signs and relays each delivery from the Mac and keeps no copy. The address and secret stay there, never on teammates’ Macs.

Payload

One meeting.exported event per call. content carries only the parts the owner picked: summary, key points, decisions and action items as Markdown, plus My Notes, the transcript and invitees when switched on.

Signature verification

The same Standard Webhooks headers as status webhooks, signed with the export’s own whsec_ secret. The owner sees the secret once; “New signing secret…” in the export’s menu replaces it, and the old one stops working at once.

  • Answer 2xx within 15 seconds.
  • 5xx, 408 and 429 are retried for up to 3 days with the same webhook-id: dedupe on it.
  • 410 Gone turns the export off; other 4xx aren’t retried, and redirects aren’t followed.
  • Verify the signature over the raw body before parsing it.

Review first

With Ask me before each send, nothing goes until the person who recorded the call presses Send on their Mac. The other choice is Send automatically (calls with a category you or your other calls set; AI-guessed categories still ask).

Pause

While paused nothing is sent from any of your Macs. Calls that start during a pause are never exported; calls from before it go when you resume.

Receive an export

{
  "id": "msg_<idempotency key>",
  "type": "meeting.exported",
  "api_version": "2026-10-01",
  "created_at": "2026-10-07T21:31:12Z",
  "test": false,
  "destination": { "id": "<uuid>", "name": "Fernhill calls" },
  "recorded_by": { "name": "Sam Rivera", "email": "sam@example.com", "is_owner": true },
  "data": {
    "schema": "switchdesk.meeting-export",
    "schema_version": 1,
    "rendered_at": "2026-10-07T21:31:12Z",
    "meeting": {
      "id": "mtg_k3j9…",
      "revision": 1,
      "title": "Lumora kickoff",
      "started_at": "2026-10-07T21:14:00Z",
      "ended_at": "2026-10-07T21:30:02Z",
      "duration_seconds": 962,
      "category": "Fernhill"
    },
    "invitees": [ { "name": "Joe Raman", "email": "joe@example.com", "is_self": false } ],
    "organizer_email": "sam@example.com",
    "content": {
      "summary": "markdown",
      "key_points": "markdown",
      "decisions": "markdown",
      "action_items": "markdown",
      "my_notes": "markdown",
      "transcript": {
        "speaker_names": true,
        "segments": [ { "start": 12.4, "speaker": "Joe Raman", "text": "…" } ]
      }
    }
  }
}

Headers

HTTP
Content-Type: application/json
User-Agent: SwitchDesk-MeetingExport/1
webhook-id: msg_<idempotency key>
webhook-timestamp: <unix seconds>
webhook-signature: v1,<base64 HMAC-SHA256>

Siri & Shortcuts

SwitchDesk’s “Add Todo” action works from Siri and the Shortcuts app, with no key. Name a category and the to-do goes straight there; otherwise SwitchDesk picks one. Settings → Siri

  • “Talk to SwitchDesk”
  • “Add a todo to SwitchDesk”
  • “Add to category in SwitchDesk”

API changelog

Build 221

Send finished calls to Zapier, Make, n8n or your own server

In Settings → Developer → Meeting exports, point a category at a webhook, and its calls are sent there when they end, signed by our server so the receiver can trust them. Only calls recorded after you set it up are sent, and by default you review each one first.

  • Meetings

Build 196

Tools that send you to-dos hear back when you finish them

Switch it on for a key in Settings → Developer, and when you finish, start, reopen or delete a to-do that key sent, the tool gets a signed webhook within a few seconds.

  • To-dos

Build 192

Settings → Developer in three steps

Settings → Developer now walks you through three steps: make a key, choose that key’s options, then copy its documentation. “Sync now” moves to Account.

  • To-dos

Build 191

Tools that sent you a to-do can mark it in progress or done

A tool that sent you a to-do with your key can now mark it in progress or done. In dictation, version numbers come out typed with their dots and dashes: say “two point four point one” and you get 2.4.1.

  • To-dos
  • Dictation

Build 190

Other tools can send you to-dos

Make a key in Settings → Developer → Incoming to-dos, and a support form or your own script can add to-dos to your list. They arrive in Incoming, and links in to-dos are now clickable.

  • To-dos

Every update in the changelog →

Make a key, then send your first to-do in.

See what the integrations do →

SwitchDesk is a Mac app

Send yourself the download link, then open it on your Mac.

switchdesk.org/download

Or scan it with your phone’s camera.