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 Basic, Pro and Ultra.
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, or archive and reactivate itdelete_clientDelete a client with no history; one with documents is archived insteadset_client_remindersPause or resume payment reminders for everything a client owesrevoke_client_portal_linkStop a client's portal link working; the next document sent carries a new one
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 offereddelete_itemDelete an unused catalogue item; one documents use is retired with update_item instead
Invoices
list_invoicesList invoices with optional filtersget_invoiceGet a single invoice with full details (items, payments, credit allocations, client)create_invoiceCreate a new invoiceupdate_invoiceEdit a draft invoice — client, dates, notes and line items (add, change, remove); an issued invoice is refused and corrected with a credit noteupdate_invoice_statusUpdate an invoice's status (draft → sent, overdue, cancelled)set_invoice_remindersPause or resume payment reminders for one invoicedelete_invoiceDelete a draft invoice; an issued one is refused (cancel it or issue a credit note)record_paymentRecord CASH RECEIVED against an invoicelist_paymentsPayments received, newest firstupdate_paymentCorrect a payment's date, method, reference or notes; the amount and invoice are fixeddelete_paymentTake a mistaken payment out of the books, reversing its ledger entry with a reasondownload_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 notedelete_credit_noteDelete a draft credit note; an issued one is voided insteadget_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 clientupdate_quoteEdit a draft or sent quote — client, dates, notes and line items (add, change, remove); an accepted quote is lockedsend_quoteEmail the quote to the client as a PDF and mark it sentupdate_quote_statusRecord the client's answer — accepted, declined, expired — or cancel a quote, as the quote screen's status buttons dodelete_quoteDelete a draft quote; anything already offered is closed with a status insteadconvert_quote_to_invoiceTurn an accepted quote into an invoice
Purchases
list_suppliersList suppliers — the businesses this organisation buys fromcreate_supplierCreate a supplierupdate_supplierChange a supplier's details, default category or payment terms, or reactivate itarchive_supplierArchive a supplier; its bills and payments are keptlist_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 billdelete_bill_paymentTake a mistaken bill payment out of the books, reversing its ledger entrydelete_billDelete a bill with nothing paid, reversing its ledger entryvoid_billVoid a bill with payments: its entry is reversed, its payments stay, and it cannot be changed afterupdate_expenseEdit an expense or bill — date, amount, supplier, category, project, VAT, due date; the ledger followsdelete_expenseDelete an expense or a bill with no payments, reversing its ledger entryattach_expense_receiptAttach a PDF or photo receipt to an expense or bill, from base64 or an https linkremove_expense_receiptRemove an expense's receipt file
Projects and time
list_projectsList projects with their client, status, budget used and unbilled valuecreate_projectCreate a projectlist_project_tasksList a project's tasks, the task_id that time is logged againstcreate_project_taskAdd a task to a project, with its own rate and billable flagupdate_project_taskRename, re-rate or archive a project tasklog_timeLog time already worked, in minuteslist_time_entriesList time entries by project, person, dates or unbilledupdate_time_entryChange a time entry's minutes, date, notes or task; billed time is lockeddelete_time_entryDelete a time entry that has not been invoicedstart_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 and fixed assetsjournals:writePost journals 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.