← Need to Know
Briefing API
Tokens

Drive Need to Know from your own code

The one metered task, plan, is available over HTTP. It takes what the surprise is, who it is kept from, the days left until the reveal, the three need-to-know rings, the shared channels you ticked, your worries, your cover story and the page's flags. It returns a threat model of the secret: a leak per channel with a risk level and a plug, a verdict, briefs, six field rules, a check of the cover story, a countdown and a fallback.

The division of labour first: the page owns the rings, the flags, the dial's loudness estimate and the verdict rule; the model owns the risk reading and the wording of the plan; the page re-checks the reply.

Base URL and the envelope

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

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

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

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required field (task, surprise, for_whom, days_left, planning, helping, told, channels, worries, cover, page_scan) is missing.
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: a plan is metered and needs a personal token.
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 and the verdicts

Every run sends task: plan (the one task is plan); a missing or different one is still answered as plan. The verdict is a rule on the leaks rated high: lockdown for 3 or more, patch for 1 or 2, airtight for none. A high leak is one the person kept in the dark would meet in the normal course of their day without looking.

Input fields (every value is a string)

FieldRequiredWhat it holds
taskyesAlways plan.
surpriseyesWhat the surprise is (at most 140 characters).
for_whomyesWho it is kept from (at most 60), or (not given).
days_leftyesWhole days until the reveal, 0 to 365.
planning / helping / toldyesThe three rings: one person per line as Name or Name - note (at most 12 per ring), or (nobody).
channelsyesOne ticked channel per line, <id> | <label>, or (none ticked). Ids: calendar, card, orders, parcels, inbox, location, groupchat, photos, device, speaker, lockscreen, social, kids, phone.
worries / coveryesAnything else that worries you, and your cover story (at most 300 each), or (not given).
page_scanyesThe page's flags, <n> | <child|housemate|two-rings|big-circle|long-fuse> | <name or detail> per line, or (none).
clip_notenoWhat the page cut, if anything.
retry_notenoOnly on the automatic reformat retry.

The reply

One JSON object, in output.output of the finished job:

{"task": "plan", "title": "...", "codename": "Operation ...",
 "leaks": [{"channel": "<channel id> | other", "risk": "high | medium | low", "how": "...", "plug": "..."}],
 "verdict": "airtight | patch | lockdown", "verdict_reason": "...",
 "circle": [{"name": "...", "ring": "planning | helping | told", "brief": "..."}],
 "field_rules": ["...", "...", "...", "...", "...", "..."],
 "cover_story": "...", "cover_holes": ["..."],
 "countdown": [{"days_before": 7, "do": "..."}], "if_rumbled": "..."}
FieldWhat it holds
leaksOne per ticked channel in order, plus up to two other leaks from the worries; each with a risk, how it leaks (at most 22 words) and a plug (at most 22).
verdict / verdict_reasonThe rule above, and one sentence naming the high leaks.
circleA brief (at most 20 words) for every person the page flagged as a child, a housemate or listed twice, then up to three more.
field_rulesExactly six spy-style rules of at most 12 words.
cover_story / cover_holesYour cover tightened into one line (or a note that none was given) and 1 to 3 places it could crack (none when no cover was given).
countdown3 to 6 steps, integer days_before from largest down to 0, no weekday or clock time.
if_rumbled / codename / titleThe fallback if the person gets suspicious (at most 40 words), an Operation ... codename and a playful title.

The page then checks the reply against the form (HushKit.check): the verdict against the rule, a leak for every ticked channel and none for an unticked one, briefs for every flagged person in the ring you gave them, six field rules, cover holes only when a cover was given, the countdown inside the days you have and ending on the reveal day, the word limits, no he or she and no internal field names.

Worked examples, one per verdict

These are the three free samples on the page: the exact input and a stand-in model's reply, unedited. Together they cover lockdown, patch and airtight.

The fortieth - lockdown

Input:

{
 "task": "plan",
 "surprise": "A surprise 40th birthday party at the community hall",
 "for_whom": "My partner, Sam",
 "days_left": "24",
 "planning": "Priya - co-host, Sam's sister\nTom - booking the hall",
 "helping": "Leo - our son, 7, making a banner\nAna - baking the cake",
 "told": "Ben\nChloe\nDev\nEllie\nFemi\nGus\nHana\nIvo",
 "channels": "calendar | A shared calendar\ncard | A joint bank account or card\norders | A shared shopping account\ngroupchat | A group chat they are in\nlockscreen | Notifications on a lock screen they can see\nsocial | Guests who post or tag online\nkids | Children who know",
 "worries": "Sam sometimes borrows my laptop for work emails.",
 "cover": "Priya takes Sam out for a quiet dinner, then needs to stop by the hall to pick something up.",
 "page_scan": "1 | child | Leo\n2 | big-circle | 12 people know"
}

Reply:

{
 "task": "plan",
 "title": "Keeping Sam's fortieth a surprise at the hall",
 "codename": "Operation Forty Hall",
 "leaks": [
  {
   "channel": "calendar",
   "risk": "high",
   "how": "A calendar entry about the hall booking or party could appear on the shared calendar Sam also uses.",
   "plug": "Keep the hall booking and party plans off the shared calendar; Tom should track dates in a private note instead."
  },
  {
   "channel": "card",
   "risk": "medium",
   "how": "Anything bought or paid for the surprise could appear as a line on the joint card statement.",
   "plug": "Keep anything bought for the surprise off the joint card; use a separate account instead."
  },
  {
   "channel": "orders",
   "risk": "medium",
   "how": "Anything bought for the surprise could sit in the shared shopping account's order history if Sam happens to browse it.",
   "plug": "Keep anything bought for the surprise off the shared shopping account; use a separate account instead."
  },
  {
   "channel": "groupchat",
   "risk": "high",
   "how": "A message about the party sent in the group chat Sam is also in could reveal the surprise.",
   "plug": "Move planning talk for the party out of that group chat into a separate thread without Sam."
  },
  {
   "channel": "lockscreen",
   "risk": "high",
   "how": "A notification preview about the party could appear on a lock screen Sam can see.",
   "plug": "Turn off notification previews on any device whose lock screen Sam can see before the reveal."
  },
  {
   "channel": "social",
   "risk": "medium",
   "how": "A guest could post or tag something about the party online where Sam might see it.",
   "plug": "Ask the wider group who know not to post or tag anything online before the reveal."
  },
  {
   "channel": "kids",
   "risk": "high",
   "how": "Leo already knows about the party and could mention it to Sam without meaning to.",
   "plug": "Give Leo a simple reason to keep the banner secret, or keep Leo away from Sam while making it."
  },
  {
   "channel": "other",
   "risk": "medium",
   "how": "Sam sometimes borrows the laptop for work emails and could come across the party planning open on it.",
   "plug": "Keep party planning off the laptop Sam borrows, or close everything and sign out before Sam uses it."
  }
 ],
 "verdict": "lockdown",
 "verdict_reason": "Four leaks are high: the shared calendar, the group chat, lock-screen notifications, and Leo already knowing.",
 "circle": [
  {
   "name": "Leo",
   "ring": "helping",
   "brief": "Make the banner where Sam won't see it, and keep it secret for now, like a surprise game."
  },
  {
   "name": "Priya",
   "ring": "planning",
   "brief": "Lead the quiet dinner cover story and keep Sam away from the hall until it is time."
  },
  {
   "name": "Tom",
   "ring": "planning",
   "brief": "Keep the hall booking off the shared calendar and don't mention it in the group chat."
  },
  {
   "name": "Ana",
   "ring": "helping",
   "brief": "Keep baking the cake quiet, and avoid mentioning it in chats or on shared accounts Sam sees."
  }
 ],
 "field_rules": [
  "Assume every screen Sam uses could show the surprise.",
  "Keep the hall booking and party talk off the calendar.",
  "Never discuss the party in the shared group chat.",
  "Silence lock-screen previews on every device Sam can see.",
  "Keep spending on the surprise off the joint card.",
  "Trust Leo with only the banner, nothing more."
 ],
 "cover_story": "Priya is taking Sam out for a quiet dinner, and they'll need to swing by the hall afterward to pick something up.",
 "cover_holes": [
  "Stopping by the hall right before the reveal day is a pretty specific detour for just picking something up.",
  "If Sam asks what's being picked up, Priya needs a consistent answer ready."
 ],
 "countdown": [
  {
   "days_before": 24,
   "do": "Agree the dinner-and-hall cover story with Priya and Tom before anyone else."
  },
  {
   "days_before": 14,
   "do": "Tom keeps the hall booking off the shared calendar and group chat."
  },
  {
   "days_before": 7,
   "do": "Turn off lock-screen notification previews on devices Sam can see."
  },
  {
   "days_before": 3,
   "do": "Move any spending on the party off the joint card and shopping account."
  },
  {
   "days_before": 1,
   "do": "Remind the wider group not to post or tag anything online."
  },
  {
   "days_before": 0,
   "do": "Priya leads Sam to the hall for the reveal under the dinner cover."
  }
 ],
 "if_rumbled": "If Sam seems suspicious, Priya can lean harder on the dinner cover story, or Priya and Tom can quietly bring Sam in early and finish the party as a smaller, planned reveal."
}

The ring - patch

Input:

{
 "task": "plan",
 "surprise": "A marriage proposal on our weekend away",
 "for_whom": "Jo",
 "days_left": "40",
 "planning": "Mia - Jo's best friend, checked the ring size",
 "helping": "(nobody)",
 "told": "Jo's parents",
 "channels": "card | A joint bank account or card\nparcels | Parcels delivered to a shared home\nlocation | Location sharing",
 "worries": "Jo usually opens the post when it arrives.",
 "cover": "It is just the weekend away we already booked for our anniversary.",
 "page_scan": "(none)"
}

Reply:

{
 "task": "plan",
 "title": "A Weekend Away With A Secret Ring",
 "codename": "Operation Ring Weekender",
 "leaks": [
  {
   "channel": "card",
   "risk": "medium",
   "how": "Anything bought for the proposal could show as a line on the joint bank account or card Jo also sees.",
   "plug": "Keep any purchase for the surprise on a separate account, or check the joint statement before Jo does."
  },
  {
   "channel": "parcels",
   "risk": "high",
   "how": "Jo usually opens the post, so anything delivered for the surprise to your shared home could be found before the reveal.",
   "plug": "Have anything delivered for the surprise sent to Mia's address or held for pickup instead of home."
  },
  {
   "channel": "location",
   "risk": "medium",
   "how": "If location sharing is on, Jo could notice trips linked to planning the proposal or the ring.",
   "plug": "Turn off location sharing, or pause it before any trip connected to planning the proposal."
  }
 ],
 "verdict": "patch",
 "verdict_reason": "One leak is high risk: deliveries to your shared home, since Jo usually opens the post before anyone else does.",
 "circle": [
  {
   "name": "Mia",
   "ring": "planning",
   "brief": "Keep the ring and proposal secret; receive any deliveries for it instead of the shared home."
  },
  {
   "name": "Jo's parents",
   "ring": "told",
   "brief": "Know about the proposal but say nothing to Jo, and avoid mentioning it near Jo."
  }
 ],
 "field_rules": [
  "Never mention the ring or proposal near the joint card.",
  "Keep any purchase for it off the joint account.",
  "Redirect deliveries for it away from your shared home.",
  "Turn off location sharing before any proposal-related trip.",
  "Only the planning ring discusses the ring or proposal.",
  "Assume Jo opens post before anyone else does."
 ],
 "cover_story": "It's just the weekend away we already booked for our anniversary.",
 "cover_holes": [
  "Jo's parents already know, so an odd comment from them could puncture the anniversary story.",
  "Mia checked the ring size, so a slip from Mia would not fit a plain anniversary trip.",
  "Anything delivered for the proposal to your shared home would not fit a plain anniversary trip either."
 ],
 "countdown": [
  {
   "days_before": 30,
   "do": "Keep the ring and proposal off the joint card."
  },
  {
   "days_before": 14,
   "do": "Redirect any deliveries for the proposal to Mia instead of your shared home."
  },
  {
   "days_before": 3,
   "do": "Turn off location sharing and agree the anniversary cover story with Mia."
  },
  {
   "days_before": 0,
   "do": "Make the proposal and let the cover story go once the reveal happens."
  }
 ],
 "if_rumbled": "If Jo grows suspicious, lean on the anniversary cover story first; if that doesn't hold, let Jo in on the proposal early rather than risk an awkward reveal."
}

The bike - airtight

Input:

{
 "task": "plan",
 "surprise": "A restored vintage bicycle as a birthday present",
 "for_whom": "Grandad",
 "days_left": "12",
 "planning": "Nina - my sister, splitting the cost",
 "helping": "Raj - fixing the brakes",
 "told": "(nobody)",
 "channels": "photos | A shared photo library\nphone | Vendors who call a home number",
 "worries": "(not given)",
 "cover": "(not given)",
 "page_scan": "(none)"
}

Reply:

{
 "task": "plan",
 "title": "A Quiet Birthday Bicycle for Grandad",
 "codename": "Operation Silent Spokes",
 "leaks": [
  {
   "channel": "photos",
   "risk": "medium",
   "how": "A photo of the bicycle being restored or its brakes fixed could land in the shared library Grandad can browse.",
   "plug": "Ask Nina and Raj to keep any bicycle or brake photos out of the shared photo library until the reveal."
  },
  {
   "channel": "phone",
   "risk": "medium",
   "how": "A vendor calling about the restored bicycle on the home phone could mention it if Grandad happens to answer.",
   "plug": "Ask vendors calling about the bicycle to reach Nina or Raj directly instead of the home phone."
  }
 ],
 "verdict": "airtight",
 "verdict_reason": "Zero leaks are high risk; the photos and phone channels are medium, needing a look or a chance call rather than everyday noticing.",
 "circle": [
  {
   "name": "Nina",
   "ring": "planning",
   "brief": "Keep bicycle or brake photos off the shared photo library and tell vendors to call you, not the home phone."
  },
  {
   "name": "Raj",
   "ring": "helping",
   "brief": "Avoid posting brake repair photos to the shared library and have any vendor call you instead of the home phone."
  }
 ],
 "field_rules": [
  "Keep bicycle and brake photos out of shared albums.",
  "Tell vendors to call Nina or Raj, not home.",
  "Say nothing about the bicycle near a shared phone.",
  "Assume any shared photo library can be seen anytime.",
  "Keep repair talk between Nina and Raj only.",
  "Vary no routine that Grandad would notice."
 ],
 "cover_story": "No cover story was given. Nina should agree one ordinary reason for the reveal day before Grandad can ask.",
 "cover_holes": [],
 "countdown": [
  {
   "days_before": 12,
   "do": "Ask Nina and Raj to keep bicycle and brake photos off the shared library."
  },
  {
   "days_before": 8,
   "do": "Have vendors call Nina or Raj instead of the home phone."
  },
  {
   "days_before": 3,
   "do": "Decide on an ordinary reason for the reveal day."
  },
  {
   "days_before": 0,
   "do": "Reveal the restored bicycle to Grandad."
  }
 ],
 "if_rumbled": "If Grandad grows suspicious, Nina and Raj should stick to the plan: keep photos off the shared library, keep vendor calls off the home phone, and say nothing more than necessary."
}

Build the input yourself

Load the script in node (global.window = global; require("./hushkit.js")), then:

const K = HushKit;
const form = K.form({ surprise: "A restored bicycle as a birthday present", forWhom: "Grandad", days: "12",
  planning: "Nina - splitting the cost", channels: ["photos", "phone"] });
K.flags(form);                                   // [] - no child, housemate, double listing, big circle or long fuse
const built = K.buildInput(form);
built.provisional;                               // {verdict: "airtight", loud: [], why: "0 loud channels on the dial ..."}
const wire = K.toWire(built.input);              // POST this to /estimate and /run
// then: const reply = K.normalize(K.parseReply(job.output.output));
// K.check(reply, built).notes                   // the page's re-check of the plan
// K.ics(built, reply, new Date())               // the countdown as a calendar file

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope. 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="paste-your-token-here"   # from https://need-to-know.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":"need-to-know"}' | 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", "surprise": "A restored bicycle as a birthday present", "for_whom": "Grandad", "days_left": "12", "planning": "Nina - splitting the cost", "helping": "(nobody)", "told": "(nobody)", "channels": "photos | A shared photo library", "worries": "(not given)", "cover": "(not given)", "page_scan": "(none)"}
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 need-to-know:<task>:<inputHash>:a<attempt> (the one task is plan), and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="need-to-know:plan:$(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}' 

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: need-to-know:plan:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json