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": "Cemento gris 50 kg",
                  "cantidad": 340, "precio_unitario": 182}]}'
{
  "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
OverviewWebhooksEventsAPILogsHealth

Available endpoints

Base: https://cordhq.app/api/v1 · authenticate with Authorization: Bearer <tu_clave>.OpenAPI spec ↗llms.txt (AI Docs) ↗

GET/meWhich business the key belongs to
GET/cotizacionesList (filter with ?status=, paginate with ?limit=&offset=)
POST/cotizacionesCreate a quote (requires write)
GET/cotizaciones/:idDetail with line items and timeline
GET/clientesClient directory
POST/clientesCreate a client (requires write)
GET/productosCatalog
POST/productosCreate a product (requires write)
GET/cobranzaReceivables, overdue and aging
Example
curl https://cordhq.app/api/v1/cotizaciones \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx"

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": "MXN", "total": 71780.8,
                "cliente": "Distribuidora El Zarco", "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 isn't 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: 'Distribuidora El Zarco' },
  6. items: [{ descripcion: 'Cemento gris 50 kg', cantidad: 340, precio_unitario: 182 }],
  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 sends your test environment webhooks to your machine, signed exactly as in production. cord trigger fires any event and cord simulate triggers what a third party does in production, like a tax provider outage or an expiring quote.

  • npx @flouviahq/cli opens the wizard: sign in from the browser, no keys to copy, 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 limited to certain IPs, expire and be rolled with a grace period.

Yes, at https://cordhq.app/openapi.yaml, plus an AI-assistant summary 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 in under a minute.

See what you'll pay

Integrated per-transaction pricing with no hidden fees.

Pricing info →

Start building

Start operating and sending quotes in just 10 minutes.

Integration options →