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.send: true. Send it before giving the customer its url.
The Upgrade button
The whole flow, from your server:- Your app creates the quote with a
returnUrl. sendmarks it sent, so itsurlopens.- You redirect the customer to
url. - The customer accepts. With
autopaythey save a card or bank account on Stripe first. The quote page then shows a Continue button that opens yourreturnUrlwith?quote=<number>&status=accepted. - Custral starts the subscription and bills any one-time total. Your app
hears
subscription.startedandpayment.succeededby webhook, andquote.acceptedas well.
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.
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.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.