Skip to content

API

Lychee exposes a JSON API that the web interface itself consumes, so anything the interface can do is reachable from a script.

PrefixStatus
/api/v2/The main surface. Everything the gallery, admin, and webshop screens use
/api/v3/A newer surface added alongside v2, currently limited to a binary asset endpoint, a struct-of-arrays album listing, and an album access-permissions listing. It does not replace v2
/api/v1/Removed. Any request returns an error page

The v3 struct-of-arrays endpoints are only consumed by the interface when STRUCT_OF_ARRAY_ENABLED=true is set, which is a v8 concern; the endpoints themselves answer regardless, and the v2 equivalents keep working either way.

The route definitions are the reference:

Since version 4.8.1 the generated API documentation lives inside your own instance, at https://yourLycheeInstance.org/docs/api. It is also browsable on the demo site: demo.lycheeorg.dev/docs/api.

Accept: application/json is mandatory on every request, and unless stated otherwise Content-Type: application/json is mandatory too. Both are enforced by middleware, so a request without them is refused before reaching the controller. The exception is the v3 asset endpoint, which returns binary data and does not negotiate JSON, while still rendering its errors as JSON.

The Content-Type requirement can be lifted instance-wide with REQUIRE_CONTENT_TYPE_ENABLED=false in the .env file. It is on by default, and turning it off is what makes the bundled interactive documentation able to issue requests.

Lychee protects all stateful (cookie/session based) requests with Laravel’s standard CSRF protection. This is what your browser uses automatically when you are logged in and is required for POST, PUT, PATCH and DELETE requests coming from a session.

When you script against the API (e.g. with curl, Python, or any external tool) you generally do not have a browser session or the associated CSRF cookie/header, so plain session-based requests will be rejected with a 419 error.

To avoid dealing with CSRF for such use cases, Lychee lets you authenticate with a personal API token instead. Any request that carries a valid Authorization: Bearer <token> header is treated as token-authenticated and skips CSRF verification entirely, regardless of the HTTP verb used.

  1. Log in to your Lychee instance and open Settings → Profile.
  2. In the API Token section, click Create token (or Reset token if one already exists).
  3. Copy the token immediately - for security reasons it is only ever shown once and cannot be retrieved again later.
  4. You can revoke the token at any time from the same dialog with Disable token. Resetting the token also invalidates the previous one.

Send the token as a Bearer token in the Authorization header of your requests:

Terminal window
curl -X POST 'https://yourLycheeInstance.org/api/v2/Albums' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <your-token-here>'

Most endpoints are unthrottled, but a few sensitive ones are limited per IP:

EndpointLimit
POST /Auth::login10 attempts per hour
POST /Album::unlock10 per minute
POST /Contact5 per day
Embed endpoints30 to 120 per minute, depending on the endpoint

Exceeding a limit returns 429 Too Many Requests.

The API applies the same authorization rules as the interface: requests are resolved against the authenticated user, and album permissions are enforced on every read. Endpoints belonging to the Supporter Edition or the Pro tier carry a licence-check middleware, so they are unavailable without a valid key.