← Remembered As
API

Drive Remembered As from your own code

The one metered task, keepsake, is available over HTTP. It takes one experience split into up to ten moments, each with a feeling from -5 to +5 and a number of minutes, plus the page's numbers - and returns a keepsake reading: the verdict, a headline, what memory is likely to lean on (the peak), what the ending does, what memory mostly leaves out, one small change to the ending and a caption for the snow globe.

The division of labour first: the page counts; the model reads. The page works out the lived average (feelings weighted by minutes), the peak (the most intense moment; a tie goes to the longer, then the later), the end, the remembered score (peak and end averaged), the gap and the verdict, and sends them as facts. The model never changes those numbers. After the reply, the page lists anything that does not match. A thinking aid, not professional advice.

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":"remembered-as"}), 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, experience, moments, facts) 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 keepsake 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 rules

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

FieldRequiredWhat it holds
taskyesAlways keepsake.
experienceyesname | it already happened (or it is still ahead) | note: .... The note is none when empty; a name is up to 70 characters, a note up to 240.
momentsyesOne moment a line, in order: N. what happened | felt: +3 | minutes: 20. Two to ten lines; feelings are whole numbers from -5 to +5, minutes 1 to 720, a moment up to 80 characters.
factsyesThe page's numbers: total minutes, lived average, peak, end, remembered score, gap, verdict, whether the ending rises, dips or is level, whether the end is above or below the lived average, what memory mostly leaves out and, for a harsher verdict, the ending what-if. Build it with MemKit.buildInput.
retry_notenoOnly on a reformat retry: why the previous reply was unusable.

The verdict, from the gap (remembered minus lived): sweeter-in-memory at +1.0 or more, harsher-in-memory at -1.0 or less, otherwise true-to-life.

The reply

One JSON object, no prose around it:

{"task": "keepsake", "verdict": "harsher-in-memory", "headline": "...", "peak_note": "...", "end_note": "...", "left_out": "...", "next_time": "...", "caption": "..."}
FieldWhat it holds
verdictsweeter-in-memory, harsher-in-memory or true-to-life - always the page's.
headlineOne sentence: what the verdict means, with the remembered score and the lived average.
peak_noteThe peak moment and why memory is likely to lean on it.
end_noteWhether the ending rises, dips or is level, and whether it pulls the memory up or down.
left_outThe longest moment memory mostly leaves out and the share of the minutes it drops; empty when every moment is the peak or the end.
next_timeOne small, physically sensible change to the ending, using only the moments given - never a date or a time.
caption3 to 8 words for the base of the snow globe, from the input's own words.

The page reads the reply with ReplyKit.normalize and checks it with ReplyKit.check, which lists, under "The page's check", a verdict that differs from the page's, a headline number that is not one of the page's, a note that does not name the right moment, a change that sets a day or time you did not give, and an overlong caption.

Worked examples - every verdict

These are the free samples on the page: the exact input the page sends and the reply that replays for free. Together they cover every verdict - sweeter in memory, harsher in memory and true to life - an experience that already happened and one still ahead, and a peak that is also the end.

A long flight home - sweeter-in-memory

Input:

{
 "task": "keepsake",
 "experience": "The long flight home | it already happened | note: none",
 "moments": "1. Boarding queue at the gate | felt: -2 | minutes: 35\n2. Middle seat, no sleep | felt: -3 | minutes: 300\n3. A film I had wanted to see | felt: +2 | minutes: 120\n4. Landing over the city lights | felt: +4 | minutes: 15",
 "facts": "Total: 470 minutes over 4 moments.\nLived average (each feeling weighted by its minutes): -1.4\nPeak (the most intense moment, either way; ties go to the longer, then the later): moment 4 \"Landing over the city lights\", felt +4, 15 minutes\nEnd (the last moment): moment 4 \"Landing over the city lights\", felt +4, 15 minutes - the peak is also the end\nRemembered score (peak and end averaged; here one moment is both): +4.0\nGap (remembered minus lived): +5.4\nVerdict: sweeter-in-memory (rule: gap +1.0 or more is sweeter-in-memory; -1.0 or less is harsher-in-memory; otherwise true-to-life)\nEnding: rises (moment 3 felt +2, the last felt +4)\nEnd against the lived average: the last moment (+4) is above the lived average (-1.4), so it pulls the memory up.\nMostly left out of memory: 3 moments that are neither peak nor end, 455 minutes of 470 minutes (97%). The longest of them: moment 2 \"Middle seat, no sleep\", 300 minutes, felt -3."
}

Reply:

{
 "task": "keepsake",
 "verdict": "sweeter-in-memory",
 "headline": "Memory is likely to keep this flight kinder than it was lived, with a remembered score of +4.0 against a lived average of -1.4.",
 "peak_note": "Landing over the city lights, felt +4, is likely to carry the whole memory of this flight.",
 "end_note": "Landing over the city lights rises from the moment before it and pulls the memory up above how the flight was lived overall.",
 "left_out": "Memory is likely to leave out \"Middle seat, no sleep,\" which lasted 300 minutes, part of the 97% of the flight that mostly goes unremembered.",
 "next_time": "Next time you're on a similar flight, keep the ending just as it is - let landing over the city lights close it out.",
 "caption": "Landing over the city lights"
}

A day at the climbing wall - harsher-in-memory

Input:

{
 "task": "keepsake",
 "experience": "Saturday at the climbing wall | it already happened | note: First time leading a route.",
 "moments": "1. Warm-up on the easy wall | felt: +2 | minutes: 20\n2. Bouldering with Sam | felt: +3 | minutes: 60\n3. Leading my first route | felt: +4 | minutes: 15\n4. Locker key stuck, missed the bus | felt: -3 | minutes: 25",
 "facts": "Total: 120 minutes over 4 moments.\nLived average (each feeling weighted by its minutes): +1.7\nPeak (the most intense moment, either way; ties go to the longer, then the later): moment 3 \"Leading my first route\", felt +4, 15 minutes\nEnd (the last moment): moment 4 \"Locker key stuck, missed the bus\", felt -3, 25 minutes\nRemembered score (peak and end averaged): +0.5\nGap (remembered minus lived): -1.2\nVerdict: harsher-in-memory (rule: gap +1.0 or more is sweeter-in-memory; -1.0 or less is harsher-in-memory; otherwise true-to-life)\nEnding: dips (moment 3 felt +4, the last felt -3)\nEnd against the lived average: the last moment (-3) is below the lived average (+1.7), so it pulls the memory down.\nEnding what-if: a last moment felt -2 or better would lift it out of harsher-in-memory.\nMostly left out of memory: 2 moments that are neither peak nor end, 80 minutes of 120 minutes (67%). The longest of them: moment 2 \"Bouldering with Sam\", 60 minutes, felt +3."
}

Reply:

{
 "task": "keepsake",
 "verdict": "harsher-in-memory",
 "headline": "Memory is likely to keep this experience harsher than it was lived, with a remembered score of +0.5 against a lived average of +1.7.",
 "peak_note": "Leading your first route, which felt +4, is likely to be the moment memory leans on most.",
 "end_note": "Your ending, \"Locker key stuck, missed the bus,\" dips from the moment before and pulls the memory down.",
 "left_out": "Bouldering with Sam, which lasted 60 minutes, is the longest of the moments memory is likely to leave out, part of the 67% of the time memory mostly drops.",
 "next_time": "For a similar one next time, you could close with a short pause to recall leading your first route.",
 "caption": "Leading my first route"
}

A market day still ahead - true-to-life

Input:

{
 "task": "keepsake",
 "experience": "Market and lunch with Jo | it is still ahead | note: none",
 "moments": "1. Bus into town | felt: -1 | minutes: 30\n2. Browsing the market stalls | felt: +2 | minutes: 60\n3. Lunch at the noodle bar | felt: +3 | minutes: 45\n4. Queue at the bakery | felt: -1 | minutes: 20\n5. Bus home | felt: -1 | minutes: 30",
 "facts": "Total: 185 minutes over 5 moments.\nLived average (each feeling weighted by its minutes): +0.9\nPeak (the most intense moment, either way; ties go to the longer, then the later): moment 3 \"Lunch at the noodle bar\", felt +3, 45 minutes\nEnd (the last moment): moment 5 \"Bus home\", felt -1, 30 minutes\nRemembered score (peak and end averaged): +1.0\nGap (remembered minus lived): +0.1\nVerdict: true-to-life (rule: gap +1.0 or more is sweeter-in-memory; -1.0 or less is harsher-in-memory; otherwise true-to-life)\nEnding: level (moment 4 felt -1, the last felt -1)\nEnd against the lived average: the last moment (-1) is below the lived average (+0.9), so it pulls the memory down.\nMostly left out of memory: 3 moments that are neither peak nor end, 110 minutes of 185 minutes (59%). The longest of them: moment 2 \"Browsing the market stalls\", 60 minutes, felt +2."
}

Reply:

{
 "task": "keepsake",
 "verdict": "true-to-life",
 "headline": "The remembered score of +1.0 sits close to the lived average of +0.9, so memory is likely to keep this experience close to how it feels when it happens.",
 "peak_note": "Lunch at the noodle bar, which felt +3, is likely to be the moment memory leans on most.",
 "end_note": "Bus home felt -1, the same as the moment before it, so the ending is level, and is likely to pull the memory down, below the lived average.",
 "left_out": "Browsing the market stalls (60 minutes) is the longest of the moments memory is likely to leave out, part of the 59% of all the minutes that mostly fade from memory.",
 "next_time": "You could move lunch at the noodle bar to come right before bus home.",
 "caption": "Lunch at the noodle bar"
}

Build the input yourself

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

const K = MemKit, R = ReplyKit;
const built = K.buildInput(K.form({ name: "The long flight home", tense: "past", note: "", moments: [
  { label: "Middle seat, no sleep", feel: -3, mins: 300 },
  { label: "Landing over the city lights", feel: 4, mins: 15 } ] }));  // built.ok is false, with built.note, until two moments have minutes
const wire = K.toWire(built.input);   // POST this to /estimate and /run; built.analysis holds lived10, rem10, gap10, verdict
// then: const reply = R.normalize(R.parseReply(job.output.output));
// R.check(reply, built).notes        // the page's re-check of the reply
// R.md(built, reply, K.title(built.form))   // the moments, the numbers and the reading as Markdown

Moment keys: label, feel (-5 to 5) and mins (1 to 720). Experience keys: name, tense (past or planned), note and moments.

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://remembered-as.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":"remembered-as"}' | 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": "keepsake", "experience": "The long flight home | it already happened | note: none", "moments": "1. Boarding queue at the gate | felt: -2 | minutes: 35 ...", "facts": "...", ...}
# - 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 remembered-as:<task>:<inputHash>:a<attempt> (the one task is keepsake), 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="remembered-as:keepsake:$(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: remembered-as:keepsake:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json