Errors, limits and security

What the API offers, a security checklist, every error a token request or API call can give and how to fix it, and the limits.

What the API offers#

The parts of the API you're most likely to use directly:

Request What it does Scopes
GET /api/v1/products Your organisation's products read
GET /api/v1/products/{slug} One product read
GET /api/v1/products/{product_slug}/journeys A product's journeys (its sets of questions) read
POST /api/v1/crm/ingests Send a lead write and crm
GET /api/v1/crm/ingests/{id} What became of a lead you sent read and crm
GET /api/v1/crm/leads The leads this application sent, and where they are now read and crm
GET /api/v1/crm/events What has happened to those leads read and crm
POST /api/v1/crm/outcomes Report what became of a lead (quoted, sold, cancelled) write, and outcomes or crm

The quote form calls the quotes and applications parts of the API itself. To build your own form, see Quote and sell through the API. All addresses start https://subcontext.com. For anything not covered here, email hello@subcontext.com.

Security checklist#

  • Keep the client secret on your server. Never put it in a web page, a mobile app, a public code repository or a support ticket. Keep it in an environment variable or your secret store.
  • Give the browser only a token, from your own token endpoint.
  • Use two applications, a server-only one for server work and a browser one for pages. Never use one for both.
  • Browser applications get read and write only. Subcontext won't let you tick crm or outcomes on an application with allowed origins.
  • Treat a browser token as public. Anyone who opens your page can get one from your token endpoint, and the website check stops other websites' pages but not someone running their own script. A browser application must only be able to do what any visitor may do anyway. That's why it never gets crm, and why the leads, outcomes, loyalty membership and outreach endpoints only accept server-only applications.
  • List only your real websites under allowed origins. Remove http://localhost:4100 when you've finished trying the examples.
  • Limit your token endpoint to your own website (its CORS header, as in token-server.mjs), and don't log the tokens it hands out.
  • Cache tokens. Don't ask for one per request or per visitor.
  • If a secret leaks, press Rotate on the application's page and update your server. The old secret and every token issued with it stop working at once.
  • Use HTTPS for your token endpoint on a real website.

Errors and fixes#

When asking for a token#

These come back from POST /oauth/token as { "error": "...", "error_description": "..." }. Subcontext checks your client id and secret first, then resource, so work down the table.

Status error error_description What to do
401 invalid_client Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method. The client id or secret is wrong, the secret was rotated, or the application was deleted. Copy them again; if you've lost the secret, rotate it.
400 invalid_target The requested resource must be https://subcontext.com/api/v1. Send resource=https://subcontext.com/api/v1 exactly (no slash at the end).
400 invalid_scope The requested scope is invalid, unknown, or malformed. You asked for a scope the application doesn't have (for example crm on a browser application). Ask for fewer, or edit the application's scopes.
400 invalid_request Missing required parameter: grant_type. Send grant_type=client_credentials, as a form, not as URL parameters.
400 unsupported_grant_type The authorization grant type is not supported by the authorization server. Use grant_type=client_credentials.
429 Rate limit exceeded. Please try again later. Too many requests from your server. See Limits.

When using a token#

API errors look like { "error": { "code": "...", "message": "...", "fields": { ... } } } (fields only when the error is about particular inputs). The table shows the message; the code is invalid_token for a 401, and forbidden for a 403, unless the table says otherwise. Conventions lists every code that means the same across the API.

Status What you get What it means and what to do
401 The access token has expired. Ask for a new one. Get a new token and try again.
401 The access token was revoked, for example because its application's secret was rotated. Ask for a new one. The secret was rotated or a scope removed. Get a new token (with the new secret, after a rotation) and try again.
401 No access token was sent. Send one as 'Authorization: Bearer <token>'. Add the Authorization header.
401 The access token is not valid. Check it was sent whole, after 'Bearer '. It isn't one Subcontext issued, or it was cut short (check for a missing Bearer before it), or its application was deleted.
403 this OAuth application is server-only and may not be used from a browser context A server-only application's token was used from a browser (the request had an Origin header). Use a browser application for pages.
403 origin 'https://...' is not allowlisted for this OAuth application The page's website isn't in the browser application's allowed origins. Check www., http against https, and the port.
403 no Origin header and request '...' is not from an allowlisted origin A browser application's token was used from a server. Use a server-only application.
403 this endpoint is only for server-only API applications (ones with no allowed origins) A browser application's token was used on an endpoint for servers only (leads, outcomes, loyalty memberships, outreach). Call it from your server with a server-only application.
403 OAuth token is not valid for this resource The token was asked for without resource=https://subcontext.com/api/v1.
403 This token does not carry the 'crm' scope. Ask for crm in the token request (the application needs it ticked).
403 This token does not carry the 'write' scope. (or This token needs the 'read' or 'write' scope.) The token is missing a scope the request needs: write for anything that changes something (any POST, PATCH or PUT, such as sending a lead or starting an application). Ask for it in the token request (the application needs it ticked).
403 OAuth application's organisation is not active Your organisation's account isn't active. Email hello@subcontext.com.
404 not_found: Resource not found The address or id is wrong, or the record belongs to another organisation (or, for leads, another application).
409 campaign_paused or campaign_archived The campaign isn't taking leads. Make it active, then send again.
422 unknown_campaign No campaign with that key in your organisation. Check the key.
422 validation_failed The message says what's wrong, for example source is required and must look like 'hubspot_webhook', and fields names the input.
422 invalid_parameter A filter or cursor in the address has a value the API doesn't accept, such as a misspelt lead status. fields names it.
429 rate_limited: Rate limit exceeded. Please try again later. See Limits.

Limits#

  • Tokens last two hours.
  • Requests: 300 in 5 minutes from one IP address, counting everything your server sends to Subcontext, token requests included. Over that, you get 429 with an X-RateLimit-Reset header saying when you can carry on (in seconds since 1970). Reusing tokens keeps you well inside it.
  • A lead can be up to 256 KB.
  • Idempotency-Key: sending the same key again within 10 minutes gives back the first answer (with an Idempotent-Replay: true header). After that, the same key still never makes a second lead.

Stuck, or something missing?

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

Contact us