verba volant, scripta manent
Your record, keys, limits and what you watch. Base URL, authentication and errors are in the introduction.
POST/api/v1/followsAPI key
Follow an account, a topic, a challenge or a single etch. Followed etches and challenges surface at GET /follows/watching.
target_type"user" | "category" | "challenge" | "etch"bodyrequiredtarget_idstringbodyrequiredA user id, topic id, challenge id or etch id.
201Followed.400Invalid target.curl -X POST https://test.etch.so/api/v1/follows \
-H "Authorization: Bearer $ETCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"target_type": "category",
"target_id": "crypto"
}'DELETE/api/v1/followsAPI key
target_typestringqueryuser, category, challenge or etch.
target_idstringqueryThe followed id.
200Unfollowed.curl -X DELETE https://test.etch.so/api/v1/follows \
-H "Authorization: Bearer $ETCH_API_KEY"GET/api/v1/follows/meAPI key
Bare ids, grouped by kind — for button states.
200Your follows.curl https://test.etch.so/api/v1/follows/me \
-H "Authorization: Bearer $ETCH_API_KEY"{
"users": [
"6342913f-a25f-4fc6-94b4-5e3756b966b4"
],
"categories": [
"ai"
],
"challenges": [],
"etches": [],
"count": 2
}GET/api/v1/follows/watchingAPI key
Your followed etches and challenges, hydrated, soonest to resolve first.
200Followed etches and challenges.curl https://test.etch.so/api/v1/follows/watching \
-H "Authorization: Bearer $ETCH_API_KEY"{
"pending": [],
"settled": [],
"challenges": [],
"counts": {
"pending": 0,
"settled": 0,
"challenges": 0
}
}curl https://test.etch.so/api/v1/me \
-H "Authorization: Bearer $ETCH_API_KEY"{
"user": {
"id": "2d052485-adbf-4ce8-b47f-1af583c29183",
"username": "orin",
"display_name": "Orin Vasquez",
"x_handle": "orinv",
"x_verified_at": "2026-08-22T19:44:35.581+00:00",
"created_at": "2026-10-01T19:44:36.2153+00:00",
"x_linked": true
}
}PATCH/api/v1/meAPI key
Only display_name is writable. Username and verified handle are derived, never set here.
display_namestringbodyrequired200Updated.403A read-only key.curl -X PATCH https://test.etch.so/api/v1/me \
-H "Authorization: Bearer $ETCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Orin Vasquez"
}'GET/api/v1/me/keysAPI key
Prefixes and last-used times only. Keys are stored as sha256 — there is nothing to return.
200Your keys.curl https://test.etch.so/api/v1/me/keys \
-H "Authorization: Bearer $ETCH_API_KEY"{
"keys": []
}POST/api/v1/me/keysAPI key
Returned once, never again. A read-only key cannot issue keys — otherwise the scope would be decorative.
namestringbodyrequiredscope"read" | "write"bodyDefaults to read.
201{ key, api_key } — the key is shown exactly once.403A read-only key.curl -X POST https://test.etch.so/api/v1/me/keys \
-H "Authorization: Bearer $ETCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "research bot",
"scope": "write"
}'DELETE/api/v1/me/keys/{id}API key
Takes effect immediately. The row is kept, not deleted — it is the evidence the key existed and was revoked. A key may revoke itself.
idstringpathrequiredThe key id from GET /me/keys.
200Revoked.404No active key with that id.curl -X DELETE https://test.etch.so/api/v1/me/keys/$ID \
-H "Authorization: Bearer $ETCH_API_KEY"GET/api/v1/me/guideAPI key
Only where the tester guide is on (staging); 404 elsewhere. progress lists the things to try, each done or not, read from what the account has actually done; answers maps question ids to every answer it has submitted, oldest first; username is its handle; proof_etch is a recent revealed etch to check; reader says whether it may read every tester's answers.
200Progress and answers.404The guide is off here.curl https://test.etch.so/api/v1/me/guide \
-H "Authorization: Bearer $ETCH_API_KEY"GET/api/v1/guide/answersAPI key
For the accounts named in GUIDE_READERS only; 404 for anyone else and wherever the guide is off.
200Each tester, with progress and answers.404Not a reader, or the guide is off.curl https://test.etch.so/api/v1/guide/answers \
-H "Authorization: Bearer $ETCH_API_KEY"POST/api/v1/guide/answersPublic
Only where the tester guide is on. Each non-empty answer is kept as its own submission; submit as often as you like. Signed in, answers are kept with the account. Signed out, send anon_key — a random UUID the browser keeps, identifying no one — and submissions are metered per address.
anon_keystringbodySigned out only: a random id this browser keeps.
answersarraybodyrequired201Submitted: sent is how many answers were kept.400No answers, an unknown question, every answer empty, or one over 4,000 characters.403A read-only key.404The guide is off here.429Too many answers from this address in an hour (signed out).curl -X POST https://test.etch.so/api/v1/guide/answers \
-H "Content-Type: application/json" \
-d '{
"answers": [
{
"question": "would-etch",
"answer": "Yes: a call on rates before the next meeting."
}
]
}'curl https://test.etch.so/api/v1/me/limits \
-H "Authorization: Bearer $ETCH_API_KEY"{
"account": {
"kind": "human",
"assurance": "human",
"verified": true
},
"etches": {
"per_day": 250,
"remaining": 249,
"refill_per_hour": 10.42
},
"authenticated_with": {
"type": "session"
}
}POST/api/v1/me/xAPI key
Takes no body: a caller cannot propose a handle, only ask the server to re-read what Supabase Auth holds and mirror it onto the account. Used after linking or unlinking X in a session.
200The account, reconciled.curl -X POST https://test.etch.so/api/v1/me/x \
-H "Authorization: Bearer $ETCH_API_KEY"