API & MCP reference
Every action a human can take in Flow Invoicer 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 (Pro plan or higher).
Base URL
https://ddmflow.com/apiAll API endpoints sit beneath this base. JSON request + response.
MCP server
Flow Invoicer 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 flow-invoicer https://ddmflow.com/mcp \
--header "Authorization: Bearer ddm_live_xxxxx"The MCP server exposes the same tools as the REST API: list_clients, create_invoice, record_payment, download_invoice_pdf, and more. The model handles tool calls automatically.
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 | advanced |
| POST | /api/quotes | Create quote | advanced |
| POST | /api/quotes/{id}/convert | Convert to invoice | advanced |
| GET | /api/recurring-invoices | List recurring schedules | advanced |
| POST | /api/recurring-invoices | Create schedule | advanced |
| POST | /api/recurring-invoices/{id}/generate | Generate next now | advanced |
| GET | /api/expenses | List expenses | advanced |
| POST | /api/expenses | Create expense | advanced |
| 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, upgrade to Pro (or use your 7-day trial), generate a scoped key, and you're off.