Skip to main content
A quote is an offer to one customer: products, prices, a discount and how they pay. The customer accepts it on a hosted page (the quote’s url), and Custral starts billing them on your workspace’s Stripe account. Your own app uses these endpoints to sell. A typical Upgrade button drafts a quote, sends the customer to its url, and learns the outcome by webhook.
Money in, money out. Request prices are in major units: 49.5 is $49.50. Every amount a response returns (unitAmount, an amount discount’s value, invoice total, subscription mrr) is in minor units (cents), the way Stripe returns them.

Attributes

string
Unique identifier, prefixed quo_. Pass it to the other quote endpoints.
string
The label a person reads, such as Q-0007.
string
draft, sent, viewed, accepted, declined, expired or void.
object
The customer record: recordId, and name (null when the record has none).
string
The hosted page where the customer reviews and accepts. It opens once the quote is sent. A draft’s link does not.
string
autopay (the customer saves a card or bank account when accepting, and is charged each cycle) or invoice (each bill is emailed with a pay link).
string | null
ISO 8601 timestamp: the end of the last day the quote can be accepted, UTC.
object[]
One per product: productRecordId, name, description, quantity, unitAmount (minor units), interval (one_time, month or year), and usageEvent on a metered line, where unitAmount is per unit.
object | null
The whole-quote discount: type (percent or amount), value, duration (once, repeating or forever) and months.
object[]
How the one-time total is split: each instalment’s label, due (on_acceptance or a date), type, value and, once invoiced, billedAt.
string | null
Why billing could not start after the customer accepted, if it could not.
object
What the quote costs, in minor units. recurring has one entry per billing interval: its listAmount per period before discounts, and phases: what each run of periods bills after discounts (toPeriod: null means from then on). oneTimeTotal is the one-time lines after discounts.
contactName, contactEmail, currency, paymentTermsDays, trialDays, billingEvery, billingAnchorDay (null when bills run from acceptance), requireSignature, contractTemplateId (null for the workspace’s default), startDate, sentAt, acceptedAt, acceptedName (the name the customer signed with) and createdAt are what they say.

Endpoints

No endpoint charges a card, bills an invoice, or changes a live subscription. The customer does that by accepting.

Naming the customer

customer is either your app’s own customer id or a Custral record id (rec_…). Your own id works once the workspace has picked a customer key in Settings → Payments: the property on the Customers object that holds your app’s id (an Account ID, say). Pass acct_123 and Custral finds the customer whose key is acct_123. If no customer matches, the call is refused with customer_not_found. On POST /v1/quotes, pass createCustomer to create one instead. It is created with your id as its key, so the next call finds it.

Create a quote

POST /v1/quotes
string
required
Your app’s customer id, or a record id. A record of an object quotes start from (a deal, set in Settings → Payments → Who pays) works too: the quote bills the customer it links to and shows on both records.
object
{name, email?}. Creates the customer when customer matches none. Needs a customer key set in Settings → Payments.
object[]
required
1 to 100 products. Each is {product, quantity?, unitPrice?}: product is a catalog product’s record id or its exact name, quantity defaults to 1, and unitPrice is a custom price per unit in major units (the catalog price when absent). A custom price never changes the catalog.
object
{type, value, duration?, months?}. type is percent (value: 20 for 20%, at most 100) or amount (value in major units). duration defaults to once; repeating needs months (1 to 120).
string
autopay or invoice. Defaults to the workspace’s setting.
string
Who the quote is addressed to.
string
Their email. Custral does not email it when you send the quote.
number
Bill the recurring products every this many months (or years, for yearly products): 3 is quarterly. Each bill charges that many periods’ price. 1 to 12; yearly products at most 3. Defaults to 1.
number
The day of the month bills land on, 1 to 31 (a shorter month bills on its last day), with the first bill prorated. 0 bills from the day the customer accepts, which is the default.
boolean
Ask the customer to sign a contract on the quote page before accepting.
string
Which contract they sign: a template’s id or exact name from GET /v1/contract-templates. Empty or default uses the workspace’s default.
string
The last day the quote can be accepted, YYYY-MM-DD.
string
Where the customer lands after accepting, with ?quote=<number>&status=accepted added to it. Must be https (http only for localhost), and its host must be listed in Settings → Payments → allowed return domains, exactly.
boolean
Also mark the quote sent, so the returned url opens right away. Saves the separate send call when the customer is about to be redirected.
A new quote is a draft unless you pass send: true. Send it before giving the customer its url.

The Upgrade button

The whole flow, from your server:
  1. Your app creates the quote with a returnUrl.
  2. send marks it sent, so its url opens.
  3. You redirect the customer to url.
  4. The customer accepts. With autopay they save a card or bank account on Stripe first. The quote page then shows a Continue button that opens your returnUrl with ?quote=<number>&status=accepted.
  5. Custral starts the subscription and bills any one-time total. Your app hears subscription.started and payment.succeeded by webhook, and quote.accepted as well.
Act on the webhook, and use the return to returnUrl only to show a page: a customer can close the tab without pressing Continue. Webhook delivery is at-least-once, so make the listeners idempotent.

List quotes

GET /v1/quotes
string
One customer’s quotes: your app’s id or a record id. A deal’s record id lists the quotes made on it. The whole workspace’s when absent.
string
Only quotes in this status.
Returns an array of quotes, newest first.

Retrieve a quote

GET /v1/quotes/{id}

Update a draft

PATCH /v1/quotes/{id} Takes the same fields as create, except customer, createCustomer and returnUrl. A field you leave out keeps its value. products replaces every line.
boolean
true removes the quote’s discount.
Only a draft can be edited. Once sent, its link must not change under the customer: void it and create another.

Send a quote

POST /v1/quotes/{id}/send Marks a draft sent, so its url opens. It emails nobody: you give the customer the link yourself, by redirect or in your own email. Sending a quote that is already sent or viewed returns it unchanged, so a retry is safe. An autopay quote needs the workspace’s Stripe account connected.

Void a quote

POST /v1/quotes/{id}/void Withdraws a quote that is draft, sent, viewed or expired. Its link stops working. An accepted or declined quote cannot be voided.

Errors

A refused returnUrl is checked before anything is written, so no quote is left behind. See Errors for the HTTP status each code maps to.

Scopes