← Box Me In
API

Drive Box Me In from your own code

The one metered task, box-limits, is available over HTTP. It takes a small project in the person's words, 3 to 6 limits around it - each a fact, someone else's rule, a habit or a limit chosen on purpose, and fixed or bendable - plus the page's own count, verdict and move target, and returns the verdict, a reading and a push question for every limit, the room the limits leave, the move, a headline and a closing line.

The division of labour first: the page counts the limits, a fixed rule decides the verdict and the page picks the move target; the model reads them. After a run the page checks that the verdict follows the rule, that every limit is read in order with the kind the person gave it, that each push is a question, that the move names the page's target, and that no ids leak into the prose.

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": "..." } }

The token is minted for this app (the guest call takes {"slug":"box-me-in"}), 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, project, limits, tally) 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 reading is metered and needs 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, the input and the verdicts

Every run sends task: box-limits (the one task is box-limits); a missing or different one is still answered as box-limits. The verdict rule, checked in order: if habits are at least half of the limits, paper-walls (move: test the first habit); otherwise if there are 5 or more limits and at least 3 in 4 are fixed, vise-locked (move: loosen the first fixed chosen limit, else the first fixed rule, else the first fixed limit); otherwise if no limit is chosen, wide-open (move: add the page's card, picked by a hash of the project); otherwise snug-fit (move: keep the first chosen limit).

FieldRequiredWhat it holds
taskyesAlways box-limits.
projectyesThe project in the person's words, up to 100 characters.
limitsyes1 line per limit, limit 2 | rule | fixed | landlord says no screws in the wall. Limits are up to 80 characters.
tallyyes1 line per limit, L2 | rule | fixed | 7 words, then the counts limits 3 | fixed 2 | bendable 1 | fact 2 | rule 1 | habit 0 | chosen 0, then verdict: wide-open, then the move target, such as move: add - finish it in a single sitting or move: test limit 1.
retry_notenoOnly on the page's single reformat retry.

The reply is one JSON object:

{"task": "box-limits", "verdict": "snug-fit", "headline": "...", "limits": [{"n": 1, "kind": "chosen", "reading": "...", "push": "...?"}, ...], "space": "...", "move": "...", "closing": "..."}
FieldWhat it holds
verdictpaper-walls, vise-locked, wide-open or snug-fit, copied from the tally.
headlineThe verdict in plain words, at most 16 words.
limits1 entry per limit, in order. n is the limit number; kind is copied from the tally; reading at most 25 words; push a question that works inside the limit, at most 22 words.
spaceThe room the limits leave together, to the person as "you", at most 40 words.
moveThe one move on the page's target (test, loosen, keep or add), at most 25 words.
closingOne sentence restating the verdict and inviting the next step, at most 20 words.

The page then checks the reply (BoxKit.check) and lists any disagreement under "The page's check".

Worked examples - every verdict

These are the four free samples on the page: the exact input the page sends and the reply that replays for free.

Walls that may be habits - paper-walls

Input:

{
 "task": "box-limits",
 "project": "Redo the corner of the spare room where I work",
 "limits": "limit 1 | habit | fixed | a desk has to face the wall\nlimit 2 | habit | fixed | I need 2 screens to get anything done\nlimit 3 | rule | fixed | budget is 150\nlimit 4 | chosen | bendable | only things I already own",
 "tally": "L1 | habit | fixed | 7 words\nL2 | habit | fixed | 8 words\nL3 | rule | fixed | 3 words\nL4 | chosen | bendable | 5 words\nlimits 4 | fixed 3 | bendable 1 | fact 0 | rule 1 | habit 2 | chosen 1\nverdict: paper-walls\nmove: test limit 1"
}

Reply:

{
 "task": "box-limits",
 "verdict": "paper-walls",
 "headline": "Some of these limits are habits, which may not be as solid as they seem.",
 "limits": [
  {
   "n": 1,
   "kind": "habit",
   "reading": "Fixed, this rules out other desk orientations, leaving the corner open; as a habit it may be an old way, not a given.",
   "push": "Desk facing the wall, what other changes could still reshape this corner of the room?"
  },
  {
   "n": 2,
   "kind": "habit",
   "reading": "Fixed, this habit rules out working with fewer than 2 screens, leaving the corner open; as a routine, it may not be a must.",
   "push": "Screens set at 2, what layout could the corner still make room for alongside the desk?"
  },
  {
   "n": 3,
   "kind": "rule",
   "reading": "Fixed, this rule rules out spending past 150 on the corner, leaving the specific materials and approach still open to choose within that limit.",
   "push": "Budget capped at 150, what combination of materials for the corner could it still stretch to cover?"
  },
  {
   "n": 4,
   "kind": "chosen",
   "reading": "Bendable, this chosen limit rules out buying anything new, forcing you to draw on what you already own to shape the corner.",
   "push": "Things you already own, what part could they play in redoing this corner?"
  }
 ],
 "space": "You have 4 limits for this corner: 3 fixed, 1 bendable. With the wall-facing desk, 2 screens, and a 150 budget set, you can still choose how to use what you already own.",
 "move": "Try the project as if limit 1 - \"a desk has to face the wall\" - weren't there, just to see if that habit holds.",
 "closing": "These habits may shape this project more than necessity does; testing limit 1 could reveal the next step."
}

Nearly every limit fixed - vise-locked

Input:

{
 "task": "box-limits",
 "project": "Sew a tote bag out of an old pair of jeans",
 "limits": "limit 1 | fact | fixed | 1 pair of jeans, nothing else\nlimit 2 | chosen | fixed | hand stitching only, no machine\nlimit 3 | rule | fixed | it must fit my laptop\nlimit 4 | chosen | fixed | finished by Sunday\nlimit 5 | fact | fixed | I only have black thread",
 "tally": "L1 | fact | fixed | 6 words\nL2 | chosen | fixed | 5 words\nL3 | rule | fixed | 5 words\nL4 | chosen | fixed | 3 words\nL5 | fact | fixed | 5 words\nlimits 5 | fixed 5 | bendable 0 | fact 2 | rule 1 | habit 0 | chosen 2\nverdict: vise-locked\nmove: loosen limit 2"
}

Reply:

{
 "task": "box-limits",
 "verdict": "vise-locked",
 "headline": "Every limit on this tote bag is fixed, leaving almost no space to shift.",
 "limits": [
  {
   "n": 1,
   "kind": "fact",
   "reading": "As a fact, fixed at 1 pair of jeans, nothing else, this rules out added fabric, leaving every cut and seam inside the jeans.",
   "push": "Jeans, as the only material allowed, can be cut and joined many ways, so what shape fits this tote bag?"
  },
  {
   "n": 2,
   "kind": "chosen",
   "reading": "Chosen and fixed, hand stitching only, no machine, forces every seam on this tote bag to be joined by hand, start to finish.",
   "push": "Hand stitching, with no machine, shapes how every seam gets joined, so what pace of stitching suits this tote bag?"
  },
  {
   "n": 3,
   "kind": "rule",
   "reading": "As a rule, fixed, it must fit your laptop sets the tote bag's inside space, ruling out a bag too small to hold it.",
   "push": "Laptop size sets the smallest space this tote bag needs, so how roomy could the bag still be?"
  },
  {
   "n": 4,
   "kind": "chosen",
   "reading": "Chosen and fixed, finished by Sunday sets a deadline for the tote bag, forcing every step of the sewing to happen before Sunday arrives.",
   "push": "Sunday marks the end point for this tote bag, so which steps of sewing could happen first?"
  },
  {
   "n": 5,
   "kind": "fact",
   "reading": "As a fact, fixed, only black thread being available rules out matching thread color, leaving every other choice for the tote bag open.",
   "push": "Thread, the only color being black, will show on every seam, so where could that stitching sit on this tote bag?"
  }
 ],
 "space": "With all 5 limits fixed and 0 of them bendable, you still choose how to cut the jeans, shape the space around your laptop, and place the black thread in each hand-stitched seam, all before Sunday.",
 "move": "You could loosen limit 2, hand stitching only, no machine, by keeping most seams hand-stitched and allowing a machine for the rest.",
 "closing": "This box stays fixed around your tote bag, so try loosening limit 2 to see what room opens up."
}

Nothing chosen on purpose - wide-open

Input:

{
 "task": "box-limits",
 "project": "Rearrange the bookshelf",
 "limits": "limit 1 | fact | fixed | the shelf has 5 rows\nlimit 2 | rule | fixed | landlord says no screws in the wall\nlimit 3 | fact | bendable | around 200 books",
 "tally": "L1 | fact | fixed | 5 words\nL2 | rule | fixed | 7 words\nL3 | fact | bendable | 3 words\nlimits 3 | fixed 2 | bendable 1 | fact 2 | rule 1 | habit 0 | chosen 0\nverdict: wide-open\nmove: add - finish it in a single sitting"
}

Reply:

{
 "task": "box-limits",
 "verdict": "wide-open",
 "headline": "None of the 3 limits was picked by choice, leaving the project plenty of room.",
 "limits": [
  {
   "n": 1,
   "kind": "fact",
   "reading": "The shelf's 5 rows are fixed, ruling out adding more rows, but leaves open how you arrange books within each row.",
   "push": "Rows in the shelf could organize the books by what?"
  },
  {
   "n": 2,
   "kind": "rule",
   "reading": "The rule that the landlord bars screws in the wall is fixed, ruling out new screws, but leaves open rearranging the shelf without them.",
   "push": "The wall without new screws still offers which other spots for the shelf?"
  },
  {
   "n": 3,
   "kind": "fact",
   "reading": "Around 200 books sets how much fits now, but being bendable leaves open changing how many stay on the shelf.",
   "push": "Books could be grouped in how many ways across the shelf?"
  }
 ],
 "space": "With 3 limits, 2 fixed and 1 bendable, and none picked by choice, you still decide how the 5 rows hold around 200 books without new wall screws.",
 "move": "You could add another limit: finish rearranging the bookshelf in a single sitting.",
 "closing": "This project still has room to grow until you choose to add a limit that gives it shape."
}

A chosen limit doing the work - snug-fit

Input:

{
 "task": "box-limits",
 "project": "Make a birthday card for my niece",
 "limits": "limit 1 | chosen | bendable | only paper scraps from the recycling box\nlimit 2 | rule | fixed | it has to fit a standard envelope\nlimit 3 | chosen | bendable | no words, just pictures\nlimit 4 | fact | bendable | 2 free evenings this week",
 "tally": "L1 | chosen | bendable | 7 words\nL2 | rule | fixed | 7 words\nL3 | chosen | bendable | 4 words\nL4 | fact | bendable | 5 words\nlimits 4 | fixed 1 | bendable 3 | fact 1 | rule 1 | habit 0 | chosen 2\nverdict: snug-fit\nmove: keep limit 1"
}

Reply:

{
 "task": "box-limits",
 "verdict": "snug-fit",
 "headline": "A chosen limit gives your birthday card real shape to work within.",
 "limits": [
  {
   "n": 1,
   "kind": "chosen",
   "reading": "Only scraps from the recycling box rule out new paper, but leave many kinds of scraps open to use; you chose this bendable limit yourself.",
   "push": "Scraps from the recycling box could become which shapes for your niece's card?"
  },
  {
   "n": 2,
   "kind": "rule",
   "reading": "Fitting a standard envelope rules out larger card sizes, but leaves open any smaller card made to fit it; this rule stays fixed.",
   "push": "Envelope dimensions leave room for which parts of the card?"
  },
  {
   "n": 3,
   "kind": "chosen",
   "reading": "Choosing no words, just pictures rules out text on the card, leaving pictures alone to carry your niece's birthday card; this chosen limit stays bendable.",
   "push": "Pictures without words could show what for your niece's birthday card?"
  },
  {
   "n": 4,
   "kind": "fact",
   "reading": "2 free evenings this week rule out a longer stretch, but leave room to work on the card during those evenings; this fact stays bendable.",
   "push": "Evenings free this week could fit which steps toward the card?"
  }
 ],
 "space": "Across your 4 limits, only 1 is fixed, so your birthday card for your niece has room to use recycling-box scraps, fit the envelope, and favor pictures within your 2 evenings.",
 "move": "Limit 1, \"only paper scraps from the recycling box,\" is shaping this birthday card and is worth keeping.",
 "closing": "This chosen limit shapes your birthday card well, ready for you to begin working within it."
}

Build the input yourself

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

const K = BoxKit;
const built = K.buildInput(K.form({ project: "Paint a mural on the shed door", limits: [
  { text: "the door is 2 metres tall", kind: "fact", firm: "fixed" },
  { text: "only the paint left in the garage", kind: "chosen", firm: "bendable" },
  { text: "neighbours can see it", kind: "rule", firm: "fixed" } ] }));
built.input.tally;                      // the page's lines, the counts, the verdict and the move target
const wire = K.toWire(built.input);     // POST this to /estimate and /run
// then: const reply = K.normalize(K.parseReply(job.output.output));
// built.facts.verdict                  // the verdict the page's rule gives
// K.check(reply, built).notes          // the page's re-check of the reply
// K.md(built, reply, "my box")         // the box as Markdown

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://box-me-in.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":"box-me-in"}' | 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": "box-limits", "project": "Rearrange the bookshelf", "limits": "limit 1 | fact | fixed | the shelf has 5 rows
...", "tally": "L1 | fact | fixed | 5 words
..."}
# - see the field 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 box-me-in:<task>:<inputHash>:a<attempt> (the one task is box-limits), 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="box-me-in:box-limits:$(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: box-me-in:box-limits:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json