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

?format=llm on the query string

on

Accept: application/vnd.spideriq+llm

on (substring match, so quality-weighted lists work)

Authorization: Bearer … with no explicit format

on by default since 16 June 2026

?format=json, ?format=yaml, ?format=md

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

use

string

one sentence — what this resource is

not

string[]

what it is not for, each entry naming the endpoint you probably wanted

next

string[]

the calls that usually follow this one

warn

string

the single most important caveat

pitfalls

string[]

mistakes callers actually make, in caller's language

limits

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.

Parametersflow_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); // → 4

Response200 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

404

RESOURCE_NOT_FOUND

no flow with that id

the envelope's what_you_sent echoes the id you passed; list flows via form_list

409

WRONG_FLOW_KIND

the id is a booking flow

use the URL in the envelope's suggested_url

GET /api/v1/booking/{flow_id}

The mirror of the above for kind='booking'. Its not names the forms endpoint.

Parametersflow_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'"]

Response200 OK with the guidance block.

Errors

Status

Code

When

Resolution

409

WRONG_FLOW_KIND

the id is a form flow

the envelope carries what_was_expected and suggested_url

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.

Parametersformat (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"

Response200 OK. pitfalls carries 4 entries; next tells you to set X-Brand-ID and verify with /auth/whoami.

Errors

Status

Code

When

Resolution

401

HTTP_ERROR_401

malformed token

the expected shape is client_id:api_key:api_secret or spideriq_pat_…

POST /api/v1/dashboard/content/pages

Create a page. Guidance states the preview-then-confirm semantics rather than leaving you to discover them.

Parametersformat (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); // → 5

Response200 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

409

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.

Parametersservice (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); // → 6

Response201 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

401

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

Publish