← Idea Desk / API
Tokens

Drive Idea Desk from your own code

The page does its free work in the browser: it reads the idea against the idea-refine skill's five sharpening questions, finds the red flags of its refinement rubric and assigns each variation its lens. Its two paid lanes are available over HTTP. You send one JSON object with task set to "expand" or "converge", and the model replies with one JSON object. The easiest way to build the input is the page itself: IdeaKit.buildInput(IdeaKit.analyze({title, idea, answers, variations, count}), lane) in ideakit.js, which also runs under Node; Recon.reconcile(reply, {A, K: IdeaKit, lane, sent}) in recon.js runs the page's checks on a normalised reply.

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, a required field is missing, 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, in the order a person meets them:

Input fields (all strings)

fieldwhencontent
taskrequiredexpand or converge
titlerequiredA short name for the idea, e.g. Permission slips.
idearequiredThe raw idea, up to 6,000 characters (the page cuts lines from the middle beyond that and marks the cut).
answersoptionalAnswers to sharpening questions as Who:, Success:, Constraints:, Tried before:, Why now: lines, up to 3,000 characters.
variationsconverge onlyA JSON string: [{id, lens, name, pitch, picked, for_whom?, note?}], at most 9,000 characters (whole variations are dropped, never cut).
factsrequiredA JSON string: idea{answered[], missing[], items{who[], success[], constraints[], prior[], why_now[]}, flags[]}, assign[{id, lens}] (expand), picked[] (converge), clip.
retry_noteoptionalOnly on a reformat retry: what was wrong with the previous reply.

The reply contract

expand: task, task_note, hmw, restated, questions[{id: who|success|constraints|prior|why_now, question, why}] (three to five), variations[{id, lens, name, pitch, for_whom, why_it_exists, hardest_part}] (one per facts.assign, in order), pushback[], headline, summary.

converge: task, task_note, directions[{id, name, from[], thesis, user_value{rating: painkiller|vitamin, why}, feasibility{rating: high|low, hardest_part}, differentiation{type, why}, betting[], could_kill[], ignoring[]}] (two or three), recommended{direction, why}, one_pager{name, problem_statement, recommended_direction[], assumptions[{assumption, tier: must|should|might, test}], mvp_scope{in[], out[]}, not_doing[{item, reason}], open_questions[]}, pushback[], headline, summary. Neither lane states a market size, price or head count your input does not contain.

Worked examples

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

expand: school permission slips that never come back, six variations

Input (the idea shown separately, facts shortened here):

{
 "task": "expand",
 "title": "Permission slips",
 "idea": "(a string - shown below)",
 "facts": "(a JSON string; decoded: 4 of 5 questions answered, missing constraints; red flags none; assigns V1: simplification; V2: inversion; V3: audience_shift; V4: expert; V5: constraint_removal; V6: combination)"
}

idea:

# Permission slips that actually come back

Problem: at our primary school, paper permission slips and payment forms go home in book bags and half of them never
come back signed. Teachers spend the morning before every trip chasing parents by phone, and a few kids miss the trip
because a form was lost in a coat pocket.

Who: working parents of primary-school children, and the class teachers who chase them

Success: every slip for a trip is back, signed, two days before the trip, without a teacher making a single phone call

Tried before:
- a coloured "important" folder in every book bag
- the school's messaging app, which most parents muted months ago
- reminder notes stapled to the reading record

Why now: the school office is moving trip payments online this term, so forms are being redone anyway

Reply:

{
 "task": "expand",
 "task_note": "",
 "hmw": "How might we get every trip permission and payment back from working parents before the deadline, without teachers having to chase anyone?",
 "restated": "At a primary school, paper permission slips and payment forms travel home in book bags and many never come back signed, so teachers spend trip mornings phoning parents and some children miss trips. The office is moving trip payments online this term, which opens a chance to redo how consent is collected.",
 "questions": [
  {
   "id": "constraints",
   "question": "What are you actually allowed to change: is the new online payment system already chosen, who at school can configure it, and must consent still exist on paper for legal or policy reasons?",
   "why": "If the payment system is fixed and paper consent is required, the answer is a process change around existing tools; if not, consent and payment can become one step."
  },
  {
   "id": "who",
   "question": "Which parents are the ones whose slips do not come back - those without easy online access, those with several children, those who share custody, or simply the busiest - and do you know who they are by name?",
   "why": "You named two groups, parents and teachers, but the fix depends on why the non-returners do not return; a digital form solves nothing for a parent who is not reachable digitally."
  },
  {
   "id": "success",
   "question": "When a slip is still missing two days before the trip, what should happen instead of a teacher's phone call - does the office follow up, does the child go anyway on a standing consent, or do they stay behind?",
   "why": "Your success measure says every slip is back with no teacher calls; the design has to say who owns the stragglers, or the chasing just moves to someone else."
  },
  {
   "id": "prior",
   "question": "Why did parents mute the school messaging app - too many messages, wrong timing, or nothing in it felt urgent?",
   "why": "Any new channel risks the same fate; knowing what killed the app tells you which mistake not to repeat."
  }
 ],
 "variations": [
  {
   "id": "V1",
   "lens": "simplification",
   "name": "Consent inside the payment",
   "pitch": "The trip payment page the office is already building gets one required checkbox: I give permission for my child to attend. Paying is signing, so there is no separate slip to lose and no second thing to chase.",
   "for_whom": "Working parents who will pay online anyway and just need consent to ride along with that action",
   "why_it_exists": "The why-now is that forms are being redone for online payments; the simplest move is to make the form that already has to come back carry the consent too.",
   "hardest_part": "Free trips and parents who do not pay online still need a path, and whether a checkbox counts as valid consent under school policy is an assumption."
  },
  {
   "id": "V2",
   "lens": "inversion",
   "name": "Annual standing consent, opt out",
   "pitch": "Instead of asking yes for each trip, parents sign one consent at the start of the year for all local trips, and each trip is announced with a notice that they can opt out of by a date. Silence means the child goes.",
   "for_whom": "Class teachers who currently need a returned yes for every single outing",
   "why_it_exists": "Every prior attempt tried to make the return more likely; inverting the default removes the return entirely for routine trips.",
   "hardest_part": "Whether the school and its policies allow opt-out consent for trips, and handling trips that are unusual enough to still need an explicit yes."
  },
  {
   "id": "V3",
   "lens": "audience_shift",
   "name": "The child as courier",
   "pitch": "Design the process for the pupils rather than the parents: the class keeps a visible board of whose slip is back, and children own getting theirs home and returned. The form is short enough for a child to explain to a parent at the door.",
   "for_whom": "Primary-school children, who carry the slip but currently have no stake in it",
   "why_it_exists": "The slip is lost in a coat pocket, which is the child's territory; shifting the audience asks who is actually holding the paper when 
... (shortened here; the page shows it whole)

expand: a vague 'AI meal planner for everyone', five variations and five red flags

Input (the idea shown separately):

{
 "task": "expand",
 "title": "AI meal planner",
 "idea": "(a string - shown below)",
 "facts": "(a JSON string; decoded: 0 of 5 questions answered, missing who, success, constraints, prior, why_now; red flags everyone, like_x, solution_first, feature_list, no_pain; assigns V1: simplification; V2: inversion; V3: audience_shift; V4: expert; V5: constraint_removal)"
}

idea:

An AI-powered meal planning app for everyone, like Supperloom but smarter. It plans your week, writes the shopping
list, suggests recipes from what is already in the fridge, tracks calories and macros, connects to supermarket
delivery, and learns your tastes over time.

Features I want:
- weekly plan generated in one tap
- shopping list sorted by aisle
- fridge photo to recipe suggestions
- calorie and macro tracking
- family profiles with allergies
- one-click supermarket delivery
- leftovers planner

Reply:

{
 "task": "expand",
 "task_note": "",
 "hmw": "How might we take the weekly 'what are we eating and what do we need to buy' decision off someone's plate without adding another chore to their week?",
 "restated": "An app that plans a week of meals, turns the plan into a shopping list, suggests recipes from what is already in the fridge, tracks calories and macros, links to supermarket delivery and adapts to tastes over time. As pasted it is aimed at everyone, framed as a smarter Supperloom, and described as a list of seven features rather than a problem.",
 "questions": [
  {
   "id": "who",
   "question": "Who is the one person you picture using this on a Sunday evening: a parent feeding a family with allergies, someone tracking macros for training, a busy single person who keeps wasting groceries, or someone else?",
   "why": "Each of those people needs a different first feature; the answer decides which one of the seven survives into a first version."
  },
  {
   "id": "success",
   "question": "What would tell you this worked for that person: fewer evenings stuck deciding what to cook, less food thrown away, hitting a nutrition target, or less time spent shopping?",
   "why": "The success measure picks the core loop; planning, waste, nutrition and shopping pull the design in different directions."
  },
  {
   "id": "prior",
   "question": "What does that person do today to plan meals, and what made them give up on Supperloom or any other planner they tried?",
   "why": "If people abandon planners because keeping them up to date is work, a smarter planner repeats the same failure; the reason they quit is the real problem to solve."
  },
  {
   "id": "constraints",
   "question": "What can you actually build and run: are you one person with a few weeks, and do you have any access to supermarket delivery integrations or reliable food-recognition from photos?",
   "why": "Delivery integration and fridge-photo recognition are the two most expensive features on the list; real constraints decide whether they are in scope at all."
  },
  {
   "id": "why_now",
   "question": "Why build this now: has something changed in your own life, in the tools available, or in how people shop that makes this easier or more needed than before?",
   "why": "Without a reason it is newly possible or newly needed, the idea competes head-on with existing planners on features alone."
  }
 ],
 "variations": [
  {
   "id": "V1",
   "lens": "simplification",
   "name": "Tonight's dinner, one answer",
   "pitch": "No weekly plan, no tracking: at a set time each afternoon the person gets one dinner suggestion that fits what they usually have at home, with one tap to swap it. If they accept, they get a short list of anything missing to pick up on the way home.",
   "for_whom": "A busy working adult who ends most days staring into the fridge without a plan",
   "why_it_exists": "Of the seven features, the daily 'what is for dinner' decision is the most frequent moment of friction; cutting everything else tests whether removing that one decision is valuable on its own.",
   "hardest_part": "Making a single suggestion good enough often enough that the person does not stop opening it after a week."
  },
  {
   "id": "V2",
   "lens": "inversion",
   "name": "Shop first, plan after",
   "pitch": "Instead of planning meals and then buying ingredients, the person shops however they like and photographs the receipt; the app then works out a week of meals from what they actually bought and nudges them to use the perishable items before they go off.",
   "for_whom": "Someone who shops on impulse or by habit and regularly throws away food they meant to cook",
   "why_it_exists": "The original assumes people will follow a plan; inverting it accepts that they will not and plans around real behaviour, turning the leftovers and fridge ideas into the core rather than add-ons.",
   "hardest_part": "Reading receipts reliably enough, across shops and abbreviations, to know what ingredients are actually in the house."
  },
  {
   "id": "V3",
   "lens": "audience_shift",
   "name": "Allergy-safe family rotation",
   "pitch": "A planner for one household where at least one pe
... (shortened here; the page shows it whole)

converge: the permission-slip variations, three picked, with the sharpening questions answered

Input (the variations are the first example's reply, handed over by the page with three marked as resonating):

{
 "task": "converge",
 "title": "Permission slips",
 "idea": "(a string - shown below)",
 "answers": "(a string - shown below)",
 "variations": "(a JSON string: 6 variations - V1 Consent inside the payment [picked], V2 Annual standing consent, opt out [picked], V3 The child as courier, V4 Clinic-style appointment reminders [picked], V5 Whole-year trip account, V6 Slips at parents' evening)",
 "facts": "(a JSON string; decoded: 5 of 5 questions answered, missing none; red flags none; picked V1, V2, V4)"
}

idea:

# Permission slips that actually come back

Problem: at our primary school, paper permission slips and payment forms go home in book bags and half of them never
come back signed. Teachers spend the morning before every trip chasing parents by phone, and a few kids miss the trip
because a form was lost in a coat pocket.

Who: working parents of primary-school children, and the class teachers who chase them

Success: every slip for a trip is back, signed, two days before the trip, without a teacher making a single phone call

Tried before:
- a coloured "important" folder in every book bag
- the school's messaging app, which most parents muted months ago
- reminder notes stapled to the reading record

Why now: the school office is moving trip payments online this term, so forms are being redone anyway

answers:

> What are you actually allowed to change?
Constraints: the payment system is already chosen by the school trust and the office can add one required field to each payment page; consent has to be kept on record, but it does not have to be on paper; I am a parent volunteer with one evening a week, not a developer.

> Which parents are the ones whose slips do not come back?
Who: mostly the same few families every time - parents on shift work, and families where the child moves between two homes during the week.

> What should happen instead of a teacher's phone call?
Success: the office, not the teacher, follows up any missing slip, and the teacher gets one list the day before the trip saying everyone is cleared.

> Why did parents mute the school messaging app?
Tried before: it sends everything - lost jumpers, cake sales, trips - with the same alert, so parents stopped reading it.

Reply:

{
 "task": "converge",
 "task_note": "",
 "directions": [
  {
   "id": "D1",
   "name": "Paying is signing",
   "from": [
    "V1"
   ],
   "thesis": "Consent rides on the online trip payment the office is already building: one required permission checkbox on each payment page, so a paid trip is a signed trip and the office follows up only the families who have not paid.",
   "user_value": {
    "rating": "painkiller",
    "why": "Lost slips, morning-of phone calls and children missing trips are an acute, repeated problem, and this removes the separate slip that gets lost entirely."
   },
   "feasibility": {
    "rating": "high",
    "hardest_part": "Getting the office to treat the payment record as the consent record and to pull a per-trip list of who has not paid early enough to chase it two days before the trip."
   },
   "differentiation": {
    "type": "better_ux",
    "why": "Parents do one thing they already have to do instead of two, which is a simpler experience rather than a new capability."
   },
   "betting": [
    "The one required field the office can add will hold a consent checkbox that the trust accepts as consent on record",
    "The families whose slips do not come back will pay online, not only on paper or in cash",
    "The payment system can show the office, per trip, which children have paid and ticked consent"
   ],
   "could_kill": [
    "Free trips, which have no payment page and so no place for the checkbox",
    "Shift-work and two-home families who pay late or not at all, leaving the same gaps under a new name",
    "The required field being needed for something else on the payment page"
   ],
   "ignoring": [
    "Medical and dietary details, because the problem stated is consent coming back, not information gathering",
    "Which of two parents pays, because one valid consent is what the trip needs"
   ]
  },
  {
   "id": "D2",
   "name": "Standing consent, opt out",
   "from": [
    "V2",
    "V6"
   ],
   "thesis": "Ask once a year (or once a term, collected in person at a school event) for consent to all local trips, then announce each trip with an opt-out date, so silence means the child goes.",
   "user_value": {
    "rating": "painkiller",
    "why": "It removes the per-trip yes that teachers currently chase, which is exactly where the phone calls come from."
   },
   "feasibility": {
    "rating": "low",
    "hardest_part": "It needs the school and trust to change consent policy, which a parent volunteer with one evening a week cannot decide."
   },
   "differentiation": {
    "type": "new_context",
    "why": "It moves consent from each trip to the start of the year, a different moment rather than a better slip."
   },
   "betting": [
    "The school and trust will accept opt-out consent for local trips",
    "Parents who miss book-bag paper still see the opt-out notice for each trip"
   ],
   "could_kill": [
    "A policy or safeguarding rule that requires an explicit yes per trip",
    "A parent who never saw the notice and objects after the child has gone"
   ],
   "ignoring": [
    "Trips further afield or residential, which would still need their own consent; local trips are enough to test the idea"
   ]
  },
  {
   "id": "D3",
   "name": "One message, only about your child",
   "from": [
    "V4"
   ],
   "thesis": "A separate channel that only ever carries one direct message per missing slip at a fixed lead time, answerable with a single tap, so parents who muted the general app still respond.",
   "user_value": {
    "rating": "painkiller",
    "why": "It targets the same few families who do not return slips and replaces the teacher's phone call with something the office can send."
   },
   "feasibility": {
    "rating": "low",
    "hardest_part": "Building and running a separate one-tap messaging channel is beyond a volunteer with one evening a week who is not a developer, and it must stay free of every other kind of message."
   },
   "differentiation": {
    "type": "better_ux",
    "why": "It is a reminder done well - rare, personal and single-purpose - rather than a new way of giving consent."
   },
   "betting": [
    "Parents muted the app because of noise, as the answe
... (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). Expand: a "How might we ...?" statement with no solution built in; three to five questions from the five sharpening questions, always including who and success when the idea leaves them out; one variation per assigned lens, in order, each complete; no two variations sharing most of their content words. Converge: two or three directions built from real variations, every picked one placed, no two from the same set; every rating present; the recommendation in the best quadrant of the decision matrix present; three to six assumptions each with a test and at least one dealbreaker; an MVP with what is in and out; at least three Not Doing items with reasons; open questions that still ask who or success when the input never says. Both: every red flag answered by name, and no percentage, price or head count the input does not contain. 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 idea 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://idea-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":"idea-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": "expand", "title": "...", "idea": "...", "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 idea-desk:<lane>:<inputHash>:a<attempt>. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="idea-desk:expand:$(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

The same run as Server-Sent Events. converge.json is a converge-lane run input, built like input.json.

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