API reference contents
API reference
Documentation
Quick start
Five calls, about five minutes: check the key, read the desk, list the work, open one item, complete it with a note.
Everything on this page can be pasted into a terminal in order. Nothing on it sends a message or spends anything: listing work reads, reading an item reads, and completing an item records an act that was going to happen anyway.
1. Create a key
Sign in to OutcomeDev, open the account menu, choose API keys, name the key and create it. The secret appears once. Copy it now.
od_K7fQ2mVx8sT1pR4nB6wY3zA
Put it in the environment rather than in the command, so it does not end up in shell history:
export OD_KEY=od_K7fQ2mVx8sT1pR4nB6wY3zA
export OD=https://outcomedev.com/api/v1
The full detail on keys, including the one-time display and what revocation does, is in Authentication.
2. Check the key
curl -s "$OD/" -H "Authorization: Bearer $OD_KEY"
The root is the cheapest call in the API. It confirms the key works and says which desk it holds:
{
"data": {
"api": "OutcomeDev",
"version": "v1",
"desk": "northgate",
"key": {
"id": 4,
"name": "reporting-script",
"prefix": "od_K7fQ2mVx"
},
"documentation": "https://outcomedev.com/docs/api/",
"openapi": "/api/v1/openapi.json",
"note": "Each key acts as one desk. Writes here are the same actions a person takes on the screen, and anything that sends a message goes out on the owner's behalf after they approved it."
}
}
If this returns 401, the key is missing, mistyped or revoked. Nothing else
in the API will work, so it is worth stopping here and reading
Errors.
3. Read the desk
curl -s "$OD/me" -H "Authorization: Bearer $OD_KEY"
{
"data": {
"key": { "id": 4, "name": "reporting-script", "prefix": "od_K7fQ2mVx" },
"desk": {
"id": "northgate",
"name": "Northgate",
"slug": "northgate",
"plan": "growth",
"status": "active"
},
"work": { "open": 34, "waiting": 6, "closed_today": 3 }
}
}
open counts everything not closed, including what is waiting on somebody
else. waiting is the subset where an answer is owed.
closed_today counts rows closed since midnight.
4. List the work
curl -s "$OD/work?limit=3" -H "Authorization: Bearer $OD_KEY"
{
"data": {
"items": [
{
"id": 47,
"title": "Renew the business licence",
"detail": null,
"kind": "renewal",
"status": "open",
"due_date": "2027-02-28",
"days_to_due": 138,
"overdue": false,
"priority": 92,
"waiting_on": null,
"days_waiting": null,
"last_error": null,
"action": {
"type": "approve",
"label": "Approve and send",
"hint": "A message is ready."
},
"permission": "reach_out",
"allowed": false,
"has_draft": true
},
{
"id": 51,
"title": "Confirm the public liability cover note",
"detail": "Sent Tuesday. No reply yet.",
"kind": "chase",
"status": "waiting",
"due_date": "2027-01-14",
"days_to_due": 93,
"overdue": false,
"priority": 71,
"waiting_on": "Harbour Underwriting",
"days_waiting": 9,
"last_error": null,
"action": {
"type": "nudge",
"label": "Chase it",
"hint": "Waiting on Harbour Underwriting for 9 days. Long enough to ask again."
},
"permission": "chase",
"allowed": true,
"has_draft": false
},
{
"id": 58,
"title": "File the quarterly return",
"detail": null,
"kind": "filing",
"status": "open",
"due_date": "2026-11-30",
"days_to_due": 48,
"overdue": false,
"priority": 64,
"waiting_on": null,
"days_waiting": null,
"last_error": null,
"action": {
"type": "run",
"label": "Let the desk do it",
"hint": "Nobody has to sign this one off, so the desk can finish it on its own."
},
"permission": "file",
"allowed": false,
"has_draft": false
}
],
"next_cursor": "aWQ6NTg",
"open_total": 34
}
}
Read that output the way a client has to. Each row carries one thing to do
(action), which permission covers it (permission), and whether
the desk is already allowed to do that class of work without asking
(allowed). Row 47 is waiting on a person's approval. Row 51 has gone quiet
and is asking to be chased — and on this desk that class is already granted. Row 58
could be finished by the desk, but allowed is false.
open_total is 34 while three items came back, because it counts the whole
open queue before filtering and before limit. That distinction is the point
of the field. See Pagination.
5. Read one item
curl -s "$OD/work/47" -H "Authorization: Bearer $OD_KEY"
{
"data": {
"work": {
"id": 47,
"title": "Renew the business licence",
"detail": null,
"kind": "renewal",
"status": "open",
"due_date": "2027-02-28",
"days_to_due": 138,
"overdue": false,
"priority": 92,
"waiting_on": null,
"days_waiting": null,
"last_error": null,
"action": {
"type": "approve",
"label": "Approve and send",
"hint": "A message is ready."
},
"permission": "reach_out",
"allowed": false,
"has_draft": true
},
"messages": [
{
"id": 212,
"channel": "email",
"direction": "outbound",
"subject": "Business licence renewal - Northgate",
"body": "Hello,\\n\\nThe business licence for Northgate is due for renewal on 28 February 2027. Could you confirm the renewal fee and the documents needed?\\n\\nThank you.",
"status": "drafted",
"actor": "desk",
"created_at": "2026-10-12T06:14:02.881Z",
"sent_at": null,
"approved_at": null
}
],
"timeline": [
{ "kind": "opened", "at": "2026-09-30T09:02:11.004Z", "text": "Opened by the desk" },
{ "kind": "drafted", "at": "2026-10-12T06:14:02.881Z", "text": "Message prepared via email" }
]
}
}
messages is everything written against the row and
timeline is the same history in sentences, oldest first. A message with
status drafted has not gone anywhere. approved means a person
signed it off but it did not leave — there was no mailbox, or the send was refused.
sent means it left, and sent_at says when.
6. Complete it
curl -s -X POST "$OD/work/47/complete" \
-H "Authorization: Bearer $OD_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: complete-47-2026-10-13" \
-d '{"note":"Approved by phone with the council, reference 88421.","actor":"nightly-script"}'
{
"data": {
"id": 47,
"status": "waiting",
"waiting_on": "the council",
"failed": false,
"delivered": true,
"delivery_note": "Sent from practice@northgate.example."
}
}
The row did not close. It moved to waiting, because the message went out and
an answer is owed — and a message going out is not the same as the job being
finished. That is status, not an error, and a client that treats it as a
failure will re-complete a row that is already out.
delivered: false would mean something quite different: the desk meant to
send and could not. In that case the row stays open carrying the reason in
last_error and arrives on the desk as an exception. Read
Actions before acting on
delivered.
Where to go from here
- Work — every field, and all eight action types.
- Actions — the five write endpoints in full.
- Code examples — the same four calls in curl, JavaScript, Python and Go.
- MCP server — point an agent at the desk without writing a client.
Do this against a desk you own. Completing a row sends the message that was prepared for it, on the owner's behalf, to a real counterparty. There is no test mode in version 1, and no route on this page is reversible.