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 at docs/openapi.json). Interactive docs at /redoc and /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


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:

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_prefix is always the constant 4-char tag "toto" — no random entropy from the token body is ever stored or surfaced. Disambiguate keys by name, 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