5.9 KiB
API
Table of Contents
Route Classification
Profilarr has two types of server routes: the public API and web app routes.
Public API (/api/v1/*)
The versioned public API. All endpoints are documented in the OpenAPI spec
(docs/api/v1/openapi.yaml), follow a stable contract, and accept either a
session cookie or an X-Api-Key header for authentication.
Both auth methods exist because the web UI and external consumers share the same endpoints. The UI calls them with a session cookie; scripts, dashboards, and future mobile clients call them with an API key. This avoids duplicating endpoints.
Web App Routes (everything else)
Routes outside /api/v1/ are internal to the web application:
- Session-only auth (API key requests get 403)
- Not in the OpenAPI spec
- No stability guarantees; can change freely
These fall into two categories:
Page-local endpoints are +server.ts files colocated with the pages that
use them. They exist because the page needs to fetch data client-side (e.g.
refreshing after a mutation) and form actions or server loads aren't practical.
Auth flows handle login, logout, and OIDC callbacks under /auth/*.
Form actions on +page.server.ts files handle mutations (create, update,
delete) triggered by form submissions. These have no URL to hit externally.
Authentication
Global security is defined at the top level of the OpenAPI spec:
security:
- apiKey: []
- session: []
All /api/v1/* endpoints require auth by default. Public endpoints override
with security: [] (e.g. the health check).
The auth middleware (src/lib/server/utils/auth/middleware.ts) enforces this by
path prefix. SvelteKit's built-in CSRF protection (Origin header check on
mutations) ensures session requests came from the Profilarr UI.
See docs/architecture/security.md for the full auth system, request flow,
rate limiting, and secret stripping.
Contract-First Workflow
New endpoints follow a spec-first process:
- Define paths and schemas in
docs/api/v1/(YAML) - Generate TypeScript types from the spec into
src/lib/api/v1.d.ts - Implement the endpoint handler, using the generated types
- Test with integration tests that verify the contract (status codes, response shape, auth behavior, no secret leakage)
OpenAPI Spec Structure
The spec lives under docs/api/v1/ with openapi.yaml as the root file.
Paths (endpoint definitions) and schemas (request/response types) are split
into separate files by domain and referenced via $ref. A paths/arr.yaml
defines the routes for arr endpoints, while schemas/arr.yaml defines the
types those routes use. This keeps the root spec concise and lets each domain
be edited independently.
Type Generation
After modifying the OpenAPI spec, regenerate the TypeScript types:
deno task generate:api-types
This runs openapi-typescript against docs/api/v1/openapi.yaml and outputs
src/lib/api/v1.d.ts. The generated file should be committed alongside spec
changes. Import types from $api/v1 in endpoint handlers:
import type { components } from '$api/v1';
type MyResponse = components['schemas']['MySchema'];
Endpoint Implementation
Handlers live under src/routes/api/v1/ and export named HTTP method functions:
import { json } from '@sveltejs/kit';
import type { RequestHandler } from '@sveltejs/kit';
export const GET: RequestHandler = async () => {
return json(data);
};
Use generated types from $api/v1 to ensure responses match the spec.
Writing Endpoint Descriptions
The route, method, parameters, and schema already communicate a lot. A
description should only add what a caller can't figure out from those alone.
GET /databases doesn't need a paragraph explaining that it returns databases.
What to Document
Write a description when the endpoint has behavior that would surprise a caller or that the contract alone doesn't capture:
Conventions
- Summaries: title case, 2-4 words, describe the action
(
List Databases,Create Backup,Download Backup) - Tags group by domain (System, Databases, Arr, Backups, Jobs, PCD), not by HTTP method
- Don't put "(public)" or "(authenticated)" in summaries; the
securityfield handles that - Document all status codes with clear triggers
Status Codes
| Code | When |
|---|---|
| 200 | Success |
| 201 | Created (upload, new resource) |
| 202 | Accepted (async job enqueued) |
| 400 | Invalid input (missing or malformed params) |
| 401 | No session or API key |
| 403 | Valid auth but not permitted |
| 404 | Resource not found |
| 409 | Conflict (cooldown, in-progress operation) |
| 503 | Service unhealthy |
Testing
Every v1 endpoint should have integration tests that verify:
- Auth enforcement (401 without credentials, 200 with API key or session)
- Response shape matches the contract (required fields present)
- No secret leakage (sensitive fields stripped)
- Correct behavior with seeded data
Tests use the shared harness (tests/integration/harness/), each running an
isolated server instance on a unique port with its own database.