Version 1 · read-only · JSON
Build apps, widgets and sites that show Rocoto's content to people. The API gives you the posts, comments, pathways and profiles that the key's owner could see on the site, as plain JSON.
If you use the Rocoto API, or anything you get from it, in a website, app, widget or any other project, you must credit Rocoto and link to the site's official address, https://www.rocoto.space. Put the credit where people can see it: next to the content you show, or on your app's About or credits screen. For example:
Powered by Rocoto - https://www.rocoto.space
Link to the official address above, not to a copy or mirror, and do not suggest that Rocoto made, runs or endorses your project. If you break this rule, or the other rules on this page, your keys may be switched off without notice. Making or using a key means you accept this condition.
Bots, scrapers and any other automated collecting are banned. The API exists so that an app or site can show Rocoto's content to a person who asked to see it, at that moment. You may not use the API, a key, or the website itself to:
This is a condition of getting a key, you accept it when you make one, and it is part of Rocoto's Terms of Use. A key that breaks it is switched off, the account that made it can be locked or deleted, and Rocoto may take the further steps the law allows, including a claim for damages. Rocoto's content belongs to the people who posted it.
To keep the rule honest the API also defends itself. Every key gets a daily share of 1,000 requests and 2,000 items, which is plenty for an app that shows content and far too little to copy the site; a key that keeps asking after it is used up is switched off automatically; and programs that identify as scrapers, crawlers or automated browsers, or send no User-Agent at all, are refused. Send a User-Agent that names your app, for example MyApp/1.0 (+https://example.com).
rk_... and is shown once, so copy it right away.Authorization header.curl -H "Authorization: Bearer rk_YOUR_KEY" \
"https://www.rocoto.space/api/v1/posts?limit=5"
Authorization: Bearer rk_... or as X-API-Key: rk_.... Keys in the address (?key=...) are not accepted, because addresses end up in logs.Each key may make 30 requests per minute and has a daily share of 1,000 requests and 2,000 items (every post, comment, pathway or profile in an answer is one item). The day ends at midnight UTC. Over the per-minute limit you get 429 with rate_limited: wait a little and try again. Over the daily share you get 429 with quota_exceeded until tomorrow, and a key that keeps asking is switched off. Every answer says what is left in X-Rocoto-Quota-Items-Remaining and X-Rocoto-Quota-Requests-Remaining. Lists give at most 50 items per request.
All paths start with https://www.rocoto.space/api/v1. Every endpoint except the index needs a key.
| Request | What it returns |
|---|---|
GET / | A short description of the API and its endpoints (no key needed). |
GET /me | The person the key belongs to. |
GET /stats | How many members, posts and pathways Rocoto has. |
GET /posts | Posts, newest first. Options: limit (1-50, default 20), before (a post id: only older ones), pathway (a pathway's name), author (a username), sort (new, or hot for what is trending now; hot has no paging). |
GET /posts/{id} | One post. |
GET /posts/{id}/comments | The post's comments, oldest first. Options: limit, after (a comment id: only newer ones). |
GET /pathways | Pathways (communities) that are not archived. Options: limit, offset. |
GET /pathways/{name} | One pathway. |
GET /users/{username} | A profile: name, picture, join date, badges and bio. A friends-only profile shows "private": true and no bio unless you are friends. |
GET /users/{username}/posts | The same as /posts?author={username}. |
GET /search?q={text} | Up to 20 people, pathways and posts matching the text. |
Lists answer with {"data": [...], "next_before": 123}. Pass that number back as before to get the next, older page when a person scrolls on; when it is null there are no more. Paging counts against your daily share, so do not page through everything. Comments use next_after and after in the same way, pathways use next_offset and offset.
GET /api/v1/posts?limit=1
{
"data": [{
"id": 482,
"url": "https://www.rocoto.space/post/482",
"title": null,
"content": "Spring planting calendar, free to print.",
"pathway": "Gardening",
"pathway_url": "https://www.rocoto.space/pathway/Gardening",
"author": {
"username": "Maya",
"profile_url": "https://www.rocoto.space/profile/Maya",
"profile_pic": "https://www.rocoto.space/static/media/profile/Maya/pic/1a2b3c.png",
"badges": []
},
"created_at": "2026-10-06T12:34:56+00:00",
"edited_at": null,
"likes": 12,
"comments": 3,
"pinned": false,
"attachment": {"type": "pdf", "url": "https://www.rocoto.space/static/media/post/482/9f8e7d.pdf", "name": "Spring planting calendar.pdf", "size": 183422},
"youtube_mode": "video"
}],
"next_before": 481
}
Attachment type is one of image, audio, video, pdf or file. Dates are ISO 8601 in UTC. Pictures and files are normal web addresses you can show or download.
import requests
API = "https://www.rocoto.space/api/v1"
headers = {"Authorization": "Bearer rk_YOUR_KEY", "User-Agent": "MyApp/1.0 (+https://example.com)"}
# Show the 10 newest posts of a pathway when a person opens that screen of your app
reply = requests.get(f"{API}/posts", headers=headers, params={"pathway": "Main", "limit": 10})
reply.raise_for_status()
for post in reply.json()["data"]:
print(post["author"]["username"], post["content"][:60])
const reply = await fetch("https://www.rocoto.space/api/v1/pathways/Main", {
headers: { Authorization: "Bearer rk_YOUR_KEY" }, // a browser sends its own User-Agent
});
if (!reply.ok) throw new Error((await reply.json()).error.message);
const { data } = await reply.json();
console.log(data.name, data.members, "members");
Errors have a matching HTTP status and a JSON body: {"error": {"code": "not_found", "message": "There is no such post."}}
| Status | code | Meaning |
|---|---|---|
| 400 | bad_parameter | A parameter is missing or not valid. |
| 401 | missing_key / invalid_key | No key was sent, or it is wrong or revoked. |
| 404 | not_found | It does not exist, or you may not see it. |
| 403 | automation_not_allowed | A scraper, crawler or automated browser (or no User-Agent at all). Not allowed. |
| 429 | rate_limited | Too many requests in a minute; wait and retry. |
| 429 | quota_exceeded | The key's daily share is used up; it resets at midnight UTC. |
Version 2 lets an app (a phone app, a desktop client) read the feed and, if the person allows it, post, comment, like and report as that person. The person signs in on rocoto.space and approves the app; your app never sees their password. Version 1 and personal keys keep working as before.
1. Register your app in Settings > Developer apps: a name, the exact return addresses (https://..., http://127.0.0.1:port/... or your own scheme such as myapp://oauth). You get a client_id. A new app is in development mode: up to 25 people, and only you can give it permission to post.
2. Send the person to https://www.rocoto.space/oauth/authorize (in a browser or web view) with response_type=code, client_id, redirect_uri, scope (space-separated), a random state, and PKCE: code_challenge = base64url(SHA-256(verifier)) with code_challenge_method=S256. After they press Allow you get ?code=...&state=... at your return address.
3. Swap the code for tokens: POST https://www.rocoto.space/oauth/token (form fields) with grant_type=authorization_code, code, redirect_uri, client_id, code_verifier. The answer has an access_token (ra_..., 30 minutes) and a refresh_token (rr_...). When the access token expires, send grant_type=refresh_token, refresh_token, client_id and store the new pair: every refresh token works once, and showing an old one again cancels the whole chain. POST /oauth/revoke with token and client_id signs out.
4. Call the API with Authorization: Bearer ra_... and a User-Agent that names your app. Everything is under /api/v2, answers are JSON (plain text, never HTML), lists use cursors and return at most 50 items. What the person cannot see answers 404, like something that does not exist.
| Scope | Lets the app |
|---|---|
read:public | Read public posts, comments, pathways and profiles |
read:feed | Read the person's feed and notifications |
write:posts | Create and delete the person's own posts (pictures, polls, quotes) |
write:comments | Comment, reply, and delete the person's own comments |
write:reactions | Like and unlike, vote in polls |
write:safety | Report content, block and unblock people |
| Call | Scope |
|---|---|
GET /api/v2/me | any |
GET /api/v2/feed?sort=new|hot|friends&before= | read:feed |
GET /api/v2/pathways, /pathways/{name}/posts | read:public |
GET /api/v2/users/{name}, /users/{name}/posts | read:public |
GET /api/v2/posts/{id}, /posts/{id}/comments?after= | read:public |
POST /api/v2/posts (JSON or multipart with image; send an Idempotency-Key header so a retry cannot post twice) | write:posts |
DELETE /api/v2/posts/{id} | write:posts |
POST /api/v2/posts/{id}/comments, DELETE /api/v2/comments/{id} | write:comments |
PUT|DELETE /api/v2/posts/{id}/like, /comments/{id}/like, POST /posts/{id}/vote | write:reactions |
GET /api/v2/notifications, POST /notifications/read | read:feed |
POST /api/v2/reports, PUT|DELETE /blocks/{name} | write:safety |
Limits per person and app: 600 requests an hour, 20 new posts, 100 comments and 20 reports a day, 300 likes an hour, 2,000 items returned a day while the app is in development (higher once it is reviewed). Posts and comments made through an app show via its name. People can remove an app any time in Settings > Connected apps, and apps that break the rules are switched off.
url) when you show one. The authors own what they post.