← Here Am I
API

Drive Here Am I from your own code

The one metered task, call-the-shabtis, is available over HTTP. It takes up to ten recurring jobs, each with how often it comes round, minutes each time, hours a year and the role you already chose (or not sure), and returns a role and a first move for every job, a reading of the whole roll and the overseer's order: the job to start with.

The division of labour first: the page works out hours a year (times a year multiplied by minutes, rounded to a whole hour, never below 1) and the reading follows a fixed order from the reply's own roles and those hours. The run keeps every role you set and chooses one only for jobs marked not sure.

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":"here-am-i"}), 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, job_count, total_hours, jobs) 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: the roll-call 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 readings

Every run sends task: call-the-shabtis (the one task is call-the-shabtis); a missing or different one is still answered as call-the-shabtis. All fields are strings.

FieldRequiredWhat it holds
taskyesAlways call-the-shabtis.
job_countyesHow many job lines follow (2 to 10), as a string.
total_hoursyesThe sum of every line's hours a year, as a string.
jobsyesNumbered lines such as 1. Ironing work shirts | once a week | 45 min | 39 h a year | role: not sure. How often is one of: every day, every weekday, 3 times a week, once a week, every 2 weeks, once a month, 4 times a year. The role is keep, hand-off, automate, let-go or not sure.
retry_notenoOnly for a reformat retry: why the previous reply was unusable.

The reply is one JSON object:

{"verdict": "a-few-helpers", "headline": "...", "items": [{"n": 1, "job": "Writing my newsletter", "role": "keep", "move": "...", "why": "..."}, ...], "order": "...", "last_word": "..."}
FieldWhat it holds
verdictFirst that applies: box-stays-shut (every role is keep), field-of-reeds (no role is keep), half-the-load (freed hours times 2 is at least total_hours), a-few-helpers. Freed hours are the hours a year of every job whose role is not keep.
headlineA plain reading with the job count, the freed hours and the total hours in digits (at most 26 words).
itemsOne per job, same order: n, job (exact), role (a role you set is copied; for not sure: let-go, then keep, then automate, then hand-off), move (one first step, at most 18 words), why (Building, Maintenance or Recovery, at most 16 words).
orderThe overseer's order: the job with the most hours among those that leave you (a tie goes to the lower number), and its first step (at most 40 words).
last_wordOne line that agrees with the reading (at most 18 words).

The page then checks the reply (RollKit.check) and lists any disagreement under "The page's check". Worked examples, one per reading:

The jobs I secretly like - every job kept: box stays shut

Input:

{
 "task": "call-the-shabtis",
 "job_count": "4",
 "total_hours": "169",
 "jobs": "1. Kneading the Sunday loaf, which I enjoy | once a week | 30 min | 26 h a year | role: not sure\n2. Practising scales on the piano | every weekday | 20 min | 87 h a year | role: not sure\n3. Watering the balcony tomatoes | every day | 5 min | 30 h a year | role: keep\n4. Phoning my gran for a catch-up | once a week | 30 min | 26 h a year | role: not sure"
}

Reply:

{
 "verdict": "box-stays-shut",
 "headline": "Of your 4 jobs, 0 hours a year are freed out of 169 hours total, so everything stays with you.",
 "items": [
  {
   "n": 1,
   "job": "Kneading the Sunday loaf, which I enjoy",
   "role": "keep",
   "move": "Put on music you enjoy while kneading the loaf.",
   "why": "Kneading the loaf is something you enjoy, fitting Recovery."
  },
  {
   "n": 2,
   "job": "Practising scales on the piano",
   "role": "keep",
   "move": "Set a timer so practising feels like a short, focused session.",
   "why": "Practising scales on your piano builds a skill of your own, fitting Building."
  },
  {
   "n": 3,
   "job": "Watering the balcony tomatoes",
   "role": "keep",
   "move": "Water the tomatoes at the same time every day to build a steady habit.",
   "why": "Watering the balcony tomatoes keeps something of yours growing, fitting Building."
  },
  {
   "n": 4,
   "job": "Phoning my gran for a catch-up",
   "role": "keep",
   "move": "Settle into a comfortable spot before calling to enjoy the catch-up.",
   "why": "Phoning your gran for a catch-up is time together, fitting Recovery."
  }
 ],
 "order": "Practising scales on the piano holds the most hours a year among your jobs, and it stays with you.",
 "last_word": "Every hour on your list stays with you each year."
}

The errand pile - no job kept: field of reeds

Input:

{
 "task": "call-the-shabtis",
 "job_count": "5",
 "total_hours": "72",
 "jobs": "1. Paying the electricity bill by hand | once a month | 15 min | 3 h a year | role: not sure\n2. Ironing work shirts | once a week | 45 min | 39 h a year | role: not sure\n3. Printing bank statements to file, though nobody ever reads them | once a month | 20 min | 4 h a year | role: not sure\n4. Reordering the same cat food online | every 2 weeks | 10 min | 4 h a year | role: automate\n5. Collecting dry cleaning across town | every 2 weeks | 50 min | 22 h a year | role: not sure"
}

Reply:

{
 "verdict": "field-of-reeds",
 "headline": "5 jobs, totalling 72 hours a year; all 72 of those hours are freed, none kept.",
 "items": [
  {
   "n": 1,
   "job": "Paying the electricity bill by hand",
   "role": "automate",
   "move": "Set up an automatic payment instruction with your bank.",
   "why": "Paying the electricity bill by hand is Maintenance that repeats the same steps."
  },
  {
   "n": 2,
   "job": "Ironing work shirts",
   "role": "hand-off",
   "move": "Look for a local laundry or ironing service nearby.",
   "why": "Ironing work shirts is Maintenance that another pair of hands could do."
  },
  {
   "n": 3,
   "job": "Printing bank statements to file, though nobody ever reads them",
   "role": "let-go",
   "move": "Pause printing the statements and check if anyone asks for them.",
   "why": "Printing bank statements to file is Maintenance that nobody ever reads."
  },
  {
   "n": 4,
   "job": "Reordering the same cat food online",
   "role": "automate",
   "move": "Set up a repeat delivery or subscription for the cat food.",
   "why": "Reordering the same cat food online is Maintenance repeating identical steps."
  },
  {
   "n": 5,
   "job": "Collecting dry cleaning across town",
   "role": "hand-off",
   "move": "Ask a delivery or courier service to collect it instead.",
   "why": "Collecting dry cleaning across town is Maintenance another person could handle."
  }
 ],
 "order": "Start with \"Ironing work shirts\": ask about a laundry or pressing service that could take this off your hands.",
 "last_word": "Every hour here could leave you, should you choose to let it."
}

The flat on a weeknight - 119 of 197 hours leave: half the load

Input:

{
 "task": "call-the-shabtis",
 "job_count": "5",
 "total_hours": "197",
 "jobs": "1. Scrubbing the bathroom | once a week | 60 min | 52 h a year | role: not sure\n2. Topping up the travel card | once a week | 10 min | 9 h a year | role: not sure\n3. Sketching after dinner | 3 times a week | 30 min | 78 h a year | role: not sure\n4. Folding laundry | 3 times a week | 20 min | 52 h a year | role: not sure\n5. Re-sorting the spice rack out of habit | once a month | 30 min | 6 h a year | role: not sure"
}

Reply:

{
 "verdict": "half-the-load",
 "headline": "5 jobs total 197 hours a year, and 119 of those hours could move away from you, leaving the rest in your hands.",
 "items": [
  {
   "n": 1,
   "job": "Scrubbing the bathroom",
   "role": "hand-off",
   "move": "Look for a local cleaning service to take over scrubbing the bathroom.",
   "why": "Scrubbing the bathroom is Maintenance that another person could take on."
  },
  {
   "n": 2,
   "job": "Topping up the travel card",
   "role": "automate",
   "move": "Set up a standing order to top up the travel card.",
   "why": "Topping up the travel card is repeating Maintenance suited to a standing arrangement."
  },
  {
   "n": 3,
   "job": "Sketching after dinner",
   "role": "keep",
   "move": "Lay out your sketching materials after dinner before sitting down.",
   "why": "Sketching after dinner is Building: making something that is your own."
  },
  {
   "n": 4,
   "job": "Folding laundry",
   "role": "hand-off",
   "move": "Look for a laundry service to take on folding the laundry.",
   "why": "Folding laundry is Maintenance housework another person could take on instead."
  },
  {
   "n": 5,
   "job": "Re-sorting the spice rack out of habit",
   "role": "let-go",
   "move": "Pause re-sorting the spice rack for now and leave it alone.",
   "why": "Re-sorting the spice rack out of habit is Maintenance kept going from habit alone."
  }
 ],
 "order": "Start with Scrubbing the bathroom: look into hiring a cleaning service to take this scrub off your hands.",
 "last_word": "At least half of your 197 hours a year could move off your plate."
}

The newsletter week - 91 of 282 hours leave, two roles set by you: a few helpers

Input:

{
 "task": "call-the-shabtis",
 "job_count": "5",
 "total_hours": "282",
 "jobs": "1. Writing my newsletter | once a week | 120 min | 104 h a year | role: not sure\n2. Answering the same three reader questions | every weekday | 15 min | 65 h a year | role: not sure\n3. Learning Portuguese on the bus | every weekday | 20 min | 87 h a year | role: keep\n4. Booking the meeting room by email | once a week | 10 min | 9 h a year | role: not sure\n5. Vacuuming the stairs | once a week | 20 min | 17 h a year | role: hand-off"
}

Reply:

{
 "verdict": "a-few-helpers",
 "headline": "5 jobs make up this roll; 91 of the 282 hours a year could move, with the rest staying with you.",
 "items": [
  {
   "n": 1,
   "job": "Writing my newsletter",
   "role": "keep",
   "move": "Draft an outline first, so each newsletter session starts with a clear shape.",
   "why": "Writing my newsletter is Building something of your own, so it stays with you."
  },
  {
   "n": 2,
   "job": "Answering the same three reader questions",
   "role": "automate",
   "move": "Set up template replies for the three questions, ready to send without rewriting.",
   "why": "Answering the same three reader questions is repeating Maintenance, so a tool fits."
  },
  {
   "n": 3,
   "job": "Learning Portuguese on the bus",
   "role": "keep",
   "move": "Pick a short phrase to repeat silently during each bus ride.",
   "why": "Learning Portuguese on the bus is Building a skill of your own."
  },
  {
   "n": 4,
   "job": "Booking the meeting room by email",
   "role": "automate",
   "move": "Set up a standing calendar hold to reserve the meeting room ahead of time.",
   "why": "Booking the meeting room by email repeats the same steps, which suits Maintenance."
  },
  {
   "n": 5,
   "job": "Vacuuming the stairs",
   "role": "hand-off",
   "move": "Look into hiring a cleaning service to handle the stairs.",
   "why": "Vacuuming the stairs is Maintenance that someone else could take on."
  }
 ],
 "order": "Start with Answering the same three reader questions: put together a ready set of answers you can send without rewriting them each time.",
 "last_word": "Most of your hours a year stay with you; only a smaller share can move."
}

Build the input yourself

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

const K = RollKit;
const built = K.buildInput(K.form({ jobs: [
  { text: "Watering the houseplants", often: "weekly", mins: 15, role: "undecided" },
  { text: "Renewing the parking permit", often: "quarterly", mins: 20, role: "undecided" } ] }));
built.facts.items                       // each job with its hours a year
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 reply
// K.md(built, reply, "my roll")        // the roll-call 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://here-am-i.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":"here-am-i"}' | 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": "call-the-shabtis", "job_count": "5", "total_hours": "282", "jobs": "1. Writing my newsletter | once a week | 120 min | 104 h a year | role: not sure\n2. ..."}
# - 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 here-am-i:<task>:<inputHash>:a<attempt> (the one task is call-the-shabtis), 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="here-am-i:call-the-shabtis:$(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: here-am-i:call-the-shabtis:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json