API
Lychee exposes a JSON API that the web interface itself consumes, so anything the interface can do is reachable from a script.
Versions
Section titled “Versions”| Prefix | Status |
|---|---|
/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:
Interactive documentation
Section titled “Interactive documentation”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.
Headers
Section titled “Headers”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.
Authentication and CSRF
Section titled “Authentication and CSRF”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.
Generating a token
Section titled “Generating a token”- Log in to your Lychee instance and open Settings → Profile.
- In the API Token section, click Create token (or Reset token if one already exists).
- Copy the token immediately - for security reasons it is only ever shown once and cannot be retrieved again later.
- You can revoke the token at any time from the same dialog with Disable token. Resetting the token also invalidates the previous one.
Using the token
Section titled “Using the token”Send the token as a Bearer token in the Authorization header of your requests:
curl -X POST 'https://yourLycheeInstance.org/api/v2/Albums' \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <your-token-here>'Rate limits
Section titled “Rate limits”Most endpoints are unthrottled, but a few sensitive ones are limited per IP:
| Endpoint | Limit |
|---|---|
POST /Auth::login | 10 attempts per hour |
POST /Album::unlock | 10 per minute |
POST /Contact | 5 per day |
| Embed endpoints | 30 to 120 per minute, depending on the endpoint |
Exceeding a limit returns 429 Too Many Requests.
Permissions
Section titled “Permissions”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.
