← Prebuild Desk / API
Tokens

Drive Prebuild Desk from your own code

The page does its free work in the browser: it reads a brief against the seven risks of the before-you-build skill (demand, positioning, monetization, retention, trust, distribution, feature adoption), grades each as backed, named or silent with line numbers, lists the claims standing in for evidence and the beliefs stated as plans, extracts the planned build, sets an evidence floor and raises flags PB-01..PB-14. Its two paid lanes are available over HTTP. You send one JSON object with task set to "review" or "test", and the model replies with one JSON object. The easiest way to build the facts string is the page itself: Briefkit.buildInput(lane, Briefkit.analyze(brief, {kind}), {note, assumption, reviewHandoff}) in briefkit.js, which also runs under Node.

Base URL: https://api.skillsafe.ai/v1/app-api. Every call sends Content-Type: application/json and, once you have a token, Authorization: Bearer <token>. Both lanes run on gpt-terra with a 10% publisher markup. A run needs a signed-in (user) token and is metered: the platform holds the estimate's hold_credits while the run is in flight and then charges only what it used (charged_credits). /estimate is free. A plain-text summary is at /llms.txt.

The response envelope

Every response is {"data": ..., "error": null} on success or {"data": null, "error": {"code", "message"}} on failure.

Error codes

HTTPcodemeaning
400VALIDATION_ERRORThe body is not a JSON object, or a field is the wrong type. Every field of the run input is a string.
401UNAUTHORIZEDNo token, or an expired one: get a new one (step 2).
402INSUFFICIENT_CREDITSThe balance is below the run's minimum. Top up at skillsafe.ai/account/billing/.
403FORBIDDENA guest token tried a metered run: sign in for a personal token.
404NOT_FOUNDUnknown job id.
429RATE_LIMITEDBack off and retry with the same Idempotency-Key.

The task field: two lanes

taskwhat the model does
"review"The pre-mortem. A low, medium or high risk verdict; all seven risks rated on the brief's own evidence with exact quotes; the single main assumption most likely to break the project; the smallest evidence to find first; one next step; what to delay and until when; the evidence that already lowers the risk; and a note separating product risk from engineering difficulty.
"test"The test plan for one assumption: the method, who and how many, 2 to 6 steps, a success line and a kill line with numbers, a timebox of six weeks or less, the cost, a ready-to-use asset (an outreach message, an interview script, landing copy, a presale offer), what to build for the test only, and what stays delayed.

Input fields (all strings)

fieldwhencontent
taskrequiredreview or test
briefrequiredThe brief, verbatim. Past 16,000 characters the page cuts the middle on a line boundary and leaves a [prebuild-desk: ...] marker.
kindrequiredproduct, feature, landing, internal or agent - the kind the facts were built with; it sets the core risks.
factsrequiredA JSON string: kind, kind_inferred, kind_why, floor (level, why, core), risks (id, status: backed|named|silent|na, signals with line, strength, label, quote), claims, beliefs, scope (items, eng_terms, eng_share), numbers, flags (uid, id, sev, risk, name), summary, notes. It is Briefkit.factsOf(A).
noteoptionalYour constraints, up to 1,500 characters.
assumptiontest onlyThe assumption to test, up to 1,200 characters. Without it the run uses the review's main assumption, or picks the riskiest one from the scan.
review_handofftest onlyA JSON string of a review reply's verdict, main_assumption{risk, statement}, evidence_first and delay[{item, until}], for the same brief.
retry_noteoptionalOnly on a reformat retry: what was wrong with the previous reply.

The reply contracts

Both lanes share an envelope: task, kind, headline, questions[], summary.

task: "review"

verdict (low|medium|high), verdict_why, risks[{risk, level: low|medium|high|n/a, evidence[], missing, note}] (seven, in order: demand, positioning, monetization, retention, trust, distribution, adoption), main_assumption{risk, statement, why}, evidence_first{signal, why}, do_next{step, scope}, delay[{item, until}], lowers_risk[{quote, why}], engineering_note.

task: "test"

assumption{risk, statement, source}, method, method_why, audience{who, where, how_many}, steps[], success{metric, threshold}, kill{metric, threshold}, timebox, cost, asset{kind, text}, build_now[], still_delay[].

Worked examples

These are the page's own bundled examples (invented companies), with the input as app.js builds it and the saved model reply the page replays for free.

Review: an invoice-chasing SaaS for freelancers

Input (brief and facts shortened here):

{
 "task": "review",
 "brief": "# Dunwick - automatic invoice follow-ups for freelancers\n\nEvery freelancer hates chasing late invoices. It is a huge market: there are 70 million freelancers in the US alone, and obviously all of them get paid late at some point. Nobody is doing this well.\n\n## The idea\n\nDunwick connects to your bank account and your invoicing tool, watches for invoices that go past due, and sends polite, escalating reminder emails written by AI in your own tone of voice. When the client pays, Dunwick notices the... (1633 characters in all)",
 "kind": "product",
 "facts": "(a JSON string; decoded: kind product, floor high, risks named: demand, monetization, retention, trust, distribution; backed: positioning; na: adoption, 5 claims, 1 beliefs, 9 planned items, flags PB-02, PB-09, PB-04, PB-06, PB-07, PB-12, PB-10)",
 "note": "We are two people with about ten weeks of savings each."
}

Reply:

{
 "task": "review",
 "kind": "product",
 "verdict": "high",
 "verdict_why": "The only behavioural evidence is the founder's own Friday spreadsheet, while the plan is a four-month, nine-part build that outlasts the savings in the note.",
 "risks": [
  {
   "risk": "demand",
   "level": "high",
   "evidence": [
    "I chase invoices manually in a spreadsheet every Friday",
    "Every freelancer hates chasing late invoices.",
    "obviously all of them get paid late at some point"
   ],
   "missing": "A count of freelancers other than the founder who chase late invoices every week or month and already spend time or money on it.",
   "note": "One person's workaround is real but tiny evidence, and the 70 million figure is an unsourced claim, not a count of people with this problem."
  },
  {
   "risk": "positioning",
   "level": "high",
   "evidence": [
    "Nobody is doing this well.",
    "automatic invoice follow-ups for freelancers"
   ],
   "missing": "What freelancers use today besides a manual spreadsheet, including the reminder settings inside their invoicing tools, and why they would switch.",
   "note": "The brief names no alternative to beat, so it cannot yet say why Dunwick wins against doing nothing or using what freelancers already have."
  },
  {
   "risk": "monetization",
   "level": "high",
   "evidence": [
    "We think freelancers will happily pay $19/month for this"
   ],
   "missing": "Any freelancer, other than the founder, who has paid or pre-paid $19/month, or who pays for a comparable tool today.",
   "note": "The price is a belief from the scan, not a 
... (shortened here; the page shows it whole)

Test plan: the same brief, carrying the review

Input (with the review handed over):

{
 "task": "test",
 "brief": "# Dunwick - automatic invoice follow-ups for freelancers\n\nEvery freelancer hates chasing late invoices. It is a huge market: there are 70 million freelancers in the US alone, and obviously all of them get paid late at some point. Nobody is doing this well.\n\n## The idea\n\nDunwick connects to your bank account and your invoicing tool, watches for invoices that go past due, and sends polite, escalating reminder emails written by AI in your own tone of voice. When the client pays, Dunwick notices the... (1633 characters in all)",
 "kind": "product",
 "facts": "(a JSON string; decoded: kind product, floor high, risks named: demand, monetization, retention, trust, distribution; backed: positioning; na: adoption, 5 claims, 1 beliefs, 9 planned items, flags PB-02, PB-09, PB-04, PB-06, PB-07, PB-12, PB-10)",
 "note": "We are two people with about ten weeks of savings each.",
 "assumption": "Freelancers other than the founder chase late invoices often enough that they will pay $19/month to have the reminders sent for them.",
 "review_handoff": "{\"verdict\":\"high\",\"main_assumption\":{\"risk\":\"demand\",\"statement\":\"Freelancers other than the founder chase late invoices often enough that they will pay $19/month to have the reminders sent for them.\"},\"evidence_first\":\"Freelancers outside the founder's own practice handing over real past-due invoices and paying $19 fo..."
}

Reply:

{
 "task": "test",
 "kind": "product",
 "assumption": {
  "risk": "demand",
  "statement": "Freelancers other than the founder chase late invoices often enough that they will pay $19/month to have the reminders sent for them.",
  "source": "review"
 },
 "method": "presale",
 "method_why": "The brief's only demand evidence is the founder's own Friday spreadsheet and a $19/month belief, so the test needs other freelancers paying real money for a hand-run service before any of the four-month build starts.",
 "audience": {
  "who": "Freelancers outside the founder's own practice, designers first, who have at least one invoice past due right now",
  "where": "The freelancer communities the brief plans to launch in, plus the founder's own network from six years of freelance design work",
  "how_many": "30 conversations"
 },
 "steps": [
  "List 40 freelancers you can reach directly in [community or group names] and your own contacts, excluding close friends.",
  "Send the outreach message below and book a 15-minute call with everyone who replies.",
  "On each call, ask how they chased their last late invoice, then offer the $19 first month of reminders sent for them by hand.",
  "For each person who pays, take one real past-due invoice and send the reminders yourself from plain templates, logging each send and payment in a spreadsheet.",
  "After 10 working days, count paid presales and handed-over invoices against the thresholds below and decide go or stop."
 ],
 "success": {
  "metric": "Freelancers (not the founder) who pay $19 for a first month and hand over at least one real 
... (shortened here; the page shows it whole)

Test plan: a waitlist page, with the assumption written by hand

Input:

{
 "task": "test",
 "brief": "# Fussbowl Suppers - waitlist page\n\nWe want to put up a landing page for Fussbowl Suppers, a weekly meal-plan subscription for parents of picky eaters: every Sunday you get five dinners that the whole family will eat, with a shopping list sorted by aisle.\n\nThe page will have a headline, three benefits, a sample week, and an email sign-up for the waitlist. We will share it in two parenting Facebook groups we belong to (about 18,000 members combined) and on our Instagram (900 followers).\n\nWe are n... (665 characters in all)",
 "kind": "landing",
 "facts": "(a JSON string; decoded: kind landing, floor medium, risks named: demand, monetization, retention; backed: positioning, distribution; silent: trust; na: adoption, 0 claims, 1 beliefs, 0 planned items, flags PB-02, PB-04, PB-10)",
 "note": "No ad budget.",
 "assumption": "Parents of picky eaters will pay for a weekly meal plan rather than use free recipe sites."
}

Reply:

{
 "task": "test",
 "kind": "landing",
 "assumption": {
  "risk": "monetization",
  "statement": "Parents of picky eaters will pay for a weekly meal plan rather than use free recipe sites: at least 5 of every 100 waitlist sign-ups will put down a refundable paid deposit for the first month.",
  "source": "given"
 },
 "method": "presale",
 "method_why": "The brief says \"We are not sure yet whether parents would pay\", and a free email sign-up cannot answer that, so the page needs a paid step that only a willing payer will take.",
 "audience": {
  "who": "Parents in the two parenting Facebook groups and your Instagram followers who have a child they describe as a picky eater",
  "where": "the two parenting Facebook groups we belong to and our Instagram, posted organically since there is no ad budget",
  "how_many": "a proposed 100 waitlist sign-ups, plus 8 short calls with sign-ups who do not pay"
 },
 "steps": [
  "Build the waitlist page as planned and add a second button after the email sign-up: \"Reserve a founding spot for [deposit amount], fully refundable\" linked to a [payment link].",
  "Hand-make one real sample week of five dinners with an aisle-sorted shopping list for the page, so people judge the actual product.",
  "Post once in each Facebook group (check each group's self-promotion rules first) and once on Instagram, then a single reminder post 5 days later.",
  "Count page visits, email sign-ups and paid deposits each day in one sheet.",
  "Message 8 sign-ups who did not pay and ask what they use for dinner planning now and what stopped them from reserving."
... (shortened here; the page shows it whole)

The page re-checks the reply; the API does not

The page holds every reply to account (recon.js): for a review, all seven risks once, every quote in the brief word for word, no low rating without a quote, no flagged claim counted as evidence, the verdict against the ratings and the evidence floor, the main assumption not on a low risk, something delayed, and no market figure the brief does not contain; for a test plan, thresholds with numbers and the kill line below the success line, a timebox of six weeks or less, and nothing built that the review delayed. Over the API you get the model's reply as it is; run the same checks yourself with Recon.reconcile in recon.js, or paste the same brief into the page.

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://prebuild-desk.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":"prebuild-desk"}' | 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 (not wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model reads gpt-5.6-terra and model_alias gpt-terra.

# input.json: the run input object - {"task": "review", "brief": "...", "kind": "product", "facts": "..."}
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 prebuild-desk:<lane>:<inputHash>:a<attempt>. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="prebuild-desk:review:$(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 lane's contract
echo "$J" | jq '.data | {charged_credits, truncated}' 

Step 6 — POST /run-stream (test plan)

The same run as Server-Sent Events. test.json is a test-plan input, with review_handoff when it follows a review.

# 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: prebuild-desk:test:$(shasum -a 256 test.json | cut -c1-16):a1" --data @test.json