Quotes and applications

An application is one customer's request for cover: their answers, their details and the cover they want. A quote prices it.

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

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

Request body application/json

FieldTypeDescription
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, YYYY-MM-DD.

customer.postal_code string
customer.address_line1 string
customer.address_line2 string
customer.city string
customer.country string
consent_answers object

When you send customer, the opt-out boxes from the details stage's consent_opt_outs the person ticked, by key, as false (they said no to it).

provenance ProvenanceInput

Where the customer was when they filled it in, for your reports on what brings people in: page_url (with its utm_ tags), referrer, page_title, locale, timezone, utm and click_ids (such as gclid). Everything is checked and tidied before it's kept, and personal details in addresses are removed.

idempotency_key string

The same as the Idempotency-Key header, for clients that can't set headers.

replaces_idempotency_key ReplacesIdempotencyKey

On a request sent again under a new Idempotency-Key because the first answer never arrived, the key it was first sent with. The application that key made is closed so nobody follows it up, if it was made in the last hour, hasn't changed since, and is for the same product, journey and customer. Otherwise it's left as it was.

outreach_landing string

The token an outreach stream's link added to the page (its ol parameter), when the quote was started from one. The quote counts as that person engaging, which can stop a sequence's later messages. A token that isn't valid for your organisation is ignored.

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

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

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"
  }
}

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.

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": "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.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "customer_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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Responses

200 The application.

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Request body application/json

FieldTypeDescription
answers required object

Answers by question key. Numbers as numbers, choices as the option's value, several choices as an array.

marketing_metadata object

Extra details to keep with the application that don't affect the price (for example the mortgage type).

Example request
{
  "answers": {
    "smoker": false,
    "sum_assured": 25000000,
    "term_years": 25
  }
}

Responses

200 The answers were saved.

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

data.application.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.application.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.application.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.application.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.application.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.application.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

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.

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": "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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Request body application/json

FieldTypeDescription
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, YYYY-MM-DD.

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

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

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

422 - `customer_invalid`: the details aren't valid (`fields` names each, as `customer.first_name`). - `validation_failed`: `customer` is missing.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Request body application/json

FieldTypeDescription
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, YYYY-MM-DD.

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

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

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.

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_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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Request body application/json

FieldTypeDescription
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, YYYY-MM-DD.

customer.postal_code string
customer.address_line1 string
customer.address_line2 string
customer.city string
customer.country string
Example request
{
  "customer": {
    "first_name": "Ada",
    "postal_code": "SW1A 2AA"
  }
}

Responses

200 The details were saved.

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

422 `validation_failed`: a required field is missing (`fields` names it).

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": "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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Request body application/json

FieldTypeDescription
choice_key required string

The key of the option the customer chose.

stage_key string

The stage's key.

stage_position integer

The stage's position, if you don't have its key.

Example request
{
  "stage_key": "add_critical_illness",
  "choice_key": "yes"
}

Responses

200 The choice was saved.

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

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

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": "No stage on this application's presentation has that stage_key or stage_position."
  }
}

422 `validation_failed`: `choice_key` is missing or blank.

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": "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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Request body application/json

FieldTypeDescription
text string

What the customer wrote.

Example request
{
  "text": "Making sure the mortgage is paid off if something happens to me."
}

Responses

200 The answer was saved.

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Responses

200 The application is with your team.

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Responses

200 The application and its new quote.

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

data.application.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.application.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.application.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.application.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.application.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.application.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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 null when no price can be offered.

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

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.

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": "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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Request body application/json

FieldTypeDescription
quote_id string (uuid)

The quote to accept. Leave it out for the application's current quote.

Example request
{
  "quote_id": "0199a3c2-7c00-7a77-9f9e-7c8d9eafb0c7"
}

Responses

200 The quote is accepted.

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

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

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": "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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Responses

200 The search has started (or was already under way). The application's `market_quotes` shows its progress.

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

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`).

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": "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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Request body application/json

FieldTypeDescription
result_id required string (uuid)

The chosen result's result_id, from the application's market_quotes.results.

Example request
{
  "result_id": "0199a3c2-7d01-7daa-8c2b-afb0c1d2e3fa"
}

Responses

200 The choice is recorded. `market_quotes.selected_result_id` names it.

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

data.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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.

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

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

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": "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.

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": "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.

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": "select_failed",
    "message": "This quote has expired: run a fresh market search"
  }
}

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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Request body application/json

FieldTypeDescription
answers object

Each answered option's key, and whether they agreed.

Example request
{
  "answers": {
    "email": true,
    "post": true,
    "sms": false
  }
}

Responses

200 The choices are recorded.

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

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.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "customer_missing",
    "message": "the application has no customer 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

NameInTypeDescription
id required path string (uuid)

The application's id.

X-Share-Token header string

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

Request body application/json

FieldTypeDescription
application_share_token required string

The application's share_token.

Example request
{
  "application_share_token": "ytPVoqdUS9HP5G-dUgYpSSu4IjkbQdS0"
}

Responses

200 The ticket.

FieldTypeDescription
data required object
data.ticket required string

The ticket. Treat it like a password.

data.expires_in required integer

How many seconds it lasts.

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

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

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": "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.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

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

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

Request body application/json

FieldTypeDescription
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, YYYY-MM-DD.

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 life or critical_illness. Send this or cover_id.

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 Idempotency-Key header.

replaces_idempotency_key ReplacesIdempotencyKey

On a request sent again under a new Idempotency-Key because the first answer never arrived, the key it was first sent with. The application that key made is closed so nobody follows it up, if it was made in the last hour, hasn't changed since, and is for the same product, journey and customer. Otherwise it's left as it was.

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

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

data.application.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.application.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.application.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.application.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.application.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.application.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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 null when no price can be offered.

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

accept_standard, accept_rated, refer, decline or postpone.

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.

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

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

data.application.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.application.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.application.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.application.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.application.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.application.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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 null when no price can be offered.

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

accept_standard, accept_rated, refer, decline or postpone.

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.

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

409 - `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.

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": "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.

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": "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

NameInTypeDescription
id required path string (uuid)

The quote's id.

X-Share-Token header string

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

Responses

200 The quote and its application.

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

data.application.status required string

Where the application is: anonymous (no customer yet), in_capture (being filled in), quoted, referred (with your team), declined, postponed, bound (the policy is set up), handed_off (sent to another provider), abandoned.

One of: "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 null before they're known.

data.application.joint_customer required one of: ApplicationCustomer, null

For joint cover, the second person; otherwise null.

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

data.application.inline_bacs_mandate required boolean

true when the Direct Debit is set up on Stripe's form (Start a Direct Debit mandate) rather than by sending bank details.

data.application.direct_debit_first required boolean

true when the first premium is taken by Direct Debit, so there's no card payment.

data.application.stripe_test_mode required boolean

true when this product's payments use Stripe's test account (use Stripe's test card and bank details).

data.application.market_quotes required one of: MarketQuotes, null

For a whole-of-market product, the latest market search; otherwise null.

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

data.quote.reference required string

The quote's reference, for the customer and your team.

data.quote.status required string

issued (current, can be bought), accepted, superseded (replaced by a newer quote), expired or rejected.

One of: "issued", "superseded", "expired", "accepted", "rejected"
data.quote.basis required string

One person (single) or two (joint).

One of: "single", "joint"
data.quote.decision required string

Offered at standard rates, with a rating on some cover (accept_rated), or both across its covers (mixed).

One of: "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 { "key": "smoker", "assumed_value": false }. Empty for a full quote.

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

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 (0 for a per_member cover, which is sized by member_count).

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 (100 is standard).

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 per_member cover, how many people it was priced for (its price is that many times the cover's member_premium_pence); otherwise null.

data.quote.components[].title required string

The cover's name, as customers see it, such as Diagnosis on a group plan (12 × Diagnosis) or Life for life cover.

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

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

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

data.policy.first_premium_record required one of: object, null

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

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

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

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

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

404 There's no such application or quote in your organisation, or a browser application didn't send the application's `share_token` in `X-Share-Token`. Both get the same answer, so it never says whether an id exists.

FieldTypeDescription
error required object
error.code required string

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

error.message required string

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

error.fields object

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

Example
{
  "error": {
    "code": "not_found",
    "message": "Resource not found. A browser application must send the application's share_token in the X-Share-Token header."
  }
}

Stuck, or something missing?

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

Contact us