Cinder API Reference

developmentapidocumentationios-shortcutsautomation

Cinder API Reference

The Cinder API exposes project heat data from your local dev directory. It runs on a Docker Bun server (port 4242) and is accessible over Tailscale from any device on your tailnet.

This is the public-facing reference. The same data is available machine-readable at /api/llms.txt (full) and /api/llms-mini.txt (iOS Shortcuts cheatsheet).

Base URLs

Local:      http://localhost:4242
Tailscale:  https://weixiangs-mac-mini.tail1ef495.ts.net:8445

The Tailscale URL uses a Let’s Encrypt cert (trusted by iOS Safari and Shortcuts without any configuration). It’s tailnet-only — your devices need Tailscale running.

Authentication

GET endpoints are public. POST endpoints require one of:

Header:  X-Cinder-Key: YOUR_KEY
Header:  Authorization: Bearer YOUR_KEY
Query:   ?key=YOUR_KEY

The API key is generated on first run and stored in /data/state.json. Find it by checking the server startup log or reading the state file.


GET /api/health

Quick liveness check.

{
  "success": true,
  "message": "Cinder API running · 39 projects loaded"
}

GET /api/digest

The main endpoint. Human-readable summary of your project heat state. Best for widgets and Shortcuts.

{
  "headline": "Blazing: 4 · Hot: 19 · Warm: 2 · Cold: 9",
  "summary": "4 projects blazing. 9 going cold.",
  "mostActive": "bythewei-dev",
  "mostUrgent": "CAR",
  "needsAttention": ["CAR", "algo-trading-python", "floraquest"],
  "hotProjects": ["bythewei-dev", "cinder", "fire-my-lawyer"],
  "totalActive": 39,
  "totalArchived": 0,
  "generatedAt": "2026-04-02T17:11:00.000Z"
}

needsAttention and hotProjects are capped at 5. mostUrgent is the oldest project (by last commit date) in the Cold/Ash tier.


GET /api/stats

Heat breakdown and swipe history counts.

{
  "totalProjects": 39,
  "archivedProjects": 0,
  "totalReignited": 4,
  "heatBreakdown": {
    "blazing": 4,
    "hot": 19,
    "warm": 2,
    "cooling": 0,
    "cold": 9,
    "ash": 5
  },
  "generatedAt": "2026-04-02T17:11:00.000Z"
}

GET /api/projects

All active (non-archived, non-snoozed) projects as an array.

Query params:

ParamExampleDescription
heat?heat=BlazingFilter by heat level (case-insensitive)
limit?limit=5Max results

Project object:

{
  "id": "/Users/wei/Local_Dev/projects/cinder",
  "name": "cinder",
  "path": "/Users/wei/Local_Dev/projects/cinder",
  "heat": "Blazing",
  "dormantDays": 0,
  "lastCommitDate": "2026-04-02T17:00:00.000Z",
  "commitCountLastMonth": 83,
  "currentBranch": "fire-my-lawyer",
  "uncommittedChanges": 3,
  "recentCommits": [
    {
      "hash": "41a9b41",
      "message": "Update Cinder post: iOS daily standup",
      "author": "Wei Zhang",
      "date": "2026-04-02T17:11:00.000Z"
    }
  ],
  "techStack": ["swift", "typescript", "bun", "docker"],
  "description": null
}

GET /api/projects/hot

Shorthand for ?heat=Blazing + ?heat=Hot combined. Returns the active projects.

GET /api/projects/cold

Cold and Ash tier only. Use for shame-based motivation.

GET /api/projects/random

One random active project. Useful for a “what should I work on today” Shortcut.

GET /api/projects/:name

Single project by name. Name matching is case-insensitive and matches either the project name or the directory basename.

GET /api/projects/cinder
GET /api/projects/Cinder     # same result

Returns 404 JSON if not found.


GET /api/skills

The Claude Code skills available for project re-entry, grouped by category.

[
  { "id": "load",       "slash": "/load",       "label": "Load Context",  "category": "Jump Back In" },
  { "id": "sprint-plan","slash": "/sprint-plan","label": "Sprint Plan",   "category": "Plan"         },
  { "id": "analyze",    "slash": "/analyze",    "label": "Analyze",       "category": "Inspect"      }
]

POST /api/projects/:name/reignite

Mark a project as reignited. Clears its cold status in the swipe record.

// body: {}
// response:
{ "success": true, "message": "Reignited cinder" }

POST /api/projects/:name/snooze

Hide a project from the active list for N days.

// body:
{ "days": 7 }
// response:
{ "success": true, "message": "Snoozed cinder" }

Default is 7 days if days is not provided.

POST /api/projects/:name/archive

Permanently hide a project. Append-only — edit state.json to undo.

// body: {}
// response:
{ "success": true, "message": "Archived old-side-project" }

POST /api/refresh

Force rescan of the projects directory. Busts the 5-minute cache.

{ "success": true, "message": "Scanned 39 projects" }

Heat Levels

Heat is derived from days since the last git commit:

LevelDaysNotes
Blazing0–3Active, committed this week
Hot3–7Still warm
Warm7–30Recent month
Cooling30–90Getting distant
Cold90–180Needs attention
Ash180+It’s just sitting there

Projects with no git history are excluded from heat calculations.


LLM Reference Endpoints

Two plain-text endpoints for LLM consumption:

GET /api/llms.txt       Full project docs — for LLMs building on the API
GET /api/llms-mini.txt  Compact cheatsheet — for iOS Shortcuts automation recipes

Both are public GET endpoints. Content is generated at request time with live stats injected.


iOS Shortcuts Integration

The recommended pattern for connecting iOS Shortcuts to Cinder:

Daily digest (read-only):

1. Get Contents of URL → https://...ts.net:8445/api/digest
2. Get Dictionary from Input
3. Get Value for Key "headline"
4. Show Result / Speak Text / Update Draft

The Drafts widget pattern:

The Cinder daily standup uses Drafts (by Agile Tortoise) as a persistent home screen display surface:

  1. Shortcut fetches /api/digest at sunrise via an automation
  2. First AI pass: Claude summarizes for text-to-speech (natural language)
  3. Shortcut speaks the summary
  4. Second AI pass: formats for widget display (max 11 lines, no ASCII, no headers)
  5. Update Draft action overwrites a pinned draft by UUID (Replace mode)
  6. Drafts widget on home screen reflects the update within ~15 minutes

Why Drafts instead of a Shortcut widget: Shortcuts widgets have limited display options. The Drafts Draft Widget shows arbitrary text content from one named draft, updates automatically when the draft changes, and requires zero extra code. You’re using it as a named, writable string that iOS can display.

The two-model approach (Claude for speech, separate model for widget) solves a real constraint: text-to-speech output wants natural sentence flow, but widget display wants minimal ASCII and hard line limits. One prompt can’t optimize for both.


Running Locally

Docker (recommended — runs independently of Xcode):

cd cinder/api
docker compose up -d

Direct (Bun):

PROJECTS_DIR=~/Local_Dev/projects bun run api/server.ts

Environment variables:

VarDefaultDescription
PORT4242Server port
PROJECTS_DIR/projectsDirectory to scan
DATA_FILE/data/state.jsonPersistence file

Tailscale Funnel Setup

To expose the API with a trusted HTTPS cert (required for iOS Shortcuts):

# Check existing serves first — don't clobber other projects
tailscale serve status

# Add Cinder on an unused port (8445 in this setup)
tailscale serve --bg --https=8445 4242

# Result:
# https://weixiangs-mac-mini.tail1ef495.ts.net:8445 (tailnet only)
# |-- / proxy http://127.0.0.1:4242

Tailscale Funnel only supports three external ports for public internet access (443, 8443, 10000). If those are taken by other projects, use tailscale serve (tailnet-only) instead — your iOS devices get a trusted cert as long as Tailscale is running on them.