Flow API
Catalog, stock, partners, sales, invoices, bills, payments and webhooks.
Base: https://flow.eel.software/api/public/v1
The finance and operations app. Every POST that creates or records something
requires an Idempotency-Key header, with one exception: the two webhook
management routes at the bottom of this page. See the shared
rules for pagination, errors and pace, once
for all four apps.
Catalog and stock
| Method | Path | Scope |
|---|---|---|
GET |
/products |
flow:catalog:read |
GET |
/products/{upid} |
flow:catalog:read |
PATCH |
/products/{upid} |
flow:catalog:write |
GET |
/products/{upid}/stock |
flow:stock:read |
GET |
/warehouses |
flow:catalog:read |
GET |
/financial-accounts |
flow:sales:write |
GET /products is cursor-paginated and filters with q (name or SKU), active,
or updated_since (ISO date). Each row carries its UPID, the id every
other product route takes: sku, name, active, list_price, currency,
tax and uom.
PATCH /products/{upid} edits name, list_price (decimal string, null
clears it), tax_code (null clears it) or active. sku and uom are
deliberately immutable through the API.
GET /products/{upid}/stock returns per-warehouse on_hand and
available-to-sell (available = on_hand minus active reservations), optionally
filtered to one warehouse. It is eventually consistent with checkout.
GET /warehouses lists all active warehouses (CENTRAL and STORE) with the
warehouse_id that sales and stock reads take; STORE rows also carry the
partner_id of the partner that represents that store.
GET /financial-accounts lists active CASH / BANK / PROCESSOR accounts
with their currency, identity only, no balances. It is the source of the
financial_account_id that a sale or a payment takes.
Partners
| Method | Path | Scope |
|---|---|---|
GET |
/partners |
flow:partners:write |
GET |
/partners/{id} |
flow:partners:write |
POST |
/partners |
flow:partners:write |
There is no dedicated read scope for partners: reading and searching both ride
flow:partners:write, the same asymmetry the scopes catalogue
notes for Wrap's notes and follow-ups.
GET /partners?q= searches by name (contains) or tax id prefix. POST
/partners upserts a partner (customer/supplier) by tax id (tax_id_type +
tax_id_number) or email: it returns the existing record when one matches,
so it also doubles as a lookup by NIT. NIT verification digits are validated,
or computed when omitted.
| Field | Type | Notes |
|---|---|---|
name |
string, 1 to 200 chars | Required. |
tax_id_type |
NIT\ |
CC\ |
tax_id_number |
string, up to 40 chars | Optional. |
verification_digit |
string, up to 2 chars | The NIT DV. Computed if omitted. |
email |
string | Optional. |
roles |
string[] | E.g. CUSTOMER, SUPPLIER, EMPLOYEE. |
Sales
| Method | Path | Scope |
|---|---|---|
POST |
/sales |
flow:sales:write |
GET |
/sales/{id} |
flow:sales:write |
POST /sales is the one-call sale: it draws stock from a warehouse, invoices
the customer, and optionally records the payment.
| Field | Type | Notes |
|---|---|---|
external_ref |
string, up to 200 chars | Your own order id. Also the dedup anchor. |
warehouse_id |
uuid | Required. |
customer |
`{ partner_id? \ | name/tax_id/email }` |
lines |
{ upid, qty, unit_price? }[], min 1 |
unit_price overrides the resolved list price when given. |
payment |
{ method, financial_account_id?, processor_ref?, amount? } |
method is CASH or PROCESSOR. |
emit_einvoice |
boolean | |
notes |
string, up to 2000 chars |
GET /sales/{id} returns the invoice number and status, the outbound
reference, and dian_status (the status of DIAN e-invoicing).
Invoices and bills
| Method | Path | Scope |
|---|---|---|
GET |
/invoices |
flow:documents:read |
GET |
/bills/list |
flow:documents:read |
POST |
/bills |
flow:bills:write |
GET /invoices browses AR documents (customer invoices) newest-issued first,
cursor-paginated: the "revenue this month" view. GET /bills/list is the same
shape for AP documents (supplier bills), plus supplier_number and
PENDING_APPROVAL as an extra status. flow_list_open_items (below) only
shows the unpaid subset of either.
Both filter by status, partner_id, or an issued_from/issued_to window
(ISO dates), and both return the formatted number (null while DRAFT),
partner_id/name, string money and the retention-aware remaining.
| Surface | Statuses |
|---|---|
| Invoices | DRAFT, OPEN, PARTIAL, PAID, VOID |
| Bills | DRAFT, OPEN, PARTIAL, PAID, VOID, PENDING_APPROVAL |
POST /bills creates a DRAFT purchase bill; a human posts it inside Flow.
Reference the supplier by partner_id or pass an inline partner object
(upserted by tax id or email). A duplicate (partner, supplier_number) pair
is rejected with conflict and the existing bill_id in the error details.
| Field | Type | Notes |
|---|---|---|
partner_id |
uuid, optional | |
partner |
object, optional | Inline supplier: name, tax_id_type, tax_id_number, email. |
supplier_number |
string, 1 to 60 chars | The supplier's own invoice number. |
issue_date |
ISO date | Required. |
due_date |
ISO date, optional | |
currency |
string, 3 chars, optional | |
notes |
string, up to 2000 chars, optional | |
lines |
{ description, qty, unit_price, tax_code?, product_upid? }[], min 1 |
Money as numbers, not strings. |
Payments
| Method | Path | Scope |
|---|---|---|
POST |
/invoices/{id}/payments |
flow:payments:write |
POST |
/bills/{id}/payments |
flow:payments:write |
Both record a payment (a receipt against an invoice, or a disbursement
against a bill), with the same rules: amount is a decimal string in the
document's own currency, and the financial_account_id must match that
currency. Rejected when the document is DRAFT/VOID/already PAID, the
amount exceeds what remains, or the fiscal month is closed.
| Field | Type | Notes |
|---|---|---|
financial_account_id |
uuid | From GET /financial-accounts. |
amount |
decimal string | E.g. "150000.00", in the document's currency. |
date |
ISO date | Required. |
reference |
string, up to 120 chars, optional |
A payment against a bill at or above the workspace's approval threshold goes
through only when the caller is a workspace administrator. Both routes
return the payment plus the document's post-payment remaining and
status.
Open items
| Method | Path | Scope |
|---|---|---|
GET |
/open-items |
flow:open-items:read |
Open (unpaid) documents with their remaining balance: side=AP for supplier
bills owed, side=AR for customer invoices owed to us. Filter by
partner_nit or an as_of date. This is the route behind "what do we owe X",
"who owes us", and cartera questions generally.
Webhooks
| Method | Path | Scope |
|---|---|---|
GET |
/webhooks |
flow:webhooks:manage |
POST |
/webhooks |
flow:webhooks:manage |
DELETE |
/webhooks/{id} |
flow:webhooks:manage |
GET |
/webhook-deliveries |
flow:webhooks:manage |
POST |
/webhook-deliveries/{id}/redeliver |
flow:webhooks:manage |
These two POST routes are the exception to the idempotency rule above: POST
/webhooks and POST /webhook-deliveries/{id}/redeliver neither require nor
look at Idempotency-Key.
POST /webhooks takes url (up to 2000 chars) and events (at least one of
invoice.posted, invoice.paid, bill.posted, payment.created,
stock.changed). The response includes the signing secret once, at
create, never again. GET /webhook-deliveries?webhook= lists recent
deliveries, optionally filtered to one webhook; POST
/webhook-deliveries/{id}/redeliver re-queues one for the next drain.
Delivery is at-least-once: a webhook consumer must be idempotent on its own side, keying off the event body, not off delivery count.