RFQ & Quotes
The core procurement integration. RFQ routes check their scopes individually — rfq.read for reads, rfq.write for writes (and rfq.write also satisfies the RFQ list/detail read checks), quotes.write to record a supplier quote.
/v1/rfq/inboundCreate an RFQ with its line items. Returns 201 with `{ data, items_created }`. Fires `rfq.created`.
Required: title, buyer_name, and a non-empty items array — otherwise 400. When buyer_email is supplied, the buyer record is matched by email or created.
{
"external_rfq_id": "PROC-2026-0042",
"title": "Office furniture — Q3",
"buyer_name": "Andi Pratama",
"buyer_company": "Nusantara Works",
"buyer_email": "andi@nusantara.example",
"buyer_phone": "+6281…",
"delivery_location": "Denpasar, Bali",
"delivery_date": "2026-10-15",
"priority": "medium",
"notes": "Split delivery acceptable.",
"items": [
{
"name": "Ergonomic office chair",
"description": "Mesh back, adjustable lumbar",
"category_slug": "furniture",
"quantity": 40,
"unit_of_measure": "piece",
"target_price": 1500000,
"target_currency": "IDR",
"notes": "Black only"
}
]
}- Items accept either
category_idorcategory_slug; the slug is resolved server-side and becomesnullif unknown. unit_of_measuredefaults topiece,target_currencytoIDR,prioritytomedium.- Line items are numbered in array order (
item_numberstarts at 1). - Store the returned
data.id— every other RFQ route is keyed by it, not byexternal_rfq_id.
/v1/rfqList RFQs with their line items. Flat list envelope, supports `limit` / `offset` and status filtering.
/v1/rfq/:idSingle RFQ with line items.
/v1/rfq/:idUpdate an RFQ. When `status` changes, fires `rfq.status_changed` including the `external_rfq_id`.
/v1/rfq/:id/suppliersAttach a supplier (business) to the RFQ so it can be quoted.
/v1/rfq/:id/suppliers/:supplierIdDetach a supplier from the RFQ.
/v1/rfq/:id/quotesAll supplier quotes for an RFQ. 404 when the RFQ does not exist.
{
"rfq_id": "9f31…",
"external_rfq_id": "PROC-2026-0042",
"rfq_number": "RFQ-2026-0117",
"title": "Office furniture — Q3",
"suppliers": [
{
"supplier_id": "c3a1…",
"business_name": "Bali Teak Works",
"business_slug": "bali-teak-works",
"status": "quoted",
"quoted_price": 56000000,
"quoted_unit_price": 1400000,
"quoted_currency": "IDR",
"quoted_lead_time_days": 21,
"quoted_payment_terms": "50% down, 50% on delivery",
"quoted_notes": "Price valid 14 days.",
"contacted_at": "2026-09-01T04:11:00Z",
"response_received_at": "2026-09-02T02:40:00Z",
"is_selected": false
}
]
}/v1/rfq/:id/quotes/:supplierIdOne supplier's full quote row. 404 when that supplier is not on the RFQ.
/v1/rfq/:id/quotesRecord a supplier quote. Sets the supplier status to `quoted` and stamps `response_received_at`. Returns 201. Fires `rfq.quote_submitted`.
{
"supplier_id": "c3a1…",
"quoted_price": 56000000,
"quoted_unit_price": 1400000,
"quoted_currency": "IDR",
"quoted_lead_time_days": 21,
"quoted_payment_terms": "50% down, 50% on delivery",
"quoted_notes": "Price valid 14 days."
}supplier_id is required (400 otherwise) and the supplier must already be attached to the RFQ. quoted_currency defaults to IDR when omitted — always send it explicitly.