For developers · For agents

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.

Included on Basic, Pro and Ultra

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.

All requestsbash
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/api

All 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 Codebash
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 organisation
  • get_clientGet a single client by ID with full details
  • create_clientCreate a new client (customer)
  • update_clientUpdate an existing client's details, or archive and reactivate it
  • delete_clientDelete a client with no history; one with documents is archived instead
  • set_client_remindersPause or resume payment reminders for everything a client owes
  • revoke_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 saved
  • create_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
  • delete_itemDelete an unused catalogue item; one documents use is retired with update_item instead

Invoices

  • list_invoicesList invoices with optional filters
  • get_invoiceGet a single invoice with full details (items, payments, credit allocations, client)
  • create_invoiceCreate a new invoice
  • update_invoiceEdit a draft invoice — client, dates, notes and line items (add, change, remove); an issued invoice is refused and corrected with a credit note
  • update_invoice_statusUpdate an invoice's status (draft → sent, overdue, cancelled)
  • set_invoice_remindersPause or resume payment reminders for one invoice
  • delete_invoiceDelete a draft invoice; an issued one is refused (cancel it or issue a credit note)
  • record_paymentRecord CASH RECEIVED against an invoice
  • list_paymentsPayments received, newest first
  • update_paymentCorrect a payment's date, method, reference or notes; the amount and invoice are fixed
  • delete_paymentTake a mistaken payment out of the books, reversing its ledger entry with a reason
  • download_invoice_pdfGenerate a PDF for the invoice and return a signed URL valid for 1 hour
  • email_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 owe
  • get_credit_noteFull detail of one credit note: line items, every allocation (including reversed ones), allocated_amount and remaining_balance
  • list_credit_notesList credit notes, newest first, each with its allocated_amount and remaining_balance
  • issue_credit_noteIssue a draft credit note: assigns its number from the credit-note sequence (never an invoice number) and makes it immutable
  • allocate_credit_noteApply some of an issued credit note's remaining balance to a specific unpaid invoice, reducing what that invoice still owes
  • reverse_credit_allocationUndo one allocation, restoring the invoice's amount_due and returning the credit to the note's remaining balance
  • void_credit_noteVoid a credit note
  • delete_credit_noteDelete a draft credit note; an issued one is voided instead
  • get_client_credit_balanceTotal unallocated credit a client holds — issued credit notes with a remaining balance, available to allocate against their open invoices
  • download_credit_note_pdfGenerate a PDF of the credit note and return a signed URL valid for 1 hour
  • email_credit_note_to_clientEmail the credit note PDF to the client

Quotes

  • list_quotesList quotes — offers, which owe nothing until accepted
  • get_quoteOne quote with its line items, client and totals
  • create_quoteCreate a quote for a client
  • update_quoteEdit a draft or sent quote — client, dates, notes and line items (add, change, remove); an accepted quote is locked
  • send_quoteEmail the quote to the client as a PDF and mark it sent
  • update_quote_statusRecord the client's answer — accepted, declined, expired — or cancel a quote, as the quote screen's status buttons do
  • delete_quoteDelete a draft quote; anything already offered is closed with a status instead
  • convert_quote_to_invoiceTurn an accepted quote into an invoice

Purchases

  • list_suppliersList suppliers — the businesses this organisation buys from
  • create_supplierCreate a supplier
  • update_supplierChange a supplier's details, default category or payment terms, or reactivate it
  • archive_supplierArchive a supplier; its bills and payments are kept
  • list_expensesList costs, settled and outstanding
  • create_expenseRecord a cost that is already settled
  • list_billsWhat the business owes: costs with a due date that are not fully paid
  • create_billRecord money owed to a supplier
  • record_bill_paymentRecord a payment against a bill
  • delete_bill_paymentTake a mistaken bill payment out of the books, reversing its ledger entry
  • delete_billDelete a bill with nothing paid, reversing its ledger entry
  • void_billVoid a bill with payments: its entry is reversed, its payments stay, and it cannot be changed after
  • update_expenseEdit an expense or bill — date, amount, supplier, category, project, VAT, due date; the ledger follows
  • delete_expenseDelete an expense or a bill with no payments, reversing its ledger entry
  • attach_expense_receiptAttach a PDF or photo receipt to an expense or bill, from base64 or an https link
  • remove_expense_receiptRemove an expense's receipt file

Projects and time

  • list_projectsList projects with their client, status, budget used and unbilled value
  • create_projectCreate a project
  • list_project_tasksList a project's tasks, the task_id that time is logged against
  • create_project_taskAdd a task to a project, with its own rate and billable flag
  • update_project_taskRename, re-rate or archive a project task
  • log_timeLog time already worked, in minutes
  • list_time_entriesList time entries by project, person, dates or unbilled
  • update_time_entryChange a time entry's minutes, date, notes or task; billed time is locked
  • delete_time_entryDelete a time entry that has not been invoiced
  • start_timerStart a running timer on a project
  • stop_timerStop the running timer and save what it measured as a time entry
  • get_unbilled_workPreview what a project would invoice: unbilled time and billable expenses, grouped into invoice lines, with resolved rates
  • invoice_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 ledger
  • get_aged_receivablesWho owes what, by how overdue: current, 1-30, 31-60, 61-90 and 90+ days
  • get_vat_summaryOutput VAT, input VAT and the net payable or refundable for a period, from the ledger
  • get_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 payments
  • invoices:writeCreate, send and update invoices and record payments
  • quotes:readSee your quotes
  • quotes:writeCreate, send and update quotes
  • clients:readSee your clients
  • clients:writeAdd and update clients
  • items:readSee your products and services
  • items:writeAdd and update products and services
  • credit_notes:readSee your credit notes
  • credit_notes:writeCreate, issue, allocate and void credit notes
  • expenses:readSee your expenses and receipts
  • expenses:writeRecord, change and delete expenses
  • suppliers:readSee your suppliers
  • suppliers:writeAdd and update suppliers
  • bills:readSee your supplier bills
  • bills:writeRecord and pay supplier bills
  • bank:readSee your bank transactions
  • bank:writeImport bank statements and match transactions
  • journals:readSee your general ledger and fixed assets
  • journals:writePost journals and fixed assets
  • tax:readSee your VAT returns
  • tax:writePrepare and file VAT returns
  • reports:readSee your reports
  • projects:readSee your projects
  • projects:writeCreate and update projects
  • time:readSee time entries
  • time:writeRecord and change time entries
  • export: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.

POST /api/invoicesbash
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 }
    ]
  }'
Response 201json
{
  "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:

MethodPathDescriptionTier
GET/api/clientsList clients
POST/api/clientsCreate client
GET/api/clients/{id}Get client
PUT/api/clients/{id}Update client
DELETE/api/clients/{id}Delete client
GET/api/itemsList catalogue items
POST/api/itemsCreate catalogue item
GET/api/invoicesList invoices (filter by status)
POST/api/invoicesCreate invoice
GET/api/invoices/{id}Get invoice with lines
PATCH/api/invoices/{id}/statusChange status
POST/api/invoices/{id}/paymentsRecord payment
POST/api/invoices/{id}/pdfGenerate PDF + signed URL
POST/api/invoices/{id}/sendEmail invoice to client
GET/api/quotesList quotesbasic
POST/api/quotesCreate quotebasic
POST/api/quotes/{id}/convertConvert to invoicebasic
GET/api/recurring-invoicesList recurring schedulesbasic
POST/api/recurring-invoicesCreate schedulebasic
POST/api/recurring-invoices/{id}/generateGenerate next nowbasic
GET/api/expensesList expensespro
POST/api/expensesCreate expensepro
GET/api/reports/revenueMonthly revenue (12mo)pro
GET/api/reports/agingAging bucketspro
GET/api/reports/top-clientsTop 10 by revenuepro
GET/api/reports/taxVAT collected vs accruedpro

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 responsejson
{
  "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).

Ready to build

Get your API key

Sign in, pick a plan that includes the API, generate a scoped key, and you're off.

Open API Keys

Cookies and analytics

A session cookie keeps you signed in, and your theme is saved in this browser. With your permission we also use PostHog to count page views and clicks. Nothing is sent to PostHog unless you allow it. Privacy policy.