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/:slug

The 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/metrics

Course 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.

On this page