diff --git a/content/get-started/migration/_index.md b/content/get-started/migration/_index.md index d72f59416e..bea8c1ea99 100644 --- a/content/get-started/migration/_index.md +++ b/content/get-started/migration/_index.md @@ -6,11 +6,14 @@ linktitle: "Migration" lead: "" type: "article" date: 2024-02-26T08:48:45+00:00 -lastmod: 2024-05-22T08:48:45+00:00 +lastmod: 2026-07-20T00:00:00+00:00 draft: false images: [] weight: 045 topic: true +crosslinks: +- title: "API v1 to v2 Migration" + url: "/platform/api/api-v2-migration/" banner: { image: "/icon-arrows_blurple.png", title: "Porting Applications to Chainguard", @@ -53,4 +56,4 @@ tutorials: [ }, ] ---- \ No newline at end of file +--- diff --git a/content/platform/api/api-v2-migration.md b/content/platform/api/api-v2-migration.md new file mode 100644 index 0000000000..d9fc45ac09 --- /dev/null +++ b/content/platform/api/api-v2-migration.md @@ -0,0 +1,250 @@ +--- +aliases: +- /chainguard/administration/api-v2-migration/ +title: "Migrating from API v1 to API v2" +linktitle: "API v1 to v2 Migration" +type: "article" +description: "How to migrate a direct integration with the Chainguard API from v1 to the GA API v2." +date: 2026-07-20T00:00:00+00:00 +lastmod: 2026-07-20T00:00:00+00:00 +draft: false +tags: ["Chainguard Console", "Procedural"] +images: [] +toc: true +weight: 040 +--- + +> **Draft status:** This page has several items marked `CONFIRM WITH ENGINEERING`. Do not publish externally until those are resolved. + +Chainguard's API v2 is now Generally Available (GA) across every product domain: Customer Platform (IAM), Containers, Ecosystems, and Integrations. At GA, the "beta" designation is dropped: endpoints move from `/v2beta1/` to `/v2/`. This guide covers what changed from v1, and the steps to migrate an existing direct integration. + +If you only use `chainctl`, the Chainguard Console, or the Terraform provider, you don't need this guide — those clients handle versioning for you, and nothing changes on your end. This guide is for anyone calling the API directly: `curl`, gRPC, or a custom SDK integration. + +v1 continues to work during a transition period, so nothing breaks today. Migrate at your own pace; see [Timeline](#timeline) for what happens next. + +## Prerequisites + +You'll need: + +- A valid Chainguard identity with access to the resources you're calling. +- `chainctl` installed, to mint a token for testing (`chainctl auth token`). + +Set up your environment for the following examples: + +```shell +export TOKEN=$(chainctl auth token) +export API=https://console-api.enforce.dev +# ORG_ID is the UID of your root organization group +export ORG_ID=YOUR_ORG_ID +``` + +## What's not changing + +- **Authentication** — same OIDC token model as v1. +- **Authorization** — same identity-based access control. +- **Scoping** — same `uidp.descendants_of` / `uidp.children_of` hierarchy filters. +- **Host** — same API host (`console-api.enforce.dev` in production). + +If your integration already authenticates against v1 successfully, no credential or auth-flow changes are needed to call v2. + +## Quick reference + +| Operation | v1 pattern | v2 pattern | +| --- | --- | --- | +| Path versioning | Unversioned (`/iam/...`, `/registry/...`, `/vulnerabilities/...`, `/libraries/...`, `/advisory/...`) | Versioned: `/v2/...` for every domain | +| Field naming | Inconsistent (`id`, `created_at`, `group_id`) | Consistent camelCase (`uid`, `createTime`/`updateTime`) | +| CREATE | Parent resource identified in the URL path | Parent identified in the request body | +| GET (single resource) | No dedicated endpoint; filter a List call | Dedicated Get endpoint, fetch by `uid` | +| LIST | Offset/page-number params, full result set, no total count | `page_size`, `page_token`, `skip`, `order_by`, `total_count` | +| UPDATE | PUT, full-resource replacement | PATCH, partial update via a field mask | +| DELETE | Same path pattern, ambiguous failure on blocked deletes | Same path pattern, returns a `PreconditionFailure` detail when blocked | +| Errors | Ambiguous — a missing resource can return an empty HTTP 200 result | Structured: `ErrorInfo`, `BadRequest`, `ResourceInfo`, `PreconditionFailure`, with proper status codes | +| Rate limiting | Not enforced | Enforced | + +### Path prefixes by domain + +| Domain | v1 path prefix | v2 path prefix (GA) | +| --- | --- | --- | +| IAM (Customer Platform: groups, roles, identities, role bindings) | `/iam/` | `/iam/v2/` | +| Registry (Containers) | `/registry/` | `/registry/v2/` | +| Vulnerabilities | `/vulnerabilities/` | `/vulnerabilities/v2/` | +| Libraries (Ecosystems) | `/libraries/` | `/libraries/v2/` | +| Advisory (Integrations) | `/advisory/` | `/advisory/v2/` | + +If you built against the beta endpoints, updating the version segment from `v2beta1` to `v2` is the only path change — the request and response shapes don't change as part of that rename. + +## Migration steps + +### Step 1: Update your endpoint paths + +Add the `v2` version segment for each domain you call, using the [path prefix table](#path-prefixes-by-domain). If you were already on `/v2beta1/`, rename it to `/v2/`. + +### Step 2: Update field references + +v2 standardizes field naming across every service: + +- Resource identifiers are always `uid`, never `id`. +- Timestamps are `createTime` / `updateTime`, not `created_at` / `updated_at`. +- All fields use camelCase. + +Update any code that reads specific field names out of API responses — these differ from v1 even for the same resource. + +### Step 3: Move CREATE calls to parent-in-body + +v1 identifies the parent resource in the URL path (for example, `/groups/{parent}`). v2 moves the parent identifier into the request body instead, alongside the fields of the resource being created. + +**v1 (illustrative):** + +```shell +curl -X POST -H "Authorization: Bearer $TOKEN" \ + "$API/iam/groups/$PARENT_UID" \ + -d '{"name": "my-group"}' +``` + +**v2 (illustrative):** + +```shell +curl -X POST -H "Authorization: Bearer $TOKEN" \ + "$API/iam/v2/groups" \ + -d '{"parent": "'"$PARENT_UID"'", "group": {"name": "my-group"}}' +``` + +> **CONFIRM WITH ENGINEERING:** this request body is illustrative. Get the exact body schema — including the `"group"` wrapper field name — from engineering before publishing. + +Stop building create-call URLs with the parent embedded in the path. Build a request body that includes both the parent identifier and the new resource's fields instead. + +### Step 4: Switch updates to PATCH with a field mask + +v1 uses PUT semantics: changing one field means sending the entire resource back, which forces a fetch-before-write pattern and creates race conditions on concurrent updates. v2 uses PATCH with a field mask, so you specify only the fields you're changing. + +```shell +curl -X PATCH -H "Authorization: Bearer $TOKEN" \ + "$API/iam/v2/groups/$GROUP_UID?updateMask=description" \ + -d '{"description": "updated description"}' +``` + +Testing against the live beta API confirmed this `updateMask` parameter name works. It's presumed unchanged at GA, but worth a final confirmation since none of the GA source docs restate it. + +Replace fetch-modify-write update logic with a PATCH call that sets only the fields you're changing. A wildcard `*` is available if you genuinely want full replacement. + +Repository updates (`UpdateRepo`) are a common case to migrate here. For a worked repo PATCH example, see [Partial updates with FieldMask](/platform/api/api-v2-tutorial/#7-partial-updates-with-fieldmask) in the tutorial. + +### Step 5: Switch pagination to cursor-based + +v1 pagination relies on page numbers or offsets, and loads the full result set into memory. v2 List endpoints add: + +- `page_size` — how many items to return per page. +- `page_token` — the opaque cursor from the previous response's `next_page_token`, used to fetch the next page. Omit it for the first page. +- `skip` — number of items to skip, for jumping directly to a specific page without walking the cursor sequentially. +- `order_by` — server-side ordering, instead of sorting client-side after fetching. +- `total_count` — total matching items, so you don't have to infer it from page math. + +```shell +curl -H "Authorization: Bearer $TOKEN" \ + "$API/iam/v2/groups?uidp.descendants_of=$ORG_ID&page_size=3&order_by=name" | jq . +``` + +Replace any page-number or offset logic with a loop that follows `page_token` until the response stops returning one. Use `skip` instead if you need to jump to an arbitrary page. Adopt `order_by` and drop any client-side sort you were doing to compensate. + +### Step 6: Use dedicated Get endpoints + +v1 typically requires filtering a List call to fetch a single resource. v2 adds a dedicated Get endpoint per resource, fetched by `uid` directly. + +```shell +curl -H "Authorization: Bearer $TOKEN" \ + "$API/iam/v2/groups/$GROUP_UID" +``` + +Replace any "list and filter to one item" pattern with the dedicated Get endpoint for that resource. + +### Step 7: Update your error handling + +In v1, an empty or ambiguous result can mean several different things — no matches, no permission, or a missing parent resource — with no way for a client to tell them apart. For example, requesting a resource that doesn't exist in v1 returns an empty result with HTTP 200. + +v2 returns proper status codes (for example, `NOT_FOUND` / HTTP 404) with structured error details, following the same error model used across Google Cloud APIs: + +- **ErrorInfo** — a machine-readable reason code and error domain. +- **BadRequest** — per-field validation errors. +- **ResourceInfo** — which specific resource type and name wasn't found. +- **PreconditionFailure** — why a request was blocked (for example, a delete blocked by a dependent resource). + +If your integration currently treats an empty v1 result as ambiguous, or works around that ambiguity with extra calls, update it to branch on the v2 status code and structured error details instead. Re-test your error-handling paths, not just the happy path. + +### Step 8: Review rate limits + +Rate limits were not enforced during beta. They are enforced starting at GA. + +> **CONFIRM WITH ENGINEERING:** specific rate limits and response headers (for example, remaining-quota headers). Add them here once confirmed, and link out to a rate-limits reference page if one exists. + +Review your call patterns — polling frequency, batch sizes — against the confirmed limits before cutting production traffic over. + +### Step 9: Update gRPC and SDK clients + +All v2 endpoints are also available directly via gRPC, at the same host. Proto definitions are at `chainguard.dev/sdk/proto/chainguard/platform/`. If you call the API via generated gRPC clients, regenerate them against the GA `v2` proto packages, renamed from `v2beta1`. + +> **CONFIRM WITH ENGINEERING:** whether the Go SDK client library actually moves from `chainguard.dev/sdk/proto/platform/clients/v2beta1` to an equivalent `v2` path at GA, or keeps the `v2beta1` package name internally even after the REST paths rename. + +## Full example: listing and deleting groups + +**Before (v1):** + +```shell +curl -H "Authorization: Bearer $TOKEN" \ + "$API/iam/groups?uidp.descendants_of=$ORG_ID" +``` + +**After (v2):** + +```shell +curl -H "Authorization: Bearer $TOKEN" \ + "$API/iam/v2/groups?uidp.descendants_of=$ORG_ID&page_size=3&order_by=name" | jq . +``` + +Delete calls follow the same path-prefix change, with no other behavior change: + +```shell +curl -X DELETE -H "Authorization: Bearer $TOKEN" \ + "$API/iam/v2/roleBindings/$BINDING_UID" +curl -X DELETE -H "Authorization: Bearer $TOKEN" \ + "$API/iam/v2/identities/$IDENTITY_UID" +curl -X DELETE -H "Authorization: Bearer $TOKEN" \ + "$API/iam/v2/groups/$GROUP_UID" +``` + +Each `DELETE` returns an empty response body on success, same as v1. A delete blocked by a dependent resource now returns a `PreconditionFailure` error detail explaining why, instead of an ambiguous failure. + +## Timeline + +| Phase | When | What it means for you | +| --- | --- | --- | +| Parallel availability | GA onward | Both v1 and v2 serve traffic. v1 responses include deprecation headers noting the eventual sunset date. Migrate at your own pace. | +| Warning escalation | `CONFIRM WITH ENGINEERING` | v1 responses add an additional warning header. If you have meaningful v1 traffic, your Chainguard account team may reach out directly. | +| Soft shutdown | `CONFIRM WITH ENGINEERING` | v1 requests return an error with migration guidance, for a short grace period. | +| Hard removal | `CONFIRM WITH ENGINEERING` | v1 is fully removed. Requests to v1 endpoints fail. | + +> **CONFIRM WITH ENGINEERING:** every date in this timeline is a placeholder. Don't ship this table as-is. + +Don't wait for a hard deadline to start testing v2 — both versions are available today, and migration can happen incrementally. + +## Troubleshooting + +**Will my existing integration stop working today?** +No. v1 keeps working during the parallel availability period. Nothing changes for existing integrations until the sunset date, which will be communicated well in advance. + +**Do I need new credentials for v2?** +No. Authentication and authorization are unchanged — the same tokens and identities that work with v1 work with v2. + +**Why did an API call that used to return an empty list now return an error?** +This is expected, and it's an improvement. In v1, a missing resource and an empty result look the same, so there was no way to tell "nothing matched" apart from "that resource doesn't exist" or "you don't have permission." v2 returns a specific, structured error in those cases instead. + +**Why did my create call stop working?** +Check whether you're building the request URL with the parent resource in the path — that's the v1 pattern. In v2, the parent goes in the request body instead. See [Step 3](#step-3-move-create-calls-to-parent-in-body). + +**Where do I send a customer who asks about migrating?** +This guide. If you're on the account or support team, reach out to Engineering for anything marked `CONFIRM WITH ENGINEERING` in this guide rather than guessing at specifics. + +## Next steps + +- [Chainguard API v2 Tutorial](/platform/api/api-v2-tutorial/) — worked examples of the v2 endpoints you'll be migrating to. +- [Chainguard API v2 Specification](/platform/api/spec-api-v2/) — full OpenAPI reference. +- Reach out to your Chainguard account team or [Chainguard Support](https://support.chainguard.dev/) if you run into issues migrating. diff --git a/content/platform/api/api-v2-tutorial.md b/content/platform/api/api-v2-tutorial.md index a0c0b97c76..dbc58fb5ce 100644 --- a/content/platform/api/api-v2-tutorial.md +++ b/content/platform/api/api-v2-tutorial.md @@ -6,7 +6,7 @@ linktitle: "API v2 Tutorial" type: "article" description: "Tutorial with examples showing how you can use the Chainguard API v2." date: 2026-03-30T08:49:31+00:00 -lastmod: 2026-04-02T00:00:01+01:00 +lastmod: 2026-07-20T00:00:00+00:00 draft: false tags: ["Chainguard Console", "Procedural"] images: [] @@ -14,11 +14,11 @@ toc: true weight: 030 --- -{{< beta feature="The Chainguard API v2" >}} +> **Draft status:** This page has several items marked `CONFIRM WITH ENGINEERING`. Do not publish externally until those are resolved. -The v2 API introduces cursor-based pagination, server-side ordering, consistent resource patterns, and structured error responses across all endpoints. +The v2 API is now Generally Available (GA) and introduces cursor-based pagination, server-side ordering, consistent resource patterns, and structured error responses across all endpoints. -This guide walks through the v2 API using real `curl` commands. +This guide walks through the v2 API using real `curl` commands. If you're migrating an existing v1 or beta (`v2beta1`) integration, see [Migrating from API v1 to API v2](/platform/api/api-v2-migration/) instead. > **Note:** The example output in this guide was captured from a development environment. Your organization's resource names, UIDs, timestamps, and counts will differ. The response structure and field names are the same across all environments. @@ -36,7 +36,7 @@ This guide walks through the v2 API using real `curl` commands. - **Structured errors** with typed detail payloads (AIP-193) - **Consistent resource patterns** — every resource has `uid`, `createTime`, `updateTime` - **Hydrated references** — role binding responses include full identity, group, and role objects -- **FieldMask updates** — partial updates via `update_mask` instead of sending the full resource +- **FieldMask updates** — partial updates via `updateMask` instead of sending the full resource ## Available endpoints @@ -45,8 +45,12 @@ This guide walks through the v2 API using real `curl` commands. | **IAM** | Groups, Identities, Roles, RoleBindings, IdentityProviders, AccountAssociations, GroupInvites | List, Get, Create, Update, Delete | | **Registry** | Repos, Tags | List, Get | | **Vulnerabilities** | Advisories | List, Get | +| **Ecosystems (Libraries)** | — | — | +| **Integrations (Advisory)** | — | — | -All endpoints live under `/iam/v2beta1/`, `/registry/v2beta1/`, or `/vulnerabilities/v2beta1/`. +All endpoints live under a versioned path per domain: `/iam/v2/`, `/registry/v2/`, `/vulnerabilities/v2/`, `/libraries/v2/`, or `/advisory/v2/`. + +> **CONFIRM WITH ENGINEERING:** Ecosystems and Integrations are new domains at GA (beta only covered IAM, Registry, and Vulnerabilities). Get the specific resource names and supported operations for these two domains before publishing — the worked examples in this guide only cover the three domains available during beta. ## Prerequisites @@ -59,16 +63,18 @@ export API=https://console-api.enforce.dev export ORG_ID=YOUR_ORG_ID ``` -All examples below use `$TOKEN`, `$API`, and `$ORG_ID` for brevity. +The following examples use `$TOKEN`, `$API`, and `$ORG_ID` for brevity. -## Beta notes +## Operational notes Keep the following in mind as you work through this guide. - **Page tokens expire after 3 days** ([AIP-158](https://google.aip.dev/158)). If a token expires, the query restarts from the beginning — no error is returned. -- **Rate limits** are not enforced during beta. They will be introduced at GA. +- **Rate limits** are enforced starting at GA. - **gRPC** — all endpoints are also available via gRPC at the same host. Proto definitions are at `chainguard.dev/sdk/proto/chainguard/platform/`. +> **CONFIRM WITH ENGINEERING:** specific rate limits and response headers, and whether the Go SDK client library moves from `chainguard.dev/sdk/proto/platform/clients/v2beta1` to a `v2` path at GA. Don't publish this section externally until both are confirmed. + --- ## 1. Your first v2 request @@ -77,7 +83,7 @@ List the first 3 groups in your organization: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ - "$API/iam/v2beta1/groups?uidp.descendants_of=$ORG_ID&page_size=3&order_by=name" | jq . + "$API/iam/v2/groups?uidp.descendants_of=$ORG_ID&page_size=3&order_by=name" | jq . ``` ```json @@ -130,7 +136,7 @@ New in v2: fetch a resource directly by UID. In v1, this required a List call wi ```shell curl -s -H "Authorization: Bearer $TOKEN" \ - "$API/iam/v2beta1/groups/$GROUP_UID" | jq '{uid, name, description}' + "$API/iam/v2/groups/$GROUP_UID" | jq '{uid, name, description}' ``` ```json @@ -149,7 +155,7 @@ Find a specific group without knowing its UID: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ - "$API/iam/v2beta1/groups?uidp.descendants_of=$ORG_ID&name=platform" \ + "$API/iam/v2/groups?uidp.descendants_of=$ORG_ID&name=platform" \ | jq '[.groups[] | {uid, name, description}]' ``` @@ -176,8 +182,8 @@ A real workflow: create an org folder, add an identity, and bind a role. ```shell curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ - "$API/iam/v2beta1/groups/$ORG_ID" \ - -d '{"name": "backend-team", "description": "Backend engineering team"}' | jq . + "$API/iam/v2/groups" \ + -d '{"parent": "'"$ORG_ID"'", "group": {"name": "backend-team", "description": "Backend engineering team"}}' | jq . ``` ```json @@ -192,7 +198,9 @@ curl -s -X POST -H "Authorization: Bearer $TOKEN" \ } ``` -> **Note:** The parent group goes in the URL path. The request body contains only the resource fields. +> **Note:** The parent group goes in the request body, not the URL path — a change from beta. See [Migrating from API v1 to API v2](/platform/api/api-v2-migration/#step-3-move-create-calls-to-parent-in-body) for details. +> +> **CONFIRM WITH ENGINEERING:** this request body shape, including the `"group"` wrapper field name, is illustrative. Confirm the exact schema before publishing. ### Create an identity @@ -202,17 +210,22 @@ export GROUP_UID=YOUR_GROUP_UID curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ - "$API/iam/v2beta1/identities/$GROUP_UID" \ + "$API/iam/v2/identities" \ -d '{ - "name": "ci-bot", - "description": "CI/CD pipeline identity", - "claimMatch": { - "issuer": "https://token.actions.githubusercontent.com", - "subject": "repo:my-org/my-repo:ref:refs/heads/main" + "parent": "'"$GROUP_UID"'", + "identity": { + "name": "ci-bot", + "description": "CI/CD pipeline identity", + "claimMatch": { + "issuer": "https://token.actions.githubusercontent.com", + "subject": "repo:my-org@123456/my-repo@654321:ref:refs/heads/main" + } } }' | jq . ``` +> **CONFIRM WITH ENGINEERING:** this request body shape, including the `"identity"` wrapper field name, is illustrative. Confirm the exact schema before publishing. + ```json { "uid": "d9e2f1a0.../fb139588d99c8efe/f462d354ca32ca9f", @@ -223,11 +236,15 @@ curl -s -X POST -H "Authorization: Bearer $TOKEN" \ "updateTime": "2026-03-27T13:55:00.785Z", "claimMatch": { "issuer": "https://token.actions.githubusercontent.com", - "subject": "repo:my-org/my-repo:ref:refs/heads/main" + "subject": "repo:my-org@123456/my-repo@654321:ref:refs/heads/main" } } ``` +{{< note >}} +The `subject` shown here uses GitHub's immutable format, which embeds the numeric owner ID (`123456`) and repository ID (`654321`). Match the exact subject your repository's token carries. For how to find these IDs and when the format applies, see [Create an Assumable Identity for a GitHub Actions Workflow](/platform/administration/assumable-ids/identity-examples/github-identity/#finding-your-repositorys-numeric-identifiers). +{{< /note >}} + Note the identity `uid` in the response — you will use it in the next step when binding a role. ### Bind a role @@ -236,7 +253,7 @@ First, find the viewer role: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ - "$API/iam/v2beta1/roles" | jq '.roles[] | select(.name == "viewer") | {uid, name, description}' + "$API/iam/v2/roles" | jq '.roles[] | select(.name == "viewer") | {uid, name, description}' ``` ```json @@ -257,10 +274,12 @@ export IDENTITY_UID=YOUR_IDENTITY_UID curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ - "$API/iam/v2beta1/roleBindings/$GROUP_UID" \ - -d "{\"identityUid\": \"$IDENTITY_UID\", \"roleUid\": \"$ROLE_UID\"}" | jq . + "$API/iam/v2/roleBindings" \ + -d "{\"parent\": \"$GROUP_UID\", \"roleBinding\": {\"identityUid\": \"$IDENTITY_UID\", \"roleUid\": \"$ROLE_UID\"}}" | jq . ``` +> **CONFIRM WITH ENGINEERING:** this request body shape, including the `"roleBinding"` wrapper field name, is illustrative. Confirm the exact schema before publishing. + ```json { "uid": "d9e2f1a0.../fb139588.../9b822036a7075d75", @@ -268,7 +287,7 @@ curl -s -X POST -H "Authorization: Bearer $TOKEN" \ "uid": "d9e2f1a0.../fb139588.../f462d354ca32ca9f", "name": "ci-bot", "description": "CI/CD pipeline identity", - "subject": "repo:my-org/my-repo:ref:refs/heads/main", + "subject": "repo:my-org@123456/my-repo@654321:ref:refs/heads/main", "issuer": "https://token.actions.githubusercontent.com" }, "group": { @@ -297,7 +316,7 @@ Every List endpoint supports cursor-based pagination with consistent parameters. ```shell curl -s -H "Authorization: Bearer $TOKEN" \ - "$API/iam/v2beta1/groups?uidp.descendants_of=$ORG_ID&page_size=5" \ + "$API/iam/v2/groups?uidp.descendants_of=$ORG_ID&page_size=5" \ | jq '{totalCount, groups: [.groups[].name], nextPageToken: .nextPageToken[:20]}' ``` @@ -313,7 +332,7 @@ Follow the cursor for the next page: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ - "$API/iam/v2beta1/groups?uidp.descendants_of=$ORG_ID&page_size=5&page_token=CqQBV3lK..." \ + "$API/iam/v2/groups?uidp.descendants_of=$ORG_ID&page_size=5&page_token=CqQBV3lK..." \ | jq '{groups: [.groups[].name]}' ``` @@ -331,7 +350,7 @@ Sort by name: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ - "$API/iam/v2beta1/groups?uidp.descendants_of=$ORG_ID&page_size=5&order_by=name" \ + "$API/iam/v2/groups?uidp.descendants_of=$ORG_ID&page_size=5&order_by=name" \ | jq '[.groups[].name]' ``` @@ -343,7 +362,7 @@ Reverse the order: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ - "$API/iam/v2beta1/groups?uidp.descendants_of=$ORG_ID&page_size=5&order_by=name%20desc" \ + "$API/iam/v2/groups?uidp.descendants_of=$ORG_ID&page_size=5&order_by=name%20desc" \ | jq '[.groups[].name]' ``` @@ -355,7 +374,7 @@ Sort by creation time (newest first): ```shell curl -s -H "Authorization: Bearer $TOKEN" \ - "$API/iam/v2beta1/groups?uidp.descendants_of=$ORG_ID&page_size=5&order_by=created_at%20desc" \ + "$API/iam/v2/groups?uidp.descendants_of=$ORG_ID&page_size=5&order_by=created_at%20desc" \ | jq '[.groups[] | {name, createTime}]' ``` @@ -377,7 +396,7 @@ Jump directly to page 3 (skip the first 10 results): ```shell curl -s -H "Authorization: Bearer $TOKEN" \ - "$API/iam/v2beta1/groups?uidp.descendants_of=$ORG_ID&page_size=5&order_by=name&skip=10" \ + "$API/iam/v2/groups?uidp.descendants_of=$ORG_ID&page_size=5&order_by=name&skip=10" \ | jq '{skipped: .skipped, groups: [.groups[].name]}' ``` @@ -396,18 +415,22 @@ The `skipped` field in the response confirms how many results were skipped, usef | ----------- | ------------- | | `page_size` | Number of results per page (default 50, max 200) | | `page_token` | Opaque cursor from previous response's `nextPageToken` | -| `order_by` | Sort field and direction, e.g. `name`, `created_at desc` | +| `order_by` | Sort field and direction, for example `name` or `created_at desc` | | `skip` | Number of results to skip (for random-access / UI page jumping) | --- ## 4. Querying the registry +Registry endpoints follow the same List, Get, and pagination patterns as the IAM examples earlier, applied to repos and tags. If your integration reads image metadata — listing repos, walking tags, or checking end-of-life status — these are the endpoints you call most. + +### List repos + List repos scoped to your organization: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ - "$API/registry/v2beta1/repos?uidp.descendants_of=$ORG_ID&page_size=3" \ + "$API/registry/v2/repos?uidp.descendants_of=$ORG_ID&page_size=3" \ | jq '[.repos[] | {uid, name, createTime}]' ``` @@ -419,11 +442,91 @@ curl -s -H "Authorization: Bearer $TOKEN" \ ] ``` -Same pagination and ordering parameters work on all List endpoints. +The same pagination and ordering parameters work on every List endpoint. + +### Get a single repo + +Fetch one repo directly by UID, the same way you fetch a group: + +```shell +# REPO_UID is a uid value from the List repos response above +export REPO_UID=YOUR_REPO_UID + +curl -s -H "Authorization: Bearer $TOKEN" \ + "$API/registry/v2/repos/$REPO_UID" | jq '{uid, name, createTime}' +``` + +```json +{ + "uid": "d9e2f1a0.../06626efd8c6b3fb7", + "name": "nginx", + "createTime": "2026-01-28T12:54:21.189Z" +} +``` + +### List tags in a repo + +Scope a tag listing to a single repo with `uidp.children_of`. Each tag carries its `digest` and a `deprecated` flag: + +```shell +curl -s -H "Authorization: Bearer $TOKEN" \ + "$API/registry/v2/tags?uidp.children_of=$REPO_UID&page_size=3" \ + | jq '[.tags[] | {name, digest, deprecated, updateTime}]' +``` + +```json +[ + {"name": "latest", "digest": "sha256:6b3f...", "deprecated": false, "updateTime": "2026-07-14T09:12:44.501Z"}, + {"name": "1.27", "digest": "sha256:8c1a...", "deprecated": false, "updateTime": "2026-07-14T09:12:44.502Z"}, + {"name": "1.26", "digest": "sha256:a90d...", "deprecated": true, "updateTime": "2026-05-02T18:30:10.114Z"} +] +``` + +### Check for deprecated tags + +In v1, a dedicated `ListEolTags` call surfaced end-of-life tags. v2beta1 has no separate end-of-life endpoint or server-side filter. Instead, each tag carries a `deprecated` boolean, which you filter on client-side: + +```shell +curl -s -H "Authorization: Bearer $TOKEN" \ + "$API/registry/v2/tags?uidp.children_of=$REPO_UID&page_size=200" \ + | jq '[.tags[] | select(.deprecated) | .name]' +``` + +> **CONFIRM WITH ENGINEERING:** v2beta1 exposes tag deprecation only as a per-tag `deprecated` field, with no server-side end-of-life filter. Confirm whether GA adds a dedicated filter or endpoint, since v1's `ListEolTags` was a common direct-API call. --- -## 5. Structured errors +## 5. Querying vulnerabilities + +The Vulnerabilities domain exposes advisory data. In v2beta1 it covers advisories with List and Get. + +### List advisories + +Advisories are scoped and paginated like every other List endpoint, with extra filters such as `artifactNames` and `advisoryIds`: + +```shell +curl -s -H "Authorization: Bearer $TOKEN" \ + "$API/vulnerabilities/v2/advisories?uidp.descendants_of=$ORG_ID&page_size=3" \ + | jq '[.advisories[] | {uid, advisoryId, artifactName, updateTime}]' +``` + +```json +[ + {"uid": "d9e2f1a0.../3b1c", "advisoryId": "CGA-abcd-1234-wxyz", "artifactName": "nginx", "updateTime": "2026-07-18T21:04:11.220Z"}, + {"uid": "d9e2f1a0.../7f2a", "advisoryId": "CGA-efgh-5678-stuv", "artifactName": "python", "updateTime": "2026-07-18T21:04:11.221Z"}, + {"uid": "d9e2f1a0.../a1b2", "advisoryId": "CGA-ijkl-9012-mnop", "artifactName": "openssl", "updateTime": "2026-07-18T21:04:11.222Z"} +] +``` + +Fetch a single advisory by UID at `/vulnerabilities/v2/advisories/{uid}`. + +### Vulnerability reports + +> **CONFIRM WITH ENGINEERING:** the v1 `GetVulnReport` and `ListVulnCountReports` calls — used heavily by direct HTTP integrations — have no equivalent in the v2beta1 spec, which exposes only advisories. Confirm whether GA adds vulnerability-report endpoints, or whether advisory data is meant to replace them. This is a real migration gap for the CI/CD audience that reads scan results programmatically. + +--- + +## 6. Structured errors API v2 returns structured error responses with machine-parseable codes and details. @@ -432,8 +535,8 @@ API v2 returns structured error responses with machine-parseable codes and detai ```shell curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ - "$API/iam/v2beta1/groups/$ORG_ID" \ - -d '{}' | jq . + "$API/iam/v2/groups" \ + -d '{"parent": "'"$ORG_ID"'", "group": {}}' | jq . ``` ```json @@ -492,14 +595,14 @@ Error responses follow [Google AIP-193](https://google.aip.dev/193) with typed d --- -## 6. Partial updates with FieldMask +## 7. Partial updates with FieldMask Update specific fields without sending the full resource. Only the fields listed in `updateMask` are changed: ```shell curl -s -X PATCH -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ - "$API/iam/v2beta1/groups/$GROUP_UID" \ + "$API/iam/v2/groups/$GROUP_UID" \ -d '{ "description": "Updated description — only this field changes" }' | jq '{uid, name, description}' @@ -520,7 +623,7 @@ To be explicit about which fields to update, pass `updateMask`: ```shell curl -s -X PATCH -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ - "$API/iam/v2beta1/groups/$GROUP_UID?updateMask=description" \ + "$API/iam/v2/groups/$GROUP_UID?updateMask=description" \ -d '{ "description": "Only this field is updated", "name": "this-is-ignored" @@ -537,16 +640,32 @@ curl -s -X PATCH -H "Authorization: Bearer $TOKEN" \ The `name` in the body is ignored because `updateMask` only includes `description`. +### Update a repo + +The same PATCH-with-field-mask pattern applies to every updatable resource. Repository updates are a common case to migrate from v1, where changing one field meant sending the full resource: + +```shell +curl -s -X PATCH -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + "$API/registry/v2/repos/$REPO_UID?updateMask=description" \ + -d '{"description": "Updated repo description"}' | jq '{uid, name, description}' +``` + +```json +{ + "uid": "d9e2f1a0.../06626efd8c6b3fb7", + "name": "nginx", + "description": "Updated repo description" +} +``` + +> **Note:** The repo update path (`/registry/v2/repos/{repo.uid}`) and request body are confirmed against the published v2beta1 spec, where `name` and `description` are writable. As with the group example, `updateMask` is accepted by the live API but isn't restated in the spec, so reconfirm it holds at GA. + --- ## Migration from v1 -v2 is additive — v1 endpoints remain available. You can migrate at your own pace: - -- Replace `/iam/v1/` with `/iam/v2beta1/` in your API calls -- Update field names: `id` → `uid`, `createdAt` → `createTime`, `updatedAt` → `updateTime` -- Add pagination handling for List endpoints (or set `page_size` high for small result sets) -- v1 will have a deprecation window after v2 reaches GA +v2 is additive — v1 endpoints remain available during a transition period, so you can migrate at your own pace. For the full field-by-field mapping and migration timeline, see [Migrating from API v1 to API v2](/platform/api/api-v2-migration/). --- @@ -557,9 +676,9 @@ Delete resources you created during this walkthrough: ```shell # Delete in reverse order: role binding, identity, group # BINDING_UID is the uid value returned in the Bind a role response above -curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$API/iam/v2beta1/roleBindings/$BINDING_UID" -curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$API/iam/v2beta1/identities/$IDENTITY_UID" -curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$API/iam/v2beta1/groups/$GROUP_UID" +curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$API/iam/v2/roleBindings/$BINDING_UID" +curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$API/iam/v2/identities/$IDENTITY_UID" +curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$API/iam/v2/groups/$GROUP_UID" ``` Each DELETE returns an empty response body on success.