Vetrix Docs

OCI / Docker registry

The OCI container registry implements the OCI Distribution Specification (the /v2 API) used by docker, podman, buildah, and compatible clients to pull and push container images and other OCI artifacts. See README.md for the conventions shared by every registry protocol.

Resource overview

All endpoints are served under /v2/ on the API host (https://api.gitvetrix.com). The repository is named in the path as {owner}/{repo}[/{image}]: the first two segments resolve to a repository, and an optional trailing segment names an image within it. A reference (<ref>) is either a tag (v1.2.3) or a content digest (sha256:<hex>); manifest and blob deletes require a digest.

Auth & scopes

OCI uses token authentication. See Authentication for the credential types and Push scopes for how a pull/push grant is resolved.

  1. A client requests GET /v2/. Without a valid credential the registry answers 401 with a WWW-Authenticate: Bearer realm="…/v2/token",service="…" challenge.
  2. The client calls GET /v2/token to exchange its credential for a short-lived (five-minute), repository-scoped token. docker login supplies the underlying credential — a session JWT, a personal access token, or a CI job token — as the HTTP Basic password.
  3. The client replays the original request with Authorization: Bearer <token>.

The minted token's actions are derived from the caller's authorization, so it can never grant more than the underlying credential would on a direct request. A pull on a public repository needs no credential at all.

Endpoints

Method Path Summary
GET /v2/ API version check / auth probe
GET /v2/token Mint a repository-scoped bearer token
GET /v2/{name}/tags/list List the tags of an image
GET, HEAD /v2/{name}/manifests/{ref} Pull a manifest (or check existence)
PUT /v2/{name}/manifests/{ref} Push a manifest
DELETE /v2/{name}/manifests/{digest} Delete a manifest / untag
GET, HEAD /v2/{name}/blobs/{digest} Pull a blob (or check existence)
DELETE /v2/{name}/blobs/{digest} Delete a blob
POST /v2/{name}/blobs/uploads/ Start a blob upload
GET /v2/{name}/blobs/uploads/{uuid} Check upload progress
PATCH /v2/{name}/blobs/uploads/{uuid} Append a chunk
PUT /v2/{name}/blobs/uploads/{uuid}?digest={digest} Finalize an upload

GET /v2/

The version-check endpoint, used by clients to discover whether the registry requires authentication. With a valid credential it returns 200 OK, an empty JSON body, and a Docker-Distribution-API-Version: registry/2.0 header. Without one it returns 401 and the Bearer challenge so the client enters the auth flow. Anonymous pulls of public images still succeed on the per-resource endpoints even though this probe challenges.

GET /v2/token

Mints a short-lived bearer token scoped to a single repository. The requested repository is passed in the scope query parameter; the granted actions are derived from the caller's authorization, not from the request.

Query parameters

Name Type Description
scope string repository:{owner}/{repo}:{actions}, for example repository:alice/app:pull,push. The repository is honored; the actions are re-derived.

Response

{
  "token": "<jwt>",
  "access_token": "<jwt>",
  "expires_in": 300,
  "issued_at": "2026-01-01T00:00:00Z",
  "scope": "repository:alice/app:pull,push"
}

The granted scope is …:pull,push for an authorized writer, …:pull for a reader, or empty when the caller has no access (an honest zero-access token, never a forged grant). access_token duplicates token for client compatibility.

GET /v2/{name}/tags/list

Returns the tags of an image.

{ "name": "alice/app", "tags": ["v1.0.0", "v1.1.0", "latest"] }

GET, HEAD /v2/{name}/manifests/{ref}

Pulls the manifest identified by {ref} (a tag or a sha256: digest). The response carries the manifest body with its Content-Type (for example application/vnd.oci.image.manifest.v1+json or application/vnd.docker.distribution.manifest.v2+json) and a Docker-Content-Digest header. A HEAD returns the same headers with no body.

PUT /v2/{name}/manifests/{ref}

Pushes a manifest, tagging it as {ref}. Returns 201 Created with a Location header (/v2/{name}/manifests/{digest}) and the Docker-Content-Digest of the stored manifest.

DELETE /v2/{name}/manifests/{digest}

Deletes a manifest (untagging the image). The reference must be a sha256: digest, not a tag. Returns 202 Accepted.

GET, HEAD /v2/{name}/blobs/{digest}

Pulls a blob (a layer or config) by its sha256: digest, with a Docker-Content-Digest header. A HEAD returns headers only.

DELETE /v2/{name}/blobs/{digest}

Deletes a blob. Returns 202 Accepted.

Blob upload

A push uploads each layer through a session, then the manifest references them:

  1. POST /v2/{name}/blobs/uploads/ starts a session and returns 202 Accepted with a Location (/v2/{name}/blobs/uploads/{uuid}) and a Docker-Upload-UUID header.
  2. PATCH the Location with chunk bytes to append data (202 Accepted).
  3. PUT the Location with ?digest={digest} to finalize. The registry verifies the content against the digest and returns 201 Created with a Location of /v2/{name}/blobs/{digest}.
  4. GET the Location at any point returns the bytes received so far.

Example

# Authenticate and pull (docker handles the /v2/token exchange):
docker login api.gitvetrix.com    # username: any; password: <jwt-or-pat-or-job-token>
docker pull api.gitvetrix.com/alice/app:v1.0.0

# Push:
docker tag local/image api.gitvetrix.com/alice/app:v1.0.0
docker push api.gitvetrix.com/alice/app:v1.0.0

Errors

OCI errors use the Distribution Spec error envelope rather than the REST error shape: a JSON body with an errors array of { "code", "message", "detail" } objects, alongside the HTTP status.

{ "errors": [ { "code": "MANIFEST_UNKNOWN", "message": "manifest unknown", "detail": null } ] }
Status Code When
401 Unauthorized UNAUTHORIZED No or invalid credential; carries the Bearer challenge.
403 Forbidden DENIED Authenticated but not permitted to push or delete here.
404 Not Found NAME_UNKNOWN Unknown repository — or a private one the caller may not read.
404 Not Found MANIFEST_UNKNOWN / BLOB_UNKNOWN The manifest or blob does not exist.
400 Bad Request MANIFEST_INVALID / DIGEST_INVALID Malformed manifest, or a content/digest mismatch on finalize.
405 Method Not Allowed UNSUPPORTED The method is not supported on this resource.

A denied private-repository read returns NAME_UNKNOWN, not DENIED, so a caller cannot probe for private images. See errors.md.

Rate limits

Metered under the registry scope. See rate-limits.md.