Getting started
- Create a key in Settings → API keys. Each key belongs to one business.
- Create a project:
POST/api /v1 /projects - Create a quotation with its items:
POST/api /v1 /projects /{id} /quotations - Attach and approve suppliers before sending:
POST/api /v1 /quotations /{id} /invitations - Send and track:
POST//api /v1 /quotations /{id} /send GET/api /v1 /quotations /{id} /compare
curl -X POST https://cotacaoia-a1.netlify.app/api/v1/projects \
-H "Authorization: Bearer cot_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Example purchase" }'Reference under review. Examples and identifiers retain the original contract data; verify availability and format before integrating.
1. Base and authentication
| Item | Value |
|---|---|
| Base URL | https://cotacaoia-a1.netlify.app/api/v1 |
| Auth | header Authorization: Bearer cot_live_<chave> — API key per business |
| Format | JSON, UTF-8, timestamps ISO-8601 UTC; commercial amounts in currency units (for example, 12.50) with currency: "BRL", "USD" or "EUR". Stripe billing has its own contract. |
| IDs | opaque strings (cuid2), without a prefix; historical examples preserve their identifiers |
| Idempotency | Each operation has its own rules; MCP has no generic Idempotency-Key contract. |
| Pagination | ?limit=50&cursor=<opaco> → { data: [...], next_cursor: "..." | null } |
| Rate limit | No per-key limit is documented in this version. |
| Versioning | /v1 in the path; new fields are additive; removal = /v2 |
Keys are created in Settings → API keys, shown once and stored as hashes. A key grants full access to the business, without per-tool scopes or automatic expiration; it can be revoked. Keys with the cot_test_ prefix are rejected in production, including key creation. Outside production, the prefix does not create a sandbox or block real sends.
2. Resources and endpoints
Historical reference for a proposed contract: not all endpoints, fields and examples in sections 2–5 were implemented. For MCP, use the names, schemas and limitations in section 6. Do not assume outgoing webhooks or header-based idempotency are available.
| Resource | Endpoints | Notes |
|---|---|---|
| projects | GET /projects · POST /projects · GET /projects/{id} · PATCH /projects/{id} |
Groups requests for quotation |
| quotations | GET /quotations?project_id= · POST /quotations · GET /quotations/{id} · PATCH /quotations/{id} · POST /quotations/{id}/cancel |
status: draft | sent | collecting | closed | ordered | cancelled; content in blocks |
| suppliers | GET /suppliers · POST /suppliers · GET /suppliers/{id} · PATCH /suppliers/{id} · POST /suppliers/import (CSV) · POST /suppliers/search |
Search returns unsaved candidates (Google ToS: only google_place_id is saved when added) |
| quotation recipients | POST /quotations/{id}/suppliers (attaches, pending) · POST /quotations/{id}/suppliers/approve · DELETE /quotations/{id}/suppliers/{supplier_id} |
Nothing is sent without approval (approved_by, approved_at) |
| send | POST /quotations/{id}/send |
= send_rfq; creates N invitations, queues a job; idempotent |
| invitations | GET /quotations/{id}/invitations · GET /invitations/{id} · POST /invitations/{id}/resend (1x, manual) |
channel: email | whatsapp; status: queued | sent | delivered | opened | responded | declined | bounced | failed | opted_out |
| quotes | GET /quotations/{id}/quotes · GET /quotes/{id} · POST /quotes/{id}/confirm · PATCH /quotes/{id} (correct extracted fields) |
source: form | extracted; is_confirmed; each extracted field has evidence |
| compare | GET /quotations/{id}/compare |
Item × supplier matrix + summary (AI, with citations) |
| orders | POST /orders · GET /orders/{id} · GET /quotations/{id}/orders |
status: sent | confirmed | rejected; can split items between suppliers |
| templates | GET /templates · POST /quotations/from-template |
Industry templates (food service, clinic) |
| usage | GET /usage?from=&to= |
Credits used, sends per channel, AI cost — mirrors usage_events |
| webhooks | GET /webhooks · POST /webhooks · DELETE /webhooks/{id} |
Customer endpoints for the events in section 4 |
| opt-out (public) | GET /sair/{token} · POST /sair/{token} |
No auth; records opted_out_at global |
| public | GET /q/{token} · POST /q/{token}/quote · GET /o/{token} · POST /o/{token}/confirm |
Supplier pages/actions, no account required |
3. Examples
3.1 POST /quotations (create_quotation)
POST /api/v1/quotations
Authorization: Bearer cot_live_xxx
Content-Type: application/json{
"project_id": "prj_01j8x",
"title": "Embalagens e descartáveis — setembro",
"category": "embalagens e descartáveis",
"deadline_at": "2026-09-12T21:00:00Z",
"delivery_city": "Campinas/SP",
"content": [
{
"type": "items",
"columns": ["qty", "unit", "description", "brand"],
"rows": [
{ "qty": 500, "unit": "un", "description": "Embalagem kraft para viagem M", "brand": "" },
{ "qty": 2000, "unit": "un", "description": "Copo 300 ml PP transparente", "brand": "Copobras ou similar" },
{ "qty": 20, "unit": "pct", "description": "Guardanapo 33x33 c/ 50", "brand": "" }
]
},
{ "type": "text", "body": "Entrega na loja, de segunda a sexta, até 11h. Frete incluso no preço, por favor." }
]
}Response 201:
{
"id": "qtn_01j8y",
"number": "0042",
"project_id": "prj_01j8x",
"workspace_id": "wrk_01j8a",
"status": "draft",
"title": "Embalagens e descartáveis — setembro",
"category": "embalagens e descartáveis",
"deadline_at": "2026-09-12T21:00:00Z",
"delivery_city": "Campinas/SP",
"items": [
{ "id": "qti_1", "qty": 500, "unit": "un", "description": "Embalagem kraft para viagem M", "brand": "" },
{ "id": "qti_2", "qty": 2000, "unit": "un", "description": "Copo 300 ml PP transparente", "brand": "Copobras ou similar" },
{ "id": "qti_3", "qty": 20, "unit": "pct", "description": "Guardanapo 33x33 c/ 50", "brand": "" }
],
"content": [ "...(como enviado)..." ],
"suppliers": [],
"created_at": "2026-09-05T14:02:11Z",
"updated_at": "2026-09-05T14:02:11Z"
}3.2 POST /quotations/{id}/send (send_rfq)
Preconditions: status = draft, ≥ 1 approved supplier, business with credits, tenant not paused (bounce/complaint), no supplier with opted_out_at.
POST /api/v1/quotations/qtn_01j8y/send
Authorization: Bearer cot_live_xxx
Idempotency-Key: 7f3c1a2e-send-0042
Content-Type: application/json{
"channels": ["email", "whatsapp"],
"followup_after_hours": 24,
"message_note": "Se puder, indique frete separado."
}Response 202 (sending is asynchronous; check invitations or subscribe to webhooks):
{
"quotation_id": "qtn_01j8y",
"status": "sent",
"job_id": "run_01j8z",
"invitations": [
{ "id": "inv_a1", "supplier_id": "sup_9k", "channel": "email", "status": "queued" },
{ "id": "inv_a2", "supplier_id": "sup_9k", "channel": "whatsapp", "status": "queued" },
{ "id": "inv_b1", "supplier_id": "sup_7m", "channel": "email", "status": "queued" },
{ "id": "inv_b2", "supplier_id": "sup_7m", "channel": "whatsapp", "status": "skipped", "skip_reason": "no_whatsapp_opt_in" }
],
"credits_consumed": 1,
"credits_remaining": 11
}WhatsApp requires permission for the channel; an email response does not open the WhatsApp window. Outside the window started by a supplier message on WhatsApp, Meta requirements and approved templates apply. Repeating with the same Idempotency-Key returns the same body without new sends.
3.3 Comparison (abridged)
{
"quotation_id": "qtn_01j8y",
"items": [
{
"item_id": "qti_1",
"description": "Embalagem kraft para viagem M",
"qty": 500,
"offers": [
{ "supplier_id": "sup_9k", "quote_id": "quo_1", "unit_price_cents": 89, "delivery_days": 3, "is_confirmed": true, "is_best": true },
{ "supplier_id": "sup_7m", "quote_id": "quo_2", "unit_price_cents": 97, "delivery_days": 2, "is_confirmed": false, "is_best": false }
]
}
],
"totals_by_supplier": [
{ "supplier_id": "sup_9k", "total_cents": 184250, "items_quoted": 3, "items_missing": 0 },
{ "supplier_id": "sup_7m", "total_cents": 191000, "items_quoted": 2, "items_missing": 1 }
],
"not_responded": ["sup_3p"],
"summary": {
"text": "Distribuidora Boa Vista tem o menor total (R$ 1.842,50) e cotou os 3 itens; Atacado Central entrega 1 dia antes mas não cotou o guardanapo e o copo ainda precisa de confirmação.",
"citations": [ { "quote_id": "quo_1", "field": "total" }, { "quote_id": "quo_2", "field": "delivery_days" } ]
}
}4. Webhooks
Proposed contract for outgoing webhooks. Confirm availability in this instance before integrating. The table and payload below describe the planned contract; they do not guarantee active delivery.
| Event | When |
|---|---|
quotation.sent |
job send_rfq completed |
invitation.delivered · invitation.opened · invitation.bounced · invitation.failed |
normalized Resend / Meta events |
invitation.opted_out |
supplier opted out (email or SAIR) |
quote.received |
Quote created (form or extraction) |
quote.needs_confirmation |
extraction completed with is_confirmed=false |
quote.confirmed |
buyer confirmed/corrected |
quotation.deadline_reached |
deadline passed; status → closed |
order.sent · order.confirmed · order.rejected |
order lifecycle |
workspace.paused |
circuit breaker (bounce > 4%, complaint > 0.08%) or no credits |
usage.credit_low |
remaining credits below the plan threshold |
Payload:
{
"id": "evt_01j9a",
"type": "quote.received",
"created_at": "2026-09-06T12:40:03Z",
"workspace_id": "wrk_01j8a",
"data": { "quote_id": "quo_1", "quotation_id": "qtn_01j8y", "supplier_id": "sup_9k", "source": "form", "is_confirmed": true }
}5. Errors
{
"error": {
"type": "validation_error",
"code": "recipient_not_approved",
"message": "Fornecedor sup_7m ainda não foi aprovado para esta cotação.",
"param": "suppliers[1]",
"doc_url": "https://cotacaoia-a1.netlify.app/docs/errors#recipient_not_approved",
"request_id": "req_01j9b"
}
}| HTTP | type |
Common codes |
|---|---|---|
| 400 | validation_error |
invalid_field, recipient_not_approved, deadline_in_past, no_recipients |
| 401 | authentication_error |
invalid_api_key, key_revoked |
| 402 | billing_error |
no_credits, subscription_inactive |
| 403 | permission_error |
workspace_paused (circuit breaker), test_key_cannot_send |
| 404 | not_found |
quotation_not_found (includes cross-tenant: never reveal existence) |
| 409 | conflict |
invalid_status_transition, supplier_opted_out, idempotency_key_reused_with_different_body |
| 422 | unprocessable |
search_source_unavailable, template_paused (WhatsApp) |
| 429 | rate_limited |
too_many_requests (+ Retry-After) |
| 500 | internal_error |
always with request_id |
Error messages are in pt-BR (shown to users by the UI); code is stable and in English for the agent.
6. MCP server
Tools, an agent workflow, and client installation are documented at /docs/mcp. Server URL: https://cotacaoia-a1.netlify.app/api/mcp, with the same API key.
Supplier communication language
POST /api/v1/suppliers, PATCH /api/v1/suppliers/{id} and the add_supplier MCP tool accept the optional communication_locale field. Valid values: pt-BR, en or es. New suppliers default to pt-BR when the field is omitted; an update without this field preserves the existing preference.
The preference selects the language of request and order emails and configured WhatsApp templates. It does not change currency, country, time zone or original item and terms content. WhatsApp still requires recorded permission and an appropriate approved template. A language without a configured template is not silently replaced with Portuguese.