← Agent Door
API
Tokens

Run Agent Door from your own code

Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page sets the agent class by surface matrix, reads robots.txt per vendor token, runs the reachability gate, checks the agent-safe API basics, and deletes and ranks the rungs with the execution path per class, then hands it all to the run as facts. The run writes the plan or the rollout; the page then holds it to its own facts (agreply.js).

Base URL and the response shape

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }

The token is minted for this app (the guest call takes {"slug":"agent-door"}), so no slug header is needed afterwards. Send it as Authorization: Bearer YOUR_TOKEN. The run body is the input object, sent bare.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required key (task, product, agents, facts) is missing. The page always sends all five of task, product, agents, carry, facts; carry is an empty string for plan.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: both lanes are metered and need a personal sign-in.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field comes first

Two lanes, chosen by task, in the order the work runs - decide who gets in and how, then ship it and prove it worked:

A missing or unknown task is answered as plan. Both lanes send the same five fields. The facts are the page's checks, the matrix and the rungs, one row per line, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the plan into carry for the rollout lane.

The input

FieldRequiredWhat
taskyesplan or rollout. If it is missing or neither, do plan and set "task": "plan".
productyesthe product, what agents must do, the surfaces that exist, how humans get in and what agents use today, the edge, agent signals and measurement, what an agent can break, the deadline, one-off or standing, the effort ceiling and the plan date; "not given" means not given.
agentsyesthe agent classes in scope, the surface paths and training calls, the pasted robots.txt and any probe results.
carrysend it (empty for plan)rollout task only: the plan decisions when the plan task ran first, or a sentence saying no plan was carried over. Either way, never mention this input or any input by its name in your reply.
factsyesthe page's checks, one line each as area | id | status | evidence, the matrix, the classes with their execution paths, the signup path, and the rungs as rung | decision | order | owning skill | moved by | notes. A line starting "must:" applies to your reply.
retry_notenooptional; if present, your previous reply was unusable - follow it.

The rules the model applies

From the source skill, @samber/agent-integration-surfaces - the only standard the run applies:

House rules

The replies

plan

Write the identity and delegation model, the execution paths, the signup path and the ranked rungs.

  1. summary - one sentence, at most 40 words: the first thing that must change, and the first rung.
  2. identity - one object per class the facts list, in that order: class (its id), identified_by (crawlers and browser agents: a vendor IP list or a verified signature, never the user-agent alone), authority (own, or the user's - delegated), credential (its type, never a human's long-lived key), scope, lifetime (one that expires - never "until revoked"), sponsor (the accountable human role), revocation (per agent, or per install for the bounded CLI session), approval (which actions need a human, and how the approval travels; "none" when nothing is irreversible).
  3. paths - one object per class, in the same order: class, path (the page's path id), why (one sentence for this product).
  4. signup - decision (the page's), detail (one sentence: the claim flow's shape, or why it was deleted or deferred).
  5. rungs - one object per rung in the page's list, in its order: rung (its id), decision (the page's), owner (the team that owns it - a team the input names, or a team you propose, labelled proposed; a skill id is never an owner), skill (the page's owning skill), why (one sentence: which answer moved or removed it, in this product's terms).
  6. caveats - at most 4 short strings; [] when there is nothing to say.
{
  "task": "plan",
  "summary": "one sentence",
  "identity": [ { "class": "coding", "identified_by": "how", "authority": "delegated", "credential": "a type", "scope": "named scopes", "lifetime": "hours or days", "sponsor": "a role", "revocation": "per agent", "approval": "which actions, and how" } ],
  "paths": [ { "class": "coding", "path": "skillcli", "why": "one sentence" } ],
  "signup": { "decision": "claim", "detail": "one sentence" },
  "rungs": [ { "rung": "A", "decision": "scheduled", "owner": "a team", "skill": "the page's owning skill", "why": "one sentence" } ],
  "caveats": [ ]
}

The page holds it to its own facts (check() in agreply.js; any warning makes the reply flagged): the reply answers the task it was sent; no web link; no backticks; no gendered pronoun; a summary of at most 40 words; one identity row per class in the page's order, every field filled, a lifetime that expires, never a human's key or session, crawlers and browser agents identified by an IP list or a signature, per-agent revocation, and - when an agent can move money or destroy data - an approval outside the agent's channel; the page's path per class; the page's signup decision with a reason; the page's rungs and decisions in order, each with its owning skill, an owner the input names or labelled proposed, and why.

rollout

Write the gate fixes, the measurement plan, one step per scheduled rung and the exit criteria.

  1. summary - one sentence, at most 40 words.
  2. gate_fixes - one object per gate the facts mark fail, in the facts' order: gate (its id), fix (one sentence: the change that makes it pass), owner (the team that makes the change - a team the input names, or a team you propose, labelled proposed; the skill that designs a fix, written "designed by" in the facts, is never the owner); [] when no gate fails.
  3. measurement - tags (short strings: the agent-class field and where it is logged, with unknown as a value), baseline (one sentence: what is recorded before the first rung ships), funnel (at least three short strings, in order).
  4. steps - one object per scheduled rung, in the page's order, no others: rung (its id), first_step (one sentence), done_when (one sentence: the observable result).
  5. exit - at least three short strings: the exit criteria the plan is checked against, in this product's terms.
  6. caveats - at most 4 short strings; [] when there is nothing to say.
{
  "task": "rollout",
  "summary": "one sentence",
  "gate_fixes": [ { "gate": "reach", "fix": "one sentence", "owner": "a team" } ],
  "measurement": { "tags": [ "a tag" ], "baseline": "one sentence", "funnel": [ "a step" ] },
  "steps": [ { "rung": "A", "first_step": "one sentence", "done_when": "one sentence" } ],
  "exit": [ "a criterion" ],
  "caveats": [ ]
}

The page requires: the reply answers the task it was sent; the same text rules; one fix per failed gate in the page's order, each with an owner the input names or labelled proposed; an agent-class field among the tags with unknown as a value, a baseline and at least three funnel steps; any agent share reported as a floor; one step per scheduled rung in order, each with a first step and when it is done; at least three exit criteria.

Worked examples

Three runs from the page's free samples (invented companies): the plan for a database agents are blocked from at the edge, the rollout for the same product carrying that plan, and the plan for a payments platform with browser agents and a demo date. Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped unedited - but the facts and long strings are shortened in this display only, and every cut is listed under the example. The last line is the page's own check of the full reply.

plan - Vanecroft - agents blocked at the door

The page read it as: agents meant to get in are blocked - fix the gate first; classes crawler, coding, chat; rungs A scheduled, C scheduled, F scheduled, B scheduled, D scheduled, E scheduled, G scheduled; 0 of 6 gates; failing checks: reach, robots, edge, keys, measure, idempotency, destructive, apisafe.

Input (facts is 4630 characters; the first 1397 are shown):

{
 "task": "plan",
 "product": "Product: Vanecroft\nWhat it does: A serverless Postgres service that developers - and the coding agents working for them - provision and query from a terminal.\nAgents must: read docs, call the API for a user, create resources or sign up\nSurfaces: public API, OpenAPI spec, CLI, SDKs\nHumans get in by: self-serve signup with a free tier; agents today use: a human's API key or session\nEdge: AI-bots preset on; CAPTCHA on signup yes; deny cells enforced at the edge: unknown\nAgent signals: agent user-agents in logs, agent-created signups, shell agents failing at login or prompts; measurement: none\nWhat an agent can break: destructive writes\nDeadline: none; a standing agent channel; effort ceiling: one engineer for a quarter; plan date: 2026-11-02",
 "agents": "Agent classes in scope: crawler (Crawlers and AI search), coding (Coding agents with a shell), chat (Chat assistants without a shell)\nPaths: marketing /, docs /docs/, signup /signup, app /console/; training on marketing: allow; training on docs: allow\n\nrobots.txt:\nUser-agent: *\nDisallow: /console/\n\nUser-agent: GPTBot\nUser-agent: ClaudeBot\nUser-agent: Claude-User\nUser-agent: CCBot\nDisallow: /\n\nProbe results (class | surface | status):\n- Claude-User | docs | 403 | Cloudflare AI bots preset -> fail\n- browser | signup | 200 | Turnstile challenge shown -> fail",
 "carry": "",
 "facts": "The page's checks (8 fail, 4 warn, 2 pass, 3 n/a); verdict: agents meant to get in are blocked - fix the gate first:\n- Reachability gate | classes | na | Crawlers and AI search, Coding agents with a shell, Chat assistants without a shell\n- Reachability gate | reach | fail | a CDN \"block AI bots\" preset is on - it blocks AI search and user-triggered fetchers on allow cells too; a CAPTCHA on signup blocks legitimate browser agents and lets determined bots through; robots.txt disallows 4 tokens on allow cells; 2 probes on allow cells failed (Claude-User | docs | 403 | Cloudflare AI bots preset; browser | signup | 200 | Turnstile challenge shown)\n- robots.txt, token by token | robots | fail | GPTBot is disallowed on marketing site and docs (Disallow: /); ClaudeBot is disallowed on marketing site and docs (Disallow: /); Claude-User is disallowed on marketing site and docs and signup - blocking it stops answers fetching your page (Disallow: /); CCBot is disallowed on marketing site and docs (Disallow: /)\n- robots.txt, token by token | unstated | warn | signup: OAI-SearchBot, Claude-SearchBot, Google-Extended, PerplexityBot and 2 more - robots.txt allows them; state the preference and enforce it at the edge\n- robots.txt, token by token | ignored | warn | ChatGPT-User, Google-Agent, Perplexity-User may ignore robots.txt - a deny for them needs a CDN rule\n- Reachability gate | edge |\n\n[... 3233 more characters cut from this display ...]"
}

Reply (long strings shortened for display - see the cuts below):

{
 "task": "plan",
 "summary": "First switch off the CDN AI-bots preset and the Turnstile CAPTCHA on /signup and drop the Disallow: / group for GPTBot, ClaudeBot, Claude-User and CCBot, then ship rung A, agent-class tags on every Vanecroft surface.",
 "identity": [
  {
   "class": "crawler",
   "identified_by": "the vendor's published IP list or a verified request signature checked at the CDN, never the GPTBot, ClaudeBot or Claude-User user-agent string alone",
   "authority": "own - a crawler or user-triggered fetcher reads marketing and docs and carries no user grant",
   "credential": "none issued; the verified signature or IP match proves the operator only",
   "scope": "read of / and /docs/ only; /signup and /console/ stay denied for training and search tokens",
   "lifetime": "per request - each signature check expires with the request; vendor IP lists refreshed daily (proposed)",
   "sponsor": "proposed: the platform engineer who owns the CDN rules",
   "revocation": "per agent - remove one vendor's token or IP range from the edge allow rule without touching the others",
   "approval": "none - crawlers only read public pages"
  },
  {
   "class": "coding",
   "identified_by": "a device-grant login from the Vanecroft CLI that registers each agent instance by name",
   "authority": "the user's - delegated",
   "credential": "an agent-labelled OAuth access token from the device grant, replacing the human's API key or session agents use today",
   "scope": "proposed: databases:read, databases:create, query:run on the user's own projects; no billing or org admin scopes",
   "lifetime": "proposed: 1-hour access token, refresh never longer-lived than the user's own token",
   "sponsor": "proposed: the developer who approved the device-grant code",
   "revocation": "per agent - each CLI agent token is listed and revocable without logging the human out",
   "approval": "dropping a database, deleting a branch or any other destructive write waits for an email or push to the sponsoring developer, outside the terminal session"
  },
  {
   "class": "chat",
   "identified_by": "the OAuth client registration of the chat assistant, with protected resource metadata on the Vanecroft API",
   "authority": "the user's - delegated",
   "credential": "a user OAuth token issued through the MCP authorization flow, bound to that assistant's client",
   "scope": "proposed: databases:read and query:run read-only by default; databases:create on request",
   "lifetime": "proposed: 1-hour access token, refresh capped at the user token's lifetime",
   "sponsor": "proposed: the Vanecroft user who granted the assistant access",
   "revocation": "per agent - the user revokes one assistant's grant from account settings",
   "approval": "destructive writes on a database wait for a push or email to the user, never a confirm prompt inside the chat"
  }
 ],
 "paths": [
  {
   "class": "crawler",
   "path": "edge",
   "why": "AI search and fetchers only need the allow cells on / and /docs/ answering, which today the AI-bots preset and robots.txt block."
  },
  {
   "class": "coding",
   "path": "skillcli",
   "why": "Vanecroft already has a CLI and logs show shell agents failing at login or prompts, so an Agent Skill piloting a --json CLI with device-grant login fits."
  },
  {
   "class": "chat",
   "path": "mcp",
   "why": "Chat assistants have no shell to run the Vanecroft CLI, so they need an MCP server, which does not exist yet."
  }
 ],
 "signup": {
  "decision": "claim",
  "detail": "One unauthenticated call creates a small free-tier Postgres database and returns it with a single-use expiring claim URL and an expiry; unclaimed databases expire holding no personal data, and claiming moves the database into the [... 63 more characters cut from this display]"
 },
 "rungs": [
  {
   "rung": "A",
   "decision": "scheduled",
   "owner": "proposed: the platform team that owns Vanecroft logging",
   "skill": "this plan (MCP telemetry: mcp-server-offering)",
   "why": "Vanecroft has no agent measurement at all, so an agent-class field must exist before any later rung can show it worked."
  },
  {
   "rung": "C",
   "decision": "scheduled",
   "owner": "proposed: the API and auth team",
   "skill": "api-auth-key-management, oauth2-provider-design",
   "why": "Agents use a human's API key or session today on a standing channel where destructive writes are possible, so per-agent delegated tokens come early."
  },
  {
   "rung": "F",
   "decision": "scheduled",
   "owner": "proposed: the growth and signup team",
   "skill": "this plan",
   "why": "Self-serve developers are the funnel and agents already create Vanecroft accounts, so provision-then-claim replaces the CAPTCHA form once write-safety lands."
  },
  {
   "rung": "B",
   "decision": "scheduled",
   "owner": "proposed: the developer docs team",
   "skill": "coding-agent-docs-optimization",
   "why": "Kept in the default order: Markdown docs and a Context7 claim help coding agents read /docs/ once the edge lets them in."
  },
  {
   "rung": "D",
   "decision": "scheduled",
   "owner": "proposed: the CLI team",
   "skill": "cli-interface-design, agent-skill-offering",
   "why": "Logs show shell agents failing at the Vanecroft CLI login and prompts, and a CLI already exists, so the effort stays low."
  },
  {
   "rung": "E",
   "decision": "scheduled",
   "owner": "proposed: the API and auth team",
   "skill": "mcp-server-offering",
   "why": "Kept in the default order for chat assistants, gated behind write-safety because destructive writes are still unguarded."
  },
  {
   "rung": "G",
   "decision": "scheduled",
   "owner": "proposed: the platform team",
   "skill": "this plan",
   "why": "Kept last in the default order as a background task alongside the OpenAPI spec, not an agent-readiness milestone."
  }
 ],
 "caveats": [
  "Whether the CDN enforces the deny cells on /signup and /console/ is unknown; read the CDN rules for each deny cell.",
  "Idempotency and destructive-operation checks fail, and rate limits and untrusted-input handling were not checked.",
  "ChatGPT-User, Google-Agent and Perplexity-User may ignore robots.txt, so any deny for them needs a CDN rule.",
  "The effort ceiling is one engineer for a quarter, so all seven rungs may not fit."
 ]
}

Cut from this display only (the saved reply and the check use the full text): signup.detail (292 characters, 229 shown).

What step 5 prints for it: A: scheduled | C: scheduled | F: scheduled | B: scheduled | D: scheduled | E: scheduled | G: scheduled

The page's check: kept - nothing to flag

rollout - Vanecroft - agents blocked at the door

The input carries the saved plan (R.laneForm fills carry from the plan reply), as the page's handoff does. The page's rungs: A scheduled, C scheduled, F scheduled, B scheduled, D scheduled, E scheduled, G scheduled.

Input (facts is 4429 characters; the first 1397 are shown):

{
 "task": "rollout",
 "product": "Product: Vanecroft\nWhat it does: A serverless Postgres service that developers - and the coding agents working for them - provision and query from a terminal.\nAgents must: read docs, call the API for a user, create resources or sign up\nSurfaces: public API, OpenAPI spec, CLI, SDKs\nHumans get in by: self-serve signup with a free tier; agents today use: a human's API key or session\nEdge: AI-bots preset on; CAPTCHA on signup yes; deny cells enforced at the edge: unknown\nAgent signals: agent user-agents in logs, agent-created signups, shell agents failing at login or prompts; measurement: none\nWhat an agent can break: destructive writes\nDeadline: none; a standing agent channel; effort ceiling: one engineer for a quarter; plan date: 2026-11-02",
 "agents": "Agent classes in scope: crawler (Crawlers and AI search), coding (Coding agents with a shell), chat (Chat assistants without a shell)\nPaths: marketing /, docs /docs/, signup /signup, app /console/; training on marketing: allow; training on docs: allow\n\nrobots.txt:\nUser-agent: *\nDisallow: /console/\n\nUser-agent: GPTBot\nUser-agent: ClaudeBot\nUser-agent: Claude-User\nUser-agent: CCBot\nDisallow: /\n\nProbe results (class | surface | status):\n- Claude-User | docs | 403 | Cloudflare AI bots preset -> fail\n- browser | signup | 200 | Turnstile challenge shown -> fail",
 "carry": "Identity: crawler - none issued; the verified signature or IP match proves the operator only, per request - each signature check expires with the request; vendor IP lists refreshed daily (proposed), sponsor proposed: the platform engineer who owns the CDN rules; coding - an agent-labelled OAuth access token from the device grant, replacing the human's API key or session agents use today, proposed: 1-hour access token, refresh never longer-lived than the user's own token, sponsor proposed: the developer who approved the device-grant code; chat - a user OAuth token issued through the MCP authorization flow, bound to that assistant's client, proposed: 1-hour access token, refresh capped at the user token's lifetime, sponsor proposed: the Vanecroft user who granted the assistant access\nPaths: crawler -> edge; coding -> skillcli; chat -> mcp\nSignup: claim - One unauthenticated call creates a small free-tier Postgres database and returns it with a single-use expiring claim URL and an expiry; unclaimed databases expire holding no personal data, and claiming moves the database into the developer's account and billing, where the terms are accepted.\nRungs: A scheduled (proposed: the platform team that owns Vanecroft logging); C scheduled (proposed: the API and auth team); F scheduled (proposed: the growth and signup team); B scheduled (proposed: the developer docs team); D scheduled (proposed: the CLI team); E scheduled (proposed: the API and auth team); G scheduled (proposed: the platform team)",
 "facts": "The page's checks (8 fail, 4 warn, 2 pass, 3 n/a); verdict: agents meant to get in are blocked - fix the gate first:\n- Reachability gate | classes | na | Crawlers and AI search, Coding agents with a shell, Chat assistants without a shell\n- Reachability gate | reach | fail | a CDN \"block AI bots\" preset is on - it blocks AI search and user-triggered fetchers on allow cells too; a CAPTCHA on signup blocks legitimate browser agents and lets determined bots through; robots.txt disallows 4 tokens on allow cells; 2 probes on allow cells failed (Claude-User | docs | 403 | Cloudflare AI bots preset; browser | signup | 200 | Turnstile challenge shown)\n- robots.txt, token by token | robots | fail | GPTBot is disallowed on marketing site and docs (Disallow: /); ClaudeBot is disallowed on marketing site and docs (Disallow: /); Claude-User is disallowed on marketing site and docs and signup - blocking it stops answers fetching your page (Disallow: /); CCBot is disallowed on marketing site and docs (Disallow: /)\n- robots.txt, token by token | unstated | warn | signup: OAI-SearchBot, Claude-SearchBot, Google-Extended, PerplexityBot and 2 more - robots.txt allows them; state the preference and enforce it at the edge\n- robots.txt, token by token | ignored | warn | ChatGPT-User, Google-Agent, Perplexity-User may ignore robots.txt - a deny for them needs a CDN rule\n- Reachability gate | edge |\n\n[... 3032 more characters cut from this display ...]"
}

Reply (long strings shortened for display - see the cuts below):

{
 "task": "rollout",
 "summary": "Vanecroft first turns off the CDN AI-bots preset and Turnstile on the allow cells, rewrites robots.txt per token, then tags every request by agent class before agent credentials, the claim signup and the CLI ship.",
 "gate_fixes": [
  {
   "gate": "reach",
   "fix": "Turn off the Cloudflare AI-bots preset on marketing and docs and replace the Turnstile challenge on /signup with verified-signature or IP-list checks, so the Claude-User docs probe returns 200 and verified browser agents reach signup.",
   "owner": "proposed: the platform engineer who owns the CDN rules"
  },
  {
   "gate": "robots",
   "fix": "Remove the Disallow: / group for GPTBot, ClaudeBot, Claude-User and CCBot, keep only the /console/ disallow and add explicit groups for the signup deny, since training is allowed on marketing and docs.",
   "owner": "proposed: the developer docs team that owns the public site"
  },
  {
   "gate": "edge",
   "fix": "Read the CDN rules for each deny cell (training and search on /signup and /console/) and add an edge rule wherever a deny relies on robots.txt alone, then probe each one.",
   "owner": "proposed: the platform engineer who owns the CDN rules"
  },
  {
   "gate": "keys",
   "fix": "Stop coding agents from running on the developer's API key or session by issuing agent-labelled device-grant tokens from the CLI and OAuth tokens through the MCP authorization flow, each revocable per agent.",
   "owner": "proposed: the API and auth team"
  },
  {
   "gate": "measure",
   "fix": "Add an agent-class field to the API, CLI, docs and signup logs, with unknown as a value, and record a baseline before rung C ships.",
   "owner": "proposed: the platform team that owns Vanecroft logging"
  },
  {
   "gate": "apisafe",
   "fix": "Close the four open items: idempotency keys on database create and write calls, a checked rate-limit policy, untrusted-input handling for query text, and a human approval sent outside the agent session before destructive writes such as [... 21 more characters cut from this display]",
   "owner": "proposed: the API and auth team, with the CLI team for destructive confirmations"
  }
 ],
 "measurement": {
  "tags": [
   "agent_class on every API request log: crawler, coding, chat or unknown",
   "agent_class on CLI telemetry, set from env-var detection, unknown when absent",
   "agent_class on docs and signup edge logs from verified signature or IP list, unknown otherwise",
   "agent_class on MCP session logs once the MCP server exists"
  ],
  "baseline": "Before rung A closes, record per class the docs fetch count, CLI login failures, API calls on human keys and agent-created signups, with crawler hits kept apart from API calls.",
  "funnel": [
   "docs page fetched by an agent",
   "CLI or MCP login completed by device grant or OAuth",
   "free-tier database provisioned by an agent",
   "claim URL opened and database claimed into a developer account",
   "first query run on the claimed database"
  ]
 },
 "steps": [
  {
   "rung": "A",
   "first_step": "Add the agent-class field with unknown as a value to the API gateway and CLI telemetry and start logging it.",
   "done_when": "Every API and CLI log line carries an agent class and a dated baseline per class is recorded."
  },
  {
   "rung": "C",
   "first_step": "Add the OAuth device grant to the Vanecroft CLI so a coding agent receives its own agent-labelled access token instead of the developer's API key.",
   "done_when": "A coding agent logs in by device grant, its token is revoked alone without logging out the developer, and the actor chain shows in the logs."
  },
  {
   "rung": "F",
   "first_step": "Ship the unauthenticated create call that returns a small free-tier database, a single-use claim URL and an expiry, behind the destructive-write approval.",
   "done_when": "An agent provisions a database with no form or Turnstile, a developer claims it into their account and billing, and unclaimed ones expire."
  },
  {
   "rung": "B",
   "first_step": "Publish markdown versions of the /docs/ pages and claim the Vanecroft library on Context7.",
   "done_when": "A coding agent fetches a docs page as markdown and resolves Vanecroft on Context7."
  },
  {
   "rung": "D",
   "first_step": "Add a --json flag to the existing CLI with JSON on stdout, diagnostics on stderr and no prompt without a TTY, then wrap it in an Agent Skill.",
   "done_when": "Logs stop showing shell agents failing at login or prompts and the Skill drives provision and query end to end."
  },
  {
   "rung": "E",
   "first_step": "Build the MCP server over the public API with the MCP authorization flow, gated behind the write-safety fixes.",
   "done_when": "A chat assistant connects with the user's OAuth grant, runs a query, and a destructive write waits for an approval outside the chat."
  },
  {
   "rung": "G",
   "first_step": "Publish an api-catalog entry pointing at the OpenAPI spec alongside the spec work.",
   "done_when": "The api-catalog and agent card resolve and list the current OpenAPI spec."
  }
 ],
 "exit": [
  "Every allow cell answers for its class: Claude-User reaches docs and verified browser agents reach signup without Turnstile",
  "No agent runs on a developer's API key or session; every agent token is revocable alone",
  "Destructive writes on a Vanecroft database wait for an out-of-band human approval",
  "Agent share is reported per class with its method and as a floor, never as one headline number",
  "Agent-provisioned databases are claimed into developer accounts, and unclaimed ones expire"
 ],
 "caveats": [
  "No MCP server exists yet, so the chat path waits for rung E.",
  "Whether the edge enforces the deny cells is unknown until the CDN rules are read.",
  "Signup stays gated behind the write-safety fixes."
 ]
}

Cut from this display only (the saved reply and the check use the full text): gate_fixes[5].fix (256 characters, 235 shown).

What step 5 prints for it: A: Every API and CLI log line carries an agent class and a dated baseline per class is recorded. | C: A coding agent logs in by device grant, its token is revoked alone without logging out the developer, and the actor chain shows in the logs. | F: An agent provisions a database with no form or Turnstile, a developer claims it into their account and billing, and unclaimed ones expire. | B: A coding agent fetches a docs page as markdown and resolves Vanecroft on Context7. | D: Logs stop showing shell agents failing at login or prompts and the Skill drives provision and query end to end. | E: A chat assistant connects with the user's OAuth grant, runs a query, and a destructive write waits for an approval outside the chat. | G: The api-catalog and agent card resolve and list the current OpenAPI spec.

The page's check: kept - nothing to flag

plan - Quillbarrow - money moves, a demo date

The page read it as: open to agents, with gaps to close; classes crawler, browser, fleet; rungs A scheduled, C scheduled, B scheduled, E deferred, G deferred, D deleted, F deleted; 4 of 6 gates; failing checks: measure, untrusted, spend, apisafe.

Input (facts is 3677 characters; the first 1399 are shown):

{
 "task": "plan",
 "product": "Product: Quillbarrow\nWhat it does: Accounts-payable automation for mid-market finance teams: invoices in, approvals, and supplier payments out.\nAgents must: read docs, call the API for a user, operate the web app, pay\nSurfaces: public API, OpenAPI spec, SDKs\nHumans get in by: sales-led accounts; an OAuth provider exists (human-only); agents today use: credentials of their own\nEdge: AI-bots preset off; CAPTCHA on signup no; deny cells enforced at the edge: yes\nAgent signals: agent user-agents in logs, RFP lines; measurement: tags, no baseline\nWhat an agent can break: destructive writes, money movement, regulated data\nDeadline: a hard date; a standing agent channel; effort ceiling: one engineer for a quarter; plan date: 2026-11-02",
 "agents": "Agent classes in scope: crawler (Crawlers and AI search), browser (Browser or computer-use agents), fleet (Customer agent fleets)\nPaths: marketing /blog/, docs /developers/, signup /demo, app /app/; training on marketing: deny; training on docs: allow\n\nrobots.txt:\nUser-agent: GPTBot\nUser-agent: ClaudeBot\nUser-agent: CCBot\nUser-agent: Google-Extended\nUser-agent: Applebot-Extended\nUser-agent: meta-externalagent\nDisallow: /blog/\nDisallow: /demo\nDisallow: /app/\n\nUser-agent: OAI-SearchBot\nUser-agent: Claude-SearchBot\nUser-agent: PerplexityBot\nDisallow: /demo\nDisallow: /app/\n\nUser-agent: *\nDisallow: /app/\n\nProbe results (class | surface | status):\n- GPTBot | blog | 403 | WAF rule -> enforced\n- OAI-SearchBot | developers docs | 200 -> pass\n- browser | app | 200 | signed agent allowed -> pass",
 "carry": "",
 "facts": "The page's checks (4 fail, 1 warn, 9 pass, 2 n/a); verdict: open to agents, with gaps to close:\n- Reachability gate | classes | na | Crawlers and AI search, Browser or computer-use agents, Customer agent fleets\n- Reachability gate | reach | pass | no AI-bot preset, no CAPTCHA-only signup; 2 probes passed\n- robots.txt, token by token | robots | pass | every allow cell is open to the tokens of its class (3 groups read)\n- robots.txt, token by token | ignored | warn | ChatGPT-User, Google-Agent, Perplexity-User may ignore robots.txt - a deny for them needs a CDN rule\n- Reachability gate | edge | pass | the CDN or WAF enforces the deny cells; 1 probe confirms it\n- Identity and credentials | keys | pass | agents get credentials of their own\n- Identity and credentials | measure | fail | tags exist but no baseline - no rung can show it worked\n- Agent-safe API basics | errors | pass | passes\n- Agent-safe API basics | idempotency | pass | passes\n- Agent-safe API basics | limits | pass | passes\n- Agent-safe API basics | untrusted | fail | fails - designed by: this plan\n- Agent-safe API basics | spend | fail | fails - designed by: this plan\n- Agent-safe API basics | sandbox | pass | passes\n- Agent-safe API basics | destructive | pass | passes\n- Agent-safe API basics | apisafe | fail | 2 items open: untrusted, spend\n- The page's plan | rungs | na | 1. A, 2. C, 3. B; E deferred, G deferred,\n\n[... 2278 more characters cut from this display ...]"
}

Reply:

{
 "task": "plan",
 "summary": "First record a baseline on the agent tags Quillbarrow already logs, so rung A, measurement, can show change before agent-labelled keys and out-of-band approval for supplier payments ship in rung C.",
 "identity": [
  {
   "class": "crawler",
   "identified_by": "the vendor's published IP list or a verified request signature at the WAF, never the GPTBot or OAI-SearchBot user-agent string alone",
   "authority": "own - search indexers and fetchers read public pages and hold no customer grant",
   "credential": "none issued; the verified signature or IP match proves the operator only",
   "scope": "search and fetch of /blog/ and /developers/, training on /developers/ only; /demo and /app/ stay denied for training and search tokens",
   "lifetime": "per request - each signature check expires with the request; vendor IP lists refreshed daily (proposed)",
   "sponsor": "proposed: the web platform engineer who owns the WAF rules",
   "revocation": "per agent - drop one vendor's token or IP range from the WAF rule",
   "approval": "none - crawlers only read public pages"
  },
  {
   "class": "browser",
   "identified_by": "a verified request signature from the agent operator, as the signed-agent probe on /app/ already shows, never the user-agent alone",
   "authority": "the user's - delegated",
   "credential": "an agent-labelled delegated access token issued by token exchange against the finance user's grant, held by the agent alone",
   "scope": "proposed: invoices:read and approvals:prepare in the user's own entity; no supplier bank-detail edits",
   "lifetime": "proposed: 15-minute token, never longer than the user token it derives from",
   "sponsor": "proposed: the finance team member who delegated the task",
   "revocation": "per agent - one browser agent's token is revoked while the finance user stays signed in",
   "approval": "releasing a supplier payment or deleting an invoice waits for a push to a second device of an approving finance user, never a confirm button in the /app/ session the agent drives"
  },
  {
   "class": "fleet",
   "identified_by": "an agent-labelled API key issued to each customer agent, with the actor chain in the logs",
   "authority": "the user's - delegated",
   "credential": "an agent-labelled scoped API key exchanged for short-lived access tokens, never a human's long-lived key",
   "scope": "proposed: invoices:read, invoices:write and payments:propose; payments:release withheld from agents",
   "lifetime": "proposed: 1-hour access token, key rotated every 90 days",
   "sponsor": "proposed: the customer's finance controller who created the key",
   "revocation": "per agent - each fleet agent's key is revoked on its own",
   "approval": "every supplier payment and any destructive write on invoices or regulated data waits for an email or push approval to a finance approver, outside the API channel"
  }
 ],
 "paths": [
  {
   "class": "crawler",
   "path": "edge",
   "why": "The WAF already enforces the deny cells, so crawlers only need /developers/ and the search cells on /blog/ to keep answering."
  },
  {
   "class": "browser",
   "path": "ui",
   "why": "Agents must operate the Quillbarrow web app, and the signed-agent probe shows /app/ already admits verified browser agents."
  },
  {
   "class": "fleet",
   "path": "api",
   "why": "Customer agent fleets call the public API with the SDKs Quillbarrow already ships, not a CLI or MCP server."
  }
 ],
 "signup": {
  "decision": "deleted",
  "detail": "Quillbarrow accounts are sales-led with no self-serve path, so there is nothing for an agent to provision and claim; the /demo form stays a human step."
 },
 "rungs": [
  {
   "rung": "A",
   "decision": "scheduled",
   "owner": "proposed: the data and analytics team",
   "skill": "this plan (MCP telemetry: mcp-server-offering)",
   "why": "Tags already exist but no baseline, so no later rung on Quillbarrow can show it worked until one is recorded."
  },
  {
   "rung": "C",
   "decision": "scheduled",
   "owner": "proposed: the identity and payments platform team",
   "skill": "api-auth-key-management, oauth2-provider-design",
   "why": "Agents move supplier money on a standing channel, so inside the hard date this shrinks to agent-labelled scoped keys plus the out-of-band approval gate, with the device grant waiting."
  },
  {
   "rung": "B",
   "decision": "scheduled",
   "owner": "proposed: the developer relations team",
   "skill": "coding-agent-docs-optimization",
   "why": "Kept in the default order so agents building on the API read /developers/ as Markdown."
  },
  {
   "rung": "E",
   "decision": "deferred",
   "owner": "proposed: the API platform team",
   "skill": "mcp-server-offering",
   "why": "An MCP server does not ship inside the hard date and stays gated behind write-safety."
  },
  {
   "rung": "G",
   "decision": "deferred",
   "owner": "proposed: the API platform team",
   "skill": "this plan",
   "why": "Discovery files are a background task beside the OpenAPI spec and do not ship inside the hard date."
  },
  {
   "rung": "D",
   "decision": "deleted",
   "owner": "proposed: none needed while deleted",
   "skill": "cli-interface-design, agent-skill-offering",
   "why": "No coding agents with a shell are in scope for Quillbarrow, and it has no CLI."
  },
  {
   "rung": "F",
   "decision": "deleted",
   "owner": "proposed: none needed while deleted",
   "skill": "this plan",
   "why": "Quillbarrow accounts are sales-led with no self-serve path, so provision-then-claim has nothing to provision."
  }
 ],
 "caveats": [
  "Untrusted-input handling and spend controls both fail and are owned by this plan; agents should not pay until they pass.",
  "ChatGPT-User, Google-Agent and Perplexity-User may ignore robots.txt, so any deny for them needs a WAF rule.",
  "The hard date is not given, so the minimal rung C scope is the planning bound."
 ]
}

What step 5 prints for it: A: scheduled | C: scheduled | B: scheduled | E: deferred | G: deferred | D: deleted | F: deleted

The page's check: kept - nothing to flag

Build the input yourself

Load the engine in node (global.window = global; require("./agkit.js"); require("./agreply.js")), then:

const K = AgentKit, R = AgentReply;
const f = K.form(Object.assign(K.blank(), { name: "Acme", c_coding: "yes", s_api: "yes", s_cli: "yes", preset: "on",
  robots: "User-agent: ClaudeBot\nUser-agent: Claude-User\nDisallow: /" }));
// f.matrix (the class by surface policy), f.robotsRead (each token on each path), f.rungs, f.paths, f.gates
// K.robotsTxt(f), K.matrixCsv(f) - a robots.txt that states the matrix, and the matrix sheet
const b = K.buildInput(f, "plan");                           // or "rollout" (R.laneForm(form, "rollout", plan) fills carry)
const wire = K.toWire(b.input);                              // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "plan");
// R.check(reply, b)                                         // {status: kept or flagged, notes}

Get a token for the steps below from /tokens.html.

Step 1 — a tiny client helper

One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN"   # from https://agent-door.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"agent-door"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.

# input.json: the run input object - {"task": "plan", "product": "Product: Vanecroft
What it does: ...", "agents": "Agent classes in scope: crawler, coding, chat ...", "carry": "", "facts": "The page's checks (...); verdict: agents meant to get in are blocked - fix the gate first:
- Reachability gate | reach | fail | ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses agent-door:<task>:<inputHash>:a<attempt> - the lane is part of the key, retry_note is left out of the hash - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A plan reply carries summary, identity, paths, signup, rungs and caveats; a rollout reply carries summary, gate_fixes, measurement, steps, exit and caveats. The code below prints the gist of either: each rung and its decision, or each step and when it is done.

KEY="agent-door:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r 'if .task == "rollout" then (.steps[] | "\(.rung): \(.done_when)") 
       else (.rungs[] | "\(.rung): \(.decision)") end' reply.json

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: agent-door:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json