Quotes
The transactional heart of Cord. Create proposals, get their public links, and manage them.
Overview
The Quote (cotizacion) object represents a commercial proposal to a client. Using the Cord API, you can inject quotes directly from an ERP, CRM, or generate automated shopping cart workflows.
All quotes generated via API are recorded in the audit log (audit_log), automatically tagged as originating from your API Key.
Creating a Quote
To create a draft quote, you must perform a POST /api/v1/cotizaciones sending the commercial details.
Request:
curl -X POST "https://cordhq.app/api/v1/cotizaciones" \
-H "Authorization: Bearer sk_live_tU..." \
-H "Content-Type: application/json" \
-d '{
"cliente_id": "8f7b2c1e-4d3a-4b9f-a6e2-1c5d7e9f0a3b",
"terminos": "net30",
"vigencia_dias": 15,
"notas": "Annual project",
"base_currency": "EUR",
"items": [
{
"descripcion": "Annual license — Pro plan",
"cantidad": 12,
"precio_unitario": 500
}
]
}'
Every line in items requires descripcion, cantidad, and precio_unitario — field names stay in Spanish regardless of the request’s language, since they are the API’s canonical schema. producto_id is optional (it references a product from your catalog to inherit its name and list price), and precio_negociado lets you capture a discounted price different from the list price. There is no sku field: to link a line to a product, use its producto_id.
Quote fields
| Field | Details |
|---|---|
items |
Required. The lines (see the next table). |
cliente_id or cliente |
A client from your directory, or the details to create one (empresa, plus optional email, contacto, telefono, rfc, and fiscal). |
terminos |
contado, net7, net15, net30, net45, net60, or net90 (N calendar days). Without it, the client’s terms or your account’s. |
vigencia_dias |
Days the quote is valid from when it’s sent. |
notas |
Notes for the client. |
base_currency |
Sale currency (ISO 4217). |
fiscal_currency |
Accounting currency; if it differs from the sale currency, the invoice declares the exchange rate. |
iva_incluido |
true if prices already include tax. |
descuento |
Document discount before tax: { "tipo": "porcentaje", "valor": 10 } or { "tipo": "monto", "valor": 2500 }. Cord calculates the amount and spreads it across the lines; an amount is capped at the subtotal. null removes it. See Discounts and coupons. |
cupon |
A coupon code from your account (for example, "WELCOME10"). Cord validates its dates, currency, and uses; if present, it wins over descuento. |
plan_pagos |
Milestone payment plan: 2 to 24 payments, each with its importe (porcentaje, monto or resto) and its vence (al_aprobar, dias, fecha or al_cumplir), and, with cliente_empresa: true, the intereses. You send the definition, never amounts: Cord calculates them and adds the interest line. It can’t be combined with a deposit or a retainer. See Milestone payment plan. |
send |
true to send it on creation. Secret key only. |
| Line field | Details |
|---|---|
descripcion, cantidad, precio_unitario |
Required. |
producto_id |
Catalog product. |
precio_negociado |
The line’s final price, if different from the list price. |
costo_unitario |
Internal cost to calculate margin. The client never sees it. |
tax_rate |
Fraction from 0 to 1 (0.16, not 16), validated against your tax catalog. 0 means deliberately exempt. |
exemption_reason |
Only with tax_rate 0. Spain: the Verifactu exemption or non-subject reason (E1–E6, N1, N2, S2). Rest of the EU: the e-invoice VATEX code. |
clave_sat, clave_unidad_sat |
Mexico only: the SAT product or service key (8 digits) and unit key for that line. They win over the product’s; a key with an invalid format responds 400. Ignored outside Mexico. |
curl -X POST "https://cordhq.app/api/v1/cotizaciones" \
-H "Authorization: Bearer sk_live_tU..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quote-order-4471" \
-d '{
"cliente_id": "8f7b2c1e-4d3a-4b9f-a6e2-1c5d7e9f0a3b",
"terminos": "net45",
"base_currency": "MXN",
"cupon": "WELCOME10",
"items": [
{ "descripcion": "Gray cement CPC 30R", "cantidad": 400, "precio_unitario": 245,
"tax_rate": 0.16, "clave_sat": "30111601", "clave_unidad_sat": "H87" }
]
}'
With a publishable key (your website’s), Cord ignores descuento, precio_negociado, costo_unitario, and plan_pagos: the only discount it accepts is a cupon. A coupon that doesn’t exist responds 404 with code: "coupon_no_existe"; an inactive, expired, exhausted, wrong-currency, or per-client coupon without a client responds 409 with coupon_inactivo, coupon_vencido, coupon_aun_no_vigente, coupon_agotado, coupon_agotado_cliente, coupon_moneda, or coupon_requiere_cliente.
base_currency is the currency the client sees and pays the quote in (ISO 4217, e.g. MXN, USD, EUR, GBP). It’s optional — omit it and Cord falls back to your organization’s accounting currency — but the API is country-agnostic: an account in Madrid, Austin, or Bogotá quotes in its own currency with no extra parameter.
Successful Response:
Cord returns the quote id (a UUID), its public token, and the public link (link_publico) already built as an absolute URL, so you can immediately send it to your client via WhatsApp, SMS, or an external email engine. If your organization has an active custom domain, the link uses that domain instead of cordhq.app.
{
"data": {
"id": "2a9d8e4f-6b1c-4d7a-9e3f-5c8b1a2d4e6f",
"folio": "COT-00104",
"token": "9f3b6c2a7e1d4f8b0a5c3e7d1b9f2a46",
"link_publico": "https://cordhq.app/q/9f3b6c2a7e1d4f8b0a5c3e7d1b9f2a46",
"needs_approval": false
}
}
If the quote requires internal approval before it can be sent (for example, a discount outside the allowed threshold), needs_approval comes back true and motivo describes the reason.
Listing Quotes
You can retrieve your paginated quote history or filter by status (for example, finding all sent or paid quotes).
Request:
curl -X GET "https://cordhq.app/api/v1/cotizaciones?status=paid&limit=10" \
-H "Authorization: Bearer sk_live_tU..."
The status value is the internal state as-is (English, never translated): draft, sent, viewed, approved, rejected, expired, paid, or invoiced. To find a quote by its number, use folio=COT-00104: it matches the full number, case-insensitive. To list only one client’s quotes, use cliente_id=<uuid>.
{
"data": [
{
"id": "2a9d8e4f-6b1c-4d7a-9e3f-5c8b1a2d4e6f",
"folio": "COT-00104",
"cliente": "Stark Industries",
"status": "paid",
"total": 6000,
"moneda": "MXN",
"terminos": "Net 30",
"terminos_codigo": "net30",
"vigencia": "10 sep 2026",
"creada": "20 ago 2026",
"link_publico": "https://cordhq.app/q/9f3b6c2a7e1d4f8b0a5c3e7d1b9f2a46",
"language": "es"
}
],
"meta": { "limit": 10, "offset": 0, "total": 1, "next_cursor": null }
}
Dates and terms in display format
In the list and the detail, terminos comes as a label in your account’s language (Due on receipt or Contado, Net 45) and vigencia and creada as display dates (Sep 10, 2026), not as ISO dates. Don’t parse them: to compare the term use terminos_codigo (contado, net7, net15, net30, net45, net60, or net90), which doesn’t change with the language; for the exact time of each change, use created_at from the event history with object_id.
In the list, link_publico is also an absolute URL, just like in the creation response. For the detail of a single quote (line items, event timeline, approval), use GET /api/v1/cotizaciones/{id}.
language is the language in which the client receives the quote: es, en, pt, fr, or de. It’s read-only: the quote takes it when sent, from the client or, if they have none, from your organization, and a new version keeps it. It’s null while the quote is a draft. To send it in another language, change the client’s language before sending.
The detail also includes descuento: null if there’s none, or the document discount with its definition, the coupon it came from, and the calculated amount before tax:
"descuento": { "tipo": "porcentaje", "valor": 10, "cupon": "WELCOME10", "monto": 9800, "moneda": "MXN" }
total already includes the discount.
It also carries plan_pagos: null without a plan, or the plan with the cash price, the interest, the total and the effective annual rate (contado, intereses, total, tasa_anual_efectiva, in moneda) and each payment with its amount, its vence, whether it’s habilitado and whether it’s exigible today. Until your client approves, it carries the plan as saved; the link shows it calculated with today’s date and, on approval, the one for the approval date stays.
Proposing lines with AI
POST /api/v1/cotizaciones/ia takes { "texto": "..." } (the client’s order, up to 6000 characters) and returns the lines matched to your catalog, without creating anything:
{ "data": { "moneda": "MXN", "items": [{ "id": "7e3c9a1f-2b4d-4c6e-8f0a-b1d3e5f7a9c2", "nombre": "Gray Cement 50kg", "unidad": "sack", "lista": 185, "negociado": null, "cantidad": 5 }] } }
id is null when the item is not in your catalog. Prices are in moneda. To create the quote, send those lines to POST /api/v1/cotizaciones. It needs a write key and uses one AI unit from your plan: when none are left you get 429 with code: "ai_quota_exceeded"; if the text has no products, 422 with code: "no_items".
Changing a quote status
With a write key (write) you can move a quote through its lifecycle with POST /api/v1/cotizaciones/{id}:
curl -X POST "https://cordhq.app/api/v1/cotizaciones/2a9d8e4f-6b1c-4d7a-9e3f-5c8b1a2d4e6f" \
-H "Authorization: Bearer sk_live_tU..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: approve-COT-00104" \
-d '{ "action": "approve" }'
action |
From status | Result |
|---|---|---|
send |
draft |
Sends the quote to the client (email and link) in their document language, which the quote saves in language. On the Free plan it uses one of the monthly sends. |
resend |
sent, viewed, expired |
Sends it to the client again. |
approve |
sent, viewed |
Marks it as approved. If its payment plan carries interest, it responds 409 aprobacion_solo_cliente: your client approves that one from their link. |
reject |
sent, viewed |
Marks it as rejected. |
mark_paid |
approved, invoiced |
Records the payment. Accepts an optional payment_method (defaults to transferencia). Pending online payments for that quote are canceled. |
The response is { "data": { "ok": true, "status": "approved" } }. If the quote is not in a status the action can apply to, or another request changed it at the same time, you get 409 with code: "invalid_state" and nothing is applied. Every change is recorded in the audit log with your key as the actor and fires its webhook event.
Issuing the invoice is not available on this route: invoices are created and issued with POST /api/v1/facturas and POST /api/v1/facturas/{id}.
Deleting a draft
DELETE /api/v1/cotizaciones/{id} deletes a quote that was never sent (draft). Any other status returns 409.