---
title: "Replace asset_key_scopes on a CI binding"
method: POST
path: "/ci/v1/bindings/{binding_id}/asset-scopes"
tags: ["CI Federation"]
---

# Replace asset_key_scopes on a CI binding

`POST /ci/v1/bindings/{binding_id}/asset-scopes`

## Request

- Base URL: `https://api.run402.com`
- URL: `https://api.run402.com/ci/v1/bindings/{binding_id}/asset-scopes`
- Auth: API key in header `SIGN-IN-WITH-X`

Mutate `asset_key_scopes` on the binding — gates the `spec.assets` slice for CI sessions. v1.48 introduced per-binding `asset_key_scopes` to lock down CI access to the assets slice, but `POST /ci/v1/bindings` doesn't accept the parameter, so existing bindings default to NULL (closed). This endpoint lets the binding owner (a principal with `ci.manage` on the project's owning org, same model as revoke) replace the scopes after the fact. Idempotent — calling with the same scope list is a no-op. Each scope is either an exact key (`astro/hero.jpg`) or a wildcard prefix (`astro/*`). Wildcards are required for `assets.sync.prune` to cover the prefix. Bare `*` and `**` are rejected. Cap of 64 entries, 256 chars each, no empty strings.

## Path parameters

- `binding_id` string, required

## Headers

- `SIGN-IN-WITH-X` string, required

## Request body

- object
  - `asset_key_scopes` string[], required: Replace-mode list. Each entry is an exact asset key OR a `prefix/*` wildcard. Empty array means CI sessions cannot mutate assets at all.

## Response `200`

Binding updated. Returns the binding row including the new `asset_key_scopes`.

- CiBindingRow: A row from internal.ci_bindings — one per CI binding. Persistent forensic record; revoked rows are kept for audit.
  - `id` string: Binding id, prefixed `bnd_`.
  - `project_id` string
  - `issuer` string: OIDC issuer URL (FK to internal.trusted_oidc_issuers). v1 only: https://token.actions.githubusercontent.com.
  - `subject_match` string
  - `allowed_actions` string[]
  - `allowed_events` string[]: Sorted-deduped on insert. Default `['push','workflow_dispatch']`.
  - `route_scopes` string[]: Sorted-deduped explicit route patterns CI may alter. Empty array means CI cannot ship non-null `spec.routes`.
  - `asset_key_scopes` string[], nullable: Per-binding allowlist for the `spec.assets` slice. NULL means no asset authority (default for bindings created via `POST /ci/v1/bindings`). Each entry is an exact asset key or `prefix/*` wildcard. Mutated via `POST /ci/v1/bindings/{binding_id}/asset-scopes`.
  - `github_repository_id` string, nullable
  - `created_by` string: Lowercase EVM wallet address.
  - `nonce` string
  - `created_sig` object: Parsed delegation: { payload, raw, signer, verified_at }. Stored verbatim for forensic re-verification.
  - `created_at` string, date-time
  - `expires_at` string, date-time, nullable
  - `revoked_at` string, date-time, nullable
  - `last_used_at` string, date-time, nullable
  - `use_count` integer

## Other responses

- `400`: Validation failure: non-array body, non-string entry, empty entry, entry > 256 chars, more than 64 entries, or bare `*` / `**`.
- `403`: SIWE wallet does not own the binding's project.
- `404`: Binding not found.
- `409`: Binding is revoked — cannot mutate `asset_key_scopes`.

---

[API](https://skmtc.dev/run402/apis/run402-api.md) · [All operations](https://skmtc.dev/run402/apis/run402-api/llms.txt) · [OpenAPI document](https://skmtc.dev/run402/apis/run402-api/revisions/893607557f50?raw)
