Cotizaciones
El corazón transaccional de Cord. Crea propuestas, obtén sus links públicos y adminístralas.
Descripción General
El objeto Cotizacion (quote) representa una propuesta comercial hacia un cliente. Usando la API de Cord, puedes inyectar cotizaciones directamente desde un ERP, CRM o generar flujos automatizados de carritos de compra.
Todas las cotizaciones generadas vía API quedan registradas en el registro de auditoría (audit_log) etiquetadas automáticamente como provenientes de tu Clave de API.
Crear una Cotización
Para crear un borrador de cotización, debes realizar un POST /api/v1/cotizaciones enviando el detalle comercial.
Petición:
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": "Proyecto anual",
"base_currency": "EUR",
"items": [
{
"descripcion": "Licencia anual — plan Pro",
"cantidad": 12,
"precio_unitario": 500
}
]
}'
Cada línea de items requiere descripcion, cantidad y precio_unitario. producto_id es opcional (referencia un producto de tu catálogo para heredar su nombre y precio de lista) y precio_negociado permite capturar un precio con descuento distinto al de lista. No existe un campo sku: si quieres enlazar la línea a un producto, usa su producto_id.
Campos de la cotización
| Campo | Detalle |
|---|---|
items |
Obligatorio. Las líneas (ver la tabla siguiente). |
cliente_id o cliente |
Un cliente de tu directorio, o los datos para darlo de alta (empresa, y opcionales email, contacto, telefono, rfc y fiscal). |
terminos |
contado, net7, net15, net30, net45, net60 o net90 (N días naturales). Sin él, los términos del cliente o los de tu cuenta. |
vigencia_dias |
Días que la cotización es válida desde su envío. |
notas |
Notas para el cliente. |
base_currency |
Divisa de venta (ISO 4217). |
fiscal_currency |
Divisa contable; si difiere de la de venta, la factura declara el tipo de cambio. |
iva_incluido |
true si los precios ya incluyen el impuesto. |
descuento |
Descuento de documento antes de impuestos: { "tipo": "porcentaje", "valor": 10 } o { "tipo": "monto", "valor": 2500 }. Cord calcula el importe y lo reparte entre las líneas; un monto se topa en el subtotal. null lo quita. Ver Descuentos y cupones. |
cupon |
Código de un cupón de tu cuenta (por ejemplo, "BIENVENIDA10"). Cord valida vigencia, divisa y usos; si viene, manda sobre descuento. |
plan_pagos |
Plan de pagos por hitos: de 2 a 24 pagos, cada uno con su importe (porcentaje, monto o resto) y su vence (al_aprobar, dias, fecha o al_cumplir), y, con cliente_empresa: true, los intereses. Mandas la definición, nunca importes: Cord los calcula y agrega la línea de intereses. No se combina con anticipo ni iguala. Ver Plan de pagos por hitos. |
send |
true para enviarla al crearla. Solo con llave secreta. |
| Campo de la línea | Detalle |
|---|---|
descripcion, cantidad, precio_unitario |
Obligatorios. |
producto_id |
Producto del catálogo. |
precio_negociado |
Precio final de la línea, si es distinto al de lista. |
costo_unitario |
Costo interno para calcular el margen. Nunca lo ve el cliente. |
tax_rate |
Fracción de 0 a 1 (0.16, no 16), validada contra tu catálogo de impuestos. 0 significa exenta a propósito. |
exemption_reason |
Solo con tax_rate 0. España: la causa de exención o no sujeción de Verifactu (E1–E6, N1, N2, S2). Resto de la UE: el código VATEX de la factura electrónica. |
clave_sat, clave_unidad_sat |
Solo México: clave de producto o servicio del SAT (8 dígitos) y clave de unidad de esa línea. Ganan sobre las del producto; una clave con forma inválida responde 400. Fuera de México se ignoran. |
curl -X POST "https://cordhq.app/api/v1/cotizaciones" \
-H "Authorization: Bearer sk_live_tU..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cotizacion-pedido-4471" \
-d '{
"cliente_id": "8f7b2c1e-4d3a-4b9f-a6e2-1c5d7e9f0a3b",
"terminos": "net45",
"base_currency": "MXN",
"cupon": "BIENVENIDA10",
"items": [
{ "descripcion": "Cemento gris CPC 30R", "cantidad": 400, "precio_unitario": 245,
"tax_rate": 0.16, "clave_sat": "30111601", "clave_unidad_sat": "H87" }
]
}'
Con una llave publicable (la de tu sitio) Cord ignora descuento, precio_negociado, costo_unitario y plan_pagos: el único descuento que acepta es un cupon. Un cupón que no existe responde 404 con code: "coupon_no_existe"; uno inactivo, vencido, agotado, de otra divisa o limitado por cliente sin cliente responde 409 con coupon_inactivo, coupon_vencido, coupon_aun_no_vigente, coupon_agotado, coupon_agotado_cliente, coupon_moneda o coupon_requiere_cliente.
base_currency es la divisa en la que el cliente ve y paga la cotización (ISO 4217, ej. MXN, USD, EUR, GBP). Es opcional — si la omites, Cord usa la divisa contable de tu organización — pero la API es agnóstica de país: una cuenta en Madrid, Austin o Bogotá cotiza en su propia divisa sin ningún parámetro adicional.
Respuesta Exitosa:
Cord te devuelve el id (UUID) de la cotización, su token público y el link público (link_publico) ya construido como URL absoluta, para que se lo envíes a tu cliente inmediatamente vía WhatsApp, SMS o un motor de correos externo. Si tu organización tiene un dominio activo en Cord Domains, el link usa ese dominio en lugar de cordhq.app.
{
"data": {
"id": "2a9d8e4f-6b1c-4d7a-9e3f-5c8b1a2d4e6f",
"folio": "COT-00104",
"token": "9f3b6c2a7e1d4f8b0a5c3e7d1b9f2a46",
"link_publico": "https://cordhq.app/q/9f3b6c2a7e1d4f8b0a5c3e7d1b9f2a46",
"needs_approval": false
}
}
Si la cotización requiere aprobación interna antes de enviarse (por ejemplo, un descuento fuera de umbral), needs_approval viene en true y motivo describe la razón.
Listar Cotizaciones
Puedes recuperar tu historial paginado de cotizaciones o filtrar por estado (por ejemplo, buscar todas las cotizaciones enviadas o pagadas).
Petición:
curl -X GET "https://cordhq.app/api/v1/cotizaciones?status=paid&limit=10" \
-H "Authorization: Bearer sk_live_tU..."
El valor de status es el estado interno tal cual (en inglés, no traducido): draft, sent, viewed, approved, rejected, expired, paid o invoiced. Para buscar una cotización por su folio usa folio=COT-00104: compara el folio completo sin distinguir mayúsculas. Para ver solo las de un cliente usa cliente_id=<uuid>. Cada cotización trae su cliente_id: null si no tiene cliente.
{
"data": [
{
"id": "2a9d8e4f-6b1c-4d7a-9e3f-5c8b1a2d4e6f",
"folio": "COT-00104",
"cliente": "Stark Industries",
"cliente_id": "8f7b2c1e-4d3a-4b9f-a6e2-1c5d7e9f0a3b",
"status": "paid",
"total": 6000,
"moneda": "MXN",
"terminos": "Net 30",
"terminos_codigo": "net30",
"vigencia": "10 sep 2026",
"creada": "20 ago 2026",
"vigencia_iso": "2026-09-10",
"creada_iso": "2026-08-20T17:42:10.000Z",
"link_publico": "https://cordhq.app/q/9f3b6c2a7e1d4f8b0a5c3e7d1b9f2a46",
"language": "es"
}
],
"meta": { "limit": 10, "offset": 0, "total": 1, "next_cursor": null }
}
Fechas y términos en formato de presentación
En la lista y el detalle, terminos llega como etiqueta en el idioma de tu cuenta (Contado o Due on receipt, Net 45) y vigencia y creada como fechas de presentación (10 sep 2026). No las parses: para ordenar o comparar usa vigencia_iso (AAAA-MM-DD) y creada_iso (ISO 8601). Para comparar el plazo usa terminos_codigo (contado, net7, net15, net30, net45, net60 o net90), que no cambia con el idioma; para la fecha exacta de cada cambio usa created_at del historial de eventos con object_id.
En el listado, link_publico también es una URL absoluta, igual que en la respuesta de creación. Para el detalle de una sola cotización (líneas, eventos, aprobación), usa GET /api/v1/cotizaciones/{id}.
language es el idioma en que el cliente recibe la cotización: es, en, pt, fr o de. Es de solo lectura: la cotización lo toma al enviarse, del cliente o, si no tiene uno, de tu organización, y una versión nueva lo conserva. Vale null mientras es borrador. Para enviarla en otro idioma, cambia el language del cliente antes de enviarla.
El detalle trae además descuento: null si no hay, o el descuento de documento con su definición, el cupón del que salió y el importe calculado antes de impuestos:
"descuento": { "tipo": "porcentaje", "valor": 10, "cupon": "BIENVENIDA10", "monto": 9800, "moneda": "MXN" }
total ya incluye el descuento.
Y trae plan_pagos: null sin plan, o el plan con el precio de contado, los intereses, el total y la tasa anual efectiva (contado, intereses, total, tasa_anual_efectiva, en moneda) y cada pago con su importe, su vence, si está habilitado y si es exigible hoy. Mientras tu cliente no aprueba, trae el plan como se guardó; el link lo muestra calculado con el día de hoy y, al aprobar, queda el del día de la aprobación.
Proponer líneas con IA
POST /api/v1/cotizaciones/ia recibe { "texto": "..." } (el pedido del cliente, hasta 6000 caracteres) y devuelve las líneas emparejadas con tu catálogo, sin crear nada:
{ "data": { "moneda": "MXN", "items": [{ "id": "7e3c9a1f-2b4d-4c6e-8f0a-b1d3e5f7a9c2", "nombre": "Cemento Gris 50kg", "unidad": "saco", "lista": 185, "negociado": null, "cantidad": 5 }] } }
id es null cuando el concepto no está en tu catálogo. Los precios van en moneda. Para crear la cotización, manda esas líneas a POST /api/v1/cotizaciones. Requiere una llave con escritura y gasta una unidad de IA de tu plan: si ya no quedan, recibes 429 con code: "ai_quota_exceeded"; si el texto no trae productos, 422 con code: "no_items".
Cambiar el estado de una cotización
Con una llave de escritura (write) puedes mover la cotización por su ciclo de vida con 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: aprobar-COT-00104" \
-d '{ "action": "approve" }'
action |
Desde el estado | Resultado |
|---|---|---|
send |
draft |
Envía la cotización al cliente (correo y link) en el idioma de sus documentos, que la cotización guarda en language. En el plan Gratis consume uno de los envíos del mes. |
resend |
sent, viewed, expired |
La reenvía al cliente. |
approve |
sent, viewed |
La marca como aprobada. Si su plan de pagos lleva intereses, responde 409 aprobacion_solo_cliente: esa la aprueba tu cliente desde su link. |
reject |
sent, viewed |
La marca como rechazada. |
mark_paid |
approved, invoiced |
Registra el pago. Acepta payment_method opcional (por defecto transferencia). Los cobros en línea pendientes de esa cotización se cancelan. |
La respuesta es { "data": { "ok": true, "status": "approved" } }. Si la cotización no está en un estado desde el que se pueda aplicar la acción, o si otra petición la cambió al mismo tiempo, recibes 409 con code: "invalid_state" y no se aplica nada. Cada cambio queda en el registro de auditoría con tu llave como actor y dispara su evento de webhook.
Emitir la factura no está disponible por esta ruta: las facturas se crean y emiten con POST /api/v1/facturas y POST /api/v1/facturas/{id}.
Editar un borrador
PATCH /api/v1/cotizaciones/{id} cambia una cotización que todavía es borrador (draft): lo que mandas reemplaza a lo guardado y lo que no mandas se conserva. items reemplaza todas las líneas; sin items se conservan las que tiene, con su impuesto. cliente_id: null deja el borrador sin cliente y notas: null borra las notas. Acepta los mismos campos que la creación salvo cliente y send; para enviarlo usa { "action": "send" }.
curl -X PATCH "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: editar-COT-00104-1" \
-d '{ "terminos": "net30", "descuento": { "tipo": "porcentaje", "valor": 5 } }'
Cord recalcula los totales con las mismas reglas que al crearla (impuesto por línea, descuento, cupón y plan de pagos) y responde el detalle actualizado, igual que GET /api/v1/cotizaciones/{id}. Un campo con el tipo equivocado responde 400 con code: "invalid_request" y field, y una cotización que ya se envió responde 409 con code: "invalid_state"; en los dos casos no cambia nada.
Eliminar un borrador
DELETE /api/v1/cotizaciones/{id} elimina una cotización que nunca se envió (draft). Cualquier otro estado responde 409.