Skip to content
129 changes: 128 additions & 1 deletion extensions/_oci.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ GET /v2/_oci/ext/discover
Repository-level extensions may be discovered with a standard GET as follows.

```HTTP
GET /v2/{name}/_oci/ext/discover
GET /v2/<name>/_oci/ext/discover
```

The base extension returns an array of supported extensions with details of the endpoints as shown below.
Expand Down Expand Up @@ -66,6 +66,133 @@ Content-Type: application/json

Enumeration of the endpoints provided on this registry (as not all "OPTIONAL" endpoints may be present in all registries)

### Component: `tag-history`

This component is for endpoints relating to tag history operations on a repository.

This endpoint returns the history of a tag, listing each manifest the tag has pointed to over time and each time the tag was deleted, in descending order by history entry timestamp (newest first).

Tag history MAY be retrieved with a standard `GET` as follows.

```HTTP
GET /v2/<name>/_oci/tag-history/<tag>
Comment thread
sudo-bmitch marked this conversation as resolved.
```

`<name>` is the namespace of the repository, and `<tag>` is the name of the tag whose history is being queried.

A successful request MUST return a `200 OK` response code.
If the repository does not exist, the registry MUST return a `404 Not Found` response code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the repository does not exist, the registry MUST return a 404 Not Found response code.

When n is zero, this endpoint MUST return an empty array, useful for determining if history is available (200) or not (404) for the tag query.

I think someone could interpret that this should they return 200 and an empty list when n=0 even if the repository does not exist.

If the registry does not implement this extension, the registry MUST return a `404 Not Found` response code.
If the tag has no history, the registry SHOULD return a `200 OK` response code with an empty array.
Tag history SHOULD remain queryable after the tag is deleted.
Deleted manifests SHOULD NOT be removed from tag history.
Tag history MAY be deleted after the repository is deleted.

Upon success, the response body MUST be a JSON array of descriptor objects in the following format:

```HTTP
200 OK
Content-Length: <length>
Content-Type: application/json

[
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 1234,
"digest": "sha256:a1a1a1...",
"annotations": {
"org.opencontainers.distribution.tag.created": "2026-05-04T03:02:01Z"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 1234,
"digest": "sha256:a1a1a1...",
"annotations": {
"org.opencontainers.distribution.tag.deleted": "2026-05-03T03:02:01Z"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 1234,
"digest": "sha256:b2b2b2...",
"annotations": {
"org.opencontainers.distribution.tag.created": "2026-04-03T02:01:00Z"
}
}
]
```

Results MUST be sorted in descending order by the history entry timestamp, which is either the `org.opencontainers.distribution.tag.created` annotation value or the `org.opencontainers.distribution.tag.deleted` annotation value (i.e. the most recent entry appears first).

Each descriptor object in the response MUST be a create or delete entry.
A descriptor for a tag creation event MUST describe the manifest the tag was assigned to.
A descriptor for a tag deletion event MUST describe the manifest the tag pointed to immediately before deletion.

Manifest descriptors MUST include the following properties:
Comment thread
jcarter3 marked this conversation as resolved.

- **`mediaType`** *string*, REQUIRED

The media type of the manifest this tag pointed to at that point in time.

- **`size`** *integer*, REQUIRED

The size in bytes of the manifest.

- **`digest`** *string*, REQUIRED

The digest of the manifest, in the form `<algorithm>:<encoded>`.

- **`annotations`** *map of strings*, REQUIRED

Annotations associated with the historical tag entry. MUST include exactly one of the following event annotations:

- **`org.opencontainers.distribution.tag.created`** *string*

REQUIRED for tag creation events. The RFC 3339 timestamp at which the tag was assigned to this manifest. Used as the sort key and as the cursor for time-based pagination.

- **`org.opencontainers.distribution.tag.deleted`** *string*

REQUIRED for tag deletion events. The RFC 3339 timestamp at which the tag was deleted from this manifest. Used as the sort key and as the cursor for time-based pagination.

##### Query Parameters

The following query parameters MAY be provided:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps there could also be a query parameter for "digest" such that a client could verify that a tag did point at the manifest when given an image reference like image:tag@sha256....

It would return 404 if the digest isn't in the history but otherwise returning the expected JSON array of descriptor objects that includes the digest. I'm not sure if that'd be just the one descriptor (or two if deleted?) or perhaps up to n. And it should respect the other query parameters like before and so also 404 if the digest is not before the specified date.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment for this above:

If a pinned digest is provided with the tag, the registry MUST return a `404 Not Found` if the digest is not part of the tag's history.

This might cover it? Or we could send digest as a separate query param... maybe that's better?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I like pinned digest in the tag, no need for a query parameter when we already have the format. Oh, but I don't see the pinned digest format defined in spec.md? 🤔

I think it may need guidance on what they should return when passed a pinned digest? Probably the only guarantee is that it must be in the list of n (barring before making it no longer in the list and thus 404).


- **`n`** *integer*, OPTIONAL

Specifies the maximum number of results to return.
The registry MAY return fewer results than requested if fewer historical entries exist.
When `n` is zero, this endpoint MUST return an empty array, useful for determining if history is available (200) or not (404) for the tag query.
When `n` is not specified, the registry MAY apply a default limit.

- **`before`** *string (RFC 3339 timestamp)*, OPTIONAL

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe from or ot (older than)

Suggested change
- **`before`** *string (RFC 3339 timestamp)*, OPTIONAL
- **`ot`** *string (RFC 3339 timestamp)*, OPTIONAL

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a chance to have two results with the exact same time stamp..

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a registry could consider every update of a tag as a delete followed by a create and so the timestamp would be the same.


When provided, only entries whose history entry timestamp is strictly less than (i.e. older than) `before` will be returned.
This is used for time-based pagination: pass the `org.opencontainers.distribution.tag.created` or `org.opencontainers.distribution.tag.deleted` value of the last entry returned in the previous response to retrieve the next page.

- **`digest`** *string*, OPTIONAL

When provided, the registry will validate if the provided digest was part of the given tag's history.
If it was not, a `400 Bad Request` response is given.
If it was, the tag listing response will include a descriptor with the provided digest along with the relevant create/delete timestamps.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another idea would be to include a "newer-than" option, or a "between" timestamp1/timestamp2 allowing a client to query new tags or a time range..

##### Pagination Example

To fetch the first page of up to 10 results:

```HTTP
GET /v2/<name>/_oci/tag-history/<tag>?n=10
```

To fetch the next page, pass the history entry timestamp of the last result from the prior response as the `before` parameter:

```HTTP
GET /v2/<name>/_oci/tag-history/<tag>?n=10&before=2026-04-03T02%3A01%3A00Z
```

When the returned array contains fewer entries than `n` (or is empty), the client has reached the end of the history.

## Code representations

Golang structures for these JSON structures is available at [`github.com/opencontainers/distribution-spec/specs-go/v1/extensions`](https://github.com/opencontainers/distribution-spec/tree/main/specs-go/v1/extensions/)
Expand Down
Loading