borkoldocs

Orders & Quotes

Quote tokens

Fetch, accept, or decline an offered quote using the tokenized link from the offer email.

View as Markdown

When the merchant sends an offer, the buyer receives an email containing a plain 48-character accept token. Only the SHA-256 hash of that token is stored server-side, so the emailed link is the only credential; there is no way to re-derive it. The storefront hosts a quote page that takes the token from the link and drives these three endpoints. No X-Customer-Token is involved: the accept token itself is the proof of access.

All three endpoints lazily expire first: if the quote is offered and valid_until has passed, its status flips to expired before the request is processed. A read after expiry therefore returns "status": "expired", and accept or decline fail with QUOTE_NOT_OFFERED.

A token that does not match any quote of the store returns 404.

GET /v1/api/commerce/quotes/by-token/{token}

Fetches the quote for this token. Requires commerce:read. Returns the customer-facing quote serialization (no merchant_note, cart_id, or customer_id; the token or its hash is never in the body). The full field reference is on the Quote requests page. Quote amounts are bare integers in minor units, not money objects.

Parameters

Path

NameTypeRequiredDescription
tokenstringyesThe plain 48-character token from the offer email.
Request
curl https://api.borkol.com/v1/api/commerce/quotes/by-token/{token} \
  -H "Authorization: Bearer {api_key}"
Response
{
  "data": {
    "id": "6e7f8a9b-0c1d-4e2f-a3b4-c5d6e7f8a9b0",
    "number": "Q-17",
    "status": "offered",
    "email": "buyer@corp.example",
    "name": "Bob Buyer",
    "phone": "+31 6 12345678",
    "billing_address": {
      "name": "Corp BV",
      "street": "Main St 1",
      "postal_code": "1000 AA",
      "city": "Amsterdam",
      "country": "NL"
    },
    "shipping_address": null,
    "currency": "EUR",
    "customer_note": "Need delivery before October.",
    "message_to_customer": "Volume price applied.",
    "valid_until": "2026-08-31T23:59:59+00:00",
    "subtotal_amount": 44421,
    "tax_total_amount": 9329,
    "shipping_total_amount": 0,
    "total_amount": 53750,
    "tax_breakdown": [{ "rate": 21, "base": 44421, "tax": 9329 }],
    "converted_order_id": null,
    "submitted_at": "2026-08-14T10:15:30+00:00",
    "offered_at": "2026-08-15T09:00:00+00:00",
    "accepted_at": null,
    "declined_at": null,
    "cancelled_at": null,
    "created_at": "2026-08-14T10:15:30+00:00",
    "updated_at": "2026-08-15T09:00:00+00:00",
    "lines": [
      {
        "id": "9b0c1d2e-3f4a-4b5c-d6e7-f8a9b0c1d2e3",
        "product_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "variant_id": null,
        "is_custom": false,
        "title": "Garden Chair",
        "sku": "CHAIR-GRN",
        "quantity": 25,
        "indicative_unit_price": 2250,
        "quoted_unit_price": 2150,
        "tax_class_id": null,
        "attributes": null,
        "sort_order": 0
      }
    ]
  }
}

Errors

StatusBodyWhen
404{"message": "..."}Unknown token, or a token belonging to another store.
403scope, module, or suspension bodyKey lacks commerce:read, module disabled, or store suspended.
429{"message": "Too Many Attempts."}Rate limit exceeded.

POST /v1/api/commerce/quotes/by-token/{token}/accept

Accepts an offered quote, converting it into an order in status pending_payment at the quoted prices. Requires commerce:write. The operation is row-locked and idempotent: accepting an already-accepted quote returns the same order again. Before creating the order it re-computes the totals and refuses with OFFER_CHANGED if they no longer equal the figures frozen at send time (for example after a merchant re-edit or a tax change), and it checks and records stock for every non-custom product line, refusing with OUT_OF_STOCK on shortage. No request body.

The response is a bare object, not wrapped in data. order_number is a plain integer. Fetch the created order with GET /v1/api/commerce/orders/{order_id} if you need the full order.

Parameters

Path

NameTypeRequiredDescription
tokenstringyesThe plain token from the offer email.

No body.

Request
curl -X POST https://api.borkol.com/v1/api/commerce/quotes/by-token/{token}/accept \
  -H "Authorization: Bearer {api_key}"
Request (JS)
const res = await fetch(
  `https://api.borkol.com/v1/api/commerce/quotes/by-token/${token}/accept`,
  { method: "POST", headers: { Authorization: `Bearer ${apiKey}` } }
);
if (res.status === 409) {
  const err = await res.json();
  // err.code: "QUOTE_NOT_OFFERED" | "OFFER_CHANGED" | "OUT_OF_STOCK"
}
const { order_id, order_number } = await res.json();
Response
{
  "order_id": "7f8a9b0c-1d2e-4f3a-b4c5-d6e7f8a9b0c1",
  "order_number": 1043
}

Errors

StatusBodyWhen
409{"code": "QUOTE_NOT_OFFERED", "message": "Quote cannot be accepted in its current status (declined)."}The quote is not currently offered (submitted, declined, expired, or cancelled; the status in the message reflects the actual one).
409{"code": "OFFER_CHANGED", "message": "This offer has changed since it was sent. Ask the merchant to re-send it."}Recomputed totals no longer match the frozen offer figures.
409{"code": "OUT_OF_STOCK", "out_of_stock": [{"product_id": "...", "variant_id": null, "requested": 25, "available": 10}]}Insufficient stock for one or more product lines.
404{"message": "..."}Unknown token.
403scope, module, or suspension bodyKey lacks commerce:write, module disabled, or store suspended.
429{"message": "Too Many Attempts."}Rate limit exceeded.

POST /v1/api/commerce/quotes/by-token/{token}/decline

Declines an offered quote, with an optional reason that is appended to the merchant's internal note. Requires commerce:write. Row-locked against a racing accept. Returns the updated customer-facing quote (status declined, declined_at set) with status 200. A declined quote is not terminal: the merchant can still re-offer or cancel it.

Parameters

Path

NameTypeRequiredDescription
tokenstringyesThe plain token from the offer email.

Body

NameTypeRequiredDescription
reasonstring or nullnoWhy the buyer declined. Appended to the merchant's internal note; never echoed back in the customer-facing quote.
Request
curl -X POST https://api.borkol.com/v1/api/commerce/quotes/by-token/{token}/decline \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Found a better price elsewhere."}'
Response
{
  "data": {
    "id": "6e7f8a9b-0c1d-4e2f-a3b4-c5d6e7f8a9b0",
    "number": "Q-17",
    "status": "declined",
    "declined_at": "2026-08-16T08:30:00+00:00",
    "...": "same customer-facing quote shape as GET /v1/api/commerce/quotes/by-token/{token}"
  }
}

Errors

StatusBodyWhen
409{"code": "QUOTE_NOT_OFFERED", "message": "Quote cannot be accepted in its current status (accepted)."}The quote is not currently offered.
422{"message": "...", "errors": {"reason": ["..."]}}reason is present but not a string.
404{"message": "..."}Unknown token.
403scope, module, or suspension bodyKey lacks commerce:write, module disabled, or store suspended.
429{"message": "Too Many Attempts."}Rate limit exceeded.