Agent Guidance (?format=llm)
Every API response is written for a reader who can tell a good answer from a wrong one. An agent cannot. It receives a well-formed 200, has no way to see that the value is wrong for its case, and builds on it.
?format=llm is the answer to that. It adds a guidance block to a success response describing what the resource is for, what it is not for, what to call next, and the mistakes callers actually make. The not and pitfalls fields are the point: they are the negative space an ordinary response has no room for.
The negotiation contract
Guidance is switched on by any one of these signals:
Signal | Effect |
|---|---|
| on |
| on (substring match, so quality-weighted lists work) |
| on by default since 16 June 2026 |
| off — an explicit format wins over the Bearer default |
a browser session cookie with no Bearer token | off |
The Bearer default exists because an agent should not have to know to ask for the truth. Pass ?format=json on a per-request basis when you want the lean payload.
The block
The vocabulary is frozen at six keys. Nothing else appears at the top level of guidance, so a parser can rely on the shape.
Key | Type | What it carries |
|---|---|---|
| string | one sentence — what this resource is |
| string[] | what it is not for, each entry naming the endpoint you probably wanted |
| string[] | the calls that usually follow this one |
| string | the single most important caveat |
| string[] | mistakes callers actually make, in caller's language |
| object | hard numeric limits |
Guidance is additive. Your existing fields are untouched, so adding the parameter to a working integration cannot break it.
GET /api/v1/forms/{flow_id}
Read a kind='form' flow. With guidance, the response names the booking endpoint as the thing this is not, which is the confusion that produced the canonical-URL work.
Parameters — flow_id (path, required). format (query, optional, llm).
Example
const url = new URL("https://spideriq.ai/api/v1/forms/3f0b1b83-3ee0-4ac9-84a3-e8513714ffa5");
url.searchParams.set("format", "llm");
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SPIDERIQ_PAT}` } });
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).error.code}`); // 404 RESOURCE_NOT_FOUND
const body = await res.json();
console.log(Object.keys(body.guidance)); // → ["limits","next","not","pitfalls","use","warn"]
console.log(body.guidance.pitfalls.length); // → 4Response — 200 OK. Your usual body plus guidance with the six keys; pitfalls carries 4 entries on this endpoint.
{
"flow_id": "3f0b1b83-…",
"kind": "form",
"guidance": {
"use": "A kind='form' flow. Render it at /f/{flow_id} on the tenant's primary domain.",
"not": ["GET /api/v1/booking/{flow_id} — that is for kind='booking'"],
"next": ["POST /api/v1/forms/{flow_id}/submit"],
"warn": "Assert on dom.shadow_hosts, not body_text_preview — the embed is cross-origin.",
"pitfalls": ["Calling /book/{flow_id} for a form silently serves the wrong page"],
"limits": { "max_questions": "50" }
}
}Errors
Status | Code | When | Resolution |
|---|---|---|---|
|
| no flow with that id | the envelope's |
|
| the id is a | use the URL in the envelope's |
GET /api/v1/booking/{flow_id}
The mirror of the above for kind='booking'. Its not names the forms endpoint.
Parameters — flow_id (path, required). format (query, optional, llm).
Example
const url = new URL("https://spideriq.ai/api/v1/booking/3f0b1b83-3ee0-4ac9-84a3-e8513714ffa5");
url.searchParams.set("format", "llm");
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SPIDERIQ_PAT}` } });
if (res.status === 409) {
const { error } = await res.json();
console.log(error.suggested_url); // → "/api/v1/forms/3f0b1b83-3ee0-4ac9-84a3-e8513714ffa5"
}
console.log((await res.json()).guidance.not); // → ["GET /api/v1/forms/{flow_id} — that is for kind='form'"]Response — 200 OK with the guidance block.
Errors
Status | Code | When | Resolution |
|---|---|---|---|
|
| the id is a | the envelope carries |
GET /api/v1/auth/workspaces
List the workspaces a token can reach. Guidance here disambiguates which key identifies a workspace, which is the commonest cause of a call landing on the wrong tenant.
Parameters — format (query, optional, llm).
Example
const url = new URL("https://spideriq.ai/api/v1/auth/workspaces");
url.searchParams.set("format", "llm");
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SPIDERIQ_PAT}` } });
if (res.status === 401) throw new Error((await res.json()).error.message); // malformed token
const body = await res.json();
console.log(body.guidance.next[0]); // → "Set X-Brand-ID: <workspace.brand_id> on subsequent requests"Response — 200 OK. pitfalls carries 4 entries; next tells you to set X-Brand-ID and verify with /auth/whoami.
Errors
Status | Code | When | Resolution |
|---|---|---|---|
|
| malformed token | the expected shape is |
POST /api/v1/dashboard/content/pages
Create a page. Guidance states the preview-then-confirm semantics rather than leaving you to discover them.
Parameters — format (query, optional, llm). dry_run (query, optional) previews without mutating and returns a confirm_token.
Example
const url = new URL("https://spideriq.ai/api/v1/dashboard/content/pages");
url.searchParams.set("format", "llm");
url.searchParams.set("dry_run", "true"); // previews without writing
const res = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SPIDERIQ_PAT}`, "Content-Type": "application/json" },
body: JSON.stringify({ slug: "about", title: "About" }),
});
if (res.status === 409) throw new Error("a page already exists at that slug");
console.log((await res.json()).guidance.pitfalls.length); // → 5Response — 200 OK. pitfalls carries 5 entries. Guidance decorates the dry_run preview as well as the real create, so you can read it without writing anything.
Errors
Status | When | Resolution |
|---|---|---|
| a page already exists at that slug | pick another slug, or update the existing page |
POST /api/v1/jobs/{service}/submit
Submit a job. This endpoint dispatches to many services, so its pitfalls list is the longest on the surface.
Parameters — service (path, required). format (query, optional, llm).
Example
const url = new URL("https://spideriq.ai/api/v1/jobs/spiderSite/submit");
url.searchParams.set("format", "llm");
const res = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SPIDERIQ_PAT}`, "Content-Type": "application/json" },
body: JSON.stringify({ url: "https://example.com" }),
});
if (res.status === 401) throw new Error("invalid or missing token");
const body = await res.json(); // 201 Created
console.log(body.guidance.pitfalls.length); // → 6Response — 201 Created. pitfalls carries 6 entries, covering cross-service payload confusion, polling faster than 5s, leaving test: true in a production payload, querying results on the wrong service, and omitting Idempotency-Key on a retry.
Errors
Status | When | Resolution |
|---|---|---|
| invalid or missing token | see the workspaces endpoint above |
Errors across the whole surface
Adding ?format=llm introduces no new failure modes. An unrecognised format value is not an error: it simply does not switch guidance on, and the Bearer default decides. Every error body on these endpoints follows the structured envelope:
{
"error": {
"code": "WRONG_FLOW_KIND",
"message": "This URL is for kind='booking' flows. Flow 3f0b1b83 is kind='form'.",
"what_you_sent": { "flow_id": "3f0b1b83", "kind": "form" },
"what_was_expected": { "kind": "booking" },
"suggested_action": "Use the form endpoint instead.",
"suggested_url": "/api/v1/forms/3f0b1b83"
}
}code is stable and safe to branch on. message is for humans and may be reworded.
Not in this release
Guidance is adopted on the five endpoints above, not across the whole API. Endpoints without it return their normal body and ignore the parameter — there is no error and no empty guidance key. Coverage widens as incidents identify the next endpoint worth annotating.
Next steps
Forms & Booking API — the operations these guidance blocks describe.
Building a Form — the concepts behind the flow endpoints.
MCP Reference Overview — calling the same surface through an agent toolkit.