API Endpoint Architecture#
Misskey's API departs from REST conventions: all ~450 endpoints use POST by default, are addressed at flat paths under /api/<endpoint-name>, and are centrally registered in a single file. Configuration for auth, rate limiting, permissions, and validation lives in per-endpoint metadata objects rather than middleware or decorators scattered across the codebase.
Registration: endpoint-list.ts#
The single source of truth for all API endpoints is endpoint-list.ts. Every endpoint is registered with a one-liner export * as statement that maps its URL path to its implementation module :
export * as 'notes/create' from './endpoints/notes/create.js';
export * as 'admin/roles/list' from './endpoints/admin/roles/list.js';
The file currently has 451 lines and registers ~450 endpoints . Adding a new endpoint requires a single new line here — the file's own comment states: "When you add new endpoint, you should add it to this file" . This registry also drives API documentation generation and the NestJS EndpointsModule.
endpoints.ts consumes the registry by iterating Object.entries(endpointsObject) to produce the typed IEndpoint[] array that the rest of the server uses .
Endpoint File Structure#
Each endpoint module exports exactly two named values plus a default class :
| Export | Purpose |
|---|---|
meta | IEndpointMeta object — auth, rate limit, kind, stability, errors, response schema |
paramDef | JSON Schema object validated by AJV before the handler runs |
default (class) | NestJS @Injectable class extending Endpoint<meta, paramDef> |
The abstract Endpoint base class in endpoint-base.ts compiles the AJV validator at construction time and wraps the executor in a exec() method that handles file-required checks and parameter validation before calling the user-provided callback .
IEndpointMeta — The Metadata Object#
Defined in endpoints.ts, IEndpointMeta controls all cross-cutting behavior per endpoint:
| Field | Behavior |
|---|---|
requireCredential | Endpoint requires a logged-in user |
requireModerator / requireAdmin | Enforces role checks (must also set kind) |
requiredRolePolicy | Requires a specific role policy |
kind | OAuth permission scope (e.g. 'write:notes', 'read:favorites') |
limit | Rate limiting: duration (ms) + max count, or minInterval; optional shared key across endpoints |
secure | Blocks third-party app tokens; only session-authenticated requests allowed |
allowGet | Opts the endpoint into GET support (default is POST-only) |
cacheSec | Sets Cache-Control: public, max-age=N on GET responses for unauthenticated callers |
prohibitMoved | Rejects requests from accounts that have moved |
stability | 'stable' |
res / errors | Response + error schemas for OpenAPI generation |
TypeScript enforces that requireCredential: true endpoints must also declare kind — and similarly for requireModerator/requireAdmin — via a discriminated union on IEndpointMeta .
Request Lifecycle#
ApiServerService registers Fastify routes by iterating over endpoints and calling fastify.all('/' + endpoint.name, ...) for each . GET is blocked at the route handler unless meta.allowGet is set .
ApiCallService then processes each request in order:
- Token extraction — from
Authorization: Bearer <token>header oribody field - Authentication — via
AuthenticateService - Rate limiting — applied if
meta.limitis set; per-user or per-IP (ifenableIpRateLimit); role-basedrateLimitFactormodifies the limit - Credential/role checks —
requireCredential,requireModerator,requireAdmin,requiredRolePolicy,prohibitMoved - OAuth scope check — validates
token.permissionagainstmeta.kind - Param coercion — boolean/integer params are coerced from strings for GET/multipart requests
- Handler execution —
ep.exec()runs; AJV validatesparamDefinsideEndpoint.execbefore the callback
Errors surface as structured JSON with { error: { message, code, id, kind } } . Rate-limit errors (HTTP 429) include a Retry-After header .
GET Support & Caching#
By default every endpoint rejects GET with HTTP 405. Endpoints opt into GET by setting allowGet: true in meta — for example, emoji sets allowGet: true, cacheSec: 3600 , enabling a one-hour public cache. When allowGet is enabled and the request is GET, query string parameters are used as the body .
Key Files#
| File | Role |
|---|---|
packages/backend/src/server/api/endpoint-list.ts | Central registry — one line per endpoint |
packages/backend/src/server/api/endpoints.ts | IEndpointMeta type + IEndpoint[] array builder |
packages/backend/src/server/api/endpoint-base.ts | Endpoint abstract base class (AJV validation) |
packages/backend/src/server/api/ApiServerService.ts | Fastify route registration loop |
packages/backend/src/server/api/ApiCallService.ts | Auth, rate-limit, permission, and dispatch logic |
packages/backend/src/server/api/endpoints/ | Individual endpoint implementations |