Quote, invoice and collect from your own code.

Cord's REST API creates and reads quotes, clients, products and invoices with a Bearer key, and runs the same actions as the app: send, approve, issue or record a payment. Predictable JSON responses, an OpenAPI spec and test keys that work in an environment isolated from your real data.

On every plan. Free includes 2 keys and 100 calls a month; test keys do not count against your quota.

POST /api/v1/cotizaciones200 OK
$ curl https://cordhq.app/api/v1/cotizaciones \
  -H "Authorization: Bearer sk_live_8f2a…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-7781" \
  -d '{"cliente_id": "9b41c2e0-3d7a-4f12-8c55-6e0b1a2d4f90", "vigencia_dias": 15,
       "items": [{"descripcion": "Portland cement 94 lb",
                  "cantidad": 240, "precio_unitario": 16.4}]}'
{
  "data": {
    "id": "c2f9a1d4-7b3e-4c1a-9e55-01a6c3d8f2b7",
    "token": "q8Fz2LkP",
    "folio": "COT-0149",
    "link_publico": "https://cordhq.app/q/q8Fz2LkP",
    "needs_approval": false,
    "motivo": null
  }
}
97 ms · v1600 requests/min per secret key

IDEMPOTENCY

Retry without fear of duplicates.

A flaky network should not create two quotes. Send an Idempotency-Key on any POST, PATCH or DELETE: if the request repeats with the same key and body, Cord returns the original response, marked Idempotent-Replayed, instead of running twice. If the body changed, you get an explicit error.

  • Works on every mutation under /api/v1
  • The replayed response is identical, byte for byte
  • Same key with a different body: an error, never a silent duplicate
Retry with the same Idempotency-Key/api/v1
# 1st attempt: the quote is created
POST /api/v1/cotizaciones
Idempotency-Key: pedido-7781
← 200  { "data": { "folio": "COT-0150", … } }

# 2nd attempt (network dropped): same key, same body
POST /api/v1/cotizaciones
Idempotency-Key: pedido-7781
← 200  Idempotent-Replayed: true
      { "data": { "folio": "COT-0150", … } }
      # the same response, byte for byte

# same key, different body
← 422  { "error": "Esta clave de idempotencia ya se usó con otra petición.",
        "code": "idempotency_key_reused" }

# the first request is still running
← 409  { "code": "idempotency_in_progress" }

RESOURCES

What you see in the app, also over the API.

This is not a read-only API. Create a quote and send it, approve it or mark it paid. Create an invoice as a draft and issue it, email it, record a payment or a credit note. All with the same permissions, validation and totals the app uses.

  • Quotes, invoices, clients, products, tasks and collections
  • Actions via POST { "action": "send" | "approve" | "finalize" | … }
  • Pagination with limit and offset, or a cursor for events and invoices
OverviewLogsErrorsEventsWebhooksShellAPI keysHealth

API reference

34 operations. Base https://cordhq.app/api/v1 with Authorization: Bearer and your key.

OpenAPI spec ↗llms.txt (AI Docs) ↗
Example
curl https://cordhq.app/api/v1/me \
  -H "Authorization: Bearer $CORD_SECRET_KEY"
Account
GET/meOrganization and key scopeTry it
Quotes
GET/cotizacionesList quotesTry it
POST/cotizacionesCreate quoteTry it
GET/cotizaciones/{id}Quote detailTry it
POST/cotizaciones/{id}Send, resend, approve, reject or mark paidTry it
DELETE/cotizaciones/{id}Delete a draftTry it
POST/cotizaciones/iaSuggest lines from the text of an orderTry it
Clients
GET/clientesList clientsTry it
POST/clientesCreate clientTry it
GET/clientes/{id}Client detailTry it
PATCH/clientes/{id}Update contact detailsTry it
Products
GET/productosList productsTry it
POST/productosCreate productTry it
Invoices
GET/facturasList invoicesTry it
POST/facturasCreate draft invoiceTry it
GET/facturas/{id}Invoice detailTry it
POST/facturas/{id}Issue, send, void, record a payment or a credit noteTry it
Collections
GET/cobranzaReceivablesTry it
Events
GET/eventsEvent historyTry it
Tasks
POST/tareasCreate taskTry it
Workflows
POST/workflows/{id}/invokeRun a "You run it" workflow on quotes or invoicesTry it
Webhooks
GET/webhooksEndpoints created by this key
POST/webhooksCreate endpoint (the secret is returned once)
DELETE/webhooks/{id}Delete endpoint
Elements
GET/elements/configConfiguration that Cord Elements rendersTry it
POST/elements/ai-draftAI-drafted lines, streamed (text/event-stream)
Test mode
POST/test_helpers/fiscalForce the outcome of the next issuanceTry it
POST/test_helpers/cotizaciones/{id}Simulate a view or expiryTry it
POST/test_helpers/webhooksTrigger a webhookTry it
POST/test_helpers/listenOpen a cord listen session
GET/test_helpers/listen/{id}Collect the session's events
DELETE/test_helpers/listen/{id}Close a cord listen session
Setup
POST/setup/plansPropose an account setup (a person approves it at review_url)Try it
GET/setup/plans/{id}Status of a setup proposalTry it

EVENTS

Sync without missing a thing.

GET /api/v1/events returns your business history, newest first: quotes sent, viewed, approved or paid, invoices issued, new clients. Filter by type or object and page with next_cursor. It is the webhooks' companion for rebuilding any state.

  • Filters ?type=quote.approved and ?object_id=
  • Up to 200 events per page with a stable cursor
  • The same types your webhooks receive
GET /api/v1/events?type=quote.approved&limit=1200 OK
{
  "data": [
    {
      "id": "5e0b9d42-6a1f-4c87-b3e2-9d4f1a7c0e65",
      "type": "quote.approved",
      "object": "quote",
      "object_id": "c2f9a1d4-7b3e-4c1a-9e55-01a6c3d8f2b7",
      "data": { "id": "c2f9a1d4-7b3e-4c1a-9e55-01a6c3d8f2b7", "folio": "COT-0149",
                "status": "approved", "moneda": "USD", "total": 4123.4,
                "cliente": "Ridgeline Builders LLC", "cliente_id": "9b41c2e0-3d7a-4f12-8c55-6e0b1a2d4f90",
                "link_publico": "https://cordhq.app/q/q8Fz2LkP" },
      "actor": "client",
      "created_at": "2026-09-30T22:31:08.000Z"
    }
  ],
  "meta": {
    "next_cursor": "MjAyNi0wOS0zMFQyMjozMTowOC4wMDAwMDBafDVlMGI5ZDQyLTZhMWYtNGM4Ny1iM2UyLTlkNGYxYTdjMGU2NQ"
  }
}

TOOLS

Your backend and your terminal, ready for Cord.

Two official npm packages, so integrating doesn't mean writing requests by hand: a typed SDK for your server and a CLI to test webhooks on your machine before you deploy.

SERVER SDK

The API, already typed for your backend.

@flouviahq/node wraps the whole API: create quotes and invoices, walk full lists and verify webhooks with their signature and age. It sends the same Idempotency-Key on every retry and pins the API version, so a Cord update never changes what you read.

  • npm install @flouviahq/node, no dependencies
  • Runs on Node, Bun, Deno, Cloudflare Workers and Vercel
  • Errors with a code, a request_id and a link to their explanation
src / server.ts@flouviahq/node
  1. import { Cord, constructEvent } from '@flouviahq/node';
  2. const cord = new Cord(process.env.CORD_SECRET_KEY!, { apiVersion: '2026-10-01' });
  3. // If the network drops, the retry reuses the same Idempotency-Key
  4. const quote = await cord.quotes.create({
  5. cliente: { empresa: 'Ridgeline Builders LLC' },
  6. items: [{ descripcion: 'Portland cement 94 lb', cantidad: 240, precio_unitario: 16.4 }],
  7. });
  8. // Walks every page; the SDK keeps the cursor
  9. for await (const invoice of cord.invoices.listAll({ estado: 'open' })) {
  10. await erp.updateBalance(invoice.numero, invoice.saldo);
  11. }
  12. // Checks X-Cord-Signature-V1 and rejects signatures older than 5 minutes
  13. export async function POST(req: Request) {
  14. const event = await constructEvent(await req.text(), req.headers,
  15. process.env.CORD_WEBHOOK_SECRET!);
  16. if (event.event === 'invoice.paid') {
  17. await erp.reconcile(event.data.numero, event.data.saldo);
  18. }
  19. return new Response(null, { status: 200 });
  20. }

CLI

Test your webhooks without deploying.

cord listen forwards your test environment's webhooks to your machine, signed exactly as in production. cord trigger fires any event, and cord simulate reproduces what a third party does in production, such as an e-invoicing service that stops responding or a quote that expires.

  • npx @flouviahq/cli opens the setup wizard: you sign in from the browser, copy no keys, and your project ends up integrated
  • It only accepts test keys: nothing touches your real data
  • cord events tail shows events as they happen
erp-materiales— zshsk_test_…9c2a

$ cord listen --forward-to http://localhost:3000/api/webhooks/cord

16:41:52quote.viewedevt_2a91f0c7d34b4e8fa15c6b0d9e7f3a21→ 200 38 ms

16:42:07quote.approvedevt_8d4c2b1e07a94f63b5e1c9a0f2d7b4e8→ 200 41 ms

16:42:58payment.partialevt_c35e9a7104bd4c2e8f6a1b0d3e9c5f72→ 200 52 ms

16:43:21invoice.finalizedevt_4be07a2d4e61b3f8a05c7d92e1aa9c1f→ 500 17 ms

16:44:05quote.expiredevt_f0a6d3b9c18e4a57b2d40e6c9a1f8b35→ 200 33 ms

16:44:12invoice.paidevt_9c1f4be07a2d4e61b3f8a05c7d92e1aa→ 200 36 ms

$ cord trigger quote.approved

$ cord simulate fiscal pac_caido

$ cord simulate quote 7e3b0c91-4d2a-4f6e-b8a5-2c9d1f0e6a47 vencer

$ cord events tail --type invoice.paid

16:44:12invoice.paidf3a8d1c4-2b6e-4d90-a1c7-5e8f0b3d2a61

FREQUENTLY ASKED QUESTIONS

What developers ask about REST API.

A REST API at https://cordhq.app/api/v1 that gives programmatic access to your quotes, invoices, clients, products, tasks, collections and events. It authenticates with a Bearer key and responds in JSON shaped as { data, meta } or { error, code }.

No. The API is on every plan, including Free, with 2 keys and 100 calls a month. Starter includes 1,000 calls, Professional 5,000, Scale 10,000 and Developer 50,000. Test keys do not count against your quota.

Use an sk_test_ key. It runs against a test environment separate from your account, so you can create and delete quotes without affecting your clients or your reports.

Send the Idempotency-Key header. If you repeat the request with the same key and body, Cord returns the original response without running it again.

Yes: @flouviahq/node for Node, Bun, Deno, Cloudflare Workers and Vercel, and @flouviahq/cli to test webhooks on your machine. Both are on npm.

No. Every key and every webhook keeps the API version it was created with, and you can pin another per request with the Cord-Version header. Cord shapes the response to that version.

Yes. A restricted key (rk_) has read or write permission per resource, and any secret key can be restricted to specific IPs, set to expire and rolled with a grace period.

Yes, at https://cordhq.app/openapi.yaml, plus a summary for AI assistants at https://cordhq.app/llms.txt. Both are linked from the Workbench.

Explore the rest of the developer platform.

Make your first call today.

Create your account, generate a test key and call /api/v1/me.

See what you'll pay

Published pricing, with the fee for every payment in plain view.

Pricing details →

Start building

Start operating and sending quotes on the Free plan.

Integration options →