Invoices
An invoice’s full lifecycle from your system: draft, issue, send, collect and cancel.
Overview
An invoice is born as a draft and only becomes final when you issue it. Issuing is an explicit, irreversible action: it uses your plan’s stamping when it applies (CFDI in Mexico) and gives the invoice its number. Creating never issues on its own.
The invoices API is available on every plan, including Free, with standard invoices (pro forma in Mexico and Spain). Integrated tax issuing (CFDI in Mexico, and Verifactu in Spain with the electronic certificate uploaded) requires Starter or higher: requesting document_mode: "fiscal" without that plan responds 400 invalid_request.
List invoices
GET /api/v1/facturas with these optional filters:
| Parameter | What it does |
|---|---|
estado |
draft, open, paid, void, uncollectible or overdue (open and past their due date). |
cliente |
Client id. |
desde, hasta |
Range by creation date, YYYY-MM-DD, both ends inclusive. |
q |
Searches by number, tax folio, originating quote folio or client name. |
limit |
From 1 to 200; defaults to 50. |
cursor |
The previous page’s next_cursor. |
Pagination is cursor-based, not offset-based: a new invoice never shifts the page you are reading.
curl "https://cordhq.app/api/v1/facturas?limit=20" \
-H "Authorization: Bearer sk_live_tU..."
{
"data": [
{
"id": "f3a1c7e9-2b4d-4f6a-8c0e-5d7b9a1c3e24", "numero": "F-0231", "folio_fiscal": "6F1C2A9E-7B4D-4E6A-8C2F-1D5B9E3A7C40", "cliente": "Aceros del Norte",
"estado": "open", "estado_fiscal": "issued", "pais": "MX", "tipo": "cfdi_40",
"moneda": "MXN", "total": 184300, "pagado": 0, "saldo": 184300,
"vence": "2026-10-15", "vencida": false, "cotizacion_id": "2a9d8e4f-6b1c-4d7a-9e3f-5c8b1a2d4e6f", "creada": "15 sep 2026", "language": "es"
}
],
"meta": { "next_cursor": null }
}
creada comes as a display date (15 sep 2026), not an ISO date; vence is YYYY-MM-DD.
| Field | Values |
|---|---|
estado |
draft, open (issued and awaiting payment), paid, void or uncollectible. |
estado_fiscal |
pending, issued (registered with the tax provider), cancelled or error. |
tipo |
cfdi_40 (income CFDI, Mexico), cfdi_egreso (CFDI credit note), proforma (commercial document in Mexico, Spain, and countries with authorized invoices), commercial_invoice (commercial invoice), commercial_credit_note (commercial credit note), verifactu_invoice or verifactu_credit_note (Spain with Verifactu). With issuing through the tax authority active: arca_invoice (Argentina), nfse_invoice and nfe_invoice (Brazil), sii_invoice (Chile), dian_invoice (Colombia), and sunat_invoice (Peru), with their *_credit_note credit notes and, in Chile, sii_debit_note. |
language |
Read-only. The language in which the client receives the invoice, its PDF, its link, and its emails: es, en, pt, fr, or de. The invoice takes it when issued, from the client or, if they have none, from your organization; a credit note inherits its invoice’s. null on a draft. |
Create a draft
POST /api/v1/facturas creates a draft. It needs a write key and accepts Idempotency-Key.
| Field | Required | Detail |
|---|---|---|
cliente_id |
Yes | A client in your organization. |
items |
Yes | From 1 to 200 line items. Each with descripcion and cantidad; optional precio_unitario, precio_negociado, producto_id, costo_unitario, tax_rate, exemption_reason and, in Mexico, clave_sat and clave_unidad_sat. |
currency |
No | Invoice currency; defaults to your account’s. |
due_date |
No | Due date as YYYY-MM-DD. |
notas |
No | Up to 1000 characters. |
iva_incluido |
No | true if prices already include tax. |
document_mode |
No | commercial or fiscal. Without it, Cord issues a tax document if your plan includes it and your country has it enabled, and a commercial one otherwise. fiscal without the plan or an enabled country responds 400 invalid_request. |
fx_buffer_pct |
No | Coverage in percentage points over the reference exchange rate when currency differs from your accounting currency (2 = +2%, capped at 25). Defaults to 0. |
descuento |
No | Document discount before tax: { "tipo": "porcentaje", "valor": 10 } or { "tipo": "monto", "valor": 2500 }. Cord calculates the amount and spreads it across the line items. See Discounts and coupons. |
cupon |
No | A coupon code from your account. It wins over descuento. Its use is counted when the invoice is issued. |
service_date, service_date_end |
No | Date or period of supply, YYYY-MM-DD. The end requires the start and can’t be earlier. Without it, the invoice says it matches the issue date (required in Germany). |
buyer_reference |
No | Buyer reference (BT-10 of the European e-invoice; in Germany, the public body’s Leitweg-ID). Without it, the client’s. If the client’s electronic address is a Leitweg-ID, its check digits are verified and one that fails responds 400. |
purchase_order |
No | The client’s purchase order number (BT-13). |
tax_rate is a fraction (0.16, not 16) and is validated against your tax catalog: you cannot declare an exempt line on a taxed sale. 0 means deliberately exempt; omitting it uses your account’s rate. clave_sat and clave_unidad_sat win over the product’s keys; with neither, the CFDI uses 01010101 and H87. A key with an invalid format responds 400.
curl -X POST "https://cordhq.app/api/v1/facturas" \
-H "Authorization: Bearer sk_live_tU..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-order-8812" \
-d '{
"cliente_id": "9b2e4c6a-8d1f-4a3b-b5c7-0e2d4f6a8c91",
"currency": "USD",
"due_date": "2026-10-15",
"items": [{ "descripcion": "Rebar 3/8\"", "cantidad": 120, "precio_unitario": 4.2, "tax_rate": 0.0825 }]
}'
{ "data": { "id": "f3a1c7e9-2b4d-4f6a-8c0e-5d7b9a1c3e24" } }
If the invoice is in a currency other than your ledger currency and no verifiable exchange rate can be obtained, it responds 503 with code: "fx_unavailable": retry later, Cord never makes up a rate.
Get an invoice
GET /api/v1/facturas/{id} returns the list fields plus subtotal, impuestos, moneda_contable, tipo_cambio, total_contable, notas, nota_credito_de, emisor, receptor, descuento, conceptos and pagos.
| Field | Details |
|---|---|
descuento |
null, or { tipo, valor, cupon, monto, moneda }: the definition, the coupon it came from, and the amount before tax. subtotal is already net. |
conceptos[].precio_unitario |
Net unit price, after the document discount, before tax. |
conceptos[].subtotal |
The line’s net base. |
conceptos[].descuento |
The share of the document discount that went to that line; 0 if there’s none. |
conceptos[].clave_sat, conceptos[].clave_unidad_sat |
Mexico: the SAT keys the line was stamped with; null outside Mexico. |
pagos |
Each payment with monto, moneda, metodo, referencia, and cuando. A charge that paid several invoices shows up on each one with its share. |
Change status
POST /api/v1/facturas/{id} with action:
action |
What it does | Fields |
|---|---|---|
finalize |
Issues the invoice. Irreversible; stamps the CFDI in Mexico when it applies. | — |
send |
Emails the invoice with its PDF to the client, in the invoice’s language (language). Requires an issued invoice and a client with an email. |
— |
payment |
Records a full or partial payment. | monto, moneda, metodo (defaults to manual), referencia |
void |
Voids the invoice. | motivo |
credit_note |
Issues a credit note against the invoice. | monto (optional; without it, for the total), motivo |
payment responds with the amount paid, the balance and the resulting status. A payment that settles the invoice fires the invoice.paid event.
If an already stamped invoice cannot be voided directly, void responds 409 with code: "credit_note_required": issue a credit note instead.
In Mexico:
voidrequests the CFDI’s cancellation under SAT reason 02. If the SAT hasn’t confirmed it yet, it responds202with{ "data": { "id": "…", "estado": "open", "cancelacion": "pendiente" } }and the invoice stays valid;invoice.voidedisn’t fired until it’s confirmed. Replacement under reason 01 and the global invoice are only in the app.paymenton a PPD CFDI triggers the automatic payment complement for that payment.
Common errors
| Code | When |
|---|---|
invalid_request (400) |
cliente_id or items is missing, a line has no description or quantity, due_date is not YYYY-MM-DD, the discount isn’t shaped { tipo, valor }, or a Leitweg-ID fails its check digits. |
coupon_no_existe (404) |
The coupon doesn’t exist on your account. |
coupon_inactivo, coupon_vencido, coupon_aun_no_vigente, coupon_agotado, coupon_agotado_cliente, coupon_moneda, coupon_requiere_cliente (409) |
The coupon doesn’t apply to this invoice; the message says why. |
invalid_state (409) |
The action does not apply to the current status (for example, sending a draft). Sending to a client with no email responds 400 with this same code. |
credit_note_required (409) |
The invoice is already stamped and can’t be voided directly: issue a credit note. |
not_found (404) |
The invoice doesn’t exist in your organization. |
provider_error (502) |
The tax provider rejected issuing; the message says why. |
send_failed (502) |
The invoice email couldn’t be sent. Retry send. |
fx_unavailable, tax_catalog_unavailable (503) |
No verifiable exchange rate, or your tax catalog could not be read. Retry. |