Internals (for contributors)

How the developer API is put together, for people changing it rather than calling it. Everything above this page is written for API consumers; this page is not.

Module map

Module

Role

lib/api/registry.ts

Single source of truth for every public endpoint. Drives the OpenAPI generator and these docs.

lib/api/scopes.ts

Pure scope catalog — shared by the API wrapper, the key-creation UI, the OpenAPI generator, and tests.

lib/api/errors.ts

Pure error taxonomy: codetype → default HTTP status, plus the envelope builder.

lib/api/openapi.ts

Renders the OpenAPI 3.1 document from the registry.

lib/api/developer-auth.server.ts

Key generation and authentication (hashing, scopes, expiry).

lib/api/with-developer-api.server.ts

The wrapper: IP gate, auth, per-key rate limiting + headers, scope enforcement, idempotency replay, request IDs, and the bound json/error/noContent helpers.

lib/api/serializers.server.ts

Shared response shaping + pagination.

lib/social/engagement.server.ts

Canonical like/comment/bookmark/follow/post mutations — counters, SSE, notifications, XP, quests, achievements, webhooks.

lib/webhooks/signature.ts

Pure HMAC signing + the SSRF guard for outbound delivery URLs.

lib/webhooks/events.ts

Event catalog.

lib/webhooks/emit.server.ts

Delivery + retry drain.

app/routes/api/v1/

The routes themselves.

app/routes/api/developer/keys/

Key management endpoints behind the /developer dashboard.

lib/api/registry.ts, lib/api/scopes.ts, lib/api/errors.ts and lib/webhooks/events.ts are pure — no server imports, no side effects. That is deliberate: it lets the docs generator, the OpenAPI route, the client-side dashboard, and the unit tests all read the same definitions.

Why engagement mutations are centralised

In-app routes and API routes both write posts, likes, comments, bookmarks and follows. Every one of those writes has side effects — counter updates, SSE fanout, notifications, XP, quest progress, achievements, webhook emission. lib/social/engagement.server.ts owns all of it so the two entry points cannot drift: an API-created post awards XP and progresses quests exactly like one from the in-app composer.

Add a side effect there, not in a route handler.

Adding an endpoint

  1. Add the route under app/routes/api/v1/, wrapped in withDeveloperApi.

  2. Add its entry to ENDPOINTS in lib/api/registry.ts — method, path, operationId, group, scope, params, body fields, and a realistic responseExample.

  3. If it needs a new permission, add it to SCOPES in lib/api/scopes.ts.

  4. Run pnpm docs:api and commit the regenerated pages under docs/developer-api/endpoints/.

Step 4 is checked in CI (pnpm docs:api --check), so a registry change without regenerated docs fails the build rather than shipping a stale reference.

Webhook retry drain

The retry queue is drained by POST /api/cron/webhooks, guarded by INTERNAL_API_SECRET. Persistent failures auto-disable the endpoint after the backoff schedule is exhausted (6 attempts).

Where the docs live

These pages are the published documentation — there is no longer an in-app wiki. docs/developer-api/*.md is hand-written prose; docs/developer-api/endpoints/*.md is generated by scripts/generate-api-docs.ts and must not be hand-edited. The three reference tables (scopes, errors, webhook events) are spliced into the hand-written guides between <!-- BEGIN GENERATED: --> markers.