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
readandwriteonly. Subcontext won't let you tickcrmoroutcomeson 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:4100when 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
429with anX-RateLimit-Resetheader 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: trueheader). After that, the same key still never makes a second lead.