← Surface Order
API
Tokens

Run Surface Order from your own code

Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page ranks the lead posture from the buyer type, names the spec and telemetry groundwork, weighs the demand signals, sequences every surface in play into capacity-wide waves behind the contract it derives from - each with its reversal-cost tier and owning skill - and checks your draft order, then hands it all to the run as facts. The run writes the plan or the handoff cards; the page then holds it to its own facts (soreply.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":"surface-order"}), 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, platform, surfaces, facts) is missing. The page always sends all five of task, platform, surfaces, 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 the order, then hand it to the teams:

A missing or unknown task is answered as plan. Both lanes send the same five fields. The facts are the page's checks and its plan, 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 handoffs lane.

The input

FieldRequiredWhat
taskyesplan or handoffs. If it is missing or neither, do plan and set "task": "plan".
platformyesthe platform, what it does, the buyer type, time to value, the spec, telemetry, agent demand, capacity, owners, the deadline, one-off or compounding, the effort ceiling, GraphQL pain and the plan date; "not given" means not given.
surfacesyeseach surface and its depth today, the demand signals as surface | kind | evidence, and the user's draft order.
carrysend it (empty for plan)handoffs task only: the plan decisions, when the plan task ran first; may be empty.
factsyesthe page's checks, one line each as area | id | status | evidence, the postures and candidates, the groundwork, the page's plan as surface | decision | wave | tier | sibling | serves | signal | why, and the audiences. 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/api-integration-surface-strategy - the only standard the run applies:

House rules

The replies

plan

Write the sequencing plan: the candidate sequences, the recommendation, the audience map and the build order.

  1. summary - one sentence, at most 40 words: the recommended posture and the first thing that must happen.
  2. candidates - one object per candidate the facts list, in that order: posture (its id), sequence (one sentence: the order this posture would build in, for this platform), tradeoff (one sentence: what it costs and what it buys, for this platform).
  3. recommend - posture (the id the facts recommend), why (one sentence tied to this platform's buyer and signals).
  4. audiences - one object per audience the facts list, in that order: audience (its id), writer (who writes the calling code), surfaces (one sentence: which surfaces serve it now and in the plan).
  5. sequence - one object per scheduled surface, in the page's order, no others: surface (its id), wave (the page's wave, a number), why (one sentence: the signal or rule that puts it there, in this platform's terms).
  6. groundwork - short strings: what happens before any new surface, from the facts; [] when nothing.
  7. caveats - at most 4 short strings: where the user's draft breaks a gate and what replaces it, deferred surfaces worth watching; [] when there is nothing to say.
{
  "task": "plan",
  "summary": "one sentence",
  "candidates": [ { "posture": "rest", "sequence": "one sentence", "tradeoff": "one sentence" } ],
  "recommend": { "posture": "rest", "why": "one sentence" },
  "audiences": [ { "audience": "integrators", "writer": "who writes the code", "surfaces": "one sentence" } ],
  "sequence": [ { "surface": "webhooks", "wave": 1, "why": "one sentence" } ],
  "groundwork": [ "a step" ],
  "caveats": [ ]
}

The page holds it to its own facts (check() in soreply.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; exactly the page's candidates, in order, each with a sequence and a trade-off; the page's recommended posture, with a reason; every audience the page lists, each with who writes the code and its surfaces; the sequence exactly the page's scheduled surfaces, in order and wave; the groundwork named when the spec or telemetry needs it; MCP monetization labelled speculative; a caveat when the draft order breaks a gate.

handoffs

Write the per-surface handoff cards and the review cadence the team will run the plan on.

  1. summary - one sentence, at most 40 words.
  2. handoffs - one object per surface in the page's plan, in its order, no others: surface (its id), decision (scheduled, deferred or declined - the page's), audience (one sentence: who it serves on this platform), signal (one sentence: the demand signal that justified the decision, or that none exists), tier (the page's reversal-cost tier, as written), owner (who owns it - from the owners the input names, or proposed and said so), sibling (the page's sibling skill id, copied as the facts write it - for embedded that is "none - decided in this plan"), next (one sentence: the first step for a scheduled surface, or what would reopen a deferred or declined one).
  3. review - date (the page's review date as YYYY-MM-DD; when the facts give none, a date a quarter out written as "proposed: YYYY-MM-DD"), triggers (at least three short strings that reopen the plan early, in this platform's terms), recheck (short strings: the evidence the next review re-checks).
  4. caveats - at most 4 short strings; [] when there is nothing to say.
{
  "task": "handoffs",
  "summary": "one sentence",
  "handoffs": [ { "surface": "webhooks", "decision": "scheduled", "audience": "one sentence", "signal": "one sentence", "tier": "low-medium", "owner": "a team", "sibling": "webhook-platform-design", "next": "one sentence" } ],
  "review": { "date": "2027-02-01", "triggers": [ "a trigger" ], "recheck": [ "evidence" ] },
  "caveats": [ ]
}

The page requires: the reply answers the task it was sent; the same text rules; one handoff per surface in the page's plan, in its order, each with the page's decision, tier and owning skill and a next step; the skill's coverage gate - an audience, a signal, a tier, an owner and a sibling on every card; owners labelled proposed when none were given; the review on the page's date (or proposed when there is no plan date), at least three triggers and something to re-check.

Worked examples

Three runs from the page's free samples (invented companies): the plan for an invoicing API whose draft puts MCP first, the handoff cards for the same platform carrying that plan, and the plan for an analytics platform that lost deals over warehouse access. 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 - Ledgerline - MCP first on customer questions

The page read it as: your draft order breaks the skill's gates; postures rest, embedded, graphql; build order webhooks (wave 1), sdks (wave 2); 1 of 7 gates; failing checks: spec, depth, derivation, novelty, concurrency, signals.

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

{
 "task": "plan",
 "platform": "Platform: Ledgerline\nWhat it does: An invoicing and payment-reminder API that developers at small SaaS companies wire into their billing.\nBuyer: a developer picks the tool bottom-up; time to value: days acceptable\nSpec: exists but drifts from the API; per-client telemetry: instrumented; agent demand: customer questions only\nCapacity: 1 surface at once; owners available: One API team of four engineers\nDeadline: no hard date; a compounding surface roadmap; effort ceiling: medium\nGraphQL: no confirmed pain; plan date: 2026-11-02",
 "surfaces": "Surfaces today (surface | depth):\n- REST API | self-serve end to end\n- Webhooks | partial - some steps still manual\n- Server SDKs | absent\n- GraphQL API | absent\n- gRPC API | absent\n- SQL / JDBC access | absent\n- Bulk data / warehouse shares | absent\n- CLI | absent\n- MCP server | absent\n- Embedded components | absent\n- Terraform / OpenTofu provider | absent\n\nDemand signals (surface | kind | evidence):\n- Server SDKs | usage telemetry | 71% of REST requests carry a hand-rolled Node client user-agent\n- Webhooks | pipeline / blocked deal | a reseller paused its rollout because payment.failed events need a manual export\n- MCP server | community ask | three customers asked on the forum whether we support agents\n\nThe user's draft order (surface | when):\n- MCP server | now\n- Server SDKs | now\n- GraphQL API | next",
 "carry": "",
 "facts": "The page's checks (6 fail, 1 warn, 1 pass, 6 n/a); verdict: your draft order breaks the skill's gates:\n- Surfaces and the spec | buyer | na | a developer picks the tool bottom-up; time to value: days acceptable\n- Surfaces and the spec | inventory | na | REST API (self-serve), Webhooks (partial)\n- Surfaces and the spec | spec | fail | exists but drifts from the API - fix it before scheduling any derived surface\n- Surfaces and the spec | depth | fail | Webhooks partial - shallow API-first: a partner discovers the manual step mid-integration\n- Demand signals | telemetry | pass | usage by client type can be read\n- Demand signals | signals | na | Server SDKs: usage telemetry; Webhooks: pipeline / blocked deal; MCP server: community ask\n- Demand signals | community | warn | MCP server - cheap to emit; confirm against pipeline data or telemetry\n- Demand signals | agents | na | customer questions only\n- Your draft order | derivation | fail | MCP server on a spec that is not the source of truth; Server SDKs on a spec that is not the source of truth\n- Your draft order | novelty | fail | MCP leads the draft with customer questions only - novelty-driven MCP-first inverts the order\n- Your draft order | concurrency | fail | 2 surfaces now against a capacity of 1\n- Your draft order | signals | fail | GraphQL API - no signal: surface sprawl without telemetry\n- The page's plan | posture | na |\n\n[... 2192 more characters cut from this display ...]"
}

Reply:

{
 "task": "plan",
 "summary": "Recommend rest: Ledgerline first makes its drifting machine-readable spec the linted source of truth in CI, then finishes webhooks end to end so the reseller no longer needs a manual payment.failed export.",
 "candidates": [
  {
   "posture": "rest",
   "sequence": "Fix the drifting spec in CI, finish the partial webhooks so payment.failed events flow without a manual export, then generate server SDKs from the corrected spec.",
   "tradeoff": "It spends the four-engineer API team on unglamorous repair before anything new ships, and buys a contract the reseller and the hand-rolled Node clients can safely depend on."
  },
  {
   "posture": "embedded",
   "sequence": "Fix the spec and finish webhooks as the API foundation, then add embedded invoicing components on top before any SDK work.",
   "tradeoff": "It adds a medium-tier layer for a non-technical buyer Ledgerline does not have, since developers pick it bottom-up and days to value are acceptable, and delays the SDKs the telemetry points at."
  }
 ],
 "recommend": {
  "posture": "rest",
  "why": "Developers at small SaaS companies choose Ledgerline bottom-up, the one blocked deal is a webhooks gap, and telemetry shows most REST traffic coming from hand-rolled Node clients, so the API rung comes first."
 },
 "audiences": [
  {
   "audience": "firstparty",
   "writer": "A person on a customer's own engineering team.",
   "surfaces": "The self-serve REST API serves them today and stays their surface through the plan."
  },
  {
   "audience": "integrators",
   "writer": "A person or a code generator at a reseller or partner.",
   "surfaces": "REST serves them now, completed webhooks arrive in wave 1 and server SDKs in wave 2."
  },
  {
   "audience": "agents",
   "writer": "An agent writing its own calls.",
   "surfaces": "Nothing serves them yet; MCP stays parked while agent demand is limited to forum questions."
  }
 ],
 "sequence": [
  {
   "surface": "webhooks",
   "wave": 1,
   "why": "A reseller paused its rollout because payment.failed events still need a manual export, a blocked deal that outranks every other signal here."
  },
  {
   "surface": "sdks",
   "wave": 2,
   "why": "Telemetry shows 71% of REST requests come from a hand-rolled Node client, and the SDKs are generated only after the spec stops drifting."
  }
 ],
 "groundwork": [
  "Make the Ledgerline machine-readable spec the linted source of truth in CI so it stops drifting from the API."
 ],
 "caveats": [
  "The draft puts MCP first on three forum questions; it stays deferred until RFPs or observed agent traffic appear.",
  "The draft builds MCP and server SDKs together against a capacity of one; webhooks takes wave 1 alone and SDKs follow in wave 2.",
  "The draft schedules GraphQL next with no signal and no named pain; it is declined.",
  "Server SDKs generated from today's drifting spec would bake the drift into a high-tier contract, so the spec fix comes first."
 ]
}

What step 5 prints for it: webhooks: 1 | sdks: 2

The page's check: kept - nothing to flag

handoffs - Ledgerline - MCP first on customer questions

The input carries the saved plan (R.laneForm fills carry from the plan reply), as the page's handoff does. The page's plan: wire, wireShown, wireNote, reply, shown, cuts, status, notes, carried, gist.

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

{
 "task": "handoffs",
 "platform": "Platform: Ledgerline\nWhat it does: An invoicing and payment-reminder API that developers at small SaaS companies wire into their billing.\nBuyer: a developer picks the tool bottom-up; time to value: days acceptable\nSpec: exists but drifts from the API; per-client telemetry: instrumented; agent demand: customer questions only\nCapacity: 1 surface at once; owners available: One API team of four engineers\nDeadline: no hard date; a compounding surface roadmap; effort ceiling: medium\nGraphQL: no confirmed pain; plan date: 2026-11-02",
 "surfaces": "Surfaces today (surface | depth):\n- REST API | self-serve end to end\n- Webhooks | partial - some steps still manual\n- Server SDKs | absent\n- GraphQL API | absent\n- gRPC API | absent\n- SQL / JDBC access | absent\n- Bulk data / warehouse shares | absent\n- CLI | absent\n- MCP server | absent\n- Embedded components | absent\n- Terraform / OpenTofu provider | absent\n\nDemand signals (surface | kind | evidence):\n- Server SDKs | usage telemetry | 71% of REST requests carry a hand-rolled Node client user-agent\n- Webhooks | pipeline / blocked deal | a reseller paused its rollout because payment.failed events need a manual export\n- MCP server | community ask | three customers asked on the forum whether we support agents\n\nThe user's draft order (surface | when):\n- MCP server | now\n- Server SDKs | now\n- GraphQL API | next",
 "carry": "Recommended posture: REST plus webhooks first - Developers at small SaaS companies choose Ledgerline bottom-up, the one blocked deal is a webhooks gap, and telemetry shows most REST traffic coming from hand-rolled Node clients, so the API rung comes first.\nGroundwork: Make the Ledgerline machine-readable spec the linted source of truth in CI so it stops drifting from the API.\nSequence: webhooks (wave 1): A reseller paused its rollout because payment.failed events still need a manual export, a blocked deal that outranks every other signal here.; sdks (wave 2): Telemetry shows 71% of REST requests come from a hand-rolled Node client, and the SDKs are generated only after the spec stops drifting.\nAudiences: firstparty - The self-serve REST API serves them today and stays their surface through the plan.; integrators - REST serves them now, completed webhooks arrive in wave 1 and server SDKs in wave 2.; agents - Nothing serves them yet; MCP stays parked while agent demand is limited to forum questions.",
 "facts": "The page's checks (6 fail, 1 warn, 1 pass, 6 n/a); verdict: your draft order breaks the skill's gates:\n- Surfaces and the spec | buyer | na | a developer picks the tool bottom-up; time to value: days acceptable\n- Surfaces and the spec | inventory | na | REST API (self-serve), Webhooks (partial)\n- Surfaces and the spec | spec | fail | exists but drifts from the API - fix it before scheduling any derived surface\n- Surfaces and the spec | depth | fail | Webhooks partial - shallow API-first: a partner discovers the manual step mid-integration\n- Demand signals | telemetry | pass | usage by client type can be read\n- Demand signals | signals | na | Server SDKs: usage telemetry; Webhooks: pipeline / blocked deal; MCP server: community ask\n- Demand signals | community | warn | MCP server - cheap to emit; confirm against pipeline data or telemetry\n- Demand signals | agents | na | customer questions only\n- Your draft order | derivation | fail | MCP server on a spec that is not the source of truth; Server SDKs on a spec that is not the source of truth\n- Your draft order | novelty | fail | MCP leads the draft with customer questions only - novelty-driven MCP-first inverts the order\n- Your draft order | concurrency | fail | 2 surfaces now against a capacity of 1\n- Your draft order | signals | fail | GraphQL API - no signal: surface sprawl without telemetry\n- The page's plan | posture | na |\n\n[... 2213 more characters cut from this display ...]"
}

Reply:

{
 "task": "handoffs",
 "summary": "Ledgerline lints its drifting spec in CI, finishes webhooks for the paused reseller, then generates server SDKs; MCP stays parked and GraphQL is declined, all owned by the four-engineer API team.",
 "handoffs": [
  {
   "surface": "webhooks",
   "decision": "scheduled",
   "audience": "Third-party integrators, such as the reseller, whose billing reacts to Ledgerline invoice and payment events.",
   "signal": "A reseller paused its rollout because payment.failed events still need a manual export, the one blocked deal on record.",
   "tier": "low-medium",
   "owner": "The API team of four engineers",
   "sibling": "webhook-platform-design",
   "next": "Replace the manual export so payment.failed events are delivered end to end, then tell the reseller the rollout can resume."
  },
  {
   "surface": "sdks",
   "decision": "scheduled",
   "audience": "Developers at small SaaS companies calling Ledgerline from their own language, starting with the Node users who hand-roll clients today.",
   "signal": "Usage telemetry shows 71% of REST requests carry a hand-rolled Node client user-agent.",
   "tier": "high",
   "owner": "The API team of four engineers, once webhooks ship",
   "sibling": "sdk-portfolio-strategy",
   "next": "Once the spec passes its CI lint against the live API, generate a Node SDK from it before any other language."
  },
  {
   "surface": "mcp",
   "decision": "deferred",
   "audience": "AI agent clients acting on Ledgerline invoices for customers, an audience nothing serves yet.",
   "signal": "Only customer questions: three customers asked on the forum whether Ledgerline supports agents.",
   "tier": "medium (today)",
   "owner": "The API team of four engineers (proposed, when reopened)",
   "sibling": "mcp-server-offering",
   "next": "Reopen when an RFP requires MCP compatibility or telemetry shows agent traffic hitting the REST API."
  },
  {
   "surface": "graphql",
   "decision": "declined",
   "audience": "Client apps that would want client-driven queries over Ledgerline invoices and payments.",
   "signal": "None exists: no request, no telemetry and no named pain from integrators.",
   "tier": "high",
   "owner": "The API team of four engineers (proposed, as the team that would review any reopening)",
   "sibling": "public-graphql-api-design",
   "next": "Reopen only if integrators name a concrete pain that the REST API cannot solve and confirm it."
  }
 ],
 "review": {
  "date": "2027-02-01",
  "triggers": [
   "Another deal or reseller rollout stalls on a missing Ledgerline surface",
   "An RFP asks for MCP compatibility or agent traffic shows up in the REST telemetry",
   "Telemetry by client type stops showing hand-rolled Node clients as the main REST callers",
   "The four-engineer API team loses capacity while webhooks or SDKs are in flight"
  ],
  "recheck": [
   "Whether the spec still passes its CI lint against the live API",
   "Whether the reseller resumed its rollout after the payment.failed fix",
   "The share of REST requests from hand-rolled Node clients versus the generated SDK",
   "Any new forum, RFP or traffic evidence of agent demand"
  ]
 },
 "caveats": [
  "The draft put MCP and server SDKs first at once; the spec lint replaces them, then webhooks alone in wave 1 against a capacity of one.",
  "GraphQL in the draft has no signal behind it, so it is declined rather than scheduled next.",
  "The three forum asks about agents are unconfirmed; check them against telemetry before MCP moves."
 ]
}

What step 5 prints for it: webhooks: scheduled | sdks: scheduled | mcp: deferred | graphql: declined

The page's check: kept - nothing to flag

plan - Tallyfold - deals lost over warehouse access

The page read it as: a sequenced plan ready to review; postures rest, embedded, graphql; build order bulk (wave 1), embedded (wave 1), sql (wave 2), mcp (wave 2); 7 of 7 gates; failing checks: none.

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

{
 "task": "plan",
 "platform": "Platform: Tallyfold\nWhat it does: A product-analytics platform: developers instrument events, and growth and finance teams read the dashboards.\nBuyer: both in the same deal; time to value: days acceptable\nSpec: linted source of truth in CI; per-client telemetry: instrumented; agent demand: RFPs or security reviews require MCP compatibility\nCapacity: 2 surfaces at once; owners available: Platform API team, Data team, a new Agents pod\nDeadline: this quarter for Bulk data / warehouse shares; a compounding surface roadmap; effort ceiling: high\nGraphQL: no confirmed pain; plan date: 2026-11-02",
 "surfaces": "Surfaces today (surface | depth):\n- REST API | self-serve end to end\n- Webhooks | self-serve end to end\n- Server SDKs | self-serve end to end\n- GraphQL API | absent\n- gRPC API | absent\n- SQL / JDBC access | absent\n- Bulk data / warehouse shares | absent\n- CLI | absent\n- MCP server | absent\n- Embedded components | absent\n- Terraform / OpenTofu provider | absent\n\nDemand signals (surface | kind | evidence):\n- Bulk data / warehouse shares | pipeline / blocked deal | lost two enterprise deals this year over missing Snowflake warehouse access\n- MCP server | pipeline / blocked deal | two RFPs this quarter list MCP compatibility as required\n- GraphQL API | community ask | 40 upvotes on a forum thread asking for GraphQL\n- SQL / JDBC access | usage telemetry | 18% of API calls are paginated full-table pulls feeding BI tools\n\nThe user's draft order (surface | when):\n- Bulk data / warehouse shares | now\n- SQL / JDBC access | next\n- MCP server | later",
 "carry": "",
 "facts": "The page's checks (0 fail, 1 warn, 7 pass, 6 n/a); verdict: a sequenced plan ready to review:\n- Surfaces and the spec | buyer | na | both in the same deal - an economic buyer and a technical user; time to value: days acceptable\n- Surfaces and the spec | inventory | na | REST API (self-serve), Webhooks (self-serve), Server SDKs (self-serve)\n- Surfaces and the spec | spec | pass | linted in CI - SDKs, MCP and generated docs can derive from it\n- Surfaces and the spec | depth | pass | 3 surfaces self-serve\n- Demand signals | telemetry | pass | usage by client type can be read\n- Demand signals | signals | na | Bulk data / warehouse shares: pipeline / blocked deal; MCP server: pipeline / blocked deal; GraphQL API: community ask; SQL / JDBC access: usage telemetry\n- Demand signals | community | warn | GraphQL API - cheap to emit; confirm against pipeline data or telemetry\n- Demand signals | agents | na | RFPs or security reviews require MCP compatibility\n- Your draft order | derivation | pass | every derived surface follows its contract\n- Your draft order | novelty | pass | MCP does not lead\n- Your draft order | concurrency | pass | 1 surface now against a capacity of 2\n- Your draft order | signals | pass | each drafted surface has a signal\n- The page's plan | posture | na | REST plus webhooks first > Embedded-first, on an API foundation > GraphQL-led\n- The page's plan | order | na |\n\n[... 2762 more characters cut from this display ...]"
}

Reply:

{
 "task": "plan",
 "summary": "Recommend rest, already in place: Tallyfold starts warehouse shares now to meet this quarter's deadline after two lost Snowflake deals, with the embedded layer in the same wave.",
 "candidates": [
  {
   "posture": "rest",
   "sequence": "Build on the self-serve REST, webhooks and SDKs by shipping warehouse shares and embedded dashboards in wave 1, then SQL access and an MCP layer generated from REST in wave 2.",
   "tradeoff": "It holds the team to two surfaces at a time on the linted spec, and buys the Snowflake deals and the MCP RFPs without reopening the existing contract."
  },
  {
   "posture": "embedded",
   "sequence": "Lead with embedded dashboards for growth and finance readers on the existing API, then add warehouse shares, SQL access and MCP.",
   "tradeoff": "It gives the economic buyer value first but risks missing this quarter's warehouse-share deadline that two lost enterprise deals set."
  },
  {
   "posture": "deal",
   "sequence": "Pair the API foundation for the developers who instrument events with an embedded layer for the growth and finance buyer, both in wave 1 next to warehouse shares.",
   "tradeoff": "It serves both sides of the same deal at once, at the cost of filling both capacity slots in wave 1 and pushing SQL and MCP to wave 2."
  }
 ],
 "recommend": {
  "posture": "rest",
  "why": "Developers instrument Tallyfold while growth and finance teams buy, REST, webhooks and SDKs are already self-serve on a linted spec, and the blocked deals point at data access built on that foundation."
 },
 "audiences": [
  {
   "audience": "firstparty",
   "writer": "A person on a customer's engineering team instrumenting events.",
   "surfaces": "The self-serve REST API serves them today and throughout the plan."
  },
  {
   "audience": "integrators",
   "writer": "A person or a code generator at a partner.",
   "surfaces": "REST, webhooks and server SDKs already serve them self-serve, with no change in the plan."
  },
  {
   "audience": "operators",
   "writer": "Nobody; growth and finance staff configure dashboards rather than code.",
   "surfaces": "Nothing serves them outside the product today; embedded components arrive in wave 1."
  },
  {
   "audience": "bi",
   "writer": "A person writing SQL, or a BI tool.",
   "surfaces": "Today they page through REST in full-table pulls; warehouse shares arrive in wave 1 and SQL access in wave 2."
  },
  {
   "audience": "agents",
   "writer": "An agent writing its own calls.",
   "surfaces": "Nothing serves them yet; an MCP server generated from the REST API arrives in wave 2."
  }
 ],
 "sequence": [
  {
   "surface": "bulk",
   "wave": 1,
   "why": "Two enterprise deals were lost this year over missing Snowflake warehouse access, and the deadline for warehouse shares is this quarter."
  },
  {
   "surface": "embedded",
   "wave": 1,
   "why": "The deal pairs a technical user with a growth and finance buyer, so the embedded layer goes on the API foundation in the same wave."
  },
  {
   "surface": "sql",
   "wave": 2,
   "why": "Telemetry shows 18% of API calls are paginated full-table pulls feeding BI tools."
  },
  {
   "surface": "mcp",
   "wave": 2,
   "why": "Two RFPs this quarter require MCP compatibility, so a thin layer generated from the linted REST spec comes last."
  }
 ],
 "groundwork": [],
 "caveats": [
  "The draft order leaves embedded components out; they join warehouse shares in wave 1 for the economic buyer.",
  "GraphQL stays deferred: 40 forum upvotes are not an integrator-confirmed pain.",
  "Any per-call charge for agent use of the MCP server is speculative; the RFPs ask for compatibility, not a paid tier."
 ]
}

What step 5 prints for it: bulk: 1 | embedded: 1 | sql: 2 | mcp: 2

The page's check: kept - nothing to flag

Build the input yourself

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

const K = SurfaceKit, R = SurfaceReply;
const f = K.form(Object.assign(K.blank(), { name: "Acme", spec: "drifts", agents: "asks", capacity: "1",
  d_rest: "selfserve", signals: "SDKs | telemetry | 60% of calls from one hand-rolled client",
  draft: "MCP server | now\nSDKs | now" }));
// f.posture (the ranked postures), f.planned (groundwork, the build order in waves), f.audiences, f.gates
// K.gateMd(f), K.orderCsv(f) - the gate sheet and the build-order sheet
const b = K.buildInput(f, "plan");                           // or "handoffs" (R.laneForm(form, "handoffs", 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://surface-order.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":"surface-order"}' | 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", "platform": "Platform: Ledgerline
What it does: ...", "surfaces": "Surfaces today (surface | depth):
- REST API | self-serve end to end ...", "carry": "", "facts": "The page's checks (...); verdict: your draft order breaks the skill's gates:
- Your draft order | novelty | 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 surface-order:<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, candidates, recommend, audiences, sequence, groundwork and caveats; a handoffs reply carries summary, handoffs, review and caveats. The code below prints the gist of either: each scheduled surface and its wave, or each surface and its decision.

KEY="surface-order:$(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 == "handoffs" then (.handoffs[] | "\(.surface): \(.decision)") 
       else (.sequence[] | "\(.surface): wave \(.wave)") 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: surface-order:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json