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 Pro and Ultra, or Basic with the MCP add-on

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 Pro and Ultra, or Basic with the MCP add-on.

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

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

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_invoice_statusUpdate an invoice's status (draft → sent, overdue, cancelled)
  • record_paymentRecord CASH RECEIVED against an invoice
  • list_paymentsPayments received, newest first
  • 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
  • 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
  • send_quoteEmail the quote to the client as a PDF and mark it sent
  • convert_quote_to_invoiceTurn an accepted quote into an invoice

Purchases

  • list_suppliersList suppliers — the businesses this organisation buys from
  • create_supplierCreate a supplier
  • 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

Projects and time

  • list_projectsList projects with their client, status, budget used and unbilled value
  • create_projectCreate a project
  • log_timeLog time already worked, in minutes
  • 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, cash book and fixed assets
  • journals:writePost journals, cash book entries 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