Journeys

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.

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

NameInTypeDescription
product_slug required path string

The product's slug, shown on its page in the dashboard under Products.

Responses

200 The product's journeys.

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

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 ("1.00" is none).

data[].position required integer

Its place among the product's journeys, from 0.

data[].lifecycle_state required string One of: "draft", "sandbox", "production", "archived"
Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

What went wrong and what to do about it, in plain English.

error.fields object

When the error is about particular inputs, which ones and why, by name: a nested one with a dot (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "error": {
    "code": "invalid_token",
    "message": "The access token has expired. Ask for a new one."
  }
}

403 The token can't be used here. It's missing a scope, it was used from the wrong place (a server-only application's token in a browser, or a browser application's token from a server, another website or a request only server-only applications may make), or it wasn't asked for with `resource=https://subcontext.com/api/v1`.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

What went wrong and what to do about it, in plain English.

error.fields object

When the error is about particular inputs, which ones and why, by name: a nested one with a dot (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "error": {
    "code": "forbidden",
    "message": "origin 'https://www.example.com' is not allowlisted for this OAuth application"
  }
}

404 There's no such record in your organisation.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

What went wrong and what to do about it, in plain English.

error.fields object

When the error is about particular inputs, which ones and why, by name: a nested one with a dot (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found"
  }
}

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

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

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

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 ("1.00" is none).

data.position required integer

Its place among the product's journeys, from 0.

data.lifecycle_state required string One of: "draft", "sandbox", "production", "archived"
Example
{
  "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.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

What went wrong and what to do about it, in plain English.

error.fields object

When the error is about particular inputs, which ones and why, by name: a nested one with a dot (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "error": {
    "code": "invalid_token",
    "message": "The access token has expired. Ask for a new one."
  }
}

403 The token can't be used here. It's missing a scope, it was used from the wrong place (a server-only application's token in a browser, or a browser application's token from a server, another website or a request only server-only applications may make), or it wasn't asked for with `resource=https://subcontext.com/api/v1`.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

What went wrong and what to do about it, in plain English.

error.fields object

When the error is about particular inputs, which ones and why, by name: a nested one with a dot (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "error": {
    "code": "forbidden",
    "message": "origin 'https://www.example.com' is not allowlisted for this OAuth application"
  }
}

404 There's no such record in your organisation.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

What went wrong and what to do about it, in plain English.

error.fields object

When the error is about particular inputs, which ones and why, by name: a nested one with a dot (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found"
  }
}

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

NameInTypeDescription
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 share_token in X-Share-Token.

X-Share-Token header string

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

Responses

200 The product, journey, questions and presentation.

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

data.product.slug required string

The product's slug, used in addresses such as /api/v1/products/{slug}.

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

data.product.lifecycle_state required string

Where the product is in its life. Only production products are sold to the public.

One of: "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 null when the product has no covers (or, for terms, no cover with a fixed term).

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

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 null for cover with no fixed term.

data.product.covers[].term_years_max required integer or null

The longest term in years, or null for cover with no fixed term.

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) or reviewed from time to time (reviewable).

One of: "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) or as an early part of it (accelerated).

One of: "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 (Diagnosis); otherwise null.

data.product.covers[].pricing_basis required string

How the cover is priced: by underwriting and rate tables (rated), from a fixed list of cover levels, each with its own price (fixed_tiers), or at a monthly price for each person on it (per_member, an employer's group plan). A per-member cover has no amount of cover or term: its quote is sized by headcounts.

One of: "rated", "fixed_tiers", "per_member"
data.product.covers[].member_premium_pence required integer or null

For a per_member cover, the monthly price for each person on it, in whole pence; otherwise null.

data.product.covers[].members_factor required string or null

For a per_member cover, the key of the answer that holds how many people it covers, sent in risk_factors; otherwise null.

data.product.covers[].opt_in_factor required string or null

For a per_member add-on, the key of the yes or no answer that adds it to the plan (it then covers the number of people members_factor names); null for a cover that's always included, and for any other cover.

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

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 ("1.00" is none).

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

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 (YYYY-MM-DD), free text (string), a number or whole number (integer), yes or no (boolean), one option (enum) or several (multi_select). A derived question is worked out from others (such as BMI from height and weight). Don't ask it.

One of: "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 min and max.

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 occupations), which list.

data.questions[].suggestions required array of object

For a question with a lookup_source, the answers to suggest as the customer types. Match on search; show and send label.

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 enum and multi_select questions, the options, in order. Empty for other kinds.

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 { "key": "smoker", "equals": true }.

data.presentation required one of: Presentation, null

How the questions are arranged into pages, or null when the journey has no presentation.

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

What went wrong and what to do about it, in plain English.

error.fields object

When the error is about particular inputs, which ones and why, by name: a nested one with a dot (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "error": {
    "code": "invalid_token",
    "message": "The access token has expired. Ask for a new one."
  }
}

403 The token can't be used here. It's missing a scope, it was used from the wrong place (a server-only application's token in a browser, or a browser application's token from a server, another website or a request only server-only applications may make), or it wasn't asked for with `resource=https://subcontext.com/api/v1`.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

What went wrong and what to do about it, in plain English.

error.fields object

When the error is about particular inputs, which ones and why, by name: a nested one with a dot (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "error": {
    "code": "forbidden",
    "message": "origin 'https://www.example.com' is not allowlisted for this OAuth application"
  }
}

404 There's no such record in your organisation.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

What went wrong and what to do about it, in plain English.

error.fields object

When the error is about particular inputs, which ones and why, by name: a nested one with a dot (customer.email), one in a list with its place (components[0].term_years). Left out when the error isn't about particular inputs.

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found"
  }
}

Stuck, or something missing?

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

Contact us