Checkout

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.

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

NameInTypeDescription
id required path string (uuid)

The quote's id.

X-Share-Token header string

The application's share_token, from the answer that started it. A browser application sends it with every request about an application that already exists, a replay of the request that started it included. Without it, or with another application's, the answer is 404 not_found (409 idempotency_replay_unproven on a replay). A server-only application doesn't need it.

Responses

200 The steps still owed, with what to show for each.

FieldTypeDescription
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

false when the product can't be bought on a form. Offer a conversation instead.

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 null.

data.confirmation required one of: object, null

A read-back of what they're buying, to ask them to agree to; or null.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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`.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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

NameInTypeDescription
id required path string (uuid)

The quote's id.

X-Share-Token header string

The application's share_token, from the answer that started it. A browser application sends it with every request about an application that already exists, a replay of the request that started it included. Without it, or with another application's, the answer is 404 not_found (409 idempotency_replay_unproven on a replay). A server-only application doesn't need it.

Responses

200 Recorded.

FieldTypeDescription
data required object
data.owed required array of SaleStep

Steps still owed, normally none.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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`.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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

NameInTypeDescription
id required path string (uuid)

The quote's id.

X-Share-Token header string

The application's share_token, from the answer that started it. A browser application sends it with every request about an application that already exists, a replay of the request that started it included. Without it, or with another application's, the answer is 404 not_found (409 idempotency_replay_unproven on a replay). A server-only application doesn't need it.

Request body application/json

FieldTypeDescription
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
Example request
{
  "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.

FieldTypeDescription
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 40-47-84.

data.account_number_last4 required string

The last four digits of the account number, as 2490.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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`.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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

NameInTypeDescription
id required path string (uuid)

The quote's id.

X-Share-Token header string

The application's share_token, from the answer that started it. A browser application sends it with every request about an application that already exists, a replay of the request that started it included. Without it, or with another application's, the answer is 404 not_found (409 idempotency_replay_unproven on a replay). A server-only application doesn't need it.

Responses

201 The session, and the keys to mount it with.

FieldTypeDescription
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 null when payments go through Subcontext's own account.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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`.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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).

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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

NameInTypeDescription
id required path string (uuid)

The quote's id.

X-Share-Token header string

The application's share_token, from the answer that started it. A browser application sends it with every request about an application that already exists, a replay of the request that started it included. Without it, or with another application's, the answer is 404 not_found (409 idempotency_replay_unproven on a replay). A server-only application doesn't need it.

Request body application/json

FieldTypeDescription
session_id required string

The Stripe Checkout session's id, from Start a Direct Debit mandate.

Example request
{
  "session_id": "cs_live_a1B2c3D4"
}

Responses

201 The mandate is recorded.

FieldTypeDescription
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.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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`.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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).

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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

NameInTypeDescription
id required path string (uuid)

The quote's id.

X-Share-Token header string

The application's share_token, from the answer that started it. A browser application sends it with every request about an application that already exists, a replay of the request that started it included. Without it, or with another application's, the answer is 404 not_found (409 idempotency_replay_unproven on a replay). A server-only application doesn't need it.

Responses

201 The PaymentIntent and the keys to mount it with.

FieldTypeDescription
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 requires_payment_method.

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 GBP.

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 null when payments go through Subcontext's own account.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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`.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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).

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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

NameInTypeDescription
id required path string (uuid)

The quote's id.

X-Share-Token header string

The application's share_token, from the answer that started it. A browser application sends it with every request about an application that already exists, a replay of the request that started it included. Without it, or with another application's, the answer is 404 not_found (409 idempotency_replay_unproven on a replay). A server-only application doesn't need it.

Request body application/json

FieldTypeDescription
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, YYYY-MM-DD, from today to 90 days ahead.

policy_number string

Server-only applications only. Your own policy number, when your systems issue them.

Example request
{
  "payment_intent_id": "pi_3Example"
}

Responses

201 The policy is set up.

FieldTypeDescription
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, pol_ and the id.

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 (retained_pence, ceded_pence, first_ceded_premium_pence).

data.first_premium_record required one of: object, null

The first premium's payment, or null before there is one.

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 null when the policy is managed elsewhere.

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 null.

Example
{
  "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.

FieldTypeDescription
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, pol_ and the id.

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 (retained_pence, ceded_pence, first_ceded_premium_pence).

data.first_premium_record required one of: object, null

The first premium's payment, or null before there is one.

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 null when the policy is managed elsewhere.

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 null.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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`.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "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).

FieldTypeDescription
error required object
error.code required string

A short code for the kind of error, such as invalid_token, forbidden, not_found or validation_failed. Each code means one thing wherever it appears.

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 (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "error": {
    "code": "sale_steps_owed",
    "message": "Before the policy can be set up the customer still needs: confirm before purchase."
  }
}

Stuck, or something missing?

Get in touch and a person on the team will help.

Contact us