{ "openapi": "3.0.3", "info": { "title": "Navidrome API", "version": "1.0.0", "description": "Navidrome API v1. Spec-first, additive within v1. Clients discover implemented\ncapability modules through `GET /capabilities` and never sniff versions.\n\nEnums are open: new values may be added to any enum within v1. Clients must\naccept values they do not recognise instead of failing.\n\nEvery operation declares `x-stability-level`: `alpha` operations may change or\ndisappear without notice, `beta` and `stable` operations only change additively.\nA level is only ever raised, never lowered.\n\n`HEAD` is accepted wherever `GET` is. A `405` response lists the allowed methods\nin its `Allow` header.\n\nOperations that need a grant declare `security: [{bearerAuth: []}]` and the scope they need in\n`x-scope` (OpenAPI 3.0 does not allow scopes on bearer schemes). Clients send the grant secret as\n`Authorization: Bearer \u003csecret\u003e`. A revoked grant stops working within one minute at most.\n", "license": { "name": "GPL-3.0", "url": "https://www.gnu.org/licenses/gpl-3.0.html" } }, "servers": [ { "url": "/api/v1" } ], "tags": [ { "name": "server", "description": "Server discovery and the published OpenAPI document." }, { "name": "auth", "description": "Grants and login methods." } ], "paths": { "/server": { "get": { "operationId": "getServerInfo", "x-module": "core", "x-stability-level": "alpha", "tags": [ "server" ], "security": [], "summary": "Describe the server", "description": "Returns the public server description. No authentication required.\nCapability modules are listed by `GET /capabilities`.\n", "responses": { "200": { "description": "Server description.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServerInfo" } } } }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/capabilities": { "get": { "operationId": "getCapabilities", "x-module": "core", "x-stability-level": "alpha", "tags": [ "server" ], "summary": "List implemented capability modules", "description": "The capability modules this server implements. Any valid grant may read it, whatever its scopes.", "security": [ { "bearerAuth": [] } ], "responses": { "200": { "description": "Implemented modules.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Capabilities" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/openapi.json": { "get": { "operationId": "getOpenAPISpecJSON", "x-module": "core", "x-stability-level": "alpha", "tags": [ "server" ], "security": [], "summary": "Get the OpenAPI document (JSON)", "description": "The bundled OpenAPI document of the running server version. Supports ETag revalidation.", "responses": { "200": { "description": "The OpenAPI document.", "headers": { "ETag": { "$ref": "#/components/headers/ETag" } }, "content": { "application/json": { "schema": { "type": "object", "description": "OpenAPI 3.0 document." } } } }, "304": { "$ref": "#/components/responses/NotModified" } } } }, "/auth/grants": { "get": { "operationId": "listGrants", "x-module": "core", "x-scope": "read", "x-stability-level": "alpha", "tags": [ "auth" ], "summary": "List my grants", "description": "The caller's grants, most recently used first. Grants idle long enough to have expired are not listed.", "security": [ { "bearerAuth": [] } ], "parameters": [ { "$ref": "#/components/parameters/offset" }, { "$ref": "#/components/parameters/limit" } ], "responses": { "200": { "description": "A page of grants.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GrantList" } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/openapi.yaml": { "get": { "operationId": "getOpenAPISpecYAML", "x-module": "core", "x-stability-level": "alpha", "tags": [ "server" ], "security": [], "summary": "Get the OpenAPI document (YAML)", "description": "The bundled OpenAPI document of the running server version. Supports ETag revalidation.", "responses": { "200": { "description": "The OpenAPI document.", "headers": { "ETag": { "$ref": "#/components/headers/ETag" } }, "content": { "application/yaml": { "schema": { "type": "object", "description": "OpenAPI 3.0 document." } } } }, "304": { "$ref": "#/components/responses/NotModified" } } } }, "/auth/grants/{id}": { "delete": { "operationId": "revokeGrant", "x-module": "core", "x-scope": "read", "x-stability-level": "alpha", "tags": [ "auth" ], "summary": "Revoke one of my grants", "description": "Revokes the grant; requests with its secret fail from then on. Another user's grant id answers 404.", "security": [ { "bearerAuth": [] } ], "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Grant id.", "schema": { "type": "string", "maxLength": 64 } } ], "responses": { "204": { "description": "Revoked." }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/auth/logout": { "post": { "operationId": "logout", "x-module": "core", "x-scope": "read", "x-stability-level": "alpha", "tags": [ "auth" ], "summary": "Log out", "description": "Revokes the grant that made this request.", "security": [ { "bearerAuth": [] } ], "responses": { "200": { "description": "Logged out.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LogoutResponse" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/auth/login": { "post": { "operationId": "login", "x-module": "password", "x-stability-level": "alpha", "tags": [ "auth" ], "summary": "Log in with a password", "description": "Checks the username and password and returns a new grant. Unknown user and wrong password fail the same way.", "security": [], "requestBody": { "description": "The credentials and a description of the client.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CredentialsRequest" } } } }, "responses": { "200": { "description": "The new grant.", "headers": { "Cache-Control": { "$ref": "#/components/headers/CacheControlNoStore" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GrantCreated" } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "429": { "$ref": "#/components/responses/TooManyRequests" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/auth/setup": { "post": { "operationId": "setupFirstAdmin", "x-module": "password", "x-stability-level": "alpha", "tags": [ "auth" ], "summary": "Create the first admin", "description": "Creates the first administrator while `setupRequired` is true and returns a grant for it. Answers 409 `setup_complete` once any user exists. A server with no setup step always answers 409.", "security": [], "requestBody": { "description": "The credentials and a description of the client.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CredentialsRequest" } } } }, "responses": { "201": { "description": "The admin was created.", "headers": { "Cache-Control": { "$ref": "#/components/headers/CacheControlNoStore" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GrantCreated" } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "409": { "$ref": "#/components/responses/Conflict" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "429": { "$ref": "#/components/responses/TooManyRequests" }, "500": { "$ref": "#/components/responses/InternalError" } } } }, "/auth/password": { "post": { "operationId": "changePassword", "x-module": "password", "x-scope": "password", "x-stability-level": "alpha", "tags": [ "auth" ], "summary": "Change my password", "description": "Changes the caller's password. By default every other grant of the user is revoked; the calling grant survives. On Navidrome the change also ends the user's sessions on its other APIs, regardless of `revokeOtherGrants`, which only covers API v1 grants. Answers 409 `password_managed_externally` when the password is not stored by this server.", "security": [ { "bearerAuth": [] } ], "requestBody": { "description": "The current and the new password.", "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PasswordChangeRequest" } } } }, "responses": { "204": { "description": "Password changed." }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "409": { "$ref": "#/components/responses/Conflict" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" }, "429": { "$ref": "#/components/responses/TooManyRequests" }, "500": { "$ref": "#/components/responses/InternalError" } } } } }, "components": { "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "description": "Grant secret from a login method (`POST /auth/login`, `POST /auth/setup`). Opaque. The required scope is in each operation's `x-scope`." } }, "schemas": { "ServerInfo": { "type": "object", "description": "Public server description. Everything an add-server screen needs before login.", "required": [ "name", "serverVersion", "specVersion", "setupRequired", "loginMethods" ], "properties": { "name": { "type": "string", "description": "Human-readable server product name." }, "serverVersion": { "type": "string", "description": "Version of the running server build." }, "specVersion": { "type": "string", "description": "Version of the OpenAPI document this server implements." }, "setupRequired": { "type": "boolean", "description": "True until the first admin user has been created." }, "loginMethods": { "$ref": "#/components/schemas/LoginMethods" } } }, "LoginMethods": { "type": "object", "description": "Login methods this server accepts, keyed by method. A missing key means the method is not offered.\nKeys are optional on purpose: discovery is read by clients of any version against servers of any\nversion, so new methods are added as new optional keys. Clients ignore keys they do not know.\n", "properties": { "password": { "$ref": "#/components/schemas/PasswordLoginMethod" } } }, "PasswordLoginMethod": { "type": "object", "description": "Username and password login (`POST /auth/login`). No settings yet." }, "Problem": { "type": "object", "description": "RFC 9457 problem details, returned for every 4xx and 5xx response.", "required": [ "title", "status", "code" ], "properties": { "type": { "type": "string", "description": "URI reference identifying the problem type. Omitted while the problem carries no semantics\nbeyond its HTTP status code, which RFC 9457 defines as `about:blank`. Problems with their\nown semantics get their own URI; switch on `code` instead.\n" }, "title": { "type": "string", "description": "Short human-readable summary, the same for all occurrences of this problem type." }, "status": { "type": "integer", "description": "HTTP status code of this response." }, "detail": { "type": "string", "description": "Human-readable explanation specific to this occurrence. Omitted unless the server marked the text as safe to show clients." }, "code": { "type": "string", "description": "Machine-readable error code, and the value clients switch on. New codes may be added.", "enum": [ "validation", "unauthorized", "forbidden", "insufficient_scope", "not_found", "method_not_allowed", "setup_complete", "password_managed_externally", "payload_too_large", "rate_limited", "unavailable", "internal" ] }, "referenceId": { "type": "string", "description": "Present on internal errors. Quote it when reporting a problem; it tags the server's log lines for this request." }, "errors": { "type": "array", "description": "Per-field failures. Present only when `code` is `validation`.", "items": { "$ref": "#/components/schemas/ValidationError" } } } }, "ValidationError": { "type": "object", "description": "One field-level validation failure.", "required": [ "field", "message" ], "properties": { "field": { "type": "string", "description": "Name of the offending query parameter, path parameter, or body field (dotted for nested)." }, "message": { "type": "string", "description": "Why the value was rejected." } } }, "Capabilities": { "type": "object", "description": "Capability modules this server implements, keyed by module. Keys are optional; a missing key means the\nmodule is not implemented. New modules are added as new optional keys. These are server facts, not what\nthe calling grant may use.\n", "properties": { "core": { "$ref": "#/components/schemas/CoreCapability" }, "password": { "$ref": "#/components/schemas/PasswordCapability" } } }, "CoreCapability": { "type": "object", "description": "The mandatory core module.", "required": [ "version" ], "properties": { "version": { "type": "integer", "description": "Module version. Bumped only on semantic change." } } }, "PasswordCapability": { "type": "object", "description": "The password login module (login, first-admin setup, password change).", "required": [ "version" ], "properties": { "version": { "type": "integer", "description": "Module version. Bumped only on semantic change." } } }, "GrantList": { "type": "object", "description": "A page of the caller's grants.", "required": [ "items", "total", "offset", "limit" ], "properties": { "items": { "type": "array", "description": "Grants on this page, by last use, most recent first; never-used grants last.", "items": { "$ref": "#/components/schemas/Grant" } }, "total": { "type": "integer", "description": "Total number of grants." }, "offset": { "type": "integer", "description": "Zero-based index of the first returned item." }, "limit": { "type": "integer", "description": "Maximum number of items in this page." } } }, "LogoutResponse": { "type": "object", "description": "Result of a logout.", "required": [ "logoutUrl" ], "properties": { "logoutUrl": { "type": "string", "nullable": true, "description": "Where to send the browser to finish logging out of an external provider. Null when there is nothing more to do." } } }, "CredentialsRequest": { "type": "object", "description": "Username, password and client description for a login or first-admin setup.", "required": [ "username", "password", "client" ], "properties": { "username": { "type": "string", "minLength": 1, "maxLength": 255, "description": "Login name." }, "password": { "type": "string", "minLength": 1, "maxLength": 1024, "description": "Password." }, "client": { "type": "string", "minLength": 1, "maxLength": 64, "description": "Name of the client app." }, "clientVersion": { "type": "string", "maxLength": 32, "description": "Version of the client app." }, "name": { "type": "string", "minLength": 1, "maxLength": 64, "description": "Label for this grant. Defaults to `client`." }, "scopes": { "type": "array", "maxItems": 32, "description": "Scopes the grant may hold. Omit for `all`.", "items": { "$ref": "#/components/schemas/ScopeRequest" } } } }, "GrantCreated": { "type": "object", "description": "Returned by every login method. The secret is shown only here; store it and never parse it.", "required": [ "secret", "grant", "user" ], "properties": { "secret": { "type": "string", "maxLength": 512, "description": "Opaque grant secret. Send it as `Authorization: Bearer \u003csecret\u003e`." }, "grant": { "description": "The new grant.", "allOf": [ { "$ref": "#/components/schemas/Grant" } ] }, "user": { "description": "The user the grant belongs to.", "allOf": [ { "$ref": "#/components/schemas/AuthUser" } ] } } }, "PasswordChangeRequest": { "type": "object", "description": "Change the caller's own password.", "required": [ "currentPassword", "newPassword" ], "properties": { "currentPassword": { "type": "string", "minLength": 1, "maxLength": 1024, "description": "The current password." }, "newPassword": { "type": "string", "minLength": 1, "maxLength": 1024, "description": "The new password." }, "revokeOtherGrants": { "type": "boolean", "default": true, "description": "Revoke every other grant of the user. The calling grant always survives. Default true." } } }, "Grant": { "type": "object", "description": "A long-lived grant held by one client of one user.", "required": [ "id", "name", "client", "clientVersion", "scopes", "provider", "createdAt", "lastUsedAt", "lastUsedIp", "current" ], "properties": { "id": { "type": "string", "description": "Grant id." }, "name": { "type": "string", "description": "Label shown to the user." }, "client": { "type": "string", "description": "Name of the client app that holds the grant." }, "clientVersion": { "type": "string", "nullable": true, "description": "Version of the client app, when it sent one." }, "scopes": { "type": "array", "description": "Scopes this grant carries.", "items": { "$ref": "#/components/schemas/Scope" } }, "provider": { "type": "string", "description": "How the grant was created, for example `password` or `setup`. Free-form; new values may appear." }, "createdAt": { "type": "string", "format": "date-time", "description": "When the grant was created." }, "lastUsedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "When the grant was last used, at a coarse granularity. Null until first use." }, "lastUsedIp": { "type": "string", "nullable": true, "description": "Client IP of the last use. Null until first use." }, "current": { "type": "boolean", "description": "True for the grant that made this request." } } }, "Scope": { "type": "string", "description": "A permission scope. Scopes mirror capability modules; `x:write` includes `x`. `all` appears only on\ngrants and means every scope the user is entitled to, now and in future releases. New scopes may be added.\n", "enum": [ "all", "read", "password" ] }, "ScopeRequest": { "type": "string", "description": "A requested scope. Scopes the server does not know are dropped, not rejected, so newer clients keep working.", "pattern": "^[a-z][a-z-]*(:write)?$", "maxLength": 64 }, "AuthUser": { "type": "object", "description": "The user a grant belongs to.", "required": [ "id", "userName", "name", "isAdmin", "passwordChangeable" ], "properties": { "id": { "type": "string", "description": "User id." }, "userName": { "type": "string", "description": "Login name." }, "name": { "type": "string", "description": "Display name." }, "isAdmin": { "type": "boolean", "description": "Whether the user is an administrator." }, "passwordChangeable": { "type": "boolean", "description": "Whether `POST /auth/password` can change this user's password. Clients hide \"change password\" when false." } } } }, "responses": { "InternalError": { "description": "Unexpected server failure. Details are in the server log.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }, "Unauthorized": { "description": "Missing, invalid, or expired credentials.", "headers": { "WWW-Authenticate": { "$ref": "#/components/headers/WWWAuthenticate" } }, "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }, "NotModified": { "description": "Not modified.", "headers": { "ETag": { "$ref": "#/components/headers/ETag" } } }, "BadRequest": { "description": "The request is malformed or fails validation.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }, "Forbidden": { "description": "The caller is authenticated but not allowed to do this.", "headers": { "WWW-Authenticate": { "$ref": "#/components/headers/WWWAuthenticate" } }, "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }, "NotFound": { "description": "No such resource or endpoint.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }, "PayloadTooLarge": { "description": "The request body is too large (`payload_too_large`).", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }, "TooManyRequests": { "description": "Rate limited (`rate_limited`). Retry after the `Retry-After` seconds.", "headers": { "Retry-After": { "description": "Seconds to wait before retrying.", "schema": { "type": "integer" } } }, "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }, "Conflict": { "description": "The request conflicts with the server's state, for example `setup_complete` or `password_managed_externally`.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } } }, "parameters": { "offset": { "name": "offset", "in": "query", "description": "Zero-based index of the first item to return.", "required": false, "schema": { "type": "integer", "minimum": 0, "default": 0 } }, "limit": { "name": "limit", "in": "query", "description": "Maximum number of items to return.", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 2000, "default": 100 } } }, "headers": { "WWWAuthenticate": { "description": "RFC 6750 Bearer challenge, for example `Bearer error=\"insufficient_scope\", scope=\"read\"`.", "schema": { "type": "string" } }, "ETag": { "description": "Entity tag for `If-None-Match` revalidation.", "schema": { "type": "string" } }, "CacheControlNoStore": { "description": "Always `no-store`, because the response carries a secret.", "schema": { "type": "string", "enum": [ "no-store" ] } } } } }