Files
profilarr/docs/backend/api.md

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:

  1. Define paths and schemas in docs/api/v1/ (YAML)
  2. Generate TypeScript types from the spec into src/lib/api/v1.d.ts
  3. Implement the endpoint handler, using the generated types
  4. 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 security field 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.