Quotes
4 endpoints
/v1/quotesList quotes
Returns the paginated list of the account's quotes (cursor v2). Stable sort
created_at DESC, id DESC.
Archived quotes (archived_at IS NOT NULL) are excluded by
default — the public API does not expose them. To retrieve an archived
quote, use the dashboard.
Filters
?status— exact match (draft,sent,signed,cancelled)
quotes:readQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
status | string | optional | Exact match on the quote lifecycle. |
Responses
| Status | Description |
|---|---|
200 | Paginated list of quotes. |
400 | The request is malformed (missing header, invalid JSON, invalid parameter). |
401 | Unknown API key or invalid format. |
403 | Authorization denied. Two causes, distinguished by |
429 | The per-window request quota has been exceeded for this API key or endpoint. |
500 | Unexpected server error. Always include the |
Example request
curl -X GET 'https://api.capturia.io/v1/quotes' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json'/v1/quotesCreate a quote
Creates a draft quote (status="draft") through the same creation
path as the dashboard: Capturia assigns the number
(quote_number, the account's sequence, never a duplicate) and applies the
account's defaults (validity, payment terms, tax profile,
deposit policy). A provided quote_number is rejected
(400 validation_error, reason read_only).
contact_id links the quote to its contact (/v1/contacts): each
omitted prospect_* field is read from the contact, and each field you provide
takes precedence. line_items writes the line items in the same operation; the
server computes the amounts. To send the quote to the
lead, use POST /v1/quotes/{id}/send.
Idempotency
The Idempotency-Key header is optional but recommended: a
retry after a timeout never creates a second quote. A
second request with the same key and the same payload returns the original
response; the same key with a different payload returns
409 idempotency_conflict. A rejection (400, 403, 404) is not
stored: the key remains usable.
quotes:writeRequest body
| Property | Type | Required | Description |
|---|---|---|---|
contact_id | string | null | optional | The account's contact (/v1/contacts) to link the quote to. Each omitted
prospect_* field is read from the contact; each field you provide takes precedence. A contact
from another account → 400 validation_error (reason not_found_in_tenant). |
prospect_name | string | null | optional | — |
prospect_email | string | null | optional | — |
prospect_phone | string | null | optional | — |
prospect_company | string | null | optional | — |
prospect_address | string | null | optional | Free-form mailing address. |
language | string | optional | Language of the quote (public page, PDF, sending email). Defaults to fr. |
valid_until | string | null | optional | Validity deadline (YYYY-MM-DD). Omitted: the account's default validity
(in days, starting today, Montreal time). null: no date. |
payment_terms | string | null | optional | Payment terms (free text). Omitted: the account's terms. null: none. |
line_items | object[] | optional | The quote's line items, written in the same operation; the server computes the amounts. |
internal_notes | string | optional | Internal notes, not visible to the prospect. |
sales_rep_id | string | null | optional | Assigned sales rep; null or omitted = none. |
tax_profile_id | string | null | optional | Applied tax profile (GST/QST); null or omitted = the account's default profile. |
signature_payment_order | string | optional | Signature ↔ payment order on the public page. Omitted → the account's default
setting (snapshot taken at creation). Value outside the list → 400 validation_error. |
tax_exempt_reason | string | null | optional | Written reason for a tax-exempt sale (see Quote.tax_exempt_reason). |
Responses
| Status | Description |
|---|---|
201 | Quote created. |
400 | Parameter or body validation failed. |
401 | Unknown API key or invalid format. |
403 | Authorization denied. Two causes, distinguished by |
409 | A previous request with the same |
415 | The |
429 | The per-window request quota has been exceeded for this API key or endpoint. |
500 | Unexpected server error. Always include the |
Example request
curl -X POST 'https://api.capturia.io/v1/quotes' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json' \
-d '{
"contact_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
"valid_until": "2026-06-30",
"payment_terms": "Net 30",
"line_items": [
{
"description": "Remplacement de la toiture",
"quantity": 1,
"unit_price": 9500
}
]
}'/v1/quotes/{id}Update a quote
Updates the provided fields of the quote (omitted fields are kept).
At least one field must be provided, otherwise 400 no_fields.
Amounts (subtotal, tps_amount, tvq_amount, total) are
not editable via PATCH — they are computed server-side from the
blocks (managed from the dashboard).
status only accepts declined (the lead declined it) and
cancelled: a quote moves to sent through POST /v1/quotes/{id}/send
(the email goes out), to viewed and signed through the lead, and to
expired through its validity date. A signed, paid, or already closed quote
can no longer be closed (409 status_locked). Closing must be done on its own:
status combined with other fields returns 400 validation_error
(reason exclusive_field).
Archived quotes (archived_at IS NOT NULL) cannot be
modified — returns 404 resource_not_found.
quotes:writeRequest body
| Property | Type | Required | Description |
|---|---|---|---|
prospect_name | string | null | optional | — |
prospect_email | string | null | optional | — |
prospect_phone | string | null | optional | — |
prospect_company | string | null | optional | — |
prospect_address | string | null | optional | — |
valid_until | string | null | optional | — |
payment_terms | string | null | optional | — |
internal_notes | string | null | optional | — |
sales_rep_id | string | null | optional | — |
tax_profile_id | string | null | optional | — |
payment_plan_type | string | optional | Payment plan structure. The amount of each installment is derived
server-side. The plan is locked once the first payment is collected (409 payment_plan_locked).
Consistency between fields (a deposit with no value, an installment count out of bounds)
is validated server-side → 400 validation_error. |
payment_deposit_type | string | null | optional | — |
payment_deposit_value | number | null | optional | — |
payment_installments_count | integer | null | optional | — |
payment_interval_days | integer | null | optional | — |
payment_execution_mode | string | optional | automatic = installments are charged to the card off-session; manual = payments
received (bank transfer, Interac) are recorded by the sales rep, and nothing is charged. Value
outside the list → 400 validation_error. |
signature_payment_order | string | optional | Signature ↔ payment order on the public page. Editable while the quote is
a draft; once it has been sent → 409 signature_payment_order_locked. Value
outside the list → 400 validation_error. |
tax_exempt_reason | string | null | optional | Written reason for a tax-exempt sale; null makes the quote taxed again. An
empty string or one longer than 200 characters → 400 validation_error. Totals
are recalculated server-side when it changes. |
status | string | optional | Closes the quote: declined (the lead declined it) or cancelled. The other
statuses are never set through PATCH: sent comes from
POST /v1/quotes/{id}/send (the email goes out), viewed and signed from the
lead, expired from the validity date. Value outside the list → 400
validation_error. A signed or paid quote can no longer be closed → 409
status_locked (nothing is written). |
Responses
| Status | Description |
|---|---|
200 | Quote updated. |
400 | Parameter or body validation failed. |
401 | Unknown API key or invalid format. |
403 | Authorization denied. Two causes, distinguished by |
404 | The requested resource doesn't exist (or isn't within the tenant's scope). |
409 | The request is syntactically valid but violates a business rule. |
429 | The per-window request quota has been exceeded for this API key or endpoint. |
500 | Unexpected server error. Always include the |
Example request
curl -X PATCH 'https://api.capturia.io/v1/quotes/{id}' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json' \
-d '{
"prospect_name": "Alexandre Tremblay",
"valid_until": "2026-07-31",
"payment_terms": "Net 60"
}'/v1/quotes/{id}/sendSend a quote to the lead
Emails the quote to the lead, through the same service as the
dashboard's “Send” button: sent from the assigned sales rep (otherwise
from the business), with a link to the quote's public page, then
status="sent" and sent_at are set. Only a draft can be sent; nothing
goes out again for a quote that was already sent.
A quote without a contact (lead entered without a contact) is first linked to the lead's contact: found by their email or phone number, otherwise created — never a duplicate. The email then opens its thread in the Inbox. The created contact stays in the API key's account, and its creation triggers the automations and CRM sync of a new contact.
Rejections
409 already_signed: the quote is signed.409 quote_cancelled: the quote was cancelled.409 already_sent: the quote was already sent.409 quote_status_changed: the quote is no longer a draft (for example, it expired).409 send_in_progress: a send of this quote is already in progress.409 send_outcome_unknown: a previous send could not be confirmed; it is not retried.409 recipient_unsubscribed: the lead unsubscribed.409 sender_unavailable: the assigned sales rep has no valid sending address.422 no_prospect_email/422 invalid_prospect_email: the lead's email is missing or invalid.422 unpriced_lines,422 untaxed_quote,422 deposit_not_payable: content guard.
No body is required.
quotes:writeRequest body
Optional body — no fields are used today (reserved for future use).
Responses
| Status | Description |
|---|---|
200 | Quote sent to the lead. |
401 | Unknown API key or invalid format. |
403 | Authorization denied. Two causes, distinguished by |
404 | The requested resource doesn't exist (or isn't within the tenant's scope). |
409 | The request is syntactically valid but violates a business rule. |
422 | The request is syntactically valid but violates a business rule. |
429 | The per-window request quota has been exceeded for this API key or endpoint. |
500 | Unexpected server error. Always include the |
Example request
curl -X POST 'https://api.capturia.io/v1/quotes/{id}/send' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json'