For programs
The API
Everything on the Umbilici and tracks page, your own umbilicus points and your private windings, as JSON for scripts and notebooks. AI assistants can use the same things through the MCP server.
- Base address:
https://scrolls.traditionalcamber.com/api/v1. Replies are JSON; an error is{"error": "what went wrong"}with a 4xx or 5xx status. EveryGETaddress also answersHEAD, with the same headers and no body. - Public data needs no key. Your own work needs an API key: make one on your account page (accounts only, not work saved without an account). Send it in a header:
Authorization: Bearer hsk_.... A key is shown once; keep it secret like a password, and revoke it on the account page if it leaks. - Big files are never inside JSON: replies give their download addresses.
- Calls: 1,200 an hour per key, 300 an hour per address without one, counted together with calls to the MCP server. Over that you get 429 with a
Retry-Afterheader in seconds. Track downloads also count against the /data limit of 300 an hour per address. - Bodies: a JSON body can be up to 32 KB, except a
PUTof your umbilicus points (up to 1 MB) and making or changing a winding or adding a note (up to 128 KB, enough for a 10,000-character description or a 4,000-character note in any script, even sent\u-escaped). A winding file goes as the raw body of aPUT, up to 1,024 MB. Multipart forms aren't accepted. - Coordinates follow the umbilicus files:
xis the column andythe row in level-0 voxels,zthe slice. Scrolls go by their PHerc name (PHerc0175A), orpracticefor the practice scroll (on PHerc0826).
Public data
GET /api/v1/umbilici
Every published umbilicus file: name, scroll, source, kind, origin, volume, points, bytes, url (the download) and share_url, plus zip_url for all of them at once.
curl https://scrolls.traditionalcamber.com/api/v1/umbilici
GET /api/v1/umbilici/{name}
One file, exactly as /data/umbilici/{name} serves it: control_points (x, y, z, sometimes score) and metadata.
curl -O https://scrolls.traditionalcamber.com/api/v1/umbilici/PHerc0826_official.json
GET /api/v1/scrolls
The scrolls this site marks: slug, pherc, volume (its path in the organisers' bucket), voxel_um, shape_zyx, papyrus_z (the slices with papyrus), slices_to_mark and the marking page.
GET /api/v1/tracks
Our surface tracks (skeletons of the m7 surface prediction): path, scroll, bytes, url, share_url; and under organisers the organisers' own track datasets, which are linked rather than copied here.
curl https://scrolls.traditionalcamber.com/api/v1/tracks | python3 -c 'import json,sys; [print(f["url"]) for f in json.load(sys.stdin)["files"]]' | xargs -n1 curl -O -C -
Your own umbilicus points (key needed)
GET /api/v1/me
Which account the key belongs to: {"username": ..., "is_admin": ..., "key": {"name": ..., "prefix": ...}}.
GET /api/v1/scrolls/{scroll}/umbilicus
Your points on one scroll: rev (send it back when you change them), finished, updated_at, checks (the same checks the marking page shows), points (x, y, z, note, id, sorted by slice) and umbilicus, the same points as an umbilicus file for the spiral fit (notes starting with ? get score 50).
curl -H "Authorization: Bearer $KEY" https://scrolls.traditionalcamber.com/api/v1/scrolls/PHerc0175A/umbilicus
PUT /api/v1/scrolls/{scroll}/umbilicus
Replaces all your points on that scroll, exactly as a save from the marking page does (the same checks, revisions and history copies). Send {"points": [...], "rev": n} with the rev the GET gave you, or 0 for a scroll you haven't started. Each point is {"x": ..., "y": ..., "z": ..., "note": "optional"}; start a note with ? when unsure. At most 400 points, in a body of up to 1 MB.
An umbilicus file works too, such as the umbilicus the GET gives you: send its {"control_points": [...], "metadata": {"notes": {...}}} with "rev" added, as above. A score under 100 means unsure, and the notes in metadata.notes (by slice) are kept.
curl -X PUT -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"rev": 0, "points": [{"x": 4000, "y": 4100, "z": 6500}, {"x": 4012, "y": 4104, "z": 6600, "note": "? faint"}]}' \
https://scrolls.traditionalcamber.com/api/v1/scrolls/PHerc0175A/umbilicus
The reply gives the new rev, saved_at, unchanged and the checks. If you, or a browser tab of yours, saved since the rev you sent, the reply is 409 with the current rev: GET again, merge, and send again. Sending the same body again, say after the reply was lost, is safe: points sent without an id get one made from the point itself, so the retry answers 200 with "unchanged": true rather than 409. The marking page keeps where you were looking; only the points change.
Your private windings (key needed)
The same windings as on My windings: a title, an optional scroll and description, files of any kind (up to 1,024 MB each, 50 per winding, 4,096 MB in all), notes (up to 500 per winding), and the accounts you share each one with. An account can keep 200 windings. Nobody else can see a winding: to any other account its id answers exactly as one that doesn't exist (404), and with no key every winding address answers 401. Each winding's link is its private link: it opens only for you and the accounts you share it with, once logged in. {id} below is the winding's id.
Each account can add 120 windings and notes an hour, run 3 uploads at once and try 30 shares an hour; past those you get 429. While the site's space for saved work is full, new windings, notes and longer descriptions get 507, and so do files while the server's disk is nearly full or everyone's windings have used their space.
GET /api/v1/windings
Your windings and those shared with you, the latest changed first: id, link, title, scroll, description, owner, your_role (owner, annotate or view), bytes, created_at, updated_at, files (name, size, content_type, sha256, url) and, on your own, shares.
POST /api/v1/windings
Makes one: {"title": "...", "scroll": "PHerc0826", "description": "..."}. Only the title is needed, 1 to 200 characters; a description can have up to 10,000. Replies 201 with the winding.
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"title": "Band 3, first pass", "scroll": "PHerc0826"}' https://scrolls.traditionalcamber.com/api/v1/windings
GET, PATCH, DELETE /api/v1/windings/{id}
One winding. PATCH changes only the fields you send (owner only). DELETE removes it with its files, notes and shares, for good (owner only).
PUT /api/v1/windings/{id}/files/{name}
Adds a file (owner only), sent as the raw body, not a form, up to 1,024 MB; a file of the same name is replaced and keeps its notes. Names are 1 to 128 letters, digits, dots, dashes or underscores, starting with a letter or digit. Up to 3 uploads can run at once on your account; another gets 429 until one finishes. Replies 201 (new) or 200 (replaced) with the file's size and sha256 to check against yours.
curl -T band3_x.tif -H "Authorization: Bearer $KEY" https://scrolls.traditionalcamber.com/api/v1/windings/$ID/files/band3_x.tif
GET, HEAD, DELETE /api/v1/windings/{id}/files/{name}
Downloads a file (anyone it's shared with; range requests work, so curl -C - resumes), or deletes it (owner only; notes on it stay, without their place on the picture).
curl -H "Authorization: Bearer $KEY" -O https://scrolls.traditionalcamber.com/api/v1/windings/$ID/files/band3_x.tif
GET /api/v1/windings/{id}/zip
All the winding's files in one zip, for anyone who may see the winding. The zip is streamed as it is made, and its files are stored as they are, not compressed. A winding with no files yet answers 404. On the winding's page, Download all gives the same zip.
curl -H "Authorization: Bearer $KEY" -o band3.zip https://scrolls.traditionalcamber.com/api/v1/windings/$ID/zip
GET, POST /api/v1/windings/{id}/notes, DELETE /api/v1/windings/{id}/notes/{note}
Notes, oldest first: id, author, text, file, x, y, created_at, yours. Post {"text": "...", "file": "pic.png", "x": 0.3, "y": 0.6} as the owner or an account that may annotate; the text is 1 to 4,000 characters, file is optional, and x and y (fractions of a picture's width and height from its top left corner) place the note on a PNG, JPEG, WebP, GIF or AVIF file. The owner can delete any note, others their own.
GET /api/v1/windings/{id}/shares, PUT, DELETE /api/v1/windings/{id}/shares/{username}
Whom your winding is shared with (owner only). PUT with {"role": "view"} (look and download) or {"role": "annotate"} (add notes too) shares it with that account, or changes its role; DELETE stops sharing (their notes stay). Only accounts with a password can be shared with. On a winding shared with you, DELETE with your own username takes it off your list: you can't open it again unless its owner shares it with you again (your notes on it stay).
Errors
- 400: the body or a value can't be used; the message says which.
- 401: a key is needed, or the one sent doesn't work (revoked, or its account is disabled). A bad key is never treated as no key.
- 403: only a winding's owner (or, for notes, an account that may annotate) can do that.
- 404: no such scroll, winding, file or address.
- 409: your points were saved elsewhere since your
rev, or a limit is reached: 200 windings per account, 50 files or 500 notes per winding. - 413: the body is too big: over 32 KB for most calls, 128 KB for making or changing a winding or adding a note, 1 MB for your umbilicus points, or 1,024 MB for a winding file.
- 415: a multipart form; send a file as the raw body of a
PUTinstead. - 429: too many calls; wait
Retry-Afterseconds. For windings it can also mean that your account already has 3 uploads running (wait for one to finish), or has added 120 windings and notes, or tried 30 shares, in the past hour (the message says how long to wait). - 503: the server can't store winding files just now.
- 507: no room for now. While the site's space for saved work is full, a first save of points on a scroll, new windings, notes and longer descriptions get it. A winding file gets it while the server's disk is nearly full, while everyone's windings have used their space, or when yours would pass 4,096 MB.