PlanyDocs
API reference

RegistryService

api.plany.cloud:443Regional9 methods

Manage private container image repositories, and the credentials that push to and pull from them. Images themselves move with ordinary tools — docker, podman, buildkit or any CI system — at <region>.plany.cr; this API creates repositories, lists and deletes what is in them, and controls who can reach them. GetRegistryCredential mints a short-lived login for a build machine, so CI can push without holding an API key.

CreateRepository

plany.containerregistry.v1.RegistryService/CreateRepository

Creates a repository.

A repository has to exist before anything can be pushed to it. That is a deliberate departure from registries that create one on first push: a repository is what quota is counted in and what a role is bound on, so create-on-push would mean a typo in a tag silently provisioning a resource and consuming a limit.

Parameters

  • parentstringRequired

    projects/{project}/locations/{location}.

  • repositoryRepositoryRequired
    Show child attributes
    • namestringIdentifier

      projects/{project}/locations/{location}/repositories/{repository}.

      The last segment is what appears in an image reference, so it is constrained by the OCI specification rather than by AIP-122 alone: lower case, digits, and ., _ or - between them. So is the project id, for the same reason and with no way to fix it afterwards — see CreateRepositoryRequest.repository_id.

    • display_namestring

      What a person calls it. Free text; not part of any reference.

    • labelsmap<string, string>

      Arbitrary key/value pairs, for the customer's own accounting.

    • immutable_tagsbool

      Refuse a push that would move a tag that already exists.

      Off by default, because :latest moving is what most people mean by a tag. On, a tag is a permanent name for one digest — which is what a deployment that has to be reproducible needs, and what makes "the same tag" a fact rather than a hope.

      Changing it does not rewrite history: tags already moved stay where they are, and the guarantee starts now.

  • repository_idstringRequired

    The last segment of the name, and the second half of every image reference that will ever point here.

    Constrained by the OCI specification rather than by AIP-122: one or more lower-case alphanumeric runs separated by a single ., _, __ or -. An id that AIP would accept and OCI would not — anything with a capital letter — produces a repository nothing can address, so it is refused here rather than discovered at the customer's first push as a parse error from their own client.

    The project's id is under the same constraint and cannot be fixed afterwards, because it is the first path segment. A project whose id no container client can parse is told so at this call, which is the last moment anybody can do anything about it.

Returns

Repository.

grpcurl
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"parent": "projects//locations/",
"repository": {
"display_name": "My server"
},
"repository_id": "my-repository"
}' \
api.plany.cloud:443 plany.containerregistry.v1.RegistryService/CreateRepository
Example response
{
"name": "projects//locations//repositories/my-repository",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"labels": {
"key": ""
},
"state": "STATE_ACTIVE",
"immutable_tags": false,
"size_bytes": "0",
"image_count": "0",
"last_push_time": "2026-10-04T12:00:00Z",
"last_pull_time": "2026-10-04T12:00:00Z",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z"
}

GetRepository

Read
plany.containerregistry.v1.RegistryService/GetRepository

Parameters

  • namestringRequired

Returns

Repository.

grpcurl
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"name": "projects//locations//repositories/my-repository"
}' \
api.plany.cloud:443 plany.containerregistry.v1.RegistryService/GetRepository
Example response
{
"name": "projects//locations//repositories/my-repository",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"labels": {
"key": ""
},
"state": "STATE_ACTIVE",
"immutable_tags": false,
"size_bytes": "0",
"image_count": "0",
"last_push_time": "2026-10-04T12:00:00Z",
"last_pull_time": "2026-10-04T12:00:00Z",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z"
}

ListRepositories

Read
plany.containerregistry.v1.RegistryService/ListRepositories

Parameters

  • parentstringRequired
  • page_sizeint32
  • page_tokenstring
  • filterstring

    AIP-160, over display_name, labels and state.

Returns

ListRepositoriesResponse.

grpcurl
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"parent": "projects//locations/",
"page_size": 50
}' \
api.plany.cloud:443 plany.containerregistry.v1.RegistryService/ListRepositories
Example response
{
"repositories": [
{
"name": "projects//locations//repositories/my-repository",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"labels": {
"key": ""
},
"state": "STATE_ACTIVE",
"immutable_tags": false,
"size_bytes": "0",
"image_count": "0",
"last_push_time": "2026-10-04T12:00:00Z",
"last_pull_time": "2026-10-04T12:00:00Z",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z"
}
],
"next_page_token": ""
}

UpdateRepository

plany.containerregistry.v1.RegistryService/UpdateRepository

Parameters

  • repositoryRepositoryRequired
    Show child attributes
    • namestringIdentifier

      projects/{project}/locations/{location}/repositories/{repository}.

      The last segment is what appears in an image reference, so it is constrained by the OCI specification rather than by AIP-122 alone: lower case, digits, and ., _ or - between them. So is the project id, for the same reason and with no way to fix it afterwards — see CreateRepositoryRequest.repository_id.

    • display_namestring

      What a person calls it. Free text; not part of any reference.

    • labelsmap<string, string>

      Arbitrary key/value pairs, for the customer's own accounting.

    • immutable_tagsbool

      Refuse a push that would move a tag that already exists.

      Off by default, because :latest moving is what most people mean by a tag. On, a tag is a permanent name for one digest — which is what a deployment that has to be reproducible needs, and what makes "the same tag" a fact rather than a hope.

      Changing it does not rewrite history: tags already moved stay where they are, and the guarantee starts now.

  • update_maskFieldMaskRequired

    Which fields to write. Only display_name, labels and immutable_tags can be updated; everything else on the message is either the identity or is measured rather than set.

Returns

Repository.

grpcurl
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"repository": {
"display_name": "My server"
},
"update_mask": "display_name"
}' \
api.plany.cloud:443 plany.containerregistry.v1.RegistryService/UpdateRepository
Example response
{
"name": "projects//locations//repositories/my-repository",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"labels": {
"key": ""
},
"state": "STATE_ACTIVE",
"immutable_tags": false,
"size_bytes": "0",
"image_count": "0",
"last_push_time": "2026-10-04T12:00:00Z",
"last_pull_time": "2026-10-04T12:00:00Z",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z"
}

DeleteRepository

plany.containerregistry.v1.RegistryService/DeleteRepository

Deletes a repository and everything in it.

Refused while the repository holds images unless force is set. A repository is addressed by a name somebody's deployment may still be pulling, and the cost of the two mistakes is not symmetric: a delete that needed one extra flag is an inconvenience, and a delete that took a running fleet's image out from under it is an outage nothing can undo.

Parameters

  • namestringRequired
  • forcebool

    Delete it even though it still holds images.

Returns

Empty.

grpcurl
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"name": "projects//locations//repositories/my-repository"
}' \
api.plany.cloud:443 plany.containerregistry.v1.RegistryService/DeleteRepository
Example response
{}

ListImages

Read
plany.containerregistry.v1.RegistryService/ListImages

Lists what is actually in a repository: exactly what a pull would find.

Parameters

  • parentstringRequired

    projects/{project}/locations/{location}/repositories/{repository}.

  • page_sizeint32
  • page_tokenstring
  • tagged_onlybool

    Leave out the manifests no tag points at.

    Off by default, deliberately: an untagged image is still storage that is still being charged for, and a list that hid it would make the bill and the console disagree with no way to see why.

Returns

ListImagesResponse.

grpcurl
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"parent": "projects//locations//repositories/my-repository",
"page_size": 50
}' \
api.plany.cloud:443 plany.containerregistry.v1.RegistryService/ListImages
Example response
{
"images": [
{
"name": "projects//locations//repositories/my-repository/images/my-image",
"digest": "",
"tags": [
""
],
"media_type": "",
"size_bytes": "0",
"platforms": [
""
],
"create_time": "2026-10-04T12:00:00Z"
}
],
"next_page_token": ""
}

GetImage

Read
plany.containerregistry.v1.RegistryService/GetImage

Parameters

  • namestringRequired

    The image's full name. The final segment may be a digest or a tag — a tag is resolved to the digest it currently points at, and the answer names the digest, so a caller can tell what they actually got.

Returns

Image.

grpcurl
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"name": "projects//locations//repositories/my-repository/images/my-image"
}' \
api.plany.cloud:443 plany.containerregistry.v1.RegistryService/GetImage
Example response
{
"name": "projects//locations//repositories/my-repository/images/my-image",
"digest": "",
"tags": [
""
],
"media_type": "",
"size_bytes": "0",
"platforms": [
""
],
"create_time": "2026-10-04T12:00:00Z"
}

DeleteImage

plany.containerregistry.v1.RegistryService/DeleteImage

Deletes one image, and every tag pointing at it.

Layers shared with other images are kept for as long as anything still uses them.

Parameters

  • namestringRequired

Returns

Empty.

grpcurl
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"name": "projects//locations//repositories/my-repository/images/my-image"
}' \
api.plany.cloud:443 plany.containerregistry.v1.RegistryService/DeleteImage
Example response
{}

GetRegistryCredential

plany.containerregistry.v1.RegistryService/GetRegistryCredential

Mints a credential a container client can log in with.

This is plany registry get-token, and what it is for is the case where there is no person: a build machine that needs to push and should not be holding an API key with the rest of an account's permissions behind it. The credential it returns reaches the registry and nothing else on the platform, expires on its own, and carries only the repository verbs the caller already holds.

Parameters

  • parentstringRequired

    projects/{project}/locations/{location}.

    The credential is scoped to one project in one region, because that is the registry hostname it is good at.

  • lifetimeDuration

    How long it should last. Capped by the server; a request for longer is clamped rather than refused, and the response says what was actually granted.

    Left unset it is an hour, which is longer than an interactive push and shorter than a working day.

  • repository_idsrepeated string

    Restrict it to these repositories, by id.

    Empty means every repository in the project the caller can already reach. Naming them is how a build for one service gets a credential that cannot touch the others, and it can only ever narrow — a repository the caller holds no permission on is not granted by being listed here.

  • read_onlybool

    Restrict it to pulling.

    What a running workload wants, and the right default for anything that is deploying rather than building. A credential that can push is one that can replace the image a fleet is about to start.

Returns

GetRegistryCredentialResponse.

grpcurl
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"parent": "projects//locations/"
}' \
api.plany.cloud:443 plany.containerregistry.v1.RegistryService/GetRegistryCredential
Example response
{
"registry": "",
"username": "",
"password": "",
"expire_time": "2026-10-04T12:00:00Z",
"granted_access": [
{
"repository_id": "my-repository",
"actions": [
""
]
}
]
}

The GrantedAccess object

GrantedAccess is one repository a credential reaches.

Attributes

  • repository_idstring

    The repository id, as it appears in an image reference.

  • actionsrepeated string

    pull, push, delete — the OCI distribution actions, named as the registry names them rather than as Plany's permissions do, because these are what a client will be refused against.

GrantedAccess
{
"repository_id": "my-repository",
"actions": [
""
]
}

GetRegistryCredentialResponse

  • registrystring

    The registry this is good at, as a client would write it: <region>.plany.cr.

  • usernamestring

    What to pass as the username.

    Ignored by the server — the credential is entirely in the password — but container clients require one to be present, so the platform names one rather than letting everybody invent their own.

  • passwordstring

    The credential. Returned exactly once and recoverable from nowhere.

  • expire_timeTimestamp
  • The repositories this credential actually reaches, and what it may do to each — after intersecting what was asked for with what the caller holds.

    Present because those two differ silently otherwise: a caller who asked for push on four repositories and holds it on three gets a working credential and finds out about the fourth from a 401 in the middle of a deploy.

    Show child attributes
    • repository_idstring

      The repository id, as it appears in an image reference.

    • actionsrepeated string

      pull, push, delete — the OCI distribution actions, named as the registry names them rather than as Plany's permissions do, because these are what a client will be refused against.

Image

Image is one manifest in a repository.

Named by digest, because that is what an image is: tags are labels attached to it that can move, and two tags pointing at one digest are one image with two names.

  • namestringIdentifier

    .../repositories/{repository}/images/{digest}, where the digest is the manifest's, sha256: prefix and all.

  • digeststringOutput only

    The manifest digest, on its own, as a client would write it.

  • tagsrepeated stringOutput only

    Every tag currently pointing at this digest. Often none: an image pushed by digest, or one whose tag has since been moved onto a newer build, is untagged and still perfectly pullable.

  • media_typestringOutput only

    The manifest's own media type — an image manifest, or an index pointing at one manifest per platform.

  • size_bytesint64Output only

    The manifest plus every layer it references.

  • platformsrepeated stringOutput only

    The platforms this image can run on, as os/arch[/variant].

    One entry for an ordinary manifest, several for an index. Worth surfacing because "it pulled and would not start" is almost always this: an image built linux/amd64 on a laptop, pulled onto Graviton.

  • create_timeTimestampOutput only

ListImagesResponse

  • Show child attributes
    • namestringIdentifier

      .../repositories/{repository}/images/{digest}, where the digest is the manifest's, sha256: prefix and all.

    • digeststringOutput only

      The manifest digest, on its own, as a client would write it.

    • tagsrepeated stringOutput only

      Every tag currently pointing at this digest. Often none: an image pushed by digest, or one whose tag has since been moved onto a newer build, is untagged and still perfectly pullable.

    • media_typestringOutput only

      The manifest's own media type — an image manifest, or an index pointing at one manifest per platform.

    • size_bytesint64Output only

      The manifest plus every layer it references.

    • platformsrepeated stringOutput only

      The platforms this image can run on, as os/arch[/variant].

      One entry for an ordinary manifest, several for an index. Worth surfacing because "it pulled and would not start" is almost always this: an image built linux/amd64 on a laptop, pulled onto Graviton.

    • create_timeTimestampOutput only
  • next_page_tokenstring

ListRepositoriesResponse

  • Show child attributes
    • namestringIdentifier

      projects/{project}/locations/{location}/repositories/{repository}.

      The last segment is what appears in an image reference, so it is constrained by the OCI specification rather than by AIP-122 alone: lower case, digits, and ., _ or - between them. So is the project id, for the same reason and with no way to fix it afterwards — see CreateRepositoryRequest.repository_id.

    • uidstringOutput only

      Server-assigned, immutable, unique for the life of the platform.

      Reused rather than the name wherever something has to outlive a rename.

    • display_namestring

      What a person calls it. Free text; not part of any reference.

    • labelsmap<string, string>

      Arbitrary key/value pairs, for the customer's own accounting.

    • stateRepository.StateOutput only
      STATE_ACTIVESTATE_DELETING
    • immutable_tagsbool

      Refuse a push that would move a tag that already exists.

      Off by default, because :latest moving is what most people mean by a tag. On, a tag is a permanent name for one digest — which is what a deployment that has to be reproducible needs, and what makes "the same tag" a fact rather than a hope.

      Changing it does not rewrite history: tags already moved stay where they are, and the guarantee starts now.

    • size_bytesint64Output only

      Total bytes attributable to this repository.

      An attribution rather than a count of unique bytes. Layers are shared, so pushing a common base image counts your reference to it — and your figure never changes because somebody else pushed the same layer.

    • image_countint64Output only

      How many manifests are in it, tagged or not.

    • last_push_timeTimestampOutput only

      When something was last written here, and last read.

      last_pull_time is the one worth having: it is how a customer finds the repository nothing has fetched since March, and it is the only signal that separates storage somebody is paying for on purpose from storage they forgot about.

    • last_pull_timeTimestampOutput only
    • create_timeTimestampOutput only
    • update_timeTimestampOutput only
  • next_page_tokenstring

Repository

Repository is one named collection of images.

It maps to a path under the registry hostname — <project>/<repository> — and to a prefix in the region's object store. A project holds many, and the ordinary shape is one per deployable thing rather than one per project.

  • namestringIdentifier

    projects/{project}/locations/{location}/repositories/{repository}.

    The last segment is what appears in an image reference, so it is constrained by the OCI specification rather than by AIP-122 alone: lower case, digits, and ., _ or - between them. So is the project id, for the same reason and with no way to fix it afterwards — see CreateRepositoryRequest.repository_id.

  • uidstringOutput only

    Server-assigned, immutable, unique for the life of the platform.

    Reused rather than the name wherever something has to outlive a rename.

  • display_namestring

    What a person calls it. Free text; not part of any reference.

  • labelsmap<string, string>

    Arbitrary key/value pairs, for the customer's own accounting.

  • stateRepository.StateOutput only
    STATE_ACTIVESTATE_DELETING
  • immutable_tagsbool

    Refuse a push that would move a tag that already exists.

    Off by default, because :latest moving is what most people mean by a tag. On, a tag is a permanent name for one digest — which is what a deployment that has to be reproducible needs, and what makes "the same tag" a fact rather than a hope.

    Changing it does not rewrite history: tags already moved stay where they are, and the guarantee starts now.

  • size_bytesint64Output only

    Total bytes attributable to this repository.

    An attribution rather than a count of unique bytes. Layers are shared, so pushing a common base image counts your reference to it — and your figure never changes because somebody else pushed the same layer.

  • image_countint64Output only

    How many manifests are in it, tagged or not.

  • last_push_timeTimestampOutput only

    When something was last written here, and last read.

    last_pull_time is the one worth having: it is how a customer finds the repository nothing has fetched since March, and it is the only signal that separates storage somebody is paying for on purpose from storage they forgot about.

  • last_pull_timeTimestampOutput only
  • create_timeTimestampOutput only
  • update_timeTimestampOutput only

Enums

Repository.State

What the platform is doing with this repository.

ValueDescription
STATE_UNSPECIFIED
STATE_ACTIVEAccepting pushes and serving pulls.
STATE_DELETINGThe row is gone from every list and the blobs are being swept. A delete is not instant: the manifests are unreachable immediately, and reclaiming the storage behind them is a separate pass that has to run when nothing is writing. A repository in this state is still billed for what it still occupies, which is the honest answer rather than the flattering one.