A product's journeys: the sets of questions a customer answers to be quoted for it, such as a short "express" journey and a full one.
The questions endpoint gives you everything you need to draw your own form: the product with its cover limits, the journey, its questions in order with their answer options, and the presentation (the pages, or stages, the quote form shows them on). Browser and server-only applications can both call these.
List a product's journeys
GET
https://subcontext.com/api/v1/products/{product_slug}/journeys
Try it
Token scopes:
read or write
Every journey on one of your products, in the order they're arranged
in the dashboard, whatever their state. Use a journey's slug to
get its questions, and to start an application.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| product_slug required | path | string | The product's slug, shown on its page in the dashboard under Products. |
Responses
200 The product's journeys.
| Field | Type | Description |
|---|---|---|
| data required | array of Journey |
The journeys, in order. |
| data[].id required | string (uuid) | |
| data[].display_id required | string |
The id as Subcontext shows it, |
| data[].slug required | string |
The journey's slug, used in addresses and to start an application. |
| data[].name required | string | |
| data[].description required | string or null | |
| data[].journey_loading required | string |
A multiplier this journey applies to the price, as a decimal string ( |
| data[].position required | integer |
Its place among the product's journeys, from 0. |
| data[].lifecycle_state required | string |
One of: "draft", "sandbox", "production", "archived"
|
{
"data": [
{
"id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"display_id": "jrn_0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"slug": "express",
"name": "Quick quote",
"description": "A few questions and a price in two minutes.",
"journey_loading": "1.00",
"position": 0,
"lifecycle_state": "production"
}
]
}
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"
}
}
Get a journey
GET
https://subcontext.com/api/v1/products/{product_slug}/journeys/{slug}
Try it
Token scopes:
read or write
One journey on one of your products, found by its slug.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| product_slug required | path | string | The product's slug, shown on its page in the dashboard under Products. |
| slug required | path | string | The journey's slug, from List a product's journeys. |
Responses
200 The journey.
| Field | Type | Description |
|---|---|---|
| data required | Journey |
One of a product's journeys. |
| data.id required | string (uuid) | |
| data.display_id required | string |
The id as Subcontext shows it, |
| data.slug required | string |
The journey's slug, used in addresses and to start an application. |
| data.name required | string | |
| data.description required | string or null | |
| data.journey_loading required | string |
A multiplier this journey applies to the price, as a decimal string ( |
| data.position required | integer |
Its place among the product's journeys, from 0. |
| data.lifecycle_state required | string |
One of: "draft", "sandbox", "production", "archived"
|
{
"data": {
"id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"display_id": "jrn_0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"slug": "express",
"name": "Quick quote",
"description": "A few questions and a price in two minutes.",
"journey_loading": "1.00",
"position": 0,
"lifecycle_state": "production"
}
}
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"
}
}
Get a journey's questions
GET
https://subcontext.com/api/v1/products/{product_slug}/journeys/{slug}/questions
Try it
Token scopes:
read or write
Everything a form needs to ask for a quote: the product (with its cover limits, so you can check amounts, ages and terms before you ask for a price), the journey, its questions in order, and the presentation that arranges them into pages.
The questions are the ones from the product's current release.
Pass application_id when you're picking up an application that's
already started. You get the questions it was started with, even
if the product has been published again since.
A question with ask_if is only asked when the answers so far meet
its condition. An answer option with a terminal_outcome ends the
application there (for example "Do you live in the UK?" answered
"No" declines), so a form can say so straight away.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| product_slug required | path | string | The product's slug, shown on its page in the dashboard under Products. |
| slug required | path | string | The journey's slug, from List a product's journeys. |
| presentation_slug | query | string | Which of the journey's presentations to use, by its address slug. Leave it out for the journey's default presentation (an unknown slug also gives the default). |
| application_id | query | string (uuid) | An application already under way on this journey. Its questions
and presentation are the ones it started with. An id that isn't
one of yours, or is on another journey, is ignored. So is one a
browser application sends without the application's
|
| X-Share-Token | header | string | The application's |
Responses
200 The product, journey, questions and presentation.
| Field | Type | Description |
|---|---|---|
| data required | object | |
| data.product required | Product |
A product, with the terms it's sold on now. |
| data.product.id required | string (uuid) |
The product's id. |
| data.product.display_id required | string |
The id as Subcontext shows it, |
| data.product.slug required | string |
The product's slug, used in addresses such as |
| data.product.name required | string |
The product's name, as customers see it. |
| data.product.product_type required | string |
What kind of product it is. One of:"term", "decreasing_term", "whole_of_life", "critical_illness", "income_protection", "bundled", "child_cover", "healthcare"
|
| data.product.currency_code required | string |
The currency it's priced in (ISO 4217), such as |
| data.product.lifecycle_state required | string |
Where the product is in its life. Only "draft", "sandbox", "production", "archived"
|
| data.product.cooldown_days required | integer |
The cooling-off period, in days, during which a customer can cancel for a full refund. |
| data.product.inclusions required | array of string |
What the product includes, as short sentences for customers. |
| data.product.exclusions required | array of string |
What the product doesn't cover, as short sentences for customers. |
| data.product.cover_bounds required | CoverBounds |
The limits for a quote across all the product's covers: the lowest
and highest amount, age at the start and term. Each is |
| data.product.cover_bounds.sum_assured_min_pence required | integer or null |
The smallest amount of cover, in whole pence (the smallest unit of the product's currency). |
| data.product.cover_bounds.sum_assured_max_pence required | integer or null |
The largest amount of cover, in whole pence. |
| data.product.cover_bounds.age_min required | integer or null |
The youngest age, in years, at which cover can start. |
| data.product.cover_bounds.age_max required | integer or null |
The oldest age at which cover can start. |
| data.product.cover_bounds.term_min required | integer or null |
The shortest term, in years. |
| data.product.cover_bounds.term_max required | integer or null |
The longest term, in years. |
| data.product.covers required | array of Cover |
The covers that make up the product, such as life cover with critical illness cover added. |
| data.product.covers[].id required | string (uuid) |
The cover's id. |
| data.product.covers[].display_id required | string |
The id as Subcontext shows it, |
| data.product.covers[].cover_type required | string |
What the cover pays out for. One of:"life", "critical_illness", "income_protection", "accidental_death", "child_benefit", "healthcare"
|
| data.product.covers[].cover_index required | integer |
The cover's position in the product, starting at 0. |
| data.product.covers[].sum_assured_min_pence required | integer |
The smallest amount of cover, in whole pence (the smallest unit of the product's currency). For a monthly benefit, the yearly amount. |
| data.product.covers[].sum_assured_max_pence required | integer |
The largest amount of cover, in whole pence. |
| data.product.covers[].age_at_entry_min required | integer |
The youngest age, in years, at which this cover can start. |
| data.product.covers[].age_at_entry_max required | integer |
The oldest age at which this cover can start. |
| data.product.covers[].term_years_min required | integer or null |
The shortest term in years, or |
| data.product.covers[].term_years_max required | integer or null |
The longest term in years, or |
| data.product.covers[].max_age_at_expiry required | integer or null |
The oldest the person can be when the cover ends, if there's a limit. |
| data.product.covers[].premium_basis required | string |
Whether the price is fixed for the whole term ( "guaranteed", "reviewable"
|
| data.product.covers[].review_period_years required | integer or null |
For reviewable premiums, how often in years the price is reviewed. |
| data.product.covers[].minimum_premium_pence required | integer |
The lowest monthly premium, in pence (or the smallest unit of the product's currency). |
| data.product.covers[].ci_payout_basis required | string |
For critical illness cover, whether it pays out on top of life cover ( "additional", "accelerated"
|
| data.product.covers[].survival_period_days required | integer |
How many days the person must survive after a diagnosis for a critical illness claim. |
| data.product.covers[].indexation_method required | string |
How the amount of cover changes over the term. One of:"none", "rpi", "fixed_percent", "decreasing"
|
| data.product.covers[].benefit_basis required | string |
Whether the cover pays a lump sum or a monthly benefit. One of:"lump_sum", "monthly_benefit"
|
| data.product.covers[].title required | string or null |
The cover's name where its type alone doesn't say which it is, such as one level of a group plan ( |
| data.product.covers[].pricing_basis required | string |
How the cover is priced: by underwriting and rate tables ( "rated", "fixed_tiers", "per_member"
|
| data.product.covers[].member_premium_pence required | integer or null |
For a |
| data.product.covers[].members_factor required | string or null |
For a |
| data.product.covers[].opt_in_factor required | string or null |
For a |
| data.journey required | Journey |
One of a product's journeys. |
| data.journey.id required | string (uuid) | |
| data.journey.display_id required | string |
The id as Subcontext shows it, |
| data.journey.slug required | string |
The journey's slug, used in addresses and to start an application. |
| data.journey.name required | string | |
| data.journey.description required | string or null | |
| data.journey.journey_loading required | string |
A multiplier this journey applies to the price, as a decimal string ( |
| data.journey.position required | integer |
Its place among the product's journeys, from 0. |
| data.journey.lifecycle_state required | string |
One of: "draft", "sandbox", "production", "archived"
|
| data.questions required | array of Question |
The journey's questions, in the order they're asked. |
| data.questions[].id required | string (uuid) | |
| data.questions[].display_id required | string |
The id as Subcontext shows it, |
| data.questions[].key required | string |
The question's key, which an answer is saved under. |
| data.questions[].label required | string |
The question, as the customer sees it. |
| data.questions[].help_text required | string or null |
Help shown with the question. |
| data.questions[].question_type required | string |
What kind of answer it takes: a "date", "string", "number", "integer", "boolean", "enum", "multi_select", "derived"
|
| data.questions[].position required | integer |
Its place in the journey. |
| data.questions[].required required | boolean | |
| data.questions[].validation required | object |
Limits on the answer, such as |
| data.questions[].group_key required | string or null |
Questions with the same group belong together on a page. |
| data.questions[].group_label required | string or null |
The group's heading. |
| data.questions[].lookup_source required | string or null |
For free-text questions answered from a list (such as |
| data.questions[].suggestions required | array of object |
For a question with a |
| data.questions[].suggestions[].label required | string | |
| data.questions[].suggestions[].search required | string |
The label and other words for it, to match against. |
| data.questions[].answer_options required | array of AnswerOption |
For |
| data.questions[].answer_options[].id required | string (uuid) | |
| data.questions[].answer_options[].value required | string |
What to send as the answer. |
| data.questions[].answer_options[].label required | string |
What the customer sees. |
| data.questions[].answer_options[].position required | integer | |
| data.questions[].answer_options[].terminal_outcome | string |
Present when choosing this ends the application, and how. One of:"decline", "refer", "postpone"
|
| data.questions[].answer_options[].terminal_reason_code | string or null |
A short code for why. |
| data.questions[].answer_options[].terminal_reason_message | string or null |
What to tell the customer. |
| data.questions[].ask_if | object |
Present when the question is only asked sometimes. A condition on earlier answers, such as |
| data.presentation required | one of: Presentation, null |
How the questions are arranged into pages, or |
{
"data": {
"product": {
"id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"display_id": "prod_0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f07",
"slug": "level-term-life",
"name": "Level Term Life",
"product_type": "term",
"currency_code": "GBP",
"lifecycle_state": "production",
"cooldown_days": 30,
"inclusions": [
"Terminal illness benefit, paid early if you're diagnosed with 12 months or less to live"
],
"exclusions": [
"Death caused by a deliberately self-inflicted injury in the first year"
],
"cover_bounds": {
"sum_assured_min_pence": 1000000,
"sum_assured_max_pence": 50000000,
"age_min": 18,
"age_max": 65,
"term_min": 5,
"term_max": 40
},
"covers": [
{
"id": "0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f08",
"display_id": "cov_0199a3c2-5d1e-7b4a-9c3f-2e8d4b6a1f08",
"cover_type": "life",
"cover_index": 0,
"sum_assured_min_pence": 1000000,
"sum_assured_max_pence": 50000000,
"age_at_entry_min": 18,
"age_at_entry_max": 65,
"term_years_min": 5,
"term_years_max": 40,
"max_age_at_expiry": 80,
"premium_basis": "guaranteed",
"review_period_years": null,
"minimum_premium_pence": 500,
"ci_payout_basis": "additional",
"survival_period_days": 14,
"indexation_method": "none",
"benefit_basis": "lump_sum",
"title": null,
"pricing_basis": "rated",
"member_premium_pence": null,
"members_factor": null,
"opt_in_factor": null
}
]
},
"journey": {
"id": "0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"display_id": "jrn_0199a3c2-6a10-7c2e-8d41-5b7f0e9c3a21",
"slug": "express",
"name": "Quick quote",
"description": "A few questions and a price in two minutes.",
"journey_loading": "1.00",
"position": 0,
"lifecycle_state": "production"
},
"questions": [
{
"id": "0199a3c2-6b20-7a11-9f3e-1c2d3e4f5a61",
"display_id": "qst_0199a3c2-6b20-7a11-9f3e-1c2d3e4f5a61",
"key": "uk_resident",
"label": "Do you live in the UK?",
"help_text": null,
"question_type": "enum",
"position": 0,
"required": true,
"validation": {},
"group_key": null,
"group_label": null,
"lookup_source": null,
"suggestions": null,
"answer_options": [
{
"id": "0199a3c2-6b21-7c40-8e5a-4b3c2d1e0f72",
"value": "yes",
"label": "Yes",
"position": 0
},
{
"id": "0199a3c2-6b22-7d51-9f6b-5c4d3e2f1083",
"value": "no",
"label": "No",
"position": 1,
"terminal_outcome": "decline",
"terminal_reason_code": "not_uk_resident",
"terminal_reason_message": "We can only cover people who live in the UK."
}
]
},
{
"id": "0199a3c2-6b30-7b22-8a4f-2d3e4f5a6b72",
"display_id": "qst_0199a3c2-6b30-7b22-8a4f-2d3e4f5a6b72",
"key": "smoker",
"label": "Have you smoked or used nicotine in the last 12 months?",
"help_text": "Including vapes and nicotine patches.",
"question_type": "boolean",
"position": 1,
"required": true,
"validation": {},
"group_key": "lifestyle",
"group_label": "Your lifestyle",
"lookup_source": null,
"suggestions": null,
"answer_options": []
},
{
"id": "0199a3c2-6b40-7c33-9b5a-3e4f5a6b7c83",
"display_id": "qst_0199a3c2-6b40-7c33-9b5a-3e4f5a6b7c83",
"key": "cigarettes_per_day",
"label": "How many cigarettes a day?",
"help_text": null,
"question_type": "integer",
"position": 2,
"required": true,
"validation": {
"min": 1,
"max": 100
},
"group_key": "lifestyle",
"group_label": "Your lifestyle",
"lookup_source": null,
"suggestions": null,
"answer_options": [],
"ask_if": {
"key": "smoker",
"equals": true
}
}
],
"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": {}
}
]
}
}
}
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"
}
}