Edit icon Edit This Page

Panel API Errors

The panel API is the set of /api/... routes that the web UI calls. A script can call it too, with the access token that POST /api/auth/login returns (Authorization: Bearer <token>). This page describes what it answers when a request fails, and how a success looks. The wire protocols of the package formats (Maven, npm, Docker and so on) are a different thing: they keep the error format their own client expects, see The Package Routes. The panel API is in beta and can change.

Successful Answers

A success is the bare resource, with no wrapper around it:

RequestAnswer
Read, update, action that returns something200 and the resource. A list is a page: content and page (size, number, totalElements, totalPages).
Create201, the new resource and a Location header that points to it.
Delete, or an update with nothing to return204 and no body. Deleting something that is not there is 404.
Work that finishes later (for example starting a scan)202, no body and a Location header that points to a status resource.

An answer that carries a secret (login, a new or rotated deploy token, a password reset) has Cache-Control: no-store.

The route POST /api/logs/frontend still answers the older wrapper {"msgId": "...", "type": "SUCCESS", "text": "...", "errorCode": null, "data": ...} on success. It will move to the bare form later. Failures are always the error document below.

The Error Document

A failed request is answered with application/problem+json, a problem document as RFC 9457 defines it:

FieldMeaning
typeA URI for the kind of problem. It is about:blank when there is none.
titleThe HTTP reason phrase of status.
statusThe HTTP status, repeated from the response.
detailA human readable text for this failure. Show it to a person, do not parse it.
instanceThe request path.
codeThe stable machine key of the failure, for example validationError or itemNotFound. Switch on this. A code is never renamed or reused.
errorsOnly on a failed validation: a list of { "field", "code", "message" }, one for each field, parameter or header that was refused. code there is the constraint (NotBlank, Min, …) and message a text.
traceIdA unique id of this one failure (a UUID). Quote it in a bug report, so the failure can be found in the log of the server.

code is what older clients know as msgId, and traceId is the old errorCode; the strings did not change. Do not confuse traceId with code.

Status Codes

StatusWhenTypical code
400A body that cannot be read, a field that fails validation, a bad page, size or sort (page is zero based, size is 1 to 100, an unknown sort property is refused).validationError, badRequest
401The credential is missing, wrong or expired. The answer has WWW-Authenticate: Bearer.loginRequired, invalidCredentials, sessionExpired
403You are signed in, but not allowed to do this. An exceeded plan quota is a 403 too, see Quotas.accessDenied
404There is no such thing. A private repository that you have no access to answers like one that does not exist, so a stranger cannot tell the difference.itemNotFound, repoNotFound
405The path exists, but not for this HTTP method. The Allow header lists the methods that work.methodNotSupported
406The Accept header names no media type the route can produce.notAcceptable
409The name is taken, or the state does not allow it (for example a scan that is already running, or two changes of the same item at once).itemAlreadyExists, concurrentModification
413An upload over the size limit.payloadTooLarge
415The body has a content type the route cannot read. The Accept header lists the ones it can.unsupportedMediaType
429Too many failed attempts, see Retry-After.tooManyRequests
503A database lock could not be taken in time. Repeat the request after the Retry-After seconds.resourceBusy

The set of codes is longer than this table: a route names the thing that failed (usernameInUse, deployTokenNotFound and so on). Treat a code you do not know like the status it came with.

Quotas

Exceeding the disk or the monthly traffic quota of your plan is 403, with the code diskUsageExceeded or trafficLimitExceeded. A limit of the plan on a count of things, such as collaborators, is 403 too: the code is collaboratorLimitReached for collaborators. A quota stops uploads only: a request that stores something new (a version, a file, a layer or a manifest). Downloads, deletes and changes to something that exists (a dist-tag, a deprecation, an unpublish) are never refused for a quota. A limit of zero means no quota at all, not an unlimited one. See the pricing page for the limits of a plan.

On the package routes (where an upload happens) the same 403 and the same codes come in the body of that route, see The Package Routes.

Retry-After

A 429 and a 503 carry a Retry-After header with the number of seconds to wait. 429 is sent when:

  • too many failed sign-ins came from one client, or too many wrong second-factor codes.
    • a new e-mail one-time code is asked for before the resend cooldown of the previous one is over.
    The answer is the same whatever user name or password was sent. Wait for the number of seconds, then try again.

Examples

Texts in detail and message are examples: they can be worded differently, and they follow the language of the server.

A failed validation (400), with one entry in errors for each refused field:

HTTP/1.1 400
Content-Type: application/problem+json

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "Validation error.",
  "instance": "/api/repos",
  "code": "validationError",
  "errors": [
    { "field": "name", "code": "Pattern", "message": "must match the allowed pattern" }
  ],
  "traceId": "0b5f3a52-7c1e-4f0e-9d0b-3a1c2f6d8e44"
}

A missing or wrong credential (401):

HTTP/1.1 401
WWW-Authenticate: Bearer
Content-Type: application/problem+json

{
  "type": "about:blank",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Please sign in.",
  "instance": "/api/profile",
  "code": "loginRequired",
  "traceId": "6f0c4a8e-1d52-4b2c-8a7e-5d9a0e3b7c11"
}

A signed-in user asks for a private repository of someone else (404, the same as for a repository that does not exist):

HTTP/1.1 404
Content-Type: application/problem+json

{
  "type": "about:blank",
  "title": "Not Found",
  "status": 404,
  "detail": "Repository not found.",
  "instance": "/api/repos/some-owner/private-repo/settings",
  "code": "repoNotFound",
  "traceId": "c2d9a7e4-8b36-47a0-9f1d-0e5b6a4c3d28"
}

A collaborator who may read a repository, but not manage it, changes its settings (403):

HTTP/1.1 403
Content-Type: application/problem+json

{
  "type": "about:blank",
  "title": "Forbidden",
  "status": 403,
  "detail": "Access Denied. Please check your credentials.",
  "instance": "/api/repos/my-owner/my-repo/settings",
  "code": "accessDenied",
  "traceId": "1e8b3c7d-5a42-4d19-b6f0-9c2a7d4e8f35"
}

A plan count limit (403):

HTTP/1.1 403
Content-Type: application/problem+json

{
  "type": "about:blank",
  "title": "Forbidden",
  "status": 403,
  "detail": "The collaborator limit of the plan is reached.",
  "instance": "/api/repos/my-owner/my-repo/users",
  "code": "collaboratorLimitReached",
  "traceId": "9a4e7c20-3b6d-4f81-a5c9-2d0e8b7f1a63"
}

Too many failed sign-ins (429):

HTTP/1.1 429
Retry-After: 30
Content-Type: application/problem+json

{
  "type": "about:blank",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Too many failed authentication attempts. Please try again later.",
  "instance": "/api/auth/login",
  "code": "tooManyRequests",
  "traceId": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}

A wrong method (405), with the methods that work in Allow:

HTTP/1.1 405
Allow: GET, HEAD, PATCH, DELETE
Content-Type: application/problem+json

{
  "type": "about:blank",
  "title": "Method Not Allowed",
  "status": 405,
  "detail": "The HTTP method is not supported for this route.",
  "instance": "/api/repos/my-repo",
  "code": "methodNotSupported",
  "traceId": "3d1b9f6a-2c47-4e85-8a0b-7f5c6e2d9a14"
}

The Package Routes

The routes that package managers use (mvn deploy, npm publish, docker push and so on) keep the error format of their own client, not the error document above. For most of them it is a JSON body with the older fields msgId (the same key as code above), type, text, errorCode (the same unique id as traceId above) and data:

{"msgId": "tooManyRequests", "type": "ERROR", "text": "Too many failed authentication attempts. Please try again later.", "errorCode": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "data": null}

The status codes are the ones of the table above, and the codes are the same strings. An upload over a quota is 403 on every package route, with msgId diskUsageExceeded or trafficLimitExceeded. A Docker client gets the registry error DENIED for it, as the Docker registry API defines.

Found a problem with this page?