photoprism/internal/api
2026-06-17 04:12:40 +00:00
..
download Docs: Bump copyright year to 2026 in Go package headers 2026-05-25 14:39:38 +00:00
embed
testdata
abort.go
AGENTS.md Docs: Split AGENTS guides into a hierarchical structure 2026-04-09 19:28:14 +02:00
albums.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
albums_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
albums_search_test.go
albums_test.go API: Scope photo reads and updates to the session 2026-05-29 07:04:47 +02:00
api.go Docs: Bump copyright year to 2026 in Go package headers 2026-05-25 14:39:38 +00:00
api_auth.go Auth: Log anonymous API requests at debug level instead of warning #5647 2026-06-15 23:56:56 +00:00
api_auth_jwt.go
api_auth_jwt_test.go Auth: Authorize cluster JWT for instance user management 2026-06-03 17:12:59 +02:00
api_auth_test.go Auth: Revoke derived sessions & gate app passwords by login state #5647 2026-06-15 23:41:01 +00:00
api_client_config.go People: Load name suggestions from typeahead cache #5666 2026-06-15 14:38:04 +00:00
api_client_config_test.go API: Add config.updated broadcast tests to api_client_config_test.go 2026-05-10 15:07:37 +02:00
api_event.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
api_event_test.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
api_log.go
api_methods.go
api_request.go
api_response.go
api_response_headers.go
api_response_test.go
api_test.go
auth_tokens.go
batch_albums.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
batch_labels.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
batch_photos.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
batch_photos_edit.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
batch_photos_edit_test.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
batch_photos_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
cache.go
cache_test.go
cameras.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
cameras_search.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
cameras_search_test.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
cameras_test.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
cluster_health_test.go
cluster_metrics.go
cluster_metrics_test.go
cluster_nodes.go Cluster: Accept all instance roles in group config, tolerate spaces 2026-06-13 12:54:54 +00:00
cluster_nodes_redaction_test.go
cluster_nodes_register.go Cluster: Accept all instance roles in group config, tolerate spaces 2026-06-13 12:54:54 +00:00
cluster_nodes_register_test.go Cluster: Accept all instance roles in group config, tolerate spaces 2026-06-13 12:54:54 +00:00
cluster_nodes_test.go Cluster: Accept all instance roles in group config, tolerate spaces 2026-06-13 12:54:54 +00:00
cluster_nodes_update_siteurl_test.go
cluster_permissions_test.go
cluster_summary.go
cluster_test_helpers.go
cluster_theme.go
cluster_theme_test.go
config_options.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
config_options_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
config_settings.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
config_settings_test.go API: Extend pre-parse request limits to more JSON handlers 2026-03-08 10:29:34 +01:00
connect.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
connect_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
covers.go
covers_test.go
doc_overrides.go
docs.go
download.go
download_album.go
download_album_test.go
download_test.go
echo.go
echo_test.go
errors.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
errors_test.go
faces.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
faces_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
faces_search_test.go
faces_test.go
file_delete.go Photos: Limit by-UID label, marker & file edits to the session scope #1307 2026-06-14 15:37:57 +00:00
file_delete_test.go
file_orientation.go Photos: Limit by-UID label, marker & file edits to the session scope #1307 2026-06-14 15:37:57 +00:00
files.go API: Scope file lookup and zip selection to the session 2026-05-29 07:04:47 +02:00
files_test.go API: Cover shared-only session access to the files endpoint 2026-06-01 15:21:10 +00:00
folders_cover.go
folders_cover_test.go
folders_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
folders_search_test.go
health.go
import.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
import_test.go
index.go Storage: Propagate insufficient-storage to CLI exit and index 507 #5613 2026-05-26 23:34:33 +00:00
index_test.go Config: Improve low storage detection #5613 2026-05-29 01:45:04 +02:00
labels.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
labels_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
labels_search_test.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
labels_test.go
lenses.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
lenses_search.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
lenses_search_test.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
lenses_test.go Metadata: Add Lens Make and Model updates via CLI & API #5644 #5656 2026-06-15 09:37:32 +02:00
links.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
links_test.go
markers.go Photos: Limit by-UID label, marker & file edits to the session scope #1307 2026-06-14 15:37:57 +00:00
markers_test.go
mcp.go Backend: Compact verbose comments in config and MCP handler 2026-05-15 17:50:08 +00:00
mcp_test.go MCP: Add HTTP scope-gate tests for admin app password #5024 2026-05-10 15:00:43 +02:00
metrics.go
metrics_test.go
moments_time.go API: Fix incorrect Swagger response schemas and "Fore more" typos 2026-04-15 15:56:13 +02:00
moments_time_test.go
oauth_authorize.go Auth: Serve Portal OIDC OP endpoints under /api/v1/oauth/* #4368 #4369 2026-06-03 15:28:05 +00:00
oauth_authorize_test.go Auth: Serve Portal OIDC OP endpoints under /api/v1/oauth/* #4368 #4369 2026-06-03 15:28:05 +00:00
oauth_error.go Auth: Land cluster sign-out on the Portal login without return_to 2026-06-12 19:56:44 +00:00
oauth_error_test.go Auth: Land cluster sign-out on the Portal login without return_to 2026-06-12 19:56:44 +00:00
oauth_handlers.go Auth: Serve Portal OIDC OP endpoints under /api/v1/oauth/* #4368 #4369 2026-06-03 15:28:05 +00:00
oauth_revoke.go
oauth_revoke_test.go
oauth_token.go Auth: Gate app passwords behind a settings feature flag #5647 2026-06-09 17:36:24 +00:00
oauth_token_ratelimit_test.go
oauth_token_test.go Auth: Gate app passwords behind a settings feature flag #5647 2026-06-09 17:36:24 +00:00
oauth_userinfo.go Auth: Serve Portal OIDC OP endpoints under /api/v1/oauth/* #4368 #4369 2026-06-03 15:28:05 +00:00
oauth_userinfo_test.go Auth: Serve Portal OIDC OP endpoints under /api/v1/oauth/* #4368 #4369 2026-06-03 15:28:05 +00:00
oidc_login.go
oidc_login_test.go
oidc_redirect.go Cluster: Collect OIDC groups at login for group-based admission 2026-06-12 16:08:38 +00:00
oidc_redirect_test.go OIDC: Surface account-collision reconcile hint and add --auth-issuer 2026-06-10 15:31:34 +00:00
oidc_session_cookie.go Cluster: Compact code comments in the OIDC follow-up changes 2026-06-09 10:20:17 +00:00
oidc_session_cookie_test.go Cluster: Add cluster OIDC, branded authorize errors, and sign-out 2026-06-09 09:39:01 +00:00
options.go
photo_label.go Photos: Limit by-UID label, marker & file edits to the session scope #1307 2026-06-14 15:37:57 +00:00
photo_label_test.go
photo_unstack.go Photos: Limit by-UID label, marker & file edits to the session scope #1307 2026-06-14 15:37:57 +00:00
photo_unstack_test.go
photos.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
photos_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
photos_search_geo.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
photos_search_geo_test.go
photos_search_test.go API: Document "GET /api/v1/photos/view" in swagger.json 2026-04-15 14:01:19 +02:00
photos_test.go Tests: Isolate reaction redaction subtest from mutation fixtures 2026-06-14 16:38:34 +00:00
places_reverse.go
places_search.go
places_test.go
reactions.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
README.md Docs: Document the API JSON field-casing convention 2026-06-09 03:18:15 +00:00
request_limits.go MCP: Cap /api/v1/mcp body size and shorten session idle timeout #5024 2026-04-20 13:54:07 +02:00
server.go
services.go API: Fix incorrect Swagger response schemas and "Fore more" typos 2026-04-15 15:56:13 +02:00
services_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
services_search_test.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
services_test.go
services_upload.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
services_upload_test.go
session.go
session_create.go Auth: Store a signed session reference in the OP cookie, not the token 2026-06-03 19:15:45 +00:00
session_delete.go Cluster: Compact code comments in the OIDC follow-up changes 2026-06-09 10:20:17 +00:00
session_get.go API: Set Cache-Control no-store on the session response 2026-06-05 21:03:38 +02:00
session_ratelimit_test.go
session_response.go Cluster: Collect OIDC groups at login for group-based admission 2026-06-12 16:08:38 +00:00
session_test.go Cluster: Collect OIDC groups at login for group-based admission 2026-06-12 16:08:38 +00:00
share.go
share_preview.go Clean-up: Drop imaging and pigo library integrations #5353 #5508 #668 2026-04-01 13:47:42 +02:00
share_preview_test.go
share_test.go
status.go
status_test.go
subjects.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
subjects_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
subjects_search_test.go
subjects_test.go
svg.go
svg_test.go
swagger.json Thumbs: Add 16K (15360px) fit size for photos and videos #5669 #5623 2026-06-17 04:12:40 +00:00
thumbnails.go API: Fix incorrect Swagger response schemas and "Fore more" typos 2026-04-15 15:56:13 +02:00
thumbnails_test.go
users_avatar.go Upload: Report a full disk as insufficient storage #5613 2026-05-30 22:23:14 +00:00
users_avatar_test.go API: Enforce pre-parse body limits for login and uploads 2026-03-07 15:31:30 +01:00
users_passcode.go Auth: Revoke derived sessions & gate app passwords by login state #5647 2026-06-15 23:41:01 +00:00
users_passcode_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
users_password.go Auth: Revoke derived sessions & gate app passwords by login state #5647 2026-06-15 23:41:01 +00:00
users_password_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
users_sessions.go API: Improve user profile authorization checks and code comments #5619 2026-05-26 22:10:58 +02:00
users_sessions_test.go
users_update.go Auth: Revoke derived sessions & gate app passwords by login state #5647 2026-06-15 23:41:01 +00:00
users_update_test.go Users: Prevent disabling own super admin status and web login 2026-06-12 20:36:35 +00:00
users_upload.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
users_upload_multipart_test.go API: Enforce pre-parse body limits for login and uploads 2026-03-07 15:31:30 +01:00
users_upload_test.go API: Enforce pre-parse body limits for login and uploads 2026-03-07 15:31:30 +01:00
video.go API: Fix incorrect hash parameter name in GetVideo Swagger annotation 2026-05-29 13:14:19 +00:00
video_test.go
vision_caption.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_caption_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_face.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_face_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_labels.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_labels_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_nsfw.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_nsfw_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
websocket.go
websocket_create.go
websocket_reader.go
websocket_test.go
websocket_topics_test.go
websocket_writer.go
zip.go API: Scope file lookup and zip selection to the session 2026-05-29 07:04:47 +02:00
zip_test.go

API Package Guide

Overview

The API package exposes PhotoPrisms HTTP endpoints via Gin handlers. Each file under internal/api contains the handlers, request/response DTOs, and Swagger annotations for a specific feature area. Handlers remain thin: they validate input, enforce security or ACL checks, and delegate domain work to services in internal/photoprism, internal/service, or other internal packages. Keep exported types aligned with the REST schema and avoid embedding business logic directly in handlers.

Routing & Wiring

  • Register handlers in internal/server/routes.go by attaching them to the proper router group (for example, APIv1 := router.Group(conf.BaseUri("/api/v1"), Api(conf))).
  • Group endpoints by resource to match existing patterns: sessions, cluster, photos, labels, files, downloads, metadata, and technical routes.
  • Apply middleware stacks (Api, AuthRequired, limiter.Auth, etc.) at the router level to keep handlers focused on request handling.
  • Use conf.BaseUri() when constructing route prefixes so configuration overrides propagate consistently.
  • When new endpoints require feature toggles, gate them in the router rather than inside the handler so disabled routes remain undiscoverable.

Handler Implementation Patterns

  • Accept and return JSON using the shared response helpers. Set header.ContentTypeJSON and ensure responses include proper cache headers (no-store for sensitive payloads).
  • Parse parameters with Gin binding and validate inputs before delegating work. For complex payloads, define dedicated request structs with validation tags.
  • Use the shared download helpers (safe.Download, avatar.SafeDownload) when calling outward HTTP APIs to inherit timeout, size, and SSRF protections.
  • Query and persist data through the corresponding services or repositories; avoid ad-hoc SQL or GORM usage in handlers when dedicated functions exist elsewhere.
  • Surface pagination consistently with count, offset, and limit following the defaults (100 max 1000). Validate offset >= 0 and clamp count to the allowed range.
  • When responses need role-specific fields, build DTOs that redact sensitive data for non-admin roles so the handler stays deterministic.
  • JSON field casing: use TitleCase field names (UUID, Name, SiteUrl, CreatedAt) for request/response bodies that correspond to a database entity (mirroring the entity/model, e.g. the cluster Node and ClusterInstance DTOs), and camelCase (storageNamespace, redirectUri) for generated or artificial payloads that do not map to a specific entity — client config, session responses, and action/RPC bodies. A filtered or computed projection of an entity stays TitleCase; an action payload that operates on an entity stays camelCase but MAY TitleCase the single identity field that mirrors the entity (e.g. a UUID).

Security & Middleware

  • Authenticate requests using the standard middleware (AuthRequired) and check roles via helpers in internal/auth/acl (acl.ParseRole, acl.ScopePermits, acl.ScopeAttrPermits).
  • Bound request bodies before parsing JSON or multipart payloads. Use LimitRequestBodyBytes(...) with a route-appropriate cap before BindJSON(...) / ShouldBindJSON(...), detect IsRequestBodyTooLarge(err), and return 413 Request Entity Too Large via AbortRequestTooLarge(...).
  • Keep new JSON binding sites on the shared request-limit path by running make check-api-request-limits (also included in make lint) after adding or refactoring API handlers in the root repo or private overlays.
  • Never log secrets or tokens. Prefer structured logging through event.Log and redact sensitive values before logging.
  • Enforce rate limiting with the shared limiters (limiter.Auth, limiter.Login) and respond with limiter.AbortJSON to maintain consistent 429 JSON payloads.
  • Derive client IPs through api.ClientIP and extract bearer tokens with header.BearerToken or the helper setters. Use constant-time comparison for tokens and secrets.
  • For downloads or proxy endpoints, validate URLs against allowed schemes (http, https) and reject private or loopback addresses unless explicitly required.
  • Upload-time NSFW screening (users_upload.go) — when PHOTOPRISM_UPLOAD_NSFW=false, the upload handler runs vision.DetectNSFW against every accepted file and deletes any file flagged above the NSFW threshold before it reaches originals/. The check is skipped entirely when UPLOAD_NSFW=true (default). See internal/ai/nsfw/README.md for the full NSFW call-graph and flag matrix.

Audit Logging

  • Emit security events via event.Audit* (AuditInfo, AuditWarn, AuditErr, AuditDebug) and always build the slice as Who → What → Outcome.
    • Who: ClientIP(c) followed by the most specific actor context ("session %s", "client %s", "user %s").
    • What: Resource constant plus action segments (for example, string(acl.ResourceCluster), "node", "%s"). Place extra context such as counts or error placeholders in separate segments before the outcome.
    • Outcome: End with a single token such as status.Succeeded, status.Failed, status.Denied, or status.Error(err) when the sanitized error message should be the outcome; nothing comes after it.
  • Prefer existing helpers (ClientIP, clean.Log, clean.LogQuote, clean.Error) instead of formatting values manually, and avoid inline = expressions.
  • Example patterns:
    event.AuditInfo([]string{
        ClientIP(c),
        "session %s",
        string(acl.ResourceCluster),
        "node", "%s",
        status.Deleted,
    }, s.RefID, uuid)
    
    event.AuditErr([]string{
        clientIp,
        "session %s",
        string(acl.ResourceCluster),
        "download theme",
        status.Error(err),
    }, refID)
    

User-Visible Notifications vs Audit Log

event.AuditInfo / AuditWarn / AuditErr write to the audit log and broadcast on audit.log.<level> — the toast component on the frontend does NOT subscribe to that channel, so an audit entry alone produces no UI feedback. To raise a red or green toast in the browser, publish on the notify.* channel via event.Error(msg) / event.ErrorMsg(id, …) (red) or event.Success(msg) (green).

The two helpers have distinct subscribers; choose based on who the message is for:

  • Short endpoints whose response the frontend reads (single-shot CRUD, login, settings updates). The calling component renders the response, so AuditErr plus an HTTP error is enough — the UI gets the error string from the response body.
  • Long-running endpoints that the UI drives via the event hub (POST /api/v1/index, POST /api/v1/import/*path, and similar). The frontend cancels the in-flight HTTP request on the first index.* / import.* wire event, so the response body is invisible in normal operation. In-flight failures that need a specific toast MUST be published via event.ErrorMsg(...) on notify.error; an HTTP error alone produces only the frontend's generic fallback toast (or nothing, if the cancel has already fired).
  • Forensic events that don't need UI surfacing (rate limiting, ACL denials, internal aborts whose user-visible signal comes from a sibling channel). AuditErr alone is the right call.

When in doubt, ask: "after this handler returns, what does the user see?" If the answer is "the frontend will read the response", AuditErr covers it. If the answer is "the page is already subscribed to wire events and the response is discarded", publish on notify.* as well.

// Forensic audit only — frontend will read the response body and render the error.
event.AuditErr([]string{ClientIP(c), "session %s", "delete album", status.Failed}, s.RefID)
AbortBadRequest(c, err)

// Forensic audit + specific red toast — needed when the request was already canceled by the wire.
event.AuditErr([]string{ClientIP(c), "session %s", "index files", status.Failed}, s.RefID)
event.ErrorMsg(i18n.ErrIndexingFailed)

Swagger Documentation

  • Annotate handlers with Swagger comments that include full /api/v1/... paths, request/response schemas, and security definitions. Only annotate routes that are externally accessible.
  • Regenerate docs after adding or updating handlers: make fmt-go swag-fmt swag. This formats Go files, normalizes annotations, and updates internal/api/swagger.json. Do not edit the generated JSON manually.
  • When adding new DTOs, keep field names aligned with the JSON schema and update client documentation if serialized names change.
  • Use enum annotations sparingly and ensure they reflect actual runtime constraints to avoid misleading generated clients.

Testing Strategy

  • Build tests around the API harness (NewApiTest) to obtain a configured Gin router, config, and dependencies. This isolates filesystem paths and avoids polluting global state.
  • Wrap requests with helper functions (for example, PerformRequestJSON, PerformAuthenticatedRequest) to capture status codes, headers, and payloads. Assert headers using constants from pkg/http/header.
  • When handlers interact with the database, initialize fixtures through config helpers such as config.NewTestConfig("api") or config.NewMinimalTestConfigWithDb("api", t.TempDir()) depending on fixture needs.
  • Stub external dependencies (httptest.Server) for remote calls and set AllowPrivate=true explicitly when the test server binds to loopback addresses.
  • Structure tests with table-driven subtests (t.Run("CaseName", ...)) and use PascalCase names. Provide cleanup functions (t.Cleanup) to remove temporary files or databases created during tests.
  • Do not run internal/api tests in parallel. These suites share fixture files, temporary assets, and database state, so parallel go test invocations can cause false failures and readonly/fixture-conflict errors.

Focused Test Runs

  • Fast iteration: go test ./internal/api -run '<Package|HandlerName>' -count=1
  • Cluster endpoints: go test ./internal/api -run 'Cluster' -count=1
  • Downloads and zip streaming: go test ./internal/api -run 'Download|Archive' -count=1
  • Combined CLI and API validation: pair go test ./internal/commands -run 'Cluster' -count=1 with the matching API suite to ensure DTOs remain compatible.
  • Keep focused internal/api runs sequential. Do not launch multiple go test ./internal/api ... commands at the same time.

Preflight Checklist

  • Format and regenerate documentation: make fmt-go swag-fmt swag
  • Compile backend: go build ./...
  • Execute targeted API suites: go test ./internal/api -run '<Name>' -count=1
  • Run integration-heavy checks before release: go test ./internal/service/cluster/registry -count=1 alongside relevant API routes to confirm cluster DTOs stay aligned.
  • Verify that photoprism show commands --json reflects any new API-driven flags or outputs when CLI exposure changes.