API & MCP reference
Every action a human can take in DDM Flow is also available programmatically. Issue invoices from Slack. Hand a scoped key to your AI agent. Reconcile payments from a script.
Bearer token auth
Generate a ddm_live_* key in Settings → API keys. Send it on every request.
MCP server built-in
Point Claude Code, Cursor, or your own MCP client at https://ddmflow.com/mcp.
Org-isolated
Every request is scoped to your workspace. Row-level security in Postgres.
Authentication
All API requests require an API key in the Authorization header. Keys start with ddm_live_ and are scoped to the workspace they were created in. Each key is shown once at creation — store it somewhere safe.
curl https://ddmflow.com/api/clients \
-H "Authorization: Bearer ddm_live_xxxxxxxxxxxxxxxxxxxx"Generate one at Settings → API keys — included on Pro and Ultra, or Basic with the MCP add-on.
Base URL
https://ddmflow.com/apiAll API endpoints sit beneath this base. JSON request + response.
MCP server
DDM Flow ships a built-in MCP server so your AI agent can call tools directly — no glue code required. Same auth, same tier gating.
claude mcp add --transport http ddm-flow https://ddmflow.com/mcp \
--header "Authorization: Bearer ddm_live_xxxxx"The model handles tool calls automatically. Every tool the server registers is listed here — this page is generated from the registry, so it cannot fall behind it.
Workspace
meReturns the current organisation, the signed-in user (if any), and the auth scopes the caller has
Clients
list_clientsList all clients in the organisationget_clientGet a single client by ID with full detailscreate_clientCreate a new client (customer)update_clientUpdate an existing client's details
Catalogue
list_itemsList all catalog items (products and services) the user has savedcreate_itemCreate a new catalog item (product or service)update_itemChange a catalogue item — its price, description, unit, VAT treatment or whether it is still offered
Invoices
list_invoicesList invoices with optional filtersget_invoiceGet a single invoice with full details (items, payments, credit allocations, client)create_invoiceCreate a new invoiceupdate_invoice_statusUpdate an invoice's status (draft → sent, overdue, cancelled)record_paymentRecord CASH RECEIVED against an invoicelist_paymentsPayments received, newest firstdownload_invoice_pdfGenerate a PDF for the invoice and return a signed URL valid for 1 houremail_invoice_to_clientGenerate the invoice PDF and email it to the client (or a custom address)
Credit notes
create_credit_noteCreate a credit note — the correct way to give a client money back off what they oweget_credit_noteFull detail of one credit note: line items, every allocation (including reversed ones), allocated_amount and remaining_balancelist_credit_notesList credit notes, newest first, each with its allocated_amount and remaining_balanceissue_credit_noteIssue a draft credit note: assigns its number from the credit-note sequence (never an invoice number) and makes it immutableallocate_credit_noteApply some of an issued credit note's remaining balance to a specific unpaid invoice, reducing what that invoice still owesreverse_credit_allocationUndo one allocation, restoring the invoice's amount_due and returning the credit to the note's remaining balancevoid_credit_noteVoid a credit noteget_client_credit_balanceTotal unallocated credit a client holds — issued credit notes with a remaining balance, available to allocate against their open invoicesdownload_credit_note_pdfGenerate a PDF of the credit note and return a signed URL valid for 1 houremail_credit_note_to_clientEmail the credit note PDF to the client
Quotes
list_quotesList quotes — offers, which owe nothing until acceptedget_quoteOne quote with its line items, client and totalscreate_quoteCreate a quote for a clientsend_quoteEmail the quote to the client as a PDF and mark it sentconvert_quote_to_invoiceTurn an accepted quote into an invoice
Purchases
list_suppliersList suppliers — the businesses this organisation buys fromcreate_supplierCreate a supplierlist_expensesList costs, settled and outstandingcreate_expenseRecord a cost that is already settledlist_billsWhat the business owes: costs with a due date that are not fully paidcreate_billRecord money owed to a supplierrecord_bill_paymentRecord a payment against a bill
Projects and time
list_projectsList projects with their client, status, budget used and unbilled valuecreate_projectCreate a projectlog_timeLog time already worked, in minutesstart_timerStart a running timer on a projectstop_timerStop the running timer and save what it measured as a time entryget_unbilled_workPreview what a project would invoice: unbilled time and billable expenses, grouped into invoice lines, with resolved ratesinvoice_projectTurn a project's unbilled work into a draft invoice, and mark those entries invoiced
Reports
get_profit_and_lossProfit and loss from the general ledgerget_aged_receivablesWho owes what, by how overdue: current, 1-30, 31-60, 61-90 and 90+ daysget_vat_summaryOutput VAT, input VAT and the net payable or refundable for a period, from the ledgerget_cash_positionWhat is actually in the bank accounts, from the ledger, as at a date (default today)
Scopes
A key carries only the scopes you give it, and a connected app only the ones approved on the consent screen. A browser session is never scope-limited — a person at the keyboard has their role instead.
invoices:readSee your invoices and paymentsinvoices:writeCreate, send and update invoices and record paymentsquotes:readSee your quotesquotes:writeCreate, send and update quotesclients:readSee your clientsclients:writeAdd and update clientsitems:readSee your products and servicesitems:writeAdd and update products and servicescredit_notes:readSee your credit notescredit_notes:writeCreate, issue, allocate and void credit notesexpenses:readSee your expenses and receiptsexpenses:writeRecord, change and delete expensessuppliers:readSee your supplierssuppliers:writeAdd and update suppliersbills:readSee your supplier billsbills:writeRecord and pay supplier billsbank:readSee your bank transactionsbank:writeImport bank statements and match transactionsjournals:readSee your general ledger, cash book and fixed assetsjournals:writePost journals, cash book entries and fixed assetstax:readSee your VAT returnstax:writePrepare and file VAT returnsreports:readSee your reportsprojects:readSee your projectsprojects:writeCreate and update projectstime:readSee time entriestime:writeRecord and change time entriesexport:readDownload a complete copy of your books
Issue an invoice
The headline endpoint. Two ways to specify the customer: client_id (existing) or client (inline new). Totals are computed server-side from your line items — don't pass totals; they'll be overwritten.
curl -X POST https://ddmflow.com/api/invoices \
-H "Authorization: Bearer ddm_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"client": { "name": "Acme Corp", "email": "billing@acme.com" },
"payment_terms": "Net 30",
"items": [
{ "description": "May consulting", "quantity": 12, "unit_price": 750 },
{ "description": "Project setup", "quantity": 1, "unit_price": 1500 }
]
}'{
"id": "8a9c0b40-…",
"invoice_number": "INV-007",
"status": "draft",
"client": { "name": "Acme Corp", "email": "billing@acme.com" },
"subtotal": 10500,
"tax_amount": 1575,
"total_amount": 12075,
"currency": "ZAR",
"due_date": "2026-06-14"
}Endpoint inventory
Full REST surface — paste this into your editor and let your AI agent discover the rest:
| Method | Path | Description | Tier |
|---|---|---|---|
| GET | /api/clients | List clients | |
| POST | /api/clients | Create client | |
| GET | /api/clients/{id} | Get client | |
| PUT | /api/clients/{id} | Update client | |
| DELETE | /api/clients/{id} | Delete client | |
| GET | /api/items | List catalogue items | |
| POST | /api/items | Create catalogue item | |
| GET | /api/invoices | List invoices (filter by status) | |
| POST | /api/invoices | Create invoice ⭐ | |
| GET | /api/invoices/{id} | Get invoice with lines | |
| PATCH | /api/invoices/{id}/status | Change status | |
| POST | /api/invoices/{id}/payments | Record payment | |
| POST | /api/invoices/{id}/pdf | Generate PDF + signed URL | |
| POST | /api/invoices/{id}/send | Email invoice to client | |
| GET | /api/quotes | List quotes | basic |
| POST | /api/quotes | Create quote | basic |
| POST | /api/quotes/{id}/convert | Convert to invoice | basic |
| GET | /api/recurring-invoices | List recurring schedules | basic |
| POST | /api/recurring-invoices | Create schedule | basic |
| POST | /api/recurring-invoices/{id}/generate | Generate next now | basic |
| GET | /api/expenses | List expenses | pro |
| POST | /api/expenses | Create expense | pro |
| GET | /api/reports/revenue | Monthly revenue (12mo) | pro |
| GET | /api/reports/aging | Aging buckets | pro |
| GET | /api/reports/top-clients | Top 10 by revenue | pro |
| GET | /api/reports/tax | VAT collected vs accrued | pro |
Rate limits & errors
Soft limit: ~120 requests/minute per API key. Hit it and you'll receive HTTP 429 with a rate_limited code. Wait a few seconds and retry.
All errors follow a consistent shape:
{
"error": {
"code": "validation_error",
"message": "client_id is required",
"details": { "field": "client_id" }
}
}Error codes: unauthorized (401), forbidden (403), not_found (404), gone (410, expired portal link), validation_error (422), subscription_required (402, your tier doesn't include this), rate_limited (429), conflict (409), internal_error (500).
Get your API key
Sign in, pick a plan that includes the API, generate a scoped key, and you're off.