Information
- OpenAPI version:
3.1.0
The public REST API for a store’s quizzes, analytics, shopper profiles, exports and webhooks.
Create a key on the dashboard (Developer > API keys) and send it as a Bearer token. See Plans for API availability.
A store admin creates keys on the dashboard (Developer > API keys). A key is shown once and never again, and may carry an expiry chosen when it is created. Send it as a Bearer token:
Authorization: Bearer oct_live_...
A key carries scopes chosen when it is created (quizzes:read, quizzes:write, quizzes:publish, analytics:read, profiles:read, webhooks:manage); the default is read-only (quizzes:read, analytics:read). A route that needs a scope the key lacks answers 403 insufficient_scope and names the scope it needs. A missing, malformed, revoked or expired key answers 401 invalid_api_key with WWW-Authenticate: Bearer.
Revoking a key takes effect within two minutes. To rotate, create the new key first, switch, then revoke the old one.
The API is available on the Plus and Enterprise plans. A key of a store on another plan answers 403 plan_required; a plan change gates within five minutes.
Every key has a leaky bucket and every store has one larger bucket shared by all its keys: three times a key’s on Plus, six times on Enterprise (bot_multiplier). A request costs 1 (reads), 2 (writes) or 3 (analytics) units; the bucket drains at a steady rate. An analytics call is charged 1 when the key is accepted and 2 more once an answer body is served, so a 304, a 404 or a 422 costs 1. The key’s bucket is charged first; a request the key’s bucket rejects never touches the store’s. GET /v1/store returns the three numbers in force (capacity, leak_per_second, bot_multiplier).
Every limited response carries:
RateLimit: limit=72, remaining=42, reset=2
RateLimit-Policy: 72;w=6
The headers are absent on a 401 and on a 403 answered before the limit is checked (plan_required, insufficient_scope). remaining is what the bucket can still take now and reset is the number of seconds until it is empty again; on an admitted request the headers describe the key’s bucket, on a 429 they describe the bucket that rejected (the store’s bucket reports its own, larger limit). Past the limit the answer is 429 rate_limited with a Retry-After header (seconds, also in the body as retry_after). When limits cannot be checked at all, reads are admitted and writes and analytics bodies answer 503 limiter_unavailable with Retry-After: 2.
Analytics are cached for a short time. If the cache is unavailable or an answer is not ready after two seconds, the answer is computed directly and may be slower. Rate-limit checks still apply: if they cannot run, the request returns 503 limiter_unavailable.
List endpoints page by cursor:
GET /v1/quizzes?limit=50
GET /v1/quizzes?limit=50&cursor=<next_cursor from the previous page>
limit is 20 by default, at least 1 and at most 100.{"data": [...], "has_more": true, "next_cursor": "..."}. When has_more is false, next_cursor is null.422 validation_error.POST /v1/profiles/search pages the same way: send the same body with every page and the cursor and limit as query parameters; total is exact up to 5,000 and total_capped says when counting stopped there. Totals on sparse filters can take about a second on very large stores./v1/analytics/quizzes pages by offset (offset, limit, sort, dir, and q), with no cursor; its default limit is 25 and its maximum is 1000. It answers data, has_more and total, and echoes limit, offset, sort and dir.422 validation_error naming it.Ids are prefixed strings (quiz_..., ver_..., key_...); an id of the wrong kind, of another store, or that does not exist answers 404 not_found.
Every non-2xx response is an RFC 9457 problem body with the media type application/problem+json:
{
"type": "https://api.octaneai.com/errors/insufficient_scope",
"title": "insufficient_scope",
"detail": "The API key does not carry the scope this route needs",
"status": 403,
"request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"required": ["quizzes:read"]
}
type is a stable identifier on the API’s host, title is the machine code, detail is for people, and request_id is the same value as the Request-Id response header. Quote the request_id when you write to support: it finds the request in the store’s API logs.
| status | title | when |
|---|---|---|
| 401 | invalid_api_key |
the key is missing, malformed, revoked or expired; the response carries WWW-Authenticate: Bearer |
| 403 | plan_required |
the store’s plan has no API access |
| 403 | insufficient_scope |
the key lacks the scope named in required |
| 404 | not_found |
no such resource for this store (an id of another store answers the same); detail is always Not found and never echoes the id |
| 405 | method_not_allowed |
the route exists, the method does not |
| 409 | conflict |
the request cannot apply in the resource’s current state: publishing a version with no pages, or starting an export of a dataset that is still being built |
| 409 | ai_run_in_progress |
the AI assistant is editing the quiz right now; archive, unarchive or write again when it has finished |
| 422 | validation_error |
the request did not validate; errors lists field and message, a query parameter the route does not declare included |
| 429 | rate_limited |
the key’s or the store’s bucket is full; see Retry-After (also retry_after in the body). A store-level reject has already spent the key’s own bucket for that request; there is no refund. |
| 500 | internal_error |
something on our side; send the request_id |
| 502 | upstream_error |
a service this request depends on failed; retry shortly |
| 503 | limiter_unavailable |
limits could not be checked for a write or analytics call; retry after Retry-After |
| 503 | dependency_unavailable |
a backing service is temporarily unavailable; retry after Retry-After |
| 504 | timeout |
the request ran past 30 seconds |
The body is the Problem schema of the OpenAPI document: the five fields above on every error, required on insufficient_scope, retry_after on 429 and 503, errors on validation_error.
The version is in the path: /v1. Within v1 the API only grows:
Deprecation and Sunset headers naming the date; the replacement is available for the whole window.Within /v1 the API only grows; nothing is removed or renamed.
An oct_live_... key minted on the dashboard
Security scheme type: http