Butterfly Keys — API notes

What the app exposes, and how to read or restore a garden.

Progress token Open the game

API & developer notes

Butterfly Keys is a no-model app. There is no AI lane, so there is no /estimate, no /run, no /run-stream and no /sessions — documenting them would be inventing an API the app does not have. What it does expose is real and useful: the platform key-value store where a player’s garden lives. That is what this page covers, so you can read a child’s progress, move it to another device, seed a classroom, or clear it.

Base URL %s. Every response is a {"ok":true,"data":{...}} / {"ok":false,"error":{...}} envelope. All calls below are free; the app charges nothing and has no metered path.

Error codes

codeHTTPwhat it means
UNAUTHORIZED401Missing or malformed bearer token.
INVALID_TOKEN401The token is expired or revoked. Mint a new guest token.
NOT_FOUND404No such key. For /data/garden this simply means this player has never played.
VALIDATION_ERROR400Malformed body. The commonest cause is sending the slug as a header instead of in the /guest body.
RATE_LIMITED429Too many calls. Back off and retry.
QUOTA_EXCEEDED413The document is over the 64 KB per-record cap.

The garden record

fieldtypemeaning
vnumberSchema version. Currently 1.
doneobjectLevel id (as a string key) → {stars: 1-3, plays: number}. Best stars are kept, never lowered.
butterfliesnumber[]Level ids whose collectible butterfly has hatched. One per level, twenty in all.
flowersnumberLifetime correct keystrokes. Monotonic.
updatednumberEpoch milliseconds of the last change.

A level is unlocked when the level before it has an entry in done. To open every level for a returning player, give each id an entry.

The app never replaces one copy of this record with another. Whenever the stored copy and the browser's local mirror disagree — on boot, on a first sign-in, or across two devices — they are merged by taking the best of each: maximum stars per level, the union of butterflies, the larger flowers, the later updated. The merge is commutative and idempotent, so a record you write here is added to, never overwritten wholesale, and a stale clock cannot delete anything. Keep the document small; the platform caps a single document at 64 KB.

1 Get a token

Every call needs a bearer token. Butterfly Keys never asks a child to sign in, so by default the token it uses is a guest token, minted by posting the app's slug. A grown-up may optionally sign in from the app's grown-ups panel, in which case the browser holds a personal account token instead and the garden record below belongs to that account rather than to an anonymous guest — the endpoints are identical either way. The slug goes in the request body — an X-App-Slug header returns 400. If you would rather reuse the token the browser already holds, open the token page; it shows it, copies it, and mints a fresh one, so you never need the DevTools console.

POST https://api.skillsafe.ai/v1/app-api/guest

{"ok":true,"data":{"token":"aut_...","guest_id":"gst_...","credits":0}}

Guest identity note: every call to /guest mints a new subject. Keep one token for the whole session, or you will be looking at a different (empty) garden each time.

2 Check who the token is

/me tells you whether the token is a guest or a signed-in user, and its credit balance. The balance is decorative here: Butterfly Keys has no model and charges nothing, so a zero-credit guest can use every feature.

GET https://api.skillsafe.ai/v1/app-api/me

{"ok":true,"data":{"subject_type":"guest","subject_id":"gst_...","credits":0}}

3 Confirm the app is free and model-less

/app-info is the authoritative statement of the app's commercial shape. For Butterfly Keys price_credits is 0 and there is no model bound, which is why none of the run endpoints exist.

GET https://api.skillsafe.ai/v1/app-api/app-info

{"ok":true,"data":{"slug":"butterfly-keys","title":"Butterfly Keys","price_credits":0,"model":null}}

4 List the keys this app stores

Butterfly Keys keeps exactly one record per player, under the key garden, in the platform per-user key-value store. Listing keys is the quickest way to confirm you are holding the right token.

GET https://api.skillsafe.ai/v1/app-api/data

{"ok":true,"data":{"keys":["garden"]}}

5 Read the garden

This is the whole of a player's progress. done maps a level id to the best stars earned and how many times it has been played; butterflies lists the level ids whose collectible has hatched; flowers is the lifetime count of correct keystrokes. A 404 means this token has never played.

GET https://api.skillsafe.ai/v1/app-api/data/garden

{"ok":true,"data":{"value":{"v":1,"done":{"1":{"stars":3,"plays":2},"2":{"stars":2,"plays":1}},"butterflies":[1,2],"flowers":46,"updated":1786000000000}}}

6 Write the garden back

Useful for moving a child's progress to a new device, or for seeding a classroom of tokens at a particular level. The app normalises whatever it reads — unknown fields are dropped, out-of-range stars are clamped into 1–3, and a corrupt record degrades to a fresh garden rather than an error — so a partial document is safe to send.

PUT https://api.skillsafe.ai/v1/app-api/data/garden

{"ok":true,"data":{"ok":true}}

The app merges a remote record with the device mirror by taking the most progress from each, so writing a smaller record will not erase butterflies that the browser still remembers locally.

7 Clear the garden

Deletes the record outright. The child's browser will still hold its local mirror until she uses the “Start the garden over” button on the grown-ups panel, which clears both.

DELETE https://api.skillsafe.ai/v1/app-api/data/garden

{"ok":true,"data":{"ok":true}}

8 Check storage use

A garden record is a few hundred bytes, so this will never be close to a limit — but the endpoint is the honest way to render a quota meter rather than inferring limits from rejection messages.

GET https://api.skillsafe.ai/v1/app-api/storage

{"ok":true,"data":{"storage":{"max_doc_bytes":65536,"records":{"bytes":412}}}}

What this app deliberately does not expose

Listed so nobody wastes an afternoon on it:

The game logic is on the client, and it is readable

Because there is no model, the whole curriculum is data in the bundle. If you want to know exactly which keys a level teaches or how a star is awarded, read /curriculum.js (levels, key→finger map, word pools, starsFor) and /engine.js (the keypress state machine). /SKILL.md-equivalent behaviour is stated on this page and in those two files; nothing about scoring is hidden server-side.