Skip to main content
Version: 2026-08-25 (archived)

App Data API reference

Every storage row is addressed by its URL from the launch's storage claim — there are no paths to construct. Call the URL exactly as it arrives in the claim, and treat it as opaque.

All app data requests authenticate with the api.token from a launch (not your API key):

Authorization: Bearer <api.token>

See authenticating for how the token works and who can access what for the permission rules.

Operations​

All four rows — context, location, member, and member_location — support the same three operations on their URL:

MethodEffect
GETRead the row's JSON object.
PUTReplace the row's contents wholesale.
PATCHApply an RFC 6902 JSON Patch to the row.

The examples below use the row URL captured at launch time, e.g. row_url = launch["storage"]["member_location"].

GET: read a row​

GET <row URL>

Returns 200 with your JSON object, exactly as last written — no envelope. A never-written row returns {}.

# $ROW_URL is storage.member_location from the launch
curl "$ROW_URL" \
-H "Authorization: Bearer $API_TOKEN"

PUT: replace a row​

PUT <row URL>
Content-Type: application/json

The body is your raw JSON object (no wrapper). It replaces the row's contents wholesale. Returns 204 No Content.

# $ROW_URL is storage.context from the launch
curl -X PUT "$ROW_URL" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"weeks": 14, "modules_open": ["intro", "loops"]}'
warning

PUT overwrites everything in the row. If two parts of your app store different keys in the same row, use PATCH so one write can't clobber the other's keys.

PATCH: apply a JSON Patch​

PATCH <row URL>
Content-Type: application/json

The body wraps an RFC 6902 JSON Patch in a patch key. Paths are relative to your object's root (/answers/q2, with no extra prefix). Returns 204 No Content.

{
"patch": [
{ "op": "add", "path": "/answers/q2", "value": "C" },
{ "op": "replace", "path": "/attempts", "value": 2 },
{ "op": "remove", "path": "/draft" }
]
}
# $ROW_URL is storage.member_location from the launch
curl -X PATCH "$ROW_URL" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"patch": [{"op": "add", "path": "/answers/q2", "value": "C"}]}'

The patch engine is tolerant of missing intermediate paths: an add to /answers/q2 works even if answers doesn't exist yet — the intermediate object is created for you. Patches apply atomically under a row lock, so concurrent patches to different keys both land.

Errors​

StatusCause
401Missing or expired bearer token — take a fresh api.token from a new launch.
403The token's user isn't allowed on this row — e.g. a student writing the shared context or location row, one student's token used on another student's member row, or a user who isn't a member of the row's course.
404The row URL doesn't exist, or the row doesn't belong to your app.
422Malformed body — e.g. a PATCH without the patch wrapper, or an invalid patch operation. Also how a context or location row answers while its level is still unprovisioned.

Error responses use the standard error envelope.