← Activation Desk / API
Tokens

Drive Activation Desk from your own code

The page does its free work in the browser: it reads a walkthrough of what a new user meets after signup (one step per line, then the checklist, tour, empty states and emails) and its context, lists and classifies every step, counts the steps before the first value step, checks the activation event for vanity, reads the five onboarding components, computes the funnel and its biggest drop, and raises flags OB-01..OB-21. Its two paid lanes are available over HTTP. You send one JSON object with task set to "audit" or "design", and the model replies with one JSON object. The easiest way to build the facts string is the page itself: Obkit.buildInput(lane, Obkit.analyze(flow, {context, product, question}), {auditHandoff}) in obkit.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
"audit"The onboarding audit: whether it is in scope (post-signup onboarding, not a signup form); the activation event (yes, vague or no, with a candidate to test); the first session; one verdict per step (keep, rework, remove, defer, prefill, skippable); the step where value happens; the five components (welcome_form, checklist, empty_states, tour, emails) each strong, weak, missing or not_needed; the issues as finding, impact and fix with the page's flag ids; the funnel read and its biggest drop; stalled users; 3 to 6 fixes; a verdict ship, fix_first, rebuild or out_of_scope.
"design"The activation flow: the goal (event, metric, target), the first-30-seconds approach, 3 to 7 steps that end on the event in fewer steps than now, what moves out of the way and when it returns, a 3 to 7 item checklist, 1 to 3 empty states, 3 to 6 onboarding emails, a plan for stalled users, 3 to 5 metrics and 2 to 4 experiments.

Input fields (all strings)

fieldwhencontent
taskrequiredaudit or design
flowrequiredThe walkthrough, verbatim. Past 6,000 characters the page cuts the middle on line boundaries and leaves an [activation-desk: ...] marker.
factsrequiredA JSON string: summary, product (type, basis), activation (found, event, vanity, evidence, target), steps (n, text, kind), path (steps, value_step, before_value, value_text), first_session (approach, first_step, competing), model (types, card), components (found, evidence, note and counts for each of the five), found, stalled, funnel (steps with label, count, pct_of_start, pct_of_prev; rates; biggest_drop with from, to, kept_pct, lost; overall_pct), scope, flags, notes. It is Obkit.factsOf(A).
contextoptionalThe product, the activation event, the model, the target and the count at each step. Cut past 4,000 characters.
productoptionalb2b_saas, b2c, mobile, marketplace or content, when the user chose it.
questionoptionalWhat the user wants to know.
audit_handoffdesign onlyA JSON string of an audit's activation, verdict, remove[], defer[], missing[], issues[{flag, finding, fix}] and fixes[], for the same input.
retry_noteoptionalOnly on a reformat retry: what was wrong with the previous reply.

The reply contracts

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

task: "audit"

scope{in_scope, note}, activation{event, defined: yes|vague|no, suggested, why, evidence[]}, first_session{approach: product_first|guided_setup|value_first|unclear, next_action: clear|competing|missing, why}, path[{step, kind, verdict: keep|rework|remove|defer|prefill|skippable, why}] (one per step, in order), value_step, components[{component, status: strong|weak|missing|not_needed, finding, evidence[], fix}] (exactly five, in order), issues[{flag, kind: blocker|friction|motivation|measurement|follow_up, severity: high|medium|low, finding, impact, evidence[], fix}], numbers{read, biggest_drop}, stalled{verdict: handled|partial|missing, fix}, fixes[] (3 to 6), verdict (ship|fix_first|rebuild|out_of_scope).

task: "design"

source (audit handoff|from the input), activation_goal{event, metric, target}, approach, flow[{step, screen, action, copy, why}] (3 to 7), deferred[{item, when: after activation|later in the first week|in the background|never, why}], checklist{items[{item, why}], prefilled, celebration}, empty_states[{area, headline, body, action}], emails[{trigger, subject, body, cta}] (3 to 6), stalled{definition, tactics[]}, metrics[{metric, definition}], experiments[{test, hypothesis, metric}] (2 to 4).

Worked examples

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

Audit: a B2B project tool with six steps and a forced tour before the first project

Input (flow and facts shortened here):

{
 "task": "audit",
 "flow": "After signup:\n\n1. Check your inbox: \"Verify your email to continue\". The app stays locked until they click the link.\n2. Welcome survey (all required): your role, company name, team size, industry, how you heard about us, your main goal\n3. Invite your team: \"Brieflane works best with your team\" [Send invites] [Skip for now]\n4. Connect Slack [Connect Slack] [Skip]\n5. Product tour: 6 tooltips over the sidebar, inbox, ti... (1121 characters in all)",
 "facts": "(a JSON string; decoded: 7 steps, first value step 7, activation event \"they log in a second time\" (vanity), approach guided_setup, components found: welcome_form, checklist, empty_states, tour, emails, biggest drop to \"created a first project\", 58.1% kept, flags OB-02, OB-05, OB-19, OB-03, OB-04, OB-06, OB-07, OB-10, OB-11, OB-18, OB-08, OB-09, OB-15, OB-16)",
 "context": "Brieflane - B2B project management for agencies of 5 to 50 people. 14-day free trial, no credit card.\nWe count a user as activated when they log in a second time.\nLast 30 days: 2,400 signups; 1,610 verified their email; 1,050 finished the welcome survey; 610 created a first project.\nDay-7 retention is 11%.\nTrial users who stay usually create 3 projects and invite 2 teammates in week 1.\n",
 "question": "Why do so few trial users create a project?"
}

Reply:

{
 "task": "audit",
 "scope": {
  "in_scope": true,
  "note": ""
 },
 "activation": {
  "event": "they log in a second time",
  "defined": "vague",
  "suggested": "Create a first project and invite a teammate in week 1; check it against Day-7 retention before adopting it.",
  "why": "A second login is a vanity event; your own note says trial users who stay create projects and invite teammates in week 1.",
  "evidence": [
   "We count a user as activated when they log in a second time."
  ]
 },
 "first_session": {
  "approach": "guided_setup",
  "next_action": "missing",
  "why": "The first screen is a verification lock, so its only action is leaving for the inbox, not a step toward a project."
 },
 "path": [
  {
   "step": "The app stays locked until they click the link",
   "kind": "verify",
   "verdict": "defer",
   "why": "Let users into Brieflane at once and ask for verification after they create a project."
  },
  {
   "step": "Welcome survey (all required): your role, company name, team size, industry, how you heard about us, your main goal",
   "kind": "form",
   "verdict": "defer",
   "why": "Six required questions stand before value; keep at most your main goal and ask the rest after activation."
  },
  {
   "step": "Invite your team: \"Brieflane works best with your team\"",
   "kind": "invite",
   "verdict": "defer",
   "why": "An invite before the user has a project of their own gives teammates nothing to join."
  },
  {
   "step": "Connect Slack [Connect Slack] [Skip]",
   "kind": "setup",
   "verdict": "defer",
   "why": "Integrations are not needed to create a first project and belong after activation."
  },
  {
   "step": "Product tour: 6 tooltips over the sidebar, inbox, timeline, files, reports and settings",
   "kind": "tour",
   "verdict": "remove",

... (shortened here; the page shows it whole)

Design: the same walkthrough, carrying the audit

Input (with the audit handed over):

{
 "task": "design",
 "flow": "After signup:\n\n1. Check your inbox: \"Verify your email to continue\". The app stays locked until they click the link.\n2. Welcome survey (all required): your role, company name, team size, industry, how you heard about us, your main goal\n3. Invite your team: \"Brieflane works best with your team\" [Send invites] [Skip for now]\n4. Connect Slack [Connect Slack] [Skip]\n5. Product tour: 6 tooltips over the sidebar, inbox, ti... (1121 characters in all)",
 "facts": "(a JSON string; decoded: 7 steps, first value step 7, activation event \"they log in a second time\" (vanity), approach guided_setup, components found: welcome_form, checklist, empty_states, tour, emails, biggest drop to \"created a first project\", 58.1% kept, flags OB-02, OB-05, OB-19, OB-03, OB-04, OB-06, OB-07, OB-10, OB-11, OB-18, OB-08, OB-09, OB-15, OB-16)",
 "context": "Brieflane - B2B project management for agencies of 5 to 50 people. 14-day free trial, no credit card.\nWe count a user as activated when they log in a second time.\nLast 30 days: 2,400 signups; 1,610 verified their email; 1,050 finished the welcome survey; 610 created a first project.\nDay-7 retention is 11%.\nTrial users who stay usually create 3 projects and invite 2 teammates in week 1.\n",
 "question": "Why do so few trial users create a project?",
 "audit_handoff": "{\"activation\":\"Create a first project and invite a teammate in week 1; check it against Day-7 retention before adopting it.\",\"verdict\":\"rebuild\",\"remove\":[\"Product tour: 6 tooltips over the sidebar, inbox, timeline, files, reports and settings\"],\"defer\":[\"The app stays locked until they click the link\",\"Welcome survey ..."
}

Reply:

{
 "task": "design",
 "source": "audit handoff",
 "activation_goal": {
  "event": "Create a first project and invite a teammate to it in week 1.",
  "metric": "Activation rate: the share of signups who create a first project and invite a teammate to it within their first week.",
  "target": "[activation target]"
 },
 "approach": "value_first",
 "flow": [
  {
   "step": 1,
   "screen": "Dashboard, opened straight after signup with no email lock: the empty state offers a New project button and agency project templates.",
   "action": "Click New project or pick an agency template.",
   "copy": "Start your first client project. Pick a template or start blank.",
   "why": "Making a first project the first screen removes the six steps that stood before value."
  },
  {
   "step": 2,
   "screen": "New project: the chosen template with sample tasks and a timeline already filled in, and one optional question about your main goal.",
   "action": "Name the project after a real client and click Create project.",
   "copy": "Name it after a client you are working with this week.",
   "why": "A template pre-fills the structure so the user sees a working project right away instead of a blank page."
  },
  {
   "step": 3,
   "screen": "The new project with its template tasks, plus a dismissable tip pointing at the task list.",
   "action": "Add one real task and assign a due date.",
   "copy": "Add the next thing your team needs to deliver.",
   "why": "One real task turns the template into the user's own work, which is the value Brieflane promises."
  },
  {
   "step": 4,
   "screen": "Inside the project: an invite panel that reads \"Brieflane works best with your team\", with the project name shown.",
   "action": "Invite a teammate to this project.",
   "copy": "Who else works on th
... (shortened here; the page shows it whole)

Audit: a reading app with the plans screen before the first session

Input:

{
 "task": "audit",
 "flow": "First launch (iOS and Android):\n\n1. Splash: \"Read more books this year\" [Get started]\n2. Quiz, three screens: How many books did you read last year? Which genres do you like? When do you usually read?\n3. \"Building your reading plan...\" animation\n4. Plans screen: Tomewren Plus - $39.99/year or $7.99/month [Start 7-day free trial] and a small x in the corner\n5. Home: today's reading goal and a timer [Start reading]\n6. ... (483 characters in all)",
 "facts": "(a JSON string; decoded: 6 steps, first value step 5, activation event not stated, approach guided_setup, components found: welcome_form, biggest drop to \"timed a first reading session\", 37.59% kept, flags OB-01, OB-13, OB-02, OB-14)",
 "context": "Tomewren - a reading habit app. Freemium: the reading timer and one shelf are free; Plus adds reading stats, unlimited shelves and book notes.\nThe plans screen shows to everyone on first launch, before they have timed a single reading session.\nLast month: 18,500 installs; 14,100 finished the quiz; 5,300 timed a first reading session.\nDay-30 retention is 9%.\nWe send no onboarding emails or push reminders yet.\n"
}

Reply:

{
 "task": "audit",
 "scope": {
  "in_scope": true,
  "note": ""
 },
 "activation": {
  "event": "",
  "defined": "no",
  "suggested": "Hypothesis: a new user times a first reading session with the [Start reading] timer; check it against Day-30 retention.",
  "why": "The input never names the action that means a reader got value, though the funnel already counts who timed a first reading session.",
  "evidence": []
 },
 "first_session": {
  "approach": "guided_setup",
  "next_action": "clear",
  "why": "The splash offers one action, Get started, and the quiz then builds a reading plan before Home."
 },
 "path": [
  {
   "step": "Splash: \"Read more books this year\" [Get started]",
   "kind": "other",
   "verdict": "remove",
   "why": "A promise screen with one button adds a tap without moving the reader closer to the timer; the line can sit on the first quiz screen."
  },
  {
   "step": "Quiz, three screens: How many books did you read last year? Which genres do you like? When do you usually read?",
   "kind": "form",
   "verdict": "skippable",
   "why": "The answers shape the reading plan, but a reader who just wants to read should be able to pass it and answer later."
  },
  {
   "step": "\"Building your reading plan...\" animation",
   "kind": "other",
   "verdict": "remove",
   "why": "A waiting screen is one more place to leave and shows nothing the reader can use."
  },
  {
   "step": "Plans screen: Tomewren Plus - $39.99/year or $7.99/month [Start 7-day free trial] and a small x in the corner",
   "kind": "upgrade",
   "verdict": "defer",
   "why": "Plus sells reading stats and book notes, which mean nothing before a first reading session; show it after the aha moment."
  },
  {
   "step": "Home: today's reading goal and a timer [Start reading]",
   "kind": "val
... (shortened here; the page shows it whole)

Design: an invoicing app, from the input

Input:

{
 "task": "design",
 "flow": "What a new user sees after signup:\n\n1. Verify email: \"Check your inbox to confirm your address\" (they can keep working while they wait)\n2. Welcome question: \"What do you invoice for?\" [Hourly work] [Projects] [Products] [Skip]\n3. Settings page: business name, address, tax ID, logo, bank details and invoice numbering\n4. Dashboard with an empty invoice list: \"You have no invoices.\" [Create invoice]\n5. Create invoice: p... (777 characters in all)",
 "facts": "(a JSON string; decoded: 6 steps, first value step 6, activation event \"the user sends their first invoice to a real client\", approach guided_setup, components found: welcome_form, checklist, empty_states, emails, biggest drop to \"created an invoice\", 60.49% kept, flags OB-02, OB-03, OB-10, OB-08, OB-09, OB-15)",
 "context": "Ledgerpine - invoicing for freelancers and two-person studios. Freemium: 3 invoices a month free; Pro is $9/month for unlimited invoices and card payments.\nAha moment: the user sends their first invoice to a real client. Our target is 40% of signups activated within 3 days.\nLast month: 3,100 signups; 2,050 finished the settings page; 1,240 created an invoice; 860 sent a first invoice.\nUsers who send an invoice in wee... (473 characters in all)",
 "product": "b2b_saas"
}

Reply:

{
 "task": "design",
 "source": "from the input",
 "activation_goal": {
  "event": "The user sends their first invoice to a real client.",
  "metric": "Activation rate: the share of signups who send a first invoice to a real client within 3 days of signup.",
  "target": "40% of signups activated within 3 days"
 },
 "approach": "value_first",
 "flow": [
  {
   "step": 1,
   "screen": "Welcome question: \"What do you invoice for?\" with [Hourly work] [Projects] [Products] [Skip]; a small banner says the confirmation email is on its way and nothing waits on it.",
   "action": "Pick one answer, or Skip.",
   "copy": "What do you invoice for? We'll set up your first invoice to match.",
   "why": "One question that changes the path: the answer picks the invoice template and line item type, so it earns its place before value."
  },
  {
   "step": 2,
   "screen": "Create invoice, opened as a draft already laid out for the chosen answer (hours and rate, a project fee, or products and quantities), with the client field first.",
   "action": "Add a client inline: a name and an email address.",
   "copy": "Who is this invoice for? Add them here, no separate client page needed.",
   "why": "Today a user must leave Create invoice to add a client first; adding the client inside the invoice removes a detour on the stretch where 810 users were lost."
  },
  {
   "step": 3,
   "screen": "The same draft with one example line item filled in from the welcome answer, ready to overwrite.",
   "action": "Add line items.",
   "copy": "Replace the example with what you did for them.",
   "why": "Editing a template is faster than facing a blank form, and it is the real work the invoice exists for."
  },
  {
   "step": 4,
   "screen": "Preview of the finished invoice as the client will see it, wit
... (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 an audit, one path entry per step in order, each quoting its own step; the value step kept; no plan screen, early team invite, verification lock, forced tour or dead-end empty state kept as it is; the activation event never called defined when the page found none or a vanity event; five components in order, none called strong that a flag faults; every high and medium flag carried and a high one never downgraded; the biggest drop equal to the page's; stalled users never "handled" when nothing treats them; no "ship" while a high flag stands; a signup form left unoptimised. For a design, a real activation event, 3 to 7 steps reaching it in fewer steps than now, no upgrade or card step, no team invite before the user's own value, a 3 to 7 item checklist opening with a quick win, empty states with an action, 3 to 6 emails with the welcome first and one triggered by what the user has not done, activation rate among the metrics, and the handed-off audit's removed steps kept out. In both, quotes are checked word for word and no count, rate, fraction or window may be invented. 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 input 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://activation-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":"activation-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": "audit", "flow": "...", "context": "...", "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 activation-desk:<lane>:<inputHash>:a<attempt>. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="activation-desk:audit:$(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 (design)

The same run as Server-Sent Events. design.json is a design input, with audit_handoff when it follows an audit.

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