REST API
How the v1 public REST surface works: base URL, bearer auth, camelCase conventions, identifiers, paging, and the error envelope.
The Iterant public API is JSON over HTTPS, versioned, and camelCase. Today it is nine reads and one writable resource: agent tasks. Everything else on this surface is a GET.
Everything documented here is built, behaves as described, and works today
with an itr_ API key. Names and payload shapes may still change. See
the developer surface overview.
Base URL
https://api.iterant.ai/api/v1/public/Every public route lives under this prefix. Paths outside it belong to the dashboard's own API and do not accept an API key.
The OpenAPI document is served separately, and you can generate a client from it directly:
https://api.iterant.ai/api/public/schema/The API reference is generated from that document, so it cannot drift from what the server does. Where this page disagrees with either, the schema is authoritative.
Authentication
Send your key as a bearer token:
curl https://api.iterant.ai/api/v1/public/brands/ \
-H "Authorization: Bearer itr_kK4dQ8v…"A missing or unrecognised key returns 401 with a WWW-Authenticate: Bearer challenge. See API keys for minting and scoping.
Naming a brand
The brand is always a path segment. Every brand-scoped route is /brands/{brandSlug}/…, and the server checks that slug against the organization your key resolves to.
curl https://api.iterant.ai/api/v1/public/brands/acme/pages/ \
-H "Authorization: Bearer itr_kK4dQ8v…"X-Brand-ID does not select a brand here. On a brand-pinned key, a header naming any other brand is refused before the view runs. An org-scoped key can omit the header.
Every route ends in /. …/pages without it does not resolve.
Conventions
camelCase on the wire
Response bodies use camelCase keys: brandSlug, urlPath, previewUrl, updatedAt. Request bodies are read the same way, so send opportunityRef rather than opportunity_ref. A validation error names the field back to you the way you spelled it.
Enum values never camelize. build_page, failed_generation, and google_ai_mode cross the wire verbatim, so a value you read out of one response is a value you can send back in the next request.
The MCP server returns the same camelCase payloads. Its tool arguments are snake_case, and a tool result carries the same fields as the REST response for the same read, plus a _note key and, on a large result, trimming.
Identifiers
Objects are addressed by the handle a human reads:
- Brands expose
brandSlug. There is no brand id on this surface. - Pages expose
urlPath. There is no page id and no/pages/{id}route; fetch one page through…/pages/by-path/?urlPath=/pricing. - Page opportunities expose a
ref:slugify(title)truncated to 60 characters, a hyphen, and the first 8 characters of the row's UUID. It resolves on the suffix, so it survives a title edit. Use a ref that came back from the list route, because refs are not constructible by hand. This object also carriesid, and the detail route accepts a bare UUID in the same path segment as a fallback. - Agent tasks expose a UUID
taskId, minted by the server and returned in the response that created the task.
Paging
List routes take limit and offset and return a fixed envelope:
curl "https://api.iterant.ai/api/v1/public/brands/acme/pages/?limit=20&offset=20" \
-H "Authorization: Bearer itr_kK4dQ8v…"{
"items": [
{ "urlPath": "/pricing", "title": "Pricing", "status": "published" }
],
"total": 41,
"offset": 20,
"limit": 20,
"note": "Showing 20 of 41 (offset 20). Narrow by status, locale or urlPathPrefix, or pass offset=40 for the next page."
}note is a steering string, empty when nothing was hidden. Most non-list responses carry one too: the visibility and linking reads, and the brand overview's nextActions.
Two reads cap without paging. visibility/overview/ and linking/advisory/ take limit but no offset, and their note tells you to narrow.
responseFormat
Every read takes responseFormat=concise (the default) or detailed. The task routes do not. Detailed adds fields rather than only adding rows: full briefs instead of the first five outline and FAQ entries, per-dimension score breakdowns, page outlines, previewUrl and liveUrl on page list rows, anchor variants on linking recommendations. It costs several times the tokens.
Metric states
Every metric block carries a state: ok, no_data, unconfigured, or
error. Its metrics object is null whenever the state is not ok, for
example an unconnected Search Console property, an analytics source that has
never reported, or a failed fetch. Do not render null as zero.
Visitor signals cover sessions, page anatomy, and conversions where isExact is false. Those figures are sampling-weighted estimates, flagged estimated: true, and are the right order of magnitude.
Errors
Every non-2xx response uses one envelope:
{
"type": "client_error",
"errors": [
{
"code": "not_found",
"detail": "Brand not found.",
"attr": null
}
]
}type is one of validation_error, client_error, or server_error. errors is always a list, with one entry per offending field on a validation failure and attr naming the field.
| Status | Meaning |
|---|---|
400 | Malformed request: an unknown enum value, a urlPath that does not start with /, a non-integer limit, a missing required query parameter |
401 | Missing, malformed, unrecognised, or revoked key |
403 | A brand-pinned key sent an X-Brand-ID naming a different brand |
404 | Not found, or not yours. See below |
405 | Method not allowed. Every route is GET only, except the task collection, which also takes POST |
409 | A task or a page build is already running for this brand. See Agent tasks |
422 | The opportunity you asked to build exists but is not eligible in its current state |
500 | Server error. Retry with backoff |
503 | The agent worker is not configured in this environment. Nothing was created |
429 is not implemented. Nothing throttles this surface yet, and the one-task-per-brand 409 is a workspace constraint rather than a quota. Rate limiting is planned, and a client that already handles 429 with backoff will need no change when it arrives.
404 on objects you cannot reach
An object in another organization and an object that has never existed return
a byte-identical 404, down to the message string. Check the slug or ref you
sent, and do not try to tell the two cases apart.
A brand-pinned key naming another brand gets a different answer depending on how you named it:
- Through the URL,
/brands/some-other-brand/pages/returns404, the same answer a stranger's brand gives. - Through the
X-Brand-IDheader, you get403 This API key is restricted to a different brand.That check runs during authentication, before any object is resolved.
Endpoints
Every endpoint has its own reference page, generated from the schema, listing each parameter, the full response shape, and a request you can send. This page covers what holds across all of them.
| Endpoint | Returns |
|---|---|
GET /brands/ | The brands this credential can reach |
GET /brands/{brandSlug}/overview/ | One-call state of a brand |
GET /brands/{brandSlug}/pages/ | A brand's pages |
GET /brands/{brandSlug}/pages/by-path/ | One page, by route |
GET /brands/{brandSlug}/reports/pages/ | Page or brand-wide measurement |
GET /brands/{brandSlug}/page-opportunities/ | The work queue |
GET /brands/{brandSlug}/page-opportunities/{opportunityRef}/ | One opportunity in full |
GET /brands/{brandSlug}/visibility/overview/ | AI visibility, projected by view |
GET /brands/{brandSlug}/linking/advisory/ | Advisory internal links for a page |
POST /brands/{brandSlug}/tasks/ | Start one agent task |
GET /brands/{brandSlug}/tasks/ | This brand's tasks, newest first |
GET /brands/{brandSlug}/tasks/{taskId}/ | Poll one task |
The nine reads are all GET and all take responseFormat. The task routes are the only place this surface accepts a POST, and a task is one autonomous Terra turn: a free-form instruction, or a build of one page opportunity. It takes about five minutes, it counts against your usage, and only one runs per brand at a time.
curl -X POST https://api.iterant.ai/api/v1/public/brands/acme/tasks/ \
-H "Authorization: Bearer itr_kK4dQ8v…" \
-H "Content-Type: application/json" \
-d '{"command": "prompt", "prompt": "Add an About page with a team section."}'Agent tasks: both commands, the poll-then-submit pattern, callbacks, and every error the routes raise.
Not built
- Publishing and unpublishing. Putting a page on a live customer-facing domain is a dashboard step, and there is no route for it.
- Batch task creation. Builds serialize per brand, so a fan-out endpoint would only hide the queue. One task per call.
- Writing back to the queue. Dismissing, scheduling, or re-opening an opportunity.
- Idempotency keys and rate limiting. Both planned; neither exists. A repeated
POSTto the task route is a second task, guarded only by the one-per-brand409. - Configuration writes and raw lead data. Not exposed here at all. See the overview.
Last updated on