CentraPoint

API

Quotes API

Create quotes (estimates) with per-line VAT, track acceptance, and convert accepted quotes into invoices.

On this page

Overview#

Quotes are priced proposals you send to a customer before invoicing. They use the same line items and VAT maths as invoices. When the customer accepts, convert the quote into an invoice in one call. Requires a plan that includes Invoicing.

Quote endpoints
EndpointPurpose
POST /api/v1/quotesCreate a quote
GET /api/v1/quotesList, filter by status or customer
GET /api/v1/quotes/{id}Get one quote
PATCH /api/v1/quotes/{id}Edit a draft or sent quote
DELETE /api/v1/quotes/{id}Delete a draft quote
POST /api/v1/quotes/{id}/sendMark it sent
POST /api/v1/quotes/{id}/acceptMark it accepted
POST /api/v1/quotes/{id}/declineMark it declined
POST /api/v1/quotes/{id}/convertCreate an invoice from it
GET /api/v1/quotes/{id}/pdfBranded quote PDF

Wherever a path takes {id}, you can also use the quote number (e.g. QUO-000017).

The quote object#

The same object is returned by every quote endpoint and sent as data in quote.* webhooks.

Quote
{
  "object": "quote",
  "id": "cmg3q1a2b0001quo0001abcd",
  "number": "QUO-000017",
  "status": "sent",
  "issueDate": "2026-09-26T22:00:00.000Z",
  "expiryDate": "2026-10-26T21:59:59.000Z",
  "periodStart": null,
  "periodEnd": null,
  "currency": "ZAR",
  "taxInclusive": false,
  "subtotal": 5000,
  "taxAmount": 750,
  "total": 5750,
  "notes": "Valid for 30 days.",
  "customer": {
    "id": "cmg2c0s7t0003cust0001abcd",
    "externalReference": "CRM-1001",
    "email": "[email protected]"
  },
  "invoice": null,
  "lineItems": [
    {
      "description": "Website redesign",
      "details": "5 pages, mobile layout\nTwo rounds of changes",
      "quantity": 1,
      "unitPrice": 4500,
      "amount": 4500,
      "taxRate": 15,
      "periodStart": null,
      "periodEnd": null
    },
    {
      "description": "Hosting setup",
      "details": null,
      "quantity": 1,
      "unitPrice": 500,
      "amount": 500,
      "taxRate": 15,
      "periodStart": null,
      "periodEnd": null
    }
  ],
  "sentAt": "2026-09-27T08:00:00.000Z",
  "acceptedAt": null,
  "declinedAt": null,
  "createdAt": "2026-09-27T07:55:00.000Z",
  "updatedAt": "2026-09-27T08:00:00.000Z"
}
Quote fields
FieldTypeDescription
objectrequiredstringAlways quote.
idrequiredstringCentraPoint quote ID.
numberrequiredstringSequential quote number, e.g. QUO-000017.
statusrequiredstringSee Statuses.
issueDaterequiredstringISO 8601 UTC timestamp.
expiryDaterequiredstring | nullEnd of the last valid day (South African time), or null for no expiry.
periodStart, periodEndrequiredstring | nullBilling period the quote covers (YYYY-MM-DD, inclusive), or null. See Billing periods and line details.
currencyrequiredstringISO 4217 code.
taxInclusiverequiredbooleanWhether line prices include tax. See VAT and totals.
subtotal, taxAmount, totalrequirednumberTotals excluding VAT, VAT, and including VAT.
notesrequiredstring | nullPrinted on the quote.
customerrequiredobject{ id, externalReference, email }
invoicerequiredobject | null{ id, number, status, externalReference } of the invoice the quote was converted into.
lineItemsrequiredobject[]description, details (string | null), quantity, unitPrice, amount, taxRate (%), periodStart / periodEnd (YYYY-MM-DD | null).
sentAt, acceptedAt, declinedAtrequiredstring | nullWhen the quote was last marked sent, accepted or declined.
createdAt, updatedAtrequiredstring

Statuses#

Quote statuses
statusMeaningCan move to
draftCreated, not yet sent. Can be edited or deleted.sent, accepted, declined
sentSent to the customer. Can still be edited.accepted, declined, expired
expiredSent, and the expiry date has passed (checked daily and whenever quotes are read).sent, accepted, declined
acceptedThe customer accepted.declined, or convert
declinedThe customer declined. Cannot be converted.—
invoicedConverted into an invoice (invoice is set). Final.—

Any quote except a declined or invoiced one can be converted, so you don't have to mark it accepted first.

Create a quote#

POST/api/v1/quotes
Create quote fields
FieldTypeDescription
customerIdoptionalstringThe customer's CentraPoint ID. Send exactly one of customerId or customerExternalReference.
customerExternalReferenceoptionalstringYour reference of an existing customer (see the Customers API). An unknown customer returns 400.
lineItemsrequiredobject[]1–200 lines: description (1–1000 chars), quantity (> 0), unitPrice (≥ 0), optional taxRate (0–100, default: your organisation's tax rate from Settings → Tax). Optional details (up to 4000 chars) and periodStart / periodEnd. Same rules as invoice lines.
currencyoptionalstringISO 4217 code, default ZAR.
issueDateoptionalstring (YYYY-MM-DD)Default today.
expiryDateoptionalstring (YYYY-MM-DD)Last day the quote is valid (inclusive). Default: no expiry. Must not be before the issue date.
periodStart, periodEndoptionalstring (YYYY-MM-DD)Billing period the quote covers, inclusive. Send both or neither; periodEnd must be on or after periodStart. Copied to the invoice on conversion.
notesoptionalstringUp to 4000 chars.
taxInclusiveoptionalbooleanDefault false. true: line prices already include tax.
sendoptionalbooleanDefault false (draft). true creates it as sent.
curl -X POST "https://app.centrapoint.co.za/api/v1/quotes" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Idempotency-Key: 1f7c3a9e-5b2d-4e8a-b6c1-0d9e8f7a6b51" \
  -H "Content-Type: application/json" \
  -d '{
    "customerExternalReference": "CRM-1001",
    "expiryDate": "2026-10-26",
    "lineItems": [
      {
        "description": "Website redesign",
        "details": "5 pages, mobile layout\nTwo rounds of changes",
        "quantity": 1,
        "unitPrice": 4500
      },
      {
        "description": "Hosting setup",
        "quantity": 1,
        "unitPrice": 500
      }
    ],
    "notes": "Valid for 30 days.",
    "send": true
  }'

Returns 201 with the quote object. Quotes have no externalReference; send an Idempotency-Key so a retried request doesn't create a second quote.

List quotes#

GET/api/v1/quotes
List query parameters
FieldTypeDescription
statusoptionalstringdraft, sent, accepted, declined, expired or invoiced.
customerIdoptionalstringOnly this customer's quotes.
limit, startingAfteroptionalKeyset pagination: the response is { data, hasMore, nextCursor }, newest first.
curl -X GET "https://app.centrapoint.co.za/api/v1/quotes?status=sent&limit=50" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY"

Get a quote#

GET/api/v1/quotes/{id}
curl -X GET "https://app.centrapoint.co.za/api/v1/quotes/cmg3q1a2b0001quo0001abcd" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY"

Update a quote#

PATCH/api/v1/quotes/{id}

Edits a draft or sent quote (others return 400). Takes the same fields as create except send, all optional; omitted fields keep their value. lineItems, when sent, replaces every line. expiryDate, notes, periodStart and periodEnd can be null to clear them (clear both period dates together). Emits quote.updated.

curl -X PATCH "https://app.centrapoint.co.za/api/v1/quotes/cmg3q1a2b0001quo0001abcd" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "expiryDate": "2026-11-15",
    "notes": "Valid until 15 November."
  }'

Delete a draft quote#

DELETE/api/v1/quotes/{id}

Permanently deletes a draft quote and returns { "object": "quote", "id": "…", "number": "QUO-000017", "deleted": true }. Quotes that were sent can't be deleted (400); decline them instead. No webhook is sent.

Send, accept or decline#

POST/api/v1/quotes/{id}/send
POST/api/v1/quotes/{id}/accept
POST/api/v1/quotes/{id}/decline

Record the quote's progress. No body is needed. Each returns the updated quote and emits quote.sent, quote.accepted or quote.declined. A move the status table doesn't allow returns 400; sending a quote that is already sent returns it unchanged.

curl -X POST "https://app.centrapoint.co.za/api/v1/quotes/cmg3q1a2b0001quo0001abcd/accept" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Idempotency-Key: 6a2e9c4f-3d1b-4a7e-8f5c-2b0d9e1a3c76"

Convert to an invoice#

POST/api/v1/quotes/{id}/convert

Creates an invoice with the quote's lines (including their details and periods), billing period, currency and tax settings (as a draft), marks the quote invoiced and links the two. Optional body { "dueDays": 14 } (0–365, default your invoice due-days setting). A pay link is created for the invoice when your plan includes payment links and a gateway accepts the currency. Send the invoice with POST /api/v1/invoices/{id}/send.

curl -X POST "https://app.centrapoint.co.za/api/v1/quotes/cmg3q1a2b0001quo0001abcd/convert" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Idempotency-Key: 9d4b1e7a-2c6f-4f3a-a8e0-5c7b3d1f9e24" \
  -H "Content-Type: application/json" \
  -d '{
    "dueDays": 14
  }'

Returns 201 with { "quote": <quote object>, "invoice": <invoice object> } (the invoice object). Emits quote.invoiced and invoice.created. Declined or already invoiced quotes return 400.

Download the quote PDF#

GET/api/v1/quotes/{id}/pdf

Returns the branded quote PDF (application/pdf, as an attachment, Cache-Control: private, no-store) or 404. See Tax & PDF documents.

Download a quote PDF
curl -o quote.pdf \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  "https://app.centrapoint.co.za/api/v1/quotes/cmg3q1a2b0001quo0001abcd/pdf"

Webhook events#

quote.created, quote.updated, quote.sent, quote.accepted, quote.declined, quote.expired and quote.invoiced are sent to your webhook endpoints with the quote object as data, whether the change came from the API or the dashboard. Moving a quote back to draft in the dashboard sends quote.updated.

Errors#

Quote errors
StatuserrorWhen
400invalid_requestValidation failed; customer not found; invalid currency; expiry before issue date; only one of periodStart/periodEnd, or periodEnd before periodStart; total not above zero; unknown status filter; status move not allowed; editing or deleting a quote in the wrong status; converting a declined or invoiced quote.
403plan_restricted / account_restrictedPlan lacks the API or Invoicing, or the account is read-only (writes).
404not_foundNo quote with this ID or number in your organisation.
409idempotency_conflictIdempotency-Key reused with a different body.
401 / 429 / 500unauthorized / rate_limited / internal_errorSee Errors