Turning an accepted quote into a policy: what the customer must see before they buy, their Direct Debit details, the first premium by card (through Stripe), and setting up the policy.
Every request here works on an issued quote that hasn't expired.
Browser and server-only applications can both call these. A browser
application sends the quote's application's share_token in the
X-Share-Token header, as for
Quotes and applications.
What to show before they buy
GET
https://subcontext.com/api/v1/quotes/{id}/before_you_buy
Try it
Token scopes:
read or write
What the customer must see before they can buy, under the rules your firm has switched on: the product's demands and needs statement, its key documents, their cancellation rights and a read-back of what they're buying. Only the steps this sale still owes are included.
When show is true, show these to the customer and, when they say
yes, call Confirm they've read it.
When it's false, there's nothing to show, so go straight on.
When sellable is false, the product can't be bought on a form
(the demands and needs rule is on but the product has no statement). Offer the customer a conversation with your team instead.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The quote's |
| X-Share-Token | header | string | The application's |
Responses
200 The steps still owed, with what to show for each.
| Field | Type | Description |
|---|---|---|
| data required | BeforeYouBuy | |
| data.show required | boolean |
Whether to show the screen at all. |
| data.steps required | array of SaleStep |
The steps still owed. |
| data.sellable required | boolean |
|
| data.demands_and_needs required | string or null |
The product's demands and needs statement, to show and have the customer confirm. |
| data.key_documents required | array of object |
Documents to give the customer before they buy. |
| data.key_documents[].document_id required | string (uuid) | |
| data.key_documents[].version_id required | string or null (uuid) | |
| data.key_documents[].kind required | string | |
| data.key_documents[].name required | string | |
| data.key_documents[].url required | string |
Where to read it. |
| data.cancellation required | one of: object, null |
The customer's right to cancel, to show them; or |
| data.confirmation required | one of: object, null |
A read-back of what they're buying, to ask them to agree to; or |
{
"data": {
"show": true,
"steps": [
"demands_and_needs",
"key_documents_before_bind",
"cancellation_rights_before_bind",
"confirm_before_purchase"
],
"sellable": true,
"demands_and_needs": "This policy meets the demands and needs of someone who wants to leave a lump sum to their family if they die during the term.",
"key_documents": [
{
"document_id": "0199a3c2-7e00-7ebb-9d3c-b0c1d2e3f40b",
"version_id": "0199a3c2-7e01-7fcc-8e4d-c1d2e3f4051c",
"kind": "ipid",
"name": "Insurance product information document",
"url": "https://subcontext.com/documents/0199a3c2-7e01-7fcc-8e4d-c1d2e3f4051c"
}
],
"cancellation": {
"days": 30,
"statement": "You can cancel within 30 days of the start date and get back everything you've paid."
},
"confirmation": {
"intro": "Before we set up your policy:",
"points": [
"Level Term Life.",
"Life cover of £250,000.00 for 25 years.",
"£12.45 a month.",
"Starting today, 12 October 2026."
],
"question": "Are you happy for us to set this up?"
}
}
}
401 No token was sent, or it isn't valid because it has expired, was revoked (for example because the application's secret was rotated), or was cut short. Get a new token and try once more.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "invalid_token",
"message": "The access token has expired. Ask for a new one."
}
}
403 The token can't be used here. It's missing a scope, it was used from the wrong place (a server-only application's token in a browser, or a browser application's token from a server, another website or a request only server-only applications may make), or it wasn't asked for with `resource=https://subcontext.com/api/v1`.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "forbidden",
"message": "origin 'https://www.example.com' is not allowlisted for this OAuth application"
}
}
404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "not_found",
"message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
}
}
Confirm they've read it
POST
https://subcontext.com/api/v1/quotes/{id}/confirm
Try it
Token scopes:
write
The customer said yes on the "before you buy" screen. Records each step it showed them (the statement confirmed, the documents and cancellation rights given, the read-back agreed), so setting up the policy finds nothing owed. There's nothing to send. The record is made from what What to show before they buy shows.
owed lists anything still owed afterwards (normally nothing).
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The quote's |
| X-Share-Token | header | string | The application's |
Responses
200 Recorded.
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.owed required | array of SaleStep |
Steps still owed, normally none. |
{
"data": {
"owed": []
}
}
401 No token was sent, or it isn't valid because it has expired, was revoked (for example because the application's secret was rotated), or was cut short. Get a new token and try once more.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "invalid_token",
"message": "The access token has expired. Ask for a new one."
}
}
403 The token can't be used here. It's missing a scope, it was used from the wrong place (a server-only application's token in a browser, or a browser application's token from a server, another website or a request only server-only applications may make), or it wasn't asked for with `resource=https://subcontext.com/api/v1`.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "forbidden",
"message": "origin 'https://www.example.com' is not allowlisted for this OAuth application"
}
}
404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "not_found",
"message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
}
}
409 The quote can't be bought any more: - `quote_not_bindable`: it isn't the current quote (it was replaced, bought or rejected). - `quote_expired`: it has expired. Quote again.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "quote_expired",
"message": "This quote expired on 2026-11-11."
}
}
422 `not_sellable`: the product can't be bought on a form. Offer a conversation instead.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "not_sellable",
"message": "This product needs a conversation with us before you buy."
}
}
Save Direct Debit details
POST
https://subcontext.com/api/v1/quotes/{id}/direct_debit_details
Try it
Token scopes:
write
The customer's bank details for their monthly premiums. They're checked (sort code and account number together) and stored encrypted. Sending them again replaces them.
Use this when the application's inline_bacs_mandate is false.
When it's true, the customer sets up the Direct Debit on Stripe's
own form instead (see Start a Direct Debit mandate).
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The quote's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| direct_debit required | object | |
| direct_debit.account_holder_name required | string |
The name on the account. |
| direct_debit.sort_code required | string |
Six digits, with or without dashes. |
| direct_debit.account_number required | string |
Eight digits. |
| direct_debit.address_line1 | string |
The account holder's address. |
| direct_debit.address_line2 | string | |
| direct_debit.address_city | string | |
| direct_debit.address_postal_code | string |
{
"direct_debit": {
"account_holder_name": "Ada Lovelace",
"sort_code": "40-47-84",
"account_number": "70872490",
"address_line1": "10 Example Street",
"address_city": "London",
"address_postal_code": "SW1A 2AA"
}
}
Responses
201 The details are saved.
| Field | Type | Description |
|---|---|---|
| data required | object |
The Direct Debit details, as saved. |
| data.id required | string (uuid) | |
| data.account_holder_name required | string | |
| data.sort_code required | string |
The sort code, as |
| data.account_number_last4 required | string |
The last four digits of the account number, as |
{
"data": {
"id": "0199a3c2-7f00-70dd-9f5e-d2e3f405162d",
"account_holder_name": "Ada Lovelace",
"sort_code": "40-47-84",
"account_number_last4": "2490"
}
}
401 No token was sent, or it isn't valid because it has expired, was revoked (for example because the application's secret was rotated), or was cut short. Get a new token and try once more.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "invalid_token",
"message": "The access token has expired. Ask for a new one."
}
}
403 The token can't be used here. It's missing a scope, it was used from the wrong place (a server-only application's token in a browser, or a browser application's token from a server, another website or a request only server-only applications may make), or it wasn't asked for with `resource=https://subcontext.com/api/v1`.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "forbidden",
"message": "origin 'https://www.example.com' is not allowlisted for this OAuth application"
}
}
404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "not_found",
"message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
}
}
409 The quote can't be bought any more: - `quote_not_bindable`: it isn't the current quote (it was replaced, bought or rejected). - `quote_expired`: it has expired. Quote again.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "quote_expired",
"message": "This quote expired on 2026-11-11."
}
}
422 - `direct_debit_invalid`: the details don't pass the checks (`fields` names each, as `direct_debit.sort_code`). - `validation_failed`: `direct_debit` is missing.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "direct_debit_invalid",
"message": "Sort code must be 6 digits",
"fields": {
"direct_debit.sort_code": "must be 6 digits"
}
}
}
Start a Direct Debit mandate
POST
https://subcontext.com/api/v1/quotes/{id}/bacs_setup_intent
Try it
Token scopes:
write
When the application's inline_bacs_mandate is true, starts a Stripe Checkout session in which the customer sets up their Direct
Debit on Stripe's own form. Mount it with Stripe.js
(initEmbeddedCheckout with the client_secret, your
publishable_key and, when it isn't null, stripe_account).
When the customer finishes, call Record the Direct Debit mandate
with the session's id.
The application needs a customer first.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The quote's |
| X-Share-Token | header | string | The application's |
Responses
201 The session, and the keys to mount it with.
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.session required | object | |
| data.session.id required | string |
The Stripe Checkout session's id. |
| data.session.client_secret required | string |
What Stripe.js mounts the form with. |
| data.publishable_key required | string or null |
The Stripe publishable key for this product's payments. |
| data.stripe_account required | string or null |
Your firm's connected Stripe account, or |
{
"data": {
"session": {
"id": "cs_live_a1B2c3D4",
"client_secret": "cs_live_a1B2c3D4_secret_e5F6"
},
"publishable_key": "pk_live_51Example",
"stripe_account": "acct_1Example"
}
}
401 No token was sent, or it isn't valid because it has expired, was revoked (for example because the application's secret was rotated), or was cut short. Get a new token and try once more.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "invalid_token",
"message": "The access token has expired. Ask for a new one."
}
}
403 The token can't be used here. It's missing a scope, it was used from the wrong place (a server-only application's token in a browser, or a browser application's token from a server, another website or a request only server-only applications may make), or it wasn't asked for with `resource=https://subcontext.com/api/v1`.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "forbidden",
"message": "origin 'https://www.example.com' is not allowlisted for this OAuth application"
}
}
404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "not_found",
"message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
}
}
409 The quote can't be bought any more: - `quote_not_bindable`: it isn't the current quote (it was replaced, bought or rejected). - `quote_expired`: it has expired. Quote again.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "quote_expired",
"message": "This quote expired on 2026-11-11."
}
}
422 - `customer_missing`: the application has no customer yet. Save their details first. - `bacs_setup_failed`: Stripe refused (the message says why).
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "customer_missing",
"message": "The application has no customer yet: save their details before setting up the Direct Debit."
}
}
Record the Direct Debit mandate
POST
https://subcontext.com/api/v1/quotes/{id}/bacs_mandate
Try it
Token scopes:
write
After the customer finishes Stripe's Direct Debit form, send the session's id. Subcontext checks with Stripe that the mandate is set up, and keeps it for the policy's premiums.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The quote's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| session_id required | string |
The Stripe Checkout session's id, from Start a Direct Debit mandate. |
{
"session_id": "cs_live_a1B2c3D4"
}
Responses
201 The mandate is recorded.
| Field | Type | Description |
|---|---|---|
| data required | object |
The mandate, as recorded. |
| data.mandate_id required | string or null |
Stripe's id for the mandate. |
| data.account_number_last4 required | string or null |
The last four digits of the account number. |
{
"data": {
"mandate_id": "mandate_1Example",
"account_number_last4": "2490"
}
}
401 No token was sent, or it isn't valid because it has expired, was revoked (for example because the application's secret was rotated), or was cut short. Get a new token and try once more.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "invalid_token",
"message": "The access token has expired. Ask for a new one."
}
}
403 The token can't be used here. It's missing a scope, it was used from the wrong place (a server-only application's token in a browser, or a browser application's token from a server, another website or a request only server-only applications may make), or it wasn't asked for with `resource=https://subcontext.com/api/v1`.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "forbidden",
"message": "origin 'https://www.example.com' is not allowlisted for this OAuth application"
}
}
404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "not_found",
"message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
}
}
409 The quote can't be bought any more: - `quote_not_bindable`: it isn't the current quote (it was replaced, bought or rejected). - `quote_expired`: it has expired. Quote again.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "quote_expired",
"message": "This quote expired on 2026-11-11."
}
}
422 - `validation_failed`: `session_id` is missing. - `bacs_mandate_unconfirmed`: Stripe doesn't have the mandate set up yet (the customer hasn't finished, or the session isn't this one).
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "bacs_mandate_unconfirmed",
"message": "the Direct Debit mandate is not confirmed yet"
}
}
Take the first premium by card
POST
https://subcontext.com/api/v1/quotes/{id}/payment_intent
Try it
Token scopes:
write
Creates the Stripe PaymentIntent for the first month's premium, and gives you the keys to mount Stripe's Payment Element with. When the customer has paid, call Set up the policy with the intent's id. Asking again for the same quote gives back the same intent, so the customer isn't charged twice.
Not for a product whose first premium is taken by Direct Debit
(the application's direct_debit_first is true). Set up the policy straight after the Direct Debit instead.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The quote's |
| X-Share-Token | header | string | The application's |
Responses
201 The PaymentIntent and the keys to mount it with.
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.payment_intent required | object | |
| data.payment_intent.id required | string |
The PaymentIntent's id. Send it to Set up the policy. |
| data.payment_intent.client_secret required | string |
What Stripe.js mounts the Payment Element with. |
| data.payment_intent.status required | string |
Stripe's status for it, such as |
| data.payment_intent.amount_pence required | integer |
The first premium, in whole pence. |
| data.payment_intent.currency required | string |
The currency (ISO 4217), such as |
| data.publishable_key required | string or null |
The Stripe publishable key for this product's payments. |
| data.stripe_account required | string or null |
Your firm's connected Stripe account, or |
{
"data": {
"payment_intent": {
"id": "pi_3Example",
"client_secret": "pi_3Example_secret_x9Y8",
"status": "requires_payment_method",
"amount_pence": 1245,
"currency": "GBP"
},
"publishable_key": "pk_live_51Example",
"stripe_account": "acct_1Example"
}
}
401 No token was sent, or it isn't valid because it has expired, was revoked (for example because the application's secret was rotated), or was cut short. Get a new token and try once more.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "invalid_token",
"message": "The access token has expired. Ask for a new one."
}
}
403 The token can't be used here. It's missing a scope, it was used from the wrong place (a server-only application's token in a browser, or a browser application's token from a server, another website or a request only server-only applications may make), or it wasn't asked for with `resource=https://subcontext.com/api/v1`.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "forbidden",
"message": "origin 'https://www.example.com' is not allowlisted for this OAuth application"
}
}
404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "not_found",
"message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
}
}
409 The quote can't be bought any more: - `quote_not_bindable`: it isn't the current quote (it was replaced, bought or rejected). - `quote_expired`: it has expired. Quote again.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "quote_expired",
"message": "This quote expired on 2026-11-11."
}
}
422 - `direct_debit_first`: this product takes its first premium by Direct Debit, so there's no card payment. - `payment_intent_creation_failed`: Stripe refused (the message says why).
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "direct_debit_first",
"message": "This product takes its first premium by Direct Debit: no card payment is needed."
}
}
503 `payments_unavailable`: your firm's Stripe account can't take payments yet. Nothing is wrong with the request. Finish connecting your Stripe account in the dashboard (**Organisation**, **Payments**), then try again.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "payments_unavailable",
"message": "This organisation's Stripe account can't take payments yet."
}
}
Set up the policy
POST
https://subcontext.com/api/v1/quotes/{id}/bind
Try it
Token scopes:
write
Sets up (binds) the policy from the quote. Everything is checked
first: the quote is still current, nothing is owed from
What to show before they buy,
the Direct Debit is in place, and the first premium was paid (send
the card payment's payment_intent_id, which is checked with Stripe;
a Direct-Debit-first product needs none).
The policy starts today. A server-only application may set another
start date (up to 90 days ahead) and its own policy_number. From a browser, both are ignored.
Calling it again once the policy is set up gives back the same
policy, with 200 OK, so it's safe to retry.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The quote's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| payment_intent_id | string |
The card payment for the first premium, from Take the first premium by card. Not needed for a Direct-Debit-first product. |
| commencement_date | string (date) |
Server-only applications only. The start date, |
| policy_number | string |
Server-only applications only. Your own policy number, when your systems issue them. |
{
"payment_intent_id": "pi_3Example"
}
Responses
201 The policy is set up.
| Field | Type | Description |
|---|---|---|
| data required | Policy |
A policy, with the customer's links to it. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.policy_number required | string | |
| data.basis required | string |
One of: "single", "joint"
|
| data.joint_customer_name required | string or null |
For joint cover, the second person's name. |
| data.currency_code required | string | |
| data.commencement_date required | string (date) |
When cover starts. |
| data.total_premium_monthly_pence required | integer |
The monthly premium, in whole pence. |
| data.overall_decision required | string |
One of: "accept_standard", "accept_rated", "mixed"
|
| data.pas_sync_status required | string |
Whether the policy has reached your policy administration system, if you use one. One of:"pending", "synced", "failed_reconciling", "failed_unrecoverable"
|
| data.bound_at required | string (date-time) | |
| data.cancelled_at required | string or null (date-time) | |
| data.components required | array of object |
The covers. |
| data.components[].id required | string (uuid) | |
| data.components[].display_id required | string | |
| data.components[].cover_id required | string (uuid) | |
| data.components[].cover_type required | string | |
| data.components[].component_index required | integer | |
| data.components[].sum_assured_pence required | integer |
The amount of cover, in whole pence. |
| data.components[].term_years required | integer or null | |
| data.components[].premium_monthly_pence required | integer |
The monthly premium, in whole pence. |
| data.components[].decision required | string | |
| data.components[].rating_pct required | integer | |
| data.components[].exclusions required | array of any | |
| data.components[].lives required | array of object | |
| data.components[].commencement_date required | string or null (date) | |
| data.components[].cancelled_at required | string or null (date-time) | |
| data.cessions required | array of object |
Reinsurance arrangements for the cover, if your firm shares its risk. Amounts are whole pence ( |
| data.first_premium_record required | one of: object, null |
The first premium's payment, or |
| data.policy_hub_path required | string or null |
Where the customer manages their policy. |
| data.schedule_path required | string or null |
The policy schedule, or |
| data.documents required | array of object |
The policy's documents, for the customer. |
| data.documents[].id required | string | |
| data.documents[].kind required | string | |
| data.documents[].name required | string | |
| data.documents[].path required | string |
Its address. |
| data.first_collection_on required | string or null (date) |
When the first Direct Debit collection is due. |
| data.collection_day_of_month required | integer or null |
The day of the month premiums are collected. |
| data.wrap_up required | one of: object, null |
Your firm's closing words after purchase, when it has them; otherwise |
{
"data": {
"id": "0199a3c2-8000-71ee-8a6f-e3f405162738",
"display_id": "pol_0199a3c2-8000-71ee-8a6f-e3f405162738",
"policy_number": "POL-AB-P001-000042",
"basis": "single",
"joint_customer_name": null,
"currency_code": "GBP",
"commencement_date": "2026-10-12",
"total_premium_monthly_pence": 1245,
"overall_decision": "accept_standard",
"pas_sync_status": "pending",
"bound_at": "2026-10-12T10:31:07+01:00",
"cancelled_at": null,
"components": [
{
"id": "0199a3c2-8001-72ff-9b70-f405162738a9",
"display_id": "comp_0199a3c2-8001-72ff-9b70-f405162738a9",
"cover_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f08",
"cover_type": "life",
"component_index": 0,
"sum_assured_pence": 25000000,
"term_years": 25,
"premium_monthly_pence": 1245,
"decision": "accept_standard",
"rating_pct": 100,
"exclusions": [],
"lives": [],
"commencement_date": "2026-10-12",
"cancelled_at": null
}
],
"cessions": [],
"first_premium_record": {
"id": "0199a3c2-8002-7300-8c81-05162738a9ba",
"display_id": "pay_0199a3c2-8002-7300-8c81-05162738a9ba",
"kind": "first_premium",
"amount_pence": 1245,
"currency_code": "GBP",
"status": "collected",
"source": "system_initiated",
"processor": "stripe",
"processor_reference": "pi_3Example",
"occurred_at": "2026-10-12T10:31:07+01:00"
},
"policy_hub_path": "https://www.example.com/policy/Ro3oicP01pB-RlxbOFd01A2f7fgMDCAe",
"schedule_path": "https://www.example.com/policy/Ro3oicP01pB-RlxbOFd01A2f7fgMDCAe/schedule",
"documents": [
{
"id": "0199a3c2-7e00-7ebb-9d3c-b0c1d2e3f40b",
"kind": "policy_wording",
"name": "Policy wording",
"path": "https://www.example.com/policy/Ro3oicP01pB-RlxbOFd01A2f7fgMDCAe/documents/0199a3c2-7e00-7ebb-9d3c-b0c1d2e3f40b"
}
],
"first_collection_on": "2026-11-12",
"collection_day_of_month": 12,
"wrap_up": null
}
}
200 The policy was already set up. This is it.
| Field | Type | Description |
|---|---|---|
| data required | Policy |
A policy, with the customer's links to it. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.policy_number required | string | |
| data.basis required | string |
One of: "single", "joint"
|
| data.joint_customer_name required | string or null |
For joint cover, the second person's name. |
| data.currency_code required | string | |
| data.commencement_date required | string (date) |
When cover starts. |
| data.total_premium_monthly_pence required | integer |
The monthly premium, in whole pence. |
| data.overall_decision required | string |
One of: "accept_standard", "accept_rated", "mixed"
|
| data.pas_sync_status required | string |
Whether the policy has reached your policy administration system, if you use one. One of:"pending", "synced", "failed_reconciling", "failed_unrecoverable"
|
| data.bound_at required | string (date-time) | |
| data.cancelled_at required | string or null (date-time) | |
| data.components required | array of object |
The covers. |
| data.components[].id required | string (uuid) | |
| data.components[].display_id required | string | |
| data.components[].cover_id required | string (uuid) | |
| data.components[].cover_type required | string | |
| data.components[].component_index required | integer | |
| data.components[].sum_assured_pence required | integer |
The amount of cover, in whole pence. |
| data.components[].term_years required | integer or null | |
| data.components[].premium_monthly_pence required | integer |
The monthly premium, in whole pence. |
| data.components[].decision required | string | |
| data.components[].rating_pct required | integer | |
| data.components[].exclusions required | array of any | |
| data.components[].lives required | array of object | |
| data.components[].commencement_date required | string or null (date) | |
| data.components[].cancelled_at required | string or null (date-time) | |
| data.cessions required | array of object |
Reinsurance arrangements for the cover, if your firm shares its risk. Amounts are whole pence ( |
| data.first_premium_record required | one of: object, null |
The first premium's payment, or |
| data.policy_hub_path required | string or null |
Where the customer manages their policy. |
| data.schedule_path required | string or null |
The policy schedule, or |
| data.documents required | array of object |
The policy's documents, for the customer. |
| data.documents[].id required | string | |
| data.documents[].kind required | string | |
| data.documents[].name required | string | |
| data.documents[].path required | string |
Its address. |
| data.first_collection_on required | string or null (date) |
When the first Direct Debit collection is due. |
| data.collection_day_of_month required | integer or null |
The day of the month premiums are collected. |
| data.wrap_up required | one of: object, null |
Your firm's closing words after purchase, when it has them; otherwise |
{
"data": {
"id": "0199a3c2-8000-71ee-8a6f-e3f405162738",
"display_id": "pol_0199a3c2-8000-71ee-8a6f-e3f405162738",
"policy_number": "POL-AB-P001-000042",
"basis": "single",
"joint_customer_name": null,
"currency_code": "GBP",
"commencement_date": "2026-10-12",
"total_premium_monthly_pence": 1245,
"overall_decision": "accept_standard",
"pas_sync_status": "pending",
"bound_at": "2026-10-12T10:31:07+01:00",
"cancelled_at": null,
"components": [
{
"id": "0199a3c2-8001-72ff-9b70-f405162738a9",
"display_id": "comp_0199a3c2-8001-72ff-9b70-f405162738a9",
"cover_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f08",
"cover_type": "life",
"component_index": 0,
"sum_assured_pence": 25000000,
"term_years": 25,
"premium_monthly_pence": 1245,
"decision": "accept_standard",
"rating_pct": 100,
"exclusions": [],
"lives": [],
"commencement_date": "2026-10-12",
"cancelled_at": null
}
],
"cessions": [],
"first_premium_record": {
"id": "0199a3c2-8002-7300-8c81-05162738a9ba",
"display_id": "pay_0199a3c2-8002-7300-8c81-05162738a9ba",
"kind": "first_premium",
"amount_pence": 1245,
"currency_code": "GBP",
"status": "collected",
"source": "system_initiated",
"processor": "stripe",
"processor_reference": "pi_3Example",
"occurred_at": "2026-10-12T10:31:07+01:00"
},
"policy_hub_path": "https://www.example.com/policy/Ro3oicP01pB-RlxbOFd01A2f7fgMDCAe",
"schedule_path": "https://www.example.com/policy/Ro3oicP01pB-RlxbOFd01A2f7fgMDCAe/schedule",
"documents": [
{
"id": "0199a3c2-7e00-7ebb-9d3c-b0c1d2e3f40b",
"kind": "policy_wording",
"name": "Policy wording",
"path": "https://www.example.com/policy/Ro3oicP01pB-RlxbOFd01A2f7fgMDCAe/documents/0199a3c2-7e00-7ebb-9d3c-b0c1d2e3f40b"
}
],
"first_collection_on": "2026-11-12",
"collection_day_of_month": 12,
"wrap_up": null
}
}
401 No token was sent, or it isn't valid because it has expired, was revoked (for example because the application's secret was rotated), or was cut short. Get a new token and try once more.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "invalid_token",
"message": "The access token has expired. Ask for a new one."
}
}
402 `payment_not_succeeded`: the card payment hasn't gone through yet (for example it needs the customer to finish a bank check). Wait for Stripe, then try again.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "payment_not_succeeded",
"message": "The payment hasn't completed (status: requires_action)."
}
}
403 The token can't be used here. It's missing a scope, it was used from the wrong place (a server-only application's token in a browser, or a browser application's token from a server, another website or a request only server-only applications may make), or it wasn't asked for with `resource=https://subcontext.com/api/v1`.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "forbidden",
"message": "origin 'https://www.example.com' is not allowlisted for this OAuth application"
}
}
404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "not_found",
"message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
}
}
409 - `quote_expired`: the quote has expired. Quote again. - `quote_not_bindable`: the quote can't be bought any more (it was replaced, or another quote was bought). - `already_bound`: the application already has a different policy.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "quote_expired",
"message": "This quote expired on 2026-11-11."
}
}
422 The policy wasn't set up: - `sale_steps_owed`: something from [What to show before they buy](/developers/api/reference/checkout#getBeforeYouBuy) is still owed. Show it, confirm, and try again. - `direct_debit_missing`: no Direct Debit yet. - `payment_missing`: no `payment_intent_id`, and the product needs a card payment. - `payment_unverified`: Stripe doesn't know that payment. - `payment_mismatch`: that payment wasn't for this quote, or not for its first premium. - `invalid_start_date`: `commencement_date` isn't a date between today and 90 days ahead. - `payment_flow_invalid`: the product's payment settings can't work together. Ask your administrator. - `treaty_breach` or `bind_failed`: the policy couldn't be issued (the message says why).
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "sale_steps_owed",
"message": "Before the policy can be set up the customer still needs: confirm before purchase."
}
}