API Reference
Curated REST API reference for Toto — every endpoint a typical integration touches, with curl examples.
Looking for the complete machine-readable surface? The auto-generated OpenAPI schema lives at
/openapi.json(and a copy is checked in atdocs/openapi.json). Interactive docs at/redocand/api-docs. Endpoints not listed below (e.g.PATCH /api/lists/{id}/metadata,POST /api/render-markdown,GET /api/activity, the/api/animations*manifest,/api/user-themes,/api/user-animations,POST /subscribe, the print-job device queue) are documented there.
Authentication
All API endpoints require a Bearer token in the Authorization header:
Authorization: Bearer toto_your_token_here
Tokens are created via Settings > API Keys on the dashboard or the /api/keys endpoint. Tokens start with the toto_ prefix.
Scopes
API keys have comma-separated scopes that control access:
| Scope | Access |
|---|---|
read |
GET endpoints (list data, items, sync changes) |
write |
POST/PATCH/DELETE endpoints (create, update, delete) |
read,write covers all end-user integrations. (A separate admin scope exists for platform-operator key management; it is gated server-side and not user-mintable.)
Rate Limiting
Failed authentication attempts are rate-limited to 20 per IP per 5-minute window.
Conventions
- Base URL: Your Toto instance URL (e.g.,
https://toto.up.railway.app) - Content-Type:
application/jsonfor all request bodies - IDs: All IDs in API responses are encoded strings (not raw integers). Use the encoded form in all requests.
- Errors: HTTP status code +
{"detail": "error message"}body - Pagination:
limit(default 50, max 200) andoffset(default 0) query parameters
Lists API
Get All Lists
GET /api/lists
Returns all lists owned by the authenticated user, including hidden ones.
Scope: read
curl -s "https://toto.up.railway.app/api/lists" \
-H "Authorization: Bearer toto_your_token"
Response:
[
{
"id": "rDJpQekZ",
"name": "Google OAuth",
"description": "OAuth login implementation",
"visible": true,
"default_category": "backend",
"parent_list_id": null,
"layout_metadata": null,
"sort_order": 0,
"created_at": "2026-04-06T12:00:00",
"updated_at": "2026-04-06T14:30:00",
"sync_seq": 1234,
"version": 3
}
]
Create a List
POST /api/lists
Scope: write
Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Display name (max 100 chars) |
description |
string | No | Description or subtitle |
default_category |
string | No | Category auto-applied to new items |
parent_list_id |
string | No | Encoded ID of parent list (for sub-lists) |
layout_metadata |
object | No | Layout config (listSpan, cardColumns, cardGap, height). 2KB cap. |
curl -s -X POST "https://toto.up.railway.app/api/lists" \
-H "Authorization: Bearer toto_your_token" \
-H "Content-Type: application/json" \
-d '{"name": "Google OAuth", "description": "OAuth login implementation"}'
Update a List
PATCH /api/lists/{list_id}
Partially update list properties. Only provided (non-null) fields are changed.
Scope: write
Body:
| Field | Type | Description |
|---|---|---|
name |
string | New display name |
description |
string | New description |
visible |
boolean | Show/hide in default views |
default_category |
string | Default category for new items |
icon |
string | List icon |
color |
string | List color |
parent_list_id |
string | Encoded parent list ID |
sort_order |
integer | Display sort order |
layout_metadata |
object | Layout config |
curl -s -X PATCH "https://toto.up.railway.app/api/lists/rDJpQekZ" \
-H "Authorization: Bearer toto_your_token" \
-H "Content-Type: application/json" \
-d '{"name": "Google OAuth v2", "visible": false}'
Update List Layout
PATCH /api/lists/{list_id}/layout
Merge layout settings into a list's layout_metadata. Existing layout fields are preserved; provided fields are overwritten.
Scope: write
curl -s -X PATCH "https://toto.up.railway.app/api/lists/rDJpQekZ/layout" \
-H "Authorization: Bearer toto_your_token" \
-H "Content-Type: application/json" \
-d '{"listSpan": 2, "cardColumns": 1}'
Delete a List
DELETE /api/lists/{list_id}
Permanently delete a list and all its items. Creates sync tombstones for offline clients.
Scope: write
curl -s -X DELETE "https://toto.up.railway.app/api/lists/rDJpQekZ" \
-H "Authorization: Bearer toto_your_token"
Response: {"ok": true}
Hide / Show a List
POST /api/lists/{list_id}/hide
POST /api/lists/{list_id}/show
Toggle list visibility in default views without deleting it.
Scope: write
curl -s -X POST "https://toto.up.railway.app/api/lists/rDJpQekZ/hide" \
-H "Authorization: Bearer toto_your_token"
Get Child Lists
GET /api/lists/{list_id}/children
Get all child lists of a parent list.
Scope: read
curl -s "https://toto.up.railway.app/api/lists/rDJpQekZ/children" \
-H "Authorization: Bearer toto_your_token"
Items API
Get Items in a List
GET /api/lists/{list_id}/items
Returns a paginated list of items with filtering, sorting, and text search.
Scope: read
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
status |
string | (none) | Filter: pending, in_progress, done, blocked, deferred |
priority |
integer | (none) | Filter: 0=none, 1=low, 2=medium, 3=high |
category |
string | (none) | Filter by category tag |
sort |
string | (none) | Sort: priority, due_date, created_at, sort_order |
limit |
integer | 50 | Max items (1-200) |
offset |
integer | 0 | Items to skip |
due_before |
string | (none) | Filter: due on or before (YYYY-MM-DD) |
due_after |
string | (none) | Filter: due on or after (YYYY-MM-DD) |
search |
string | (none) | Text search on task and notes fields |
curl -s "https://toto.up.railway.app/api/lists/rDJpQekZ/items?status=pending&sort=priority" \
-H "Authorization: Bearer toto_your_token"
Response:
{
"items": [
{
"id": "px6K4KkE",
"list_id": "rDJpQekZ",
"task": "Add OAuth middleware",
"description": "Implement Google OAuth...",
"notes": "",
"status": "pending",
"priority": 3,
"category": "backend",
"due_date": "",
"printed": "false",
"completed_at": "",
"completed_by": "",
"metadata": {
"component": "auth-middleware",
"files": ["app/api/auth_web.py"],
"keywords": ["oauth", "google"],
"scope": "backend",
"intent": "Users can log in via Google"
},
"version": 1,
"sync_seq": 2963,
"updated_at": "2026-04-06T12:00:00"
}
],
"total": 5,
"limit": 50,
"offset": 0
}
Add Item to a List
POST /api/lists/{list_id}/items
Scope: write
Body:
| Field | Type | Required | Description |
|---|---|---|---|
task |
string | Yes | Task text (max 500 chars, auto-truncated) |
description |
string | No | Markdown description |
notes |
string | No | Legacy notes field (prefer description) |
priority |
integer | No | 0=none, 1=low, 2=medium, 3=high |
category |
string | No | Freeform category tag |
due_date |
string | No | ISO 8601 date (YYYY-MM-DD) |
metadata |
object | No | Arbitrary JSON (10KB cap, toto_ prefix reserved) |
curl -s -X POST "https://toto.up.railway.app/api/lists/rDJpQekZ/items" \
-H "Authorization: Bearer toto_your_token" \
-H "Content-Type: application/json" \
-d '{
"task": "Add OAuth middleware",
"description": "Implement Google OAuth login flow",
"priority": 3,
"category": "backend",
"metadata": {
"component": "auth-middleware",
"files": ["app/api/auth_web.py"],
"keywords": ["oauth", "google", "authlib"],
"scope": "backend",
"intent": "Users can log in via Google OAuth"
}
}'
Batch Add Items
POST /api/lists/{list_id}/items/batch
Create up to 50 items in a single request.
Scope: write
Body:
{
"items": [
{"task": "Add OAuth middleware", "priority": 3, "category": "backend"},
{"task": "Create user model", "priority": 3, "category": "schema"},
{"task": "Write auth tests", "priority": 1, "category": "test"}
]
}
curl -s -X POST "https://toto.up.railway.app/api/lists/rDJpQekZ/items/batch" \
-H "Authorization: Bearer toto_your_token" \
-H "Content-Type: application/json" \
-d '{"items": [{"task": "Task 1"}, {"task": "Task 2"}, {"task": "Task 3"}]}'
Response:
{
"succeeded": [
{"id": "abc123", "task": "Task 1", ...},
{"id": "def456", "task": "Task 2", ...}
],
"failed": []
}
Edit an Item
POST /api/items/{item_id}/edit
Partially update an item. Only provided (non-null) fields are changed.
Scope: write
Body:
| Field | Type | Description |
|---|---|---|
task |
string | Updated task text |
description |
string | Updated Markdown description |
notes |
string | Updated notes (legacy) |
priority |
integer | 0-3 |
category |
string | Updated category tag |
due_date |
string | ISO 8601 date |
metadata |
object | Updated JSON metadata |
curl -s -X POST "https://toto.up.railway.app/api/items/px6K4KkE/edit" \
-H "Authorization: Bearer toto_your_token" \
-H "Content-Type: application/json" \
-d '{"task": "Add Google OAuth middleware", "priority": 3}'
Set Item Status
POST /api/items/{item_id}/status
Set an item's status. When set to done, records completed_at and completed_by.
Scope: write
Body:
{"status": "in_progress"}
Valid statuses: pending, in_progress, done, blocked, deferred.
curl -s -X POST "https://toto.up.railway.app/api/items/px6K4KkE/status" \
-H "Authorization: Bearer toto_your_token" \
-H "Content-Type: application/json" \
-d '{"status": "done"}'
Mark Item Done / Undone (Convenience)
POST /api/items/{item_id}/done
POST /api/items/{item_id}/undone
Convenience endpoints. /done marks the item as done. /undone reverts to pending, clearing completed_at and completed_by. Both return the updated item list.
Scope: write
curl -s -X POST "https://toto.up.railway.app/api/items/px6K4KkE/done" \
-H "Authorization: Bearer toto_your_token"
Delete an Item
POST /api/items/{item_id}/delete
Permanently delete an item. Creates a sync tombstone for offline clients. Returns the updated item list.
Scope: write
curl -s -X POST "https://toto.up.railway.app/api/items/px6K4KkE/delete" \
-H "Authorization: Bearer toto_your_token"
Batch Update Status
PATCH /api/batch/items/status
Update the status of up to 50 items in a single request.
Scope: write
curl -s -X PATCH "https://toto.up.railway.app/api/batch/items/status" \
-H "Authorization: Bearer toto_your_token" \
-H "Content-Type: application/json" \
-d '{
"updates": [
{"id": "px6K4KkE", "status": "done"},
{"id": "yBWXbGDr", "status": "in_progress"}
]
}'
Response:
{
"succeeded": [...],
"failed": [...]
}
Batch Delete Items
POST /api/batch/items/delete
Delete up to 50 items in a single request. Creates sync tombstones.
Scope: write
curl -s -X POST "https://toto.up.railway.app/api/batch/items/delete" \
-H "Authorization: Bearer toto_your_token" \
-H "Content-Type: application/json" \
-d '{"item_ids": ["px6K4KkE", "yBWXbGDr"]}'
Sub-Lists API
Create a Sub-List for an Item
POST /api/items/{item_id}/sublist
Create a new sub-list linked to a parent item. Depth is capped at 2 levels.
Scope: write
Body:
| Field | Type | Description |
|---|---|---|
name |
string | Name for the sub-list (defaults to parent item's task text) |
curl -s -X POST "https://toto.up.railway.app/api/items/px6K4KkE/sublist" \
-H "Authorization: Bearer toto_your_token" \
-H "Content-Type: application/json" \
-d '{"name": "OAuth Sub-tasks"}'
Get an Item's Sub-List
GET /api/items/{item_id}/sublist
Returns the sub-list and its items for a parent item.
Scope: read
curl -s "https://toto.up.railway.app/api/items/px6K4KkE/sublist" \
-H "Authorization: Bearer toto_your_token"
Response:
{
"list": {"id": "abc123", "name": "OAuth Sub-tasks", ...},
"items": [
{"id": "def456", "task": "Configure redirect URIs", ...}
]
}
Expand Item to Sub-List
POST /api/lists/{list_id}/items/{item_id}/expand
Create a child sub-list from an item's category tag and move all items with that category into the new list.
Scope: write
curl -s -X POST "https://toto.up.railway.app/api/lists/rDJpQekZ/items/px6K4KkE/expand" \
-H "Authorization: Bearer toto_your_token"
Sync API
Get Changes Since Sequence
GET /api/sync/changes?since={seq}
Returns all lists, items, and tombstones modified since the given sync sequence number. Clients store the returned current_seq and pass it on the next call.
Auth: Device token (Bearer)
curl -s "https://toto.up.railway.app/api/sync/changes?since=0" \
-H "Authorization: Bearer your_device_token"
Response:
{
"current_seq": 2974,
"lists": [...],
"items": [...],
"tombstones": [
{
"entity_type": "listitem",
"entity_id": "abc123",
"list_id": "rDJpQekZ",
"sync_seq": 2950,
"deleted_at": "2026-04-06T12:00:00"
}
],
"resync_required": false
}
If resync_required is true, the client should discard local state and do a full resync with since=0.
Push Client Changes
POST /api/sync/push
Push a batch of client-side changes (max 1000). Server uses Last-Writer-Wins conflict resolution based on timestamps, with 5-minute clock skew tolerance.
Auth: Device token (Bearer)
Body:
{
"changes": [
{
"entity_type": "listitem",
"entity_id": "abc123",
"fields": {"task": "Updated task", "status": "done"},
"updated_at": "2026-04-06T12:00:00"
}
]
}
Response:
{
"current_seq": 2975,
"resolved": [...],
"conflicts": [...],
"clock_skew_detected": false
}
SSE Events
Subscribe to Real-Time Events
GET /api/events
Server-Sent Events stream for real-time updates. User-scoped -- you only receive events for your own data.
Query Parameters:
| Parameter | Default | Description |
|---|---|---|
detail |
minimal |
minimal or full. Full payloads include complete entity data. |
Auth: Session cookie, anonymous cookie, or Bearer token.
curl -N "https://toto.up.railway.app/api/events?detail=full" \
-H "Authorization: Bearer toto_your_token"
Event format:
event: connected
data: {"status": "ok"}
event: item_updated
data: {"item_id": "px6K4KkE", "list_id": "rDJpQekZ", "status": "done"}
: keepalive
Keepalive comments are sent every 15 seconds to prevent connection timeout.
Devices API
Register a Device
POST /devices/register
Register a new device. Requires an authenticated caller (web session cookie or API key with write scope, since SEC-002). The newly registered device is bound to that user. Returns a Bearer token that must be stored securely — it is shown once and never retrievable.
Auth: Session cookie or Authorization: Bearer toto_... (write scope)
Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable name (max 100 chars) |
device_type |
string | Yes | desktop or mobile |
has_printer |
boolean | No | Whether device has a DYMO printer |
setup_code |
string | No | Optional defense-in-depth code. Must match server DEVICE_SETUP_CODE env var when present. Pre-SEC-002 callers used this as the only auth signal — that path is no longer accepted. |
Errors:
401— no session and no bearer token403— bearer token missingwritescope, orsetup_codemismatch500— serverDEVICE_SETUP_CODEnot configured
curl -s -X POST "https://toto.up.railway.app/devices/register" \
-H "Authorization: Bearer toto_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Alex MacBook",
"device_type": "desktop",
"has_printer": true
}'
Response:
{
"device_id": "abc123",
"token": "a1b2c3d4e5f6..."
}
Warning: > The token is shown once and never stored in plaintext on the server. Store it securely.
Rotate Device Token
POST /devices/rotate-token
Generate a new Bearer token and invalidate the old one immediately.
Auth: Existing device token (Bearer)
curl -s -X POST "https://toto.up.railway.app/devices/rotate-token" \
-H "Authorization: Bearer your_device_token"
Response: {"token": "new_token_here"}
API Keys
Create an API Key
POST /api/keys
Create a new API key. The raw token is returned once and cannot be retrieved later.
Auth: Session cookie or Bearer token
Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable name (max 100 chars) |
scope |
string | No | Comma-separated scopes (default: read,write) |
expires_in_days |
integer | No | Expiration in days (1-365) |
curl -s -X POST "https://toto.up.railway.app/api/keys" \
-H "Authorization: Bearer toto_your_token" \
-H "Content-Type: application/json" \
-d '{"name": "CI Pipeline", "scope": "read,write", "expires_in_days": 90}'
Response:
{
"id": "abc123",
"name": "CI Pipeline",
"key_prefix": "toto",
"scope": "read,write",
"token": "toto_XJ4vkSkEh7bcI...",
"created_at": "2026-04-06T12:00:00",
"last_used_at": null,
"expires_at": "2026-07-05T12:00:00"
}
Since SEC-028 the stored
key_prefixis always the constant 4-char tag"toto"— no random entropy from the token body is ever stored or surfaced. Disambiguate keys byname, not prefix. Max 10 active keys per user.
List API Keys
GET /api/keys
List all active (non-revoked) API keys. Never returns raw tokens.
Auth: Session cookie or Bearer token
curl -s "https://toto.up.railway.app/api/keys" \
-H "Authorization: Bearer toto_your_token"
Revoke an API Key
DELETE /api/keys/{key_id}
Soft-revoke an API key. The key remains in the database but can no longer authenticate.
Auth: Session cookie or Bearer token
curl -s -X DELETE "https://toto.up.railway.app/api/keys/abc123" \
-H "Authorization: Bearer toto_your_token"
Response: {"status": "revoked", "id": "abc123"}
Skill Installer
Get Installer Script
GET /install
Returns a bash script that installs all Claude Code slash commands. Rate limited: 10 req/min.
curl -fsSL https://toto.up.railway.app/install | bash
List Available Skills
GET /install/skills
JSON manifest of available skills with SHA-256 checksums. Rate limited: 10 req/min.
Get Individual Skill
GET /install/skills/{skill_name}
Returns a single SKILL.md file as plain text. Rate limited: 30 req/min.
Valid skill names: toto-setup, toto-plan, toto-add, toto-start, toto-done, toto-status, toto-reconcile, toto-enrich, toto-lint, toto-search, toto-help, th. The /install bash script also fetches /install/claude-md, which returns the contents of docs/CLAUDE.template.md (the starter CLAUDE.md users copy into their projects).
Error Responses
All errors return an HTTP status code with a JSON body:
{"detail": "error message"}
| Status | Meaning |
|---|---|
| 400 | Bad request (invalid ID encoding, malformed body) |
| 401 | Missing or invalid auth token |
| 403 | Insufficient scope or forbidden action |
| 404 | Resource not found |
| 422 | Validation error (empty name, invalid status, etc.) |
| 429 | Rate limit exceeded (retry after 60 seconds) |
| 500 | Server error |