Courses API
Browse courses and record privacy-preserving lifecycle progress.
The public API is served from https://dojo.foo/api/v1. Its browsing routes follow the
skills.sh API contract with skills renamed to courses.
On Vercel, TanStack Start and the Bun/Elysia API deploy as separate Services. Public API
requests rewrite to courses_api, and the web service receives its private URL through the
DOJO_API_ORIGIN service binding. Production metrics require a persistent DATABASE_URL.
Browse courses
GET /api/v1/courses?view=all-time&page=0&per_page=100
GET /api/v1/courses/search?q=effect&limit=50
GET /api/v1/courses/curated
GET /api/v1/courses/:source/:slug
GET /api/v1/courses/audit/:source/:slugThe listing response uses { data, pagination }. Search uses
{ data, query, searchType, count, durationMs }. The view parameter accepts all-time,
trending, or hot; hot results also include installsYesterday and change.
Catalog and search responses cache for 30–60 seconds. Course snapshots, curated results,
and audit results cache for five minutes. A course without an audit returns the standard
{ error, message } response with HTTP 404.
Course metadata and progress
Dojo-specific data stays outside the compatible course object:
GET /api/v1/course-profiles
GET /api/v1/courses/:source/:slug/metricsCourse profiles include author, language, optional framework, and topical tags.
Language and framework are separate discovery facets; tags should not repeat either value.
Metrics include installs, started, progressing, finished, completion rate, and per-kata started/finished/active counts.
Record a lifecycle event
POST /api/v1/events
Content-Type: application/json
{
"instanceId": "opaque-project-id",
"courseId": "dojofoo/effect-ts",
"event": "kata_completed",
"kata": "001-hello-effect",
"occurredAt": "2026-08-11T10:00:00.000Z"
}event is one of installed, started, kata_completed, or finished. Repeating the same
course, instance, event, and kata is idempotent. The server stores a SHA-256 hash of
instanceId, not the submitted identifier.
The first install of an external public GitHub dojo also includes its canonical source:
{
"courseId": "owner/repository",
"event": "installed",
"source": {
"type": "github",
"repository": "owner/repository",
"integrity": "sha256-..."
}
}The API reads dojo.json and the course snapshot from GitHub and validates them before
persisting the listing. Later progress events use the source lock to retain the same
owner/repository course ID. Local paths, session IDs, and chat transcripts are not sent.