An application is one customer's request for cover: their answers, their details and the cover they want. A quote prices it.
The usual order is to start an application, save the customer's answers and details as they give them, ask for a quote, and accept it. Then take payment and set up the policy with the requests under Checkout. The guide Quote and sell through the API walks through it with examples.
Browser and server-only applications can both call these. Amounts are
whole pence (the smallest unit of the product's currency), in fields
named *_pence.
Anyone can get a browser application's token, so for one an
application's id isn't enough to reach it. Every request about an
application that already exists, and every Checkout request, also
sends the application's share_token in the X-Share-Token header.
Keep it from the answer that started the application. Without it, or
with another application's, the answer is 404 not_found, the same
as for an id that doesn't exist. A server-only application doesn't
need it.
Start an application
POST
https://subcontext.com/api/v1/applications
Try it
Token scopes:
write
Starts an application for one of your products, on one of its journeys. Start it as soon as the customer begins, so their answers can be saved as they go.
You can start it without a customer (it's anonymous until you
add one with Save the customer's details),
or with one (it's in_capture). Answers you already have can go in
answers.
The answer includes the application's share_token. Keep it. It's the key to the application for the quote form and the webchat, and
a browser application only sees it here. A browser application
sends it in the X-Share-Token header with every later request
about the application.
Idempotency: send an Idempotency-Key header (or
idempotency_key in the body). Sending the same key again with the
same inputs gives back the application it started, with 200 OK,
rather than starting a second one. The same key with a different
product, journey, presentation or customer (by email), or an answer
the application already holds a different value for, is refused
with 409 idempotency_key_conflict.
A browser application gets the share_token only on the request
that started the application, not on a replay. Keep it from the first answer. A server-only
application gets it every time.
A browser application's tokens are effectively public and a key can
be guessed, so its replay must send the application's share_token
in X-Share-Token, as with any request about the application.
Without it the answer is 409 idempotency_replay_unproven and
nothing is read or changed. If the first answer never arrived (so
you have no share_token), send the request again with a new key,
and the old one in replaces_idempotency_key. The application the
old key started is then closed (abandoned, reason
superseded_by_retry), so nobody follows it up. A token handed out
for one application replays only that one.
A customer whose email matches an existing customer is that
customer, but a browser application never changes their record:
see Save the customer's details.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| Idempotency-Key | header | string | A value unique to this request in your system, so a retry doesn't do it twice. |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| product_slug required | string |
The product's slug. |
| journey_slug required | string |
The journey's slug. |
| presentation_slug | string |
Which of the journey's presentations to use. Leave it out for the default. |
| answers | object |
Answers you already have, by question key (see Get a journey's questions). |
| customer | CustomerInput |
A person's details. Every field is optional on its own, but a customer needs a first name, last name and email address. |
| customer.title | string | |
| customer.first_name | string | |
| customer.last_name | string | |
| customer.email | string (email) |
Customers are found by email address within your organisation. A browser application never changes an existing customer's record unless its application created it. |
| customer.phone_number | string | |
| customer.dob | string (date) |
Date of birth, |
| customer.postal_code | string | |
| customer.address_line1 | string | |
| customer.address_line2 | string | |
| customer.city | string | |
| customer.country | string | |
| consent_answers | object |
When you send |
| provenance | ProvenanceInput |
Where the customer was when they filled it in, for your reports on
what brings people in: |
| idempotency_key | string |
The same as the |
| replaces_idempotency_key | ReplacesIdempotencyKey |
On a request sent again under a new |
| outreach_landing | string |
The token an outreach stream's link added to the page
(its |
{
"product_slug": "level-term-life",
"journey_slug": "express",
"answers": {
"uk_resident": "yes"
},
"provenance": {
"page_url": "https://www.example.com/life-cover?utm_source=newsletter",
"referrer": "https://www.google.com/"
}
}
Responses
201 The application was started.
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "anonymous",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": null,
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes"
},
"cover_amount_pence": null,
"term_years": null,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:15:00+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}
}
200 An application was already started with this idempotency key and the same inputs. This is it, as it is now (with `share_token` only for a server-only application).
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "anonymous",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": null,
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes"
},
"cover_amount_pence": null,
"term_years": null,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:15:00+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}
}
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 record in your organisation.
| 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"
}
}
409 - `idempotency_key_conflict`: the `Idempotency-Key` started an application with different inputs. The message names them. - `idempotency_replay_unproven`: a browser application sent an `Idempotency-Key` that already started an application, without that application's `share_token` in `X-Share-Token`. Nothing changed. Send the `share_token`, or start again with a new key.
| 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": "idempotency_key_conflict",
"message": "Idempotency-Key 'quote-0001' has already been used with different inputs (journey_slug, answers)."
}
}
422 The request can't be used as sent (`fields` names what to change): - `validation_failed`: `product_slug` or `journey_slug` is missing. - `customer_invalid`: the customer's details aren't valid, for example a malformed email address.
| 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_invalid",
"message": "Email is invalid",
"fields": {
"customer.email": "is invalid"
}
}
}
Get an application
GET
https://subcontext.com/api/v1/applications/{id}
Try it
Token scopes:
read or write
The application as it is now: its status, answers, customer, cover and, for a whole-of-market product, the comparison's progress.
share_token is only included for a server-only application (or a
token handed out for this one application).
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Responses
200 The application.
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "quoted",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes",
"smoker": false
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:19:42+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}
}
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."
}
}
Save answers
PATCH
https://subcontext.com/api/v1/applications/{id}/answers
Try it
Token scopes:
write
Saves some of the customer's answers, by question key. Send what they've just answered (each page of your form, for example). Answers you've saved before are kept unless you send them again.
Two keys set the cover rather than answer a question:
sum_assured (the amount of cover, in pence) and term_years.
The answer says which questions have been answered (captured_keys),
which are still to ask (missing_keys) and, in knockout, any answer that means the application can't go on (for example a decline), so you can tell the customer.
An application that was declined, postponed or referred on earlier answers is opened again, and the new answers are a fresh attempt.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| answers required | object |
Answers by question key. Numbers as numbers, choices as the option's |
| marketing_metadata | object |
Extra details to keep with the application that don't affect the price (for example the mortgage type). |
{
"answers": {
"smoker": false,
"sum_assured": 25000000,
"term_years": 25
}
}
Responses
200 The answers were saved.
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.application required | Application |
One customer's request for cover. |
| data.application.id required | string (uuid) | |
| data.application.display_id required | string |
The id as Subcontext shows it, |
| data.application.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.application.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.application.product_id required | string (uuid) | |
| data.application.journey_id required | string (uuid) | |
| data.application.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.application.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.application.risk_factors required | object |
The answers so far, by question key. |
| data.application.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.application.term_years required | integer or null |
The term asked for, in years. |
| data.application.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.application.abandoned_reason required | string or null | |
| data.application.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.application.inline_bacs_mandate required | boolean |
|
| data.application.direct_debit_first required | boolean |
|
| data.application.stripe_test_mode required | boolean |
|
| data.application.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.application.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.application.created_at required | string (date-time) | |
| data.application.updated_at required | string (date-time) | |
| data.application.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
| data.captured_keys required | array of string |
The question keys answered in this request. |
| data.missing_keys required | array of string |
The questions still to answer (those that apply, given the answers so far). |
| data.knockout required | one of: Knockout, null |
When an answer on the application ends it, what it was; otherwise |
{
"data": {
"application": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "quoted",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes",
"smoker": false
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:19:42+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
},
"captured_keys": [
"smoker"
],
"missing_keys": [
"weight_kg",
"height_cm"
],
"knockout": 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."
}
}
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."
}
}
422 The answers weren't saved: - `answers_invalid`: a key isn't one of the journey's questions, or a value doesn't fit its question (`fields` names each, by question key, and says why), or the application can't take answers any more (it's bound or abandoned). - `validation_failed`: `answers` 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": "answers_invalid",
"message": "invalid answers: cigarettes_per_day must be at most 100",
"fields": {
"cigarettes_per_day": "must be at most 100"
}
}
}
Save the customer's details
PATCH
https://subcontext.com/api/v1/applications/{id}/customer
Try it
Token scopes:
write
Adds the customer (the person to be covered) to the application, or updates them. Customers are found by email address within your organisation, so the same person applying twice is one customer.
Anyone can type anyone's email address, so a customer who's already on record isn't always updated:
- A server-only application updates them, except a policyholder, whose record only gains the details it's missing.
- A browser application never changes an existing customer's record, unless the customer was created by this application (by Start an application, Quote in one request or this request). Otherwise the application is theirs, but what was typed is kept on the application.
When details are kept off the record, the answer shows what was typed, and quotes for the application are priced on it. Your staff still see the stored record.
An anonymous application moves to in_capture. A quoted or referred one keeps its status. consent_answers records any opt-out
boxes the person ticked on the details page.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| customer required | CustomerInput |
A person's details. Every field is optional on its own, but a customer needs a first name, last name and email address. |
| customer.title | string | |
| customer.first_name | string | |
| customer.last_name | string | |
| customer.email | string (email) |
Customers are found by email address within your organisation. A browser application never changes an existing customer's record unless its application created it. |
| customer.phone_number | string | |
| customer.dob | string (date) |
Date of birth, |
| customer.postal_code | string | |
| customer.address_line1 | string | |
| customer.address_line2 | string | |
| customer.city | string | |
| customer.country | string | |
| consent_answers | object |
The details stage's opt-out boxes the person ticked, by key, as |
{
"customer": {
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"city": "London",
"country": "United Kingdom"
}
}
Responses
200 The customer was saved.
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "quoted",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes",
"smoker": false
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:19:42+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}
}
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."
}
}
422 - `customer_invalid`: the details aren't valid (`fields` names each, as `customer.first_name`). - `validation_failed`: `customer` 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": "customer_invalid",
"message": "First name can't be blank",
"fields": {
"customer.first_name": "can't be blank"
}
}
}
Save the second person's details
PATCH
https://subcontext.com/api/v1/applications/{id}/partner
Try it
Token scopes:
write
For joint cover, adds the second person to be covered, or updates them. Only for an application on a joint basis. An existing customer found by email is treated as in Save the customer's details.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| partner required | object |
The second person. |
| partner.title | string | |
| partner.first_name | string | |
| partner.last_name | string | |
| partner.email | string |
Optional. Couples often share one address, so it may be the same as the first person's. |
| partner.phone_number | string | |
| partner.dob | string (date) |
Date of birth, |
{
"partner": {
"first_name": "Charles",
"last_name": "Babbage",
"dob": "1986-12-26"
}
}
Responses
200 The second person was saved. They're the application's `joint_customer`.
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "quoted",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes",
"smoker": false
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:19:42+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}
}
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."
}
}
422 - `not_joint_basis`: the application is for one person. - `partner_invalid`: the details aren't valid (`fields` names each, as `partner.dob`). - `validation_failed`: `partner` 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": "not_joint_basis",
"message": "application is not on a joint basis"
}
}
Save early details
PATCH
https://subcontext.com/api/v1/applications/{id}/soft_customer
Try it
Token scopes:
write
For a presentation with a "soft identity" stage, saves the few details a customer gives early on (often a first name and a postcode) without making them a customer yet. They fill in the details stage later, and the quote form and webchat can greet the person by name in the meantime.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| customer required | CustomerInput |
A person's details. Every field is optional on its own, but a customer needs a first name, last name and email address. |
| customer.title | string | |
| customer.first_name | string | |
| customer.last_name | string | |
| customer.email | string (email) |
Customers are found by email address within your organisation. A browser application never changes an existing customer's record unless its application created it. |
| customer.phone_number | string | |
| customer.dob | string (date) |
Date of birth, |
| customer.postal_code | string | |
| customer.address_line1 | string | |
| customer.address_line2 | string | |
| customer.city | string | |
| customer.country | string |
{
"customer": {
"first_name": "Ada",
"postal_code": "SW1A 2AA"
}
}
Responses
200 The details were saved.
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "anonymous",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": null,
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes"
},
"cover_amount_pence": null,
"term_years": null,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:15:00+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}
}
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."
}
}
422 `validation_failed`: a required field is missing (`fields` names it).
| 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": "validation_failed",
"message": "customer is required.",
"fields": {
"customer": "is required"
}
}
}
Save a choice on an interstitial page
PATCH
https://subcontext.com/api/v1/applications/{id}/interstitial_choice
Try it
Token scopes:
write
For a presentation with an interstitial stage (a page that offers
the customer a choice, such as "Would you like to add critical
illness cover?"), records which option they chose, by its key. Name
the stage by its stage_key (or, for older forms, its
stage_position).
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| choice_key required | string |
The key of the option the customer chose. |
| stage_key | string |
The stage's |
| stage_position | integer |
The stage's |
{
"stage_key": "add_critical_illness",
"choice_key": "yes"
}
Responses
200 The choice was saved.
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "anonymous",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": null,
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes"
},
"cover_amount_pence": null,
"term_years": null,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:15:00+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}
}
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 `not_found`: no such application (or, for a browser application, no `X-Share-Token` that matches it), or no stage on its presentation has that `stage_key` or `stage_position`.
| 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": "No stage on this application's presentation has that stage_key or stage_position."
}
}
422 `validation_failed`: `choice_key` is missing or blank.
| 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": "validation_failed",
"message": "choice_key is required.",
"fields": {
"choice_key": "is required"
}
}
}
Save a reflection
PATCH
https://subcontext.com/api/v1/applications/{id}/reflection
Try it
Token scopes:
write
For a presentation with a reflection stage (a page that asks an open
question, such as "What matters most to you about this cover?"), saves the customer's answer. It's optional, so send an empty or missing text when they skip it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| text | string |
What the customer wrote. |
{
"text": "Making sure the mortgage is paid off if something happens to me."
}
Responses
200 The answer was saved.
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "anonymous",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": null,
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes"
},
"cover_amount_pence": null,
"term_years": null,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:15:00+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}
}
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."
}
}
Ask for a person to follow up
PATCH
https://subcontext.com/api/v1/applications/{id}/handoff
Try it
Token scopes:
write
The customer would rather talk to someone. The application becomes
referred, and your team is told (it appears on the dashboard's
escalations, and whoever is set up to hear about them is notified).
Asking again does nothing more.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Responses
200 The application is with your team.
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "quoted",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes",
"smoker": false
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:19:42+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}
}
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."
}
}
Quote the application
POST
https://subcontext.com/api/v1/applications/{id}/quote
Try it
Token scopes:
write
Prices the application as it stands: the cover saved with
Save answers
(sum_assured and term_years) and the answers so far. Use it for
a price part way through the form, and again at the end.
A price part way through may rest on assumptions about questions not answered yet. The quote's assumptions lists them, and the price is
only indicative until they're answered. Asking again replaces the
previous quote.
quote is null when the answers mean no price can be offered, and the application's status says why (declined, postponed or referred).
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Responses
200 The application and its new quote.
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.application required | Application |
One customer's request for cover. |
| data.application.id required | string (uuid) | |
| data.application.display_id required | string |
The id as Subcontext shows it, |
| data.application.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.application.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.application.product_id required | string (uuid) | |
| data.application.journey_id required | string (uuid) | |
| data.application.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.application.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.application.risk_factors required | object |
The answers so far, by question key. |
| data.application.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.application.term_years required | integer or null |
The term asked for, in years. |
| data.application.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.application.abandoned_reason required | string or null | |
| data.application.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.application.inline_bacs_mandate required | boolean |
|
| data.application.direct_debit_first required | boolean |
|
| data.application.stripe_test_mode required | boolean |
|
| data.application.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.application.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.application.created_at required | string (date-time) | |
| data.application.updated_at required | string (date-time) | |
| data.application.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
| data.quote required | one of: Quote, null |
The new quote, or |
{
"data": {
"application": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "quoted",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes",
"smoker": false
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:19:42+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
},
"quote": {
"id": "0199a3c2-7c00-7a77-9f9e-7c8d9eafb0c7",
"display_id": "quo_0199a3c2-7c00-7a77-9f9e-7c8d9eafb0c7",
"reference": "QT-AB-P001-000042",
"status": "issued",
"basis": "single",
"decision": "accept_standard",
"currency_code": "GBP",
"total_premium_monthly_pence": 1245,
"policy_fee_pence": 0,
"bundle_discount_pence": 0,
"issued_at": "2026-10-12T10:19:42+01:00",
"expires_at": "2026-11-11T10:19:42+00:00",
"accepted_at": null,
"superseded_by_id": null,
"assumptions": [],
"components": [
{
"id": "0199a3c2-7c01-7b88-8a0f-8d9eafb0c1d8",
"display_id": "qco_0199a3c2-7c01-7b88-8a0f-8d9eafb0c1d8",
"cover_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f08",
"cover_type": "life",
"component_index": 0,
"sum_assured_pence": 25000000,
"term_years": 25,
"decision": "accept_standard",
"rating_pct": 100,
"premium_monthly_pence": 1245,
"premium_annual_pence": 14940,
"exclusions": [],
"reasons": [],
"lives": [],
"member_count": null,
"title": "Life"
}
],
"premium_illustration": 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."
}
}
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."
}
}
422 No quote could be made: - `cover_missing`: the amount of cover or the term isn't saved yet. - `no_quotable_cover`: the product has no cover that can be priced. - `quote_failed`: pricing failed (the message says why). - `rate_unavailable`: the details are acceptable, but there's no price for them.
| 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": "cover_missing",
"message": "application has no cover_amount_pence / term_years yet"
}
}
Accept the quote
POST
https://subcontext.com/api/v1/applications/{id}/accept_quote
Try it
Token scopes:
write
The customer wants to go ahead with the quote. Records that they
accepted it, so the webchat (if they're also chatting) knows too.
Send quote_id to name the quote. Without it, the application's current quote is accepted. Accepting it again is fine.
Then go on to Checkout.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| quote_id | string (uuid) |
The quote to accept. Leave it out for the application's current quote. |
{
"quote_id": "0199a3c2-7c00-7a77-9f9e-7c8d9eafb0c7"
}
Responses
200 The quote is accepted.
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "quoted",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes",
"smoker": false
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:19:42+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}
}
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."
}
}
422 `accept_failed`: there's no quote yet, it can't be bought any more (it expired or was replaced), or the answers have changed since it was priced (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": "accept_failed",
"message": "no quote to proceed with yet: create one first"
}
}
Search the market
POST
https://subcontext.com/api/v1/applications/{id}/market_quotes
Try it
Token scopes:
write
For a whole-of-market product, starts a search for quotes from
providers across the market, using the application's answers. The
search takes a little while, so this answers straight away with the
application, whose market_quotes.status is pending or fetching.
Fetch the application (every few seconds) until it's completed
(or failed).
Asking again with the same answers gives back the same search rather than starting another.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Responses
200 The search has started (or was already under way). The application's `market_quotes` shows its progress.
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "in_capture",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"sex": "female",
"smoker": false,
"occupation": "Software Engineer"
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": null,
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": {
"request_id": "0199a3c2-7d00-7c99-9b1a-9eafb0c1d2e9",
"status": "completed",
"fetched_at": "2026-10-12T10:21:03+01:00",
"expires_at": "2026-11-12T10:21:03+00:00",
"stale": false,
"provider_count": 2,
"failure_code": null,
"selected_result_id": null,
"results": [
{
"ordinal": 1,
"result_id": "0199a3c2-7d01-7daa-8c2b-afb0c1d2e3fa",
"provider_name": "Example Life",
"logo_url": null,
"product_name": "Example Life Protection",
"premium_pence": 1180,
"premium_monthly": "£11.80",
"plan_fee_pence": null,
"status": "success",
"status_reason": null,
"variations": [],
"features": [],
"evidence_required": false,
"further_underwriting": false,
"standard_or_underwritten": null,
"expiry_date": "2026-11-12",
"selected": false
}
]
},
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:21:03+01:00"
}
}
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."
}
}
422 The search couldn't start: - `not_market_product`: the product is priced by your own rates, not across the market. Use [Quote the application](/developers/api/reference/quotes-and-applications#quoteApplication). - `mapping_incomplete`: answers the providers need are missing (`fields` names each, as `answers.sex`).
| 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": "mapping_incomplete",
"message": "Missing inputs for market quoting: sex, smoker",
"fields": {
"answers.sex": "is needed to search the market",
"answers.smoker": "is needed to search the market"
}
}
}
Choose a market quote
POST
https://subcontext.com/api/v1/applications/{id}/select_market_quote
Try it
Token scopes:
write
The customer has chosen one provider's quote from the market search.
Name it by its result_id. Then ask for the link that takes them to
the provider: Get the link to the provider.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| result_id required | string (uuid) |
The chosen result's |
{
"result_id": "0199a3c2-7d01-7daa-8c2b-afb0c1d2e3fa"
}
Responses
200 The choice is recorded. `market_quotes.selected_result_id` names it.
| Field | Type | Description |
|---|---|---|
| data required | Application |
One customer's request for cover. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.product_id required | string (uuid) | |
| data.journey_id required | string (uuid) | |
| data.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.risk_factors required | object |
The answers so far, by question key. |
| data.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.term_years required | integer or null |
The term asked for, in years. |
| data.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.abandoned_reason required | string or null | |
| data.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.inline_bacs_mandate required | boolean |
|
| data.direct_debit_first required | boolean |
|
| data.stripe_test_mode required | boolean |
|
| data.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.created_at required | string (date-time) | |
| data.updated_at required | string (date-time) | |
| data.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
{
"data": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "in_capture",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"sex": "female",
"smoker": false,
"occupation": "Software Engineer"
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": null,
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": {
"request_id": "0199a3c2-7d00-7c99-9b1a-9eafb0c1d2e9",
"status": "completed",
"fetched_at": "2026-10-12T10:21:03+01:00",
"expires_at": "2026-11-12T10:21:03+00:00",
"stale": false,
"provider_count": 2,
"failure_code": null,
"selected_result_id": null,
"results": [
{
"ordinal": 1,
"result_id": "0199a3c2-7d01-7daa-8c2b-afb0c1d2e3fa",
"provider_name": "Example Life",
"logo_url": null,
"product_name": "Example Life Protection",
"premium_pence": 1180,
"premium_monthly": "£11.80",
"plan_fee_pence": null,
"status": "success",
"status_reason": null,
"variations": [],
"features": [],
"evidence_required": false,
"further_underwriting": false,
"standard_or_underwritten": null,
"expiry_date": "2026-11-12",
"selected": false
}
]
},
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:21:03+01:00"
}
}
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 `not_found`: no such application (or, for a browser application, no `X-Share-Token` that matches it), or no quote with that `result_id` on its search.
| 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": "No such quote on this application's market search."
}
}
409 `results_stale`: the answers have changed since the search, or a newer search replaced it. Search 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": "results_stale",
"message": "These results are out of date: refresh the market search"
}
}
422 - `select_failed`: that provider didn't return a usable quote, the search hasn't finished, or the quote has expired. - `validation_failed`: `result_id` 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": "select_failed",
"message": "This quote has expired: run a fresh market search"
}
}
Get the link to the provider
POST
https://subcontext.com/api/v1/applications/{id}/handoff_link
Try it
Token scopes:
write
After Choose a market quote, the link that takes the customer to the chosen provider to finish
their application. The application becomes handed_off. Asking
again gives a fresh link for the same choice.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Responses
200 The link, and the provider's name for your button.
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.url required | string (uri) |
Where to send the customer. |
| data.provider_name required | string |
The provider's name. |
{
"data": {
"url": "https://subcontext.com/handoff/eyJfcmFpbHMiOnsiZGF0YSI6e30sInB1ciI6Im1hcmtldF9oYW5kb2ZmIn19--3f1b2c",
"provider_name": "Example Life"
}
}
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."
}
}
422 - `no_selection`: no market quote has been chosen yet. - `expired`: the chosen quote has expired. Search 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": "no_selection",
"message": "No market quote has been selected"
}
}
Record marketing choices
POST
https://subcontext.com/api/v1/applications/{id}/marketing_preference
Try it
Token scopes:
write
Records the customer's answer to your firm's marketing question (the
marketing question on the quote and comparison stages, set under
consent in the dashboard): true for each option they agreed to,
false for each they said no to. Leave out the options they didn't answer, because an untouched box isn't a no. A key that isn't one of the
question's options is refused, and nothing is recorded.
The application needs a customer first.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| answers | object |
Each answered option's key, and whether they agreed. |
{
"answers": {
"email": true,
"post": true,
"sms": false
}
}
Responses
200 The choices are recorded.
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.agreed required | array of string |
The options they agreed to. |
| data.declined required | array of string |
The options they said no to. |
{
"data": {
"agreed": [
"email",
"post"
],
"declined": [
"sms"
]
}
}
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."
}
}
422 Nothing was recorded: - `customer_missing`: the application has no customer yet. - `validation_failed`: `answers` has a key that isn't one of the question's options (`fields` names it, as `answers.sms`), or isn't an object.
| 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 to record this for"
}
}
Get a live updates ticket
POST
https://subcontext.com/api/v1/applications/{id}/cable_ticket
Try it
Token scopes:
write
For pages that show an application's changes as they happen (the quote form does, so a customer can fill it in while chatting), a short-lived ticket for opening the application's live updates
connection. Prove you hold the application by sending its
share_token in the body. For a browser application that body
value also counts as the X-Share-Token header. The ticket lasts
two hours; ask for a new one each time you connect.
You only need this if you're building your own live view. Fetching the application now and then does the same job more simply.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The application's |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| application_share_token required | string |
The application's |
{
"application_share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}
Responses
200 The ticket.
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.ticket required | string |
The ticket. Treat it like a password. |
| data.expires_in required | integer |
How many seconds it lasts. |
{
"data": {
"ticket": "eyJfcmFpbHMiOnsiZGF0YSI6eyJhcHBsaWNhdGlvbl9pZCI6IjAxOTkifX19--46dfd79138c98ea6",
"expires_in": 7200
}
}
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 `forbidden`: the token can't be used here (a missing scope, or the wrong kind of application or website), or the `share_token` isn't this application's.
| 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": "application_share_token isn't this application's."
}
}
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."
}
}
Quote in one request
POST
https://subcontext.com/api/v1/quotes
Try it
Token scopes:
write
Prices cover in a single request. You send the customer, their answers and the cover they want, and get back the application and its quote. Use it when your own system already has everything. To build an application up as the customer goes, use Start an application and the requests after it.
components lists the cover wanted. Each names a cover by
cover_type (the product's first cover of that type) or cover_id,
with the amount in whole pence (sum_assured_pence) and the term in
whole years. Amounts, ages and terms are checked against the
product's limits.
Send application_id to quote an application you started earlier. A browser application sends that application's share_token in X-Share-Token too. Changed answers or cover give a new quote. A bound application is
returned as it is. If that application already has a customer, the
customer you send must be the same person (by email).
Idempotency: send an Idempotency-Key header. A retry with the
same key and the same inputs gives back the same application and
quote (200 OK). The same key with different inputs is refused (409).
A browser application's retry must send the application's
share_token in X-Share-Token: without it the answer is
409 idempotency_replay_unproven, and neither the application nor
the customer is touched. If the first answer never arrived, send the
request again with a new key, and the old one in
replaces_idempotency_key: the application the old key made is
closed (abandoned, reason superseded_by_retry) and its quote
withdrawn, so nobody follows it up.
A customer whose email matches an existing customer is that
customer, but a browser application never changes their record:
what it sends is kept on the application, shown back and priced on
(see Save the customer's details).
The application's share_token comes with a new application
(201). An application that already existed (200, found by
Idempotency-Key or application_id) shows it only to a
server-only application, or a token handed out for that one
application, as Start an application
does on a replay.
quote is null when the answers mean no price can be offered. The application's status says why, and executions has the detail.
Each execution's trace (the steps of the assessment) is only for a server-only application.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| Idempotency-Key | header | string | A value unique to this request in your system, so a retry doesn't do it twice. |
| X-Share-Token | header | string | The application's |
Request body application/json
| Field | Type | Description |
|---|---|---|
| product_slug required | string |
The product's slug. |
| journey_slug required | string |
The journey's slug. |
| customer required | CustomerInput |
A person's details. Every field is optional on its own, but a customer needs a first name, last name and email address. |
| customer.title | string | |
| customer.first_name | string | |
| customer.last_name | string | |
| customer.email | string (email) |
Customers are found by email address within your organisation. A browser application never changes an existing customer's record unless its application created it. |
| customer.phone_number | string | |
| customer.dob | string (date) |
Date of birth, |
| customer.postal_code | string | |
| customer.address_line1 | string | |
| customer.address_line2 | string | |
| customer.city | string | |
| customer.country | string | |
| risk_factors | object |
The answers, by question key. Answers to the journey's questions are checked as Save answers checks them; other keys are kept as they are. |
| components required | array of object |
The cover wanted, at least one. |
| components[].cover_type | string |
The kind of cover, such as |
| components[].cover_id | string (uuid) |
A particular cover on the product, from Get a product. |
| components[].sum_assured_pence required | integer |
The amount of cover, in whole pence (for a monthly benefit, the yearly amount). |
| components[].term_years | integer |
The term in years. Leave it out for cover with no fixed term. |
| application_id | string (uuid) |
An application you started earlier, to quote that one. |
| idempotency_key | string |
The same as the |
| replaces_idempotency_key | ReplacesIdempotencyKey |
On a request sent again under a new |
{
"product_slug": "level-term-life",
"journey_slug": "express",
"customer": {
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA"
},
"risk_factors": {
"uk_resident": "yes",
"smoker": false
},
"components": [
{
"cover_type": "life",
"sum_assured_pence": 25000000,
"term_years": 25
}
]
}
Responses
201 A new application was made and quoted.
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.application required | Application |
One customer's request for cover. |
| data.application.id required | string (uuid) | |
| data.application.display_id required | string |
The id as Subcontext shows it, |
| data.application.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.application.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.application.product_id required | string (uuid) | |
| data.application.journey_id required | string (uuid) | |
| data.application.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.application.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.application.risk_factors required | object |
The answers so far, by question key. |
| data.application.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.application.term_years required | integer or null |
The term asked for, in years. |
| data.application.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.application.abandoned_reason required | string or null | |
| data.application.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.application.inline_bacs_mandate required | boolean |
|
| data.application.direct_debit_first required | boolean |
|
| data.application.stripe_test_mode required | boolean |
|
| data.application.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.application.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.application.created_at required | string (date-time) | |
| data.application.updated_at required | string (date-time) | |
| data.application.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
| data.quote required | one of: Quote, null |
The quote, or |
| data.executions required | array of RuleExecution |
How each cover was assessed. |
| data.executions[].id required | string (uuid) | |
| data.executions[].display_id required | string | |
| data.executions[].cover_id required | string or null (uuid) | |
| data.executions[].mode required | string |
One of: "live", "shadow", "test"
|
| data.executions[].decision required | string or null |
|
| data.executions[].rating_pct required | integer or null | |
| data.executions[].exclusions required | array of any | |
| data.executions[].premium_monthly_pence required | integer or null |
In whole pence, before any policy fee. |
| data.executions[].premium_annual_pence required | integer or null |
In whole pence. |
| data.executions[].rate_per_mille required | string or number or null |
The rate per £1,000 of cover. |
| data.executions[].decline_reasons required | array of any | |
| data.executions[].referral_reasons required | array of any | |
| data.executions[].executed_at required | string or null (date-time) | |
| data.executions[].trace | array of any |
The steps of the assessment, including the rules and rate tables it used. Only for a server-only application. |
{
"data": {
"application": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "quoted",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes",
"smoker": false
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:19:42+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
},
"quote": {
"id": "0199a3c2-7c00-7a77-9f9e-7c8d9eafb0c7",
"display_id": "quo_0199a3c2-7c00-7a77-9f9e-7c8d9eafb0c7",
"reference": "QT-AB-P001-000042",
"status": "issued",
"basis": "single",
"decision": "accept_standard",
"currency_code": "GBP",
"total_premium_monthly_pence": 1245,
"policy_fee_pence": 0,
"bundle_discount_pence": 0,
"issued_at": "2026-10-12T10:19:42+01:00",
"expires_at": "2026-11-11T10:19:42+00:00",
"accepted_at": null,
"superseded_by_id": null,
"assumptions": [],
"components": [
{
"id": "0199a3c2-7c01-7b88-8a0f-8d9eafb0c1d8",
"display_id": "qco_0199a3c2-7c01-7b88-8a0f-8d9eafb0c1d8",
"cover_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f08",
"cover_type": "life",
"component_index": 0,
"sum_assured_pence": 25000000,
"term_years": 25,
"decision": "accept_standard",
"rating_pct": 100,
"premium_monthly_pence": 1245,
"premium_annual_pence": 14940,
"exclusions": [],
"reasons": [],
"lives": [],
"member_count": null,
"title": "Life"
}
],
"premium_illustration": null
},
"executions": [
{
"id": "0199a3c2-7c02-7c99-9b10-9eafb0c1d2e9",
"display_id": "exe_0199a3c2-7c02-7c99-9b10-9eafb0c1d2e9",
"cover_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f08",
"mode": "live",
"decision": "accept_standard",
"rating_pct": 100,
"exclusions": [],
"premium_monthly_pence": 1245,
"premium_annual_pence": 14940,
"rate_per_mille": "0.498",
"decline_reasons": [],
"referral_reasons": [],
"executed_at": "2026-10-12T10:19:42+01:00",
"trace": []
}
]
}
}
200 The application already existed (an idempotent retry, or `application_id`). This is it, with its quote (and its `share_token` only for a server-only application).
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.application required | Application |
One customer's request for cover. |
| data.application.id required | string (uuid) | |
| data.application.display_id required | string |
The id as Subcontext shows it, |
| data.application.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.application.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.application.product_id required | string (uuid) | |
| data.application.journey_id required | string (uuid) | |
| data.application.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.application.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.application.risk_factors required | object |
The answers so far, by question key. |
| data.application.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.application.term_years required | integer or null |
The term asked for, in years. |
| data.application.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.application.abandoned_reason required | string or null | |
| data.application.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.application.inline_bacs_mandate required | boolean |
|
| data.application.direct_debit_first required | boolean |
|
| data.application.stripe_test_mode required | boolean |
|
| data.application.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.application.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.application.created_at required | string (date-time) | |
| data.application.updated_at required | string (date-time) | |
| data.application.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
| data.quote required | one of: Quote, null |
The quote, or |
| data.executions required | array of RuleExecution |
How each cover was assessed. |
| data.executions[].id required | string (uuid) | |
| data.executions[].display_id required | string | |
| data.executions[].cover_id required | string or null (uuid) | |
| data.executions[].mode required | string |
One of: "live", "shadow", "test"
|
| data.executions[].decision required | string or null |
|
| data.executions[].rating_pct required | integer or null | |
| data.executions[].exclusions required | array of any | |
| data.executions[].premium_monthly_pence required | integer or null |
In whole pence, before any policy fee. |
| data.executions[].premium_annual_pence required | integer or null |
In whole pence. |
| data.executions[].rate_per_mille required | string or number or null |
The rate per £1,000 of cover. |
| data.executions[].decline_reasons required | array of any | |
| data.executions[].referral_reasons required | array of any | |
| data.executions[].executed_at required | string or null (date-time) | |
| data.executions[].trace | array of any |
The steps of the assessment, including the rules and rate tables it used. Only for a server-only application. |
{
"data": {
"application": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "quoted",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes",
"smoker": false
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:19:42+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
},
"quote": {
"id": "0199a3c2-7c00-7a77-9f9e-7c8d9eafb0c7",
"display_id": "quo_0199a3c2-7c00-7a77-9f9e-7c8d9eafb0c7",
"reference": "QT-AB-P001-000042",
"status": "issued",
"basis": "single",
"decision": "accept_standard",
"currency_code": "GBP",
"total_premium_monthly_pence": 1245,
"policy_fee_pence": 0,
"bundle_discount_pence": 0,
"issued_at": "2026-10-12T10:19:42+01:00",
"expires_at": "2026-11-11T10:19:42+00:00",
"accepted_at": null,
"superseded_by_id": null,
"assumptions": [],
"components": [
{
"id": "0199a3c2-7c01-7b88-8a0f-8d9eafb0c1d8",
"display_id": "qco_0199a3c2-7c01-7b88-8a0f-8d9eafb0c1d8",
"cover_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f08",
"cover_type": "life",
"component_index": 0,
"sum_assured_pence": 25000000,
"term_years": 25,
"decision": "accept_standard",
"rating_pct": 100,
"premium_monthly_pence": 1245,
"premium_annual_pence": 14940,
"exclusions": [],
"reasons": [],
"lives": [],
"member_count": null,
"title": "Life"
}
],
"premium_illustration": null
},
"executions": [
{
"id": "0199a3c2-7c02-7c99-9b10-9eafb0c1d2e9",
"display_id": "exe_0199a3c2-7c02-7c99-9b10-9eafb0c1d2e9",
"cover_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f08",
"mode": "live",
"decision": "accept_standard",
"rating_pct": 100,
"exclusions": [],
"premium_monthly_pence": 1245,
"premium_annual_pence": 14940,
"rate_per_mille": "0.498",
"decline_reasons": [],
"referral_reasons": [],
"executed_at": "2026-10-12T10:19:42+01:00",
"trace": []
}
]
}
}
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 - `idempotency_key_conflict`: the `Idempotency-Key` was used before with different inputs. - `idempotency_replay_unproven`: a browser application sent an `Idempotency-Key` that already made an application, without that application's `share_token` in `X-Share-Token`. Nothing changed. Send the `share_token`, or start again with a new key. - `customer_mismatch`: `application_id` names an application that belongs to a different customer.
| 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": "idempotency_key_conflict",
"message": "Idempotency-Key 'quote-0001' has already been used with different inputs"
}
}
422 No quote was made. `fields` names what to change, where it can: - `validation_failed`: a required field is missing. - `customer_invalid`: the customer's details aren't valid (as `customer.email`). - `answers_invalid`: an answer doesn't fit its question (as `risk_factors.smoker`). - `components_invalid`: a component can't be read: it names no cover (neither `cover_type` nor `cover_id`), or its `sum_assured_pence` or `term_years` is missing or isn't a whole number (as `components[0].term_years`). - `cover_not_in_product`: the product has no such cover. - `cover_out_of_envelope`: the amount, age or term is outside the cover's limits (the message says which). - `quote_failed`: pricing failed (the message says why). - `rate_unavailable`: the details are acceptable, but there's no price for them.
| 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": "cover_out_of_envelope",
"message": "Life cover is available from age 18 to 65 at the start of the policy (they would be 70)."
}
}
Get a quote
GET
https://subcontext.com/api/v1/quotes/{id}
Try it
Token scopes:
read or write
A quote, with its application. Once the policy is set up, policy
is there too, with the customer's links to their documents.
The application's share_token is only included for a server-only
application (or a token handed out for this one application).
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The quote's |
| X-Share-Token | header | string | The application's |
Responses
200 The quote and its application.
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.application required | Application |
One customer's request for cover. |
| data.application.id required | string (uuid) | |
| data.application.display_id required | string |
The id as Subcontext shows it, |
| data.application.status required | string |
Where the application is: "started", "in_capture", "quoted", "referred", "bound", "declined", "postponed", "abandoned", "anonymous", "handed_off"
|
| data.application.idempotency_key required | string |
The idempotency key it was started with (one is made up when you don't send one). |
| data.application.product_id required | string (uuid) | |
| data.application.journey_id required | string (uuid) | |
| data.application.customer required | one of: ApplicationCustomer, null |
The person to be covered, or |
| data.application.joint_customer required | one of: ApplicationCustomer, null |
For joint cover, the second person; otherwise |
| data.application.risk_factors required | object |
The answers so far, by question key. |
| data.application.cover_amount_pence required | integer or null |
The amount of cover asked for, in pence. |
| data.application.term_years required | integer or null |
The term asked for, in years. |
| data.application.policy_id required | string or null (uuid) |
The policy, once it's set up. |
| data.application.abandoned_reason required | string or null | |
| data.application.presentation required | one of: Presentation, null |
The presentation it's being filled in on, or |
| data.application.inline_bacs_mandate required | boolean |
|
| data.application.direct_debit_first required | boolean |
|
| data.application.stripe_test_mode required | boolean |
|
| data.application.market_quotes required | one of: MarketQuotes, null |
For a whole-of-market product, the latest market search; otherwise |
| data.application.marketing_answered required | boolean |
Whether the customer has answered your firm's marketing question. |
| data.application.created_at required | string (date-time) | |
| data.application.updated_at required | string (date-time) | |
| data.application.share_token | string |
The key to this application for the quote form and webchat. Only included where you may see it. |
| data.quote required | Quote |
A price for an application. |
| data.quote.id required | string (uuid) | |
| data.quote.display_id required | string |
The id as Subcontext shows it, |
| data.quote.reference required | string |
The quote's reference, for the customer and your team. |
| data.quote.status required | string |
"issued", "superseded", "expired", "accepted", "rejected"
|
| data.quote.basis required | string |
One person ( "single", "joint"
|
| data.quote.decision required | string |
Offered at standard rates, with a rating on some cover ( "accept_standard", "accept_rated", "mixed"
|
| data.quote.currency_code required | string | |
| data.quote.total_premium_monthly_pence required | integer |
The monthly premium, in whole pence, with any policy fee and bundle discount. |
| data.quote.policy_fee_pence required | integer |
The policy fee included each month, in pence. |
| data.quote.bundle_discount_pence required | integer |
The discount for taking several covers together, in pence a month. |
| data.quote.issued_at required | string (date-time) | |
| data.quote.expires_at required | string (date-time) |
After this, the quote can't be bought. |
| data.quote.accepted_at required | string or null (date-time) | |
| data.quote.superseded_by_id required | string or null (uuid) |
The quote that replaced it. |
| data.quote.assumptions required | array of object |
For a price part way through, the answers it assumed because they weren't given yet, such as |
| data.quote.components required | array of QuoteComponent |
The price of each cover. |
| data.quote.components[].id required | string (uuid) | |
| data.quote.components[].display_id required | string |
The id as Subcontext shows it, |
| data.quote.components[].cover_id required | string (uuid) | |
| data.quote.components[].cover_type required | string |
One of: "life", "critical_illness", "income_protection", "accidental_death", "child_benefit", "healthcare"
|
| data.quote.components[].component_index required | integer | |
| data.quote.components[].sum_assured_pence required | integer |
The amount of cover, in whole pence ( |
| data.quote.components[].term_years required | integer or null | |
| data.quote.components[].decision required | string |
One of: "accept_standard", "accept_rated"
|
| data.quote.components[].rating_pct required | integer |
The price as a percentage of the standard rate ( |
| data.quote.components[].premium_monthly_pence required | integer |
The monthly premium, in whole pence. |
| data.quote.components[].premium_annual_pence required | integer |
The yearly premium, in whole pence. |
| data.quote.components[].exclusions required | array of any |
What this cover won't pay out for, for this customer. |
| data.quote.components[].reasons required | array of any |
Why it was rated, if it was. |
| data.quote.components[].lives required | array of object |
On joint cover, the outcome for each person. |
| data.quote.components[].member_count required | integer or null |
For a |
| data.quote.components[].title required | string |
The cover's name, as customers see it, such as |
| data.quote.premium_illustration required | one of: PremiumIllustration, null |
For cover with no fixed end, what the premiums would add up to by a natural end date, worded for the customer; otherwise |
| data.policy | Policy |
A policy, with the customer's links to it. |
| data.policy.id required | string (uuid) | |
| data.policy.display_id required | string |
The id as Subcontext shows it, |
| data.policy.policy_number required | string | |
| data.policy.basis required | string |
One of: "single", "joint"
|
| data.policy.joint_customer_name required | string or null |
For joint cover, the second person's name. |
| data.policy.currency_code required | string | |
| data.policy.commencement_date required | string (date) |
When cover starts. |
| data.policy.total_premium_monthly_pence required | integer |
The monthly premium, in whole pence. |
| data.policy.overall_decision required | string |
One of: "accept_standard", "accept_rated", "mixed"
|
| data.policy.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.policy.bound_at required | string (date-time) | |
| data.policy.cancelled_at required | string or null (date-time) | |
| data.policy.components required | array of object |
The covers. |
| data.policy.components[].id required | string (uuid) | |
| data.policy.components[].display_id required | string | |
| data.policy.components[].cover_id required | string (uuid) | |
| data.policy.components[].cover_type required | string | |
| data.policy.components[].component_index required | integer | |
| data.policy.components[].sum_assured_pence required | integer |
The amount of cover, in whole pence. |
| data.policy.components[].term_years required | integer or null | |
| data.policy.components[].premium_monthly_pence required | integer |
The monthly premium, in whole pence. |
| data.policy.components[].decision required | string | |
| data.policy.components[].rating_pct required | integer | |
| data.policy.components[].exclusions required | array of any | |
| data.policy.components[].lives required | array of object | |
| data.policy.components[].commencement_date required | string or null (date) | |
| data.policy.components[].cancelled_at required | string or null (date-time) | |
| data.policy.cessions required | array of object |
Reinsurance arrangements for the cover, if your firm shares its risk. Amounts are whole pence ( |
| data.policy.first_premium_record required | one of: object, null |
The first premium's payment, or |
| data.policy.policy_hub_path required | string or null |
Where the customer manages their policy. |
| data.policy.schedule_path required | string or null |
The policy schedule, or |
| data.policy.documents required | array of object |
The policy's documents, for the customer. |
| data.policy.documents[].id required | string | |
| data.policy.documents[].kind required | string | |
| data.policy.documents[].name required | string | |
| data.policy.documents[].path required | string |
Its address. |
| data.policy.first_collection_on required | string or null (date) |
When the first Direct Debit collection is due. |
| data.policy.collection_day_of_month required | integer or null |
The day of the month premiums are collected. |
| data.policy.wrap_up required | one of: object, null |
Your firm's closing words after purchase, when it has them; otherwise |
{
"data": {
"application": {
"id": "0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"display_id": "app_0199a3c2-7a00-7e55-9d7c-5a6b7c8d9ea5",
"status": "quoted",
"idempotency_key": "quote-0001",
"product_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"journey_id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"customer": {
"id": "0199a3c2-7b00-7f66-8e8d-6b7c8d9eafb6",
"title": "Ms",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+447700900123",
"dob": "1988-04-12",
"postal_code": "SW1A 2AA",
"address_line1": "10 Example Street",
"address_line2": null,
"city": "London",
"country": "United Kingdom"
},
"joint_customer": null,
"risk_factors": {
"uk_resident": "yes",
"smoker": false
},
"cover_amount_pence": 25000000,
"term_years": 25,
"policy_id": null,
"abandoned_reason": null,
"presentation": {
"id": "0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"display_id": "prs_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94",
"slug": "standard",
"name": "Standard",
"template_key": "standard",
"is_default": true,
"stages": [
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_1",
"key": "details",
"position": 1,
"stage_type": "personal_details",
"title": "Your details",
"cta_label": null,
"config": {
"fields": [
"first_name",
"last_name",
"email",
"phone",
"dob",
"address"
]
},
"consent_notice": "We'll use these details to give you your quote and set up your policy."
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_2",
"key": "cover",
"position": 2,
"stage_type": "cover_capture",
"title": "Cover",
"cta_label": null,
"config": {}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_3",
"key": "health_questions",
"position": 3,
"stage_type": "questions",
"title": "Health questions",
"cta_label": null,
"config": {
"all_remaining": true
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_4",
"key": "quote",
"position": 4,
"stage_type": "quote",
"title": "Your quote",
"cta_label": "Buy your policy",
"config": {
"label": "final"
}
},
{
"id": "stg_0199a3c2-6c00-7d44-8c6b-4f5a6b7c8d94_5",
"key": "payment",
"position": 5,
"stage_type": "payment",
"title": "Payment",
"cta_label": null,
"config": {}
}
]
},
"inline_bacs_mandate": false,
"direct_debit_first": false,
"stripe_test_mode": false,
"market_quotes": null,
"marketing_answered": false,
"created_at": "2026-10-12T10:15:00+01:00",
"updated_at": "2026-10-12T10:19:42+01:00",
"share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
},
"quote": {
"id": "0199a3c2-7c00-7a77-9f9e-7c8d9eafb0c7",
"display_id": "quo_0199a3c2-7c00-7a77-9f9e-7c8d9eafb0c7",
"reference": "QT-AB-P001-000042",
"status": "issued",
"basis": "single",
"decision": "accept_standard",
"currency_code": "GBP",
"total_premium_monthly_pence": 1245,
"policy_fee_pence": 0,
"bundle_discount_pence": 0,
"issued_at": "2026-10-12T10:19:42+01:00",
"expires_at": "2026-11-11T10:19:42+00:00",
"accepted_at": null,
"superseded_by_id": null,
"assumptions": [],
"components": [
{
"id": "0199a3c2-7c01-7b88-8a0f-8d9eafb0c1d8",
"display_id": "qco_0199a3c2-7c01-7b88-8a0f-8d9eafb0c1d8",
"cover_id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f08",
"cover_type": "life",
"component_index": 0,
"sum_assured_pence": 25000000,
"term_years": 25,
"decision": "accept_standard",
"rating_pct": 100,
"premium_monthly_pence": 1245,
"premium_annual_pence": 14940,
"exclusions": [],
"reasons": [],
"lives": [],
"member_count": null,
"title": "Life"
}
],
"premium_illustration": 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."
}
}
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."
}
}