> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackshift.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Typed metadata and bucket governance

> Create and publish metadata schemas, bind exact revisions to buckets, validate asset fields, and migrate existing delivery into governance.

## Goal

Capture consistent editorial metadata and make publication depend on explicit bucket rules.

## Prerequisites

* Space-wide policy-management permission and an API key with assets:admin for schema/policy management.
* Readable assets and edit\_metadata permission to edit their fields.

## Workflow

<Steps>
  <Step>
    Create a schema draft and define typed fields.
  </Step>

  <Step>
    Publish a schema revision and bind it to a bucket.
  </Step>

  <Step>
    Choose review, channel and AR policies.
  </Step>

  <Step>
    Complete asset metadata, then create and review a publication.
  </Step>
</Steps>

## Create a schema in the dashboard

Open Assets → Manage → Metadata and create a metadata schema. Give it a name, add fields, and configure each field’s label, stable key and type. Mark required editorial fields Required for publication. Mark fields Included in search only when they should contribute to library search.

Save draft does not change the schema used by an existing bucket. Publish the draft to create an immutable published revision. Under Bucket policy, choose a bucket, select the published schema and save the policy. Selecting that schema again explicitly opts the bucket into its latest published revision.

## Field types and validation

* text: a JSON string; maximum length counts Unicode characters. The default maximum is 10,000; a configured maximum must be between 0 and 10,000, where 0 uses the default.
* integer: a JSON number with no fractional part and absolute value at most 9,007,199,254,740,991. decimal: a finite JSON number. Optional minimum/maximum bounds are inclusive. Numeric strings are not numbers.
* boolean: JSON true or false, not the strings "true" or "false".
* date: a YYYY-MM-DD string representing a valid date. It is not a timestamp or locale-formatted date.
* single\_choice: one exact configured option string. multiple\_choice: an array of distinct configured option strings. Unknown or repeated choices are rejected.
* A schema supports at most 100 fields and 12 searchable fields. Keys are unique and match \[a-z]\[a-z0-9\_]\{0,63}; labels are nonempty and at most 120 bytes.
* Choice fields require 1–200 unique, nonblank options, each at most 200 bytes. Required fields must be present and nonempty at publication validation; an empty required multiple-choice array is incomplete.

## Draft, published revision and bucket binding

The schema has a mutable draft revision and a published\_revision. Publishing captures an immutable definition. The bucket records both schema\_id and schema\_revision, so a later schema edit does not silently reinterpret existing content.

Asset metadata, asset/source version, schema and policy revisions are captured by a publication snapshot. If those inputs change, do not assume an older approval authorizes the new content. Refresh the publication from the current asset and repeat review.

## Create and publish a schema through the SDK

```ts theme={null}
const draft = await sdk.assets.dam.createMetadataSchema({
  name: 'Product media',
  fields: [
    { key: 'sku', label: 'SKU', type: 'text', required: true, searchable: true, max_length: 80 },
    { key: 'material', label: 'Material', type: 'single_choice', options: ['wood', 'metal', 'fabric'] },
    { key: 'launch_date', label: 'Launch date', type: 'date' },
  ],
})
const published = await sdk.assets.dam.publishMetadataSchema(draft.id, draft.revision)
const policy = await sdk.assets.dam.bucketGovernance(bucketId)
await sdk.assets.dam.saveBucketGovernance(bucketId, {
  revision: policy.revision, enabled: policy.enabled,
  schema_id: published.id, schema_revision: published.published_revision,
  require_other_reviewer: true, allow_ar: false, channels: ['web'],
})
```

## Edit an asset’s typed metadata

Open the asset inspector’s metadata editor. It loads the fields for the bound schema and indicates whether you can edit. Save actual typed values; a failed validation does not update the record. Partial drafts can be saved, but required publication fields must be complete before the editorial snapshot is accepted.

```ts theme={null}
const editor = await sdk.assets.dam.metadataEditor(assetId)
if (!editor.can_edit) throw new Error('Metadata editing is not permitted')
const result = await sdk.assets.patchMetadata(assetId, editor.revision, {
  ...editor.metadata, sku: 'CHAIR-001', material: 'wood', launch_date: '2026-09-15',
})
// Read the asset again before creating a publication with asset_revision.
```

## Choose governed delivery rules

Governance keeps originals private and delivers selected renditions through reviewed publications. Publication channels are explicit identifiers such as web; a publication must use an allowed channel. Another reviewer required prevents the uploader/editor from approving their own snapshot. Allow AR enables the additional AR authorization path for publications with the required model companions.

Empty buckets can enable governed delivery directly. A nonempty bucket needs the migration flow below. Once enabled, the dashboard does not offer a toggle that silently restores old public-original delivery.

## Migrate a bucket containing existing assets

This changes delivery behavior. Existing public embeds and signed download/playback links can stop working. Plan replacement publication references and notify affected consumers before beginning. This is an explicit customer action, not an automatic migration performed by uploading a new file.

Save the intended schema/channel/review policy with governed delivery off. In Migrate existing assets, select Review migration and then Confirm migration. Each request revokes old access and purges up to 100 assets; select Continue migration until the returned policy is enabled and no longer migrating.

If cache invalidation fails, delivery remains closed. Correct the reported problem and continue the checkpointed migration. Do not make originals public or reset migration state as a recovery shortcut.

```ts theme={null}
let policy = await sdk.assets.dam.bucketGovernance(bucketId)
// Run one batch only after the operator/user has approved this delivery change.
policy = await sdk.assets.dam.migrateBucketGovernance(bucketId, policy.revision)
console.log({ enabled: policy.enabled, migrating: policy.migrating })
// Re-read and explicitly continue subsequent batches until activation completes.
```

## Routes and permission boundaries

* GET/POST /api/v1/assets/metadata-schemas list/create schema drafts; PUT /metadata-schemas/\{id} edits a draft; POST /metadata-schemas/\{id}/publish publishes its current revision. Reads use assets:read; management uses assets:admin plus policy authority.
* GET/PUT /api/v1/assets/buckets/\{bucketID}/governance read/save policy; POST /governance/migrate advances the approved migration. These are administrative policy operations.
* GET /api/v1/assets/\{assetID}/metadata-editor returns fields, metadata, revision, metadata\_revision and can\_edit. PATCH /assets/\{assetID}/metadata sends \{metadata} with If-Match and requires assets:write plus edit\_metadata authority.
* Schema publishing and mutation use the current revision. A 412 revision conflict requires a fresh read and reconciliation, not forced overwriting.

## Go, Python and PHP: create, publish and bind the schema

Use a DAM client scoped to the bucket’s asset space. This procedure creates the schema, publishes its exact draft revision, reads the current bucket policy and binds the published revision. It preserves the existing delivery, reviewer, channel and AR settings; enabling or migrating governed delivery remains the explicit operation described above.

Save the returned policy revision for display, but read it again before subsequent edits. If publishing succeeds and binding fails, keep the published schema ID and retry only the binding step after reconciling the current policy. Repeating the whole function would create another schema.

<CodeGroup>
  ```go Go theme={null}
  func configureMetadata(ctx context.Context, dam *stackshift.AssetDAMClient, bucketID string) (map[string]any, error) {
  	draft, err := dam.CreateMetadataSchema(ctx, map[string]any{
  		"name": "Product media", "fields": []map[string]any{{
  			"key": "sku", "label": "SKU", "type": "text",
  			"required": true, "searchable": true, "max_length": 80,
  		}},
  	})
  	if err != nil {
  		return nil, err
  	}
  	published, err := dam.PublishMetadataSchema(ctx, draft.ID, draft.Revision)
  	if err != nil {
  		return nil, err
  	}
  	policy, err := dam.BucketGovernance(ctx, bucketID)
  	if err != nil {
  		return nil, err
  	}
  	return dam.SaveBucketGovernance(ctx, bucketID, map[string]any{
  		"revision": policy["revision"], "enabled": policy["enabled"],
  		"schema_id": published.ID, "schema_revision": published.PublishedRevision,
  		"require_other_reviewer": policy["require_other_reviewer"],
  		"allow_ar":               policy["allow_ar"], "channels": policy["channels"],
  	})
  }
  ```

  ```python Python theme={null}
  def configure_metadata(dam, bucket_id):
      draft = dam.create_metadata_schema({
          "name": "Product media", "fields": [{
              "key": "sku", "label": "SKU", "type": "text",
              "required": True, "searchable": True, "max_length": 80,
          }],
      })
      published = dam.publish_metadata_schema(draft["id"], draft["revision"])
      policy = dam.bucket_governance(bucket_id)
      return dam.save_bucket_governance(bucket_id, {
          "revision": policy["revision"], "enabled": policy["enabled"],
          "schema_id": published["id"], "schema_revision": published["published_revision"],
          "require_other_reviewer": policy["require_other_reviewer"],
          "allow_ar": policy["allow_ar"], "channels": policy["channels"],
      })
  ```

  ```php PHP theme={null}
  function configureMetadata(\StackShift\AssetDAMClient $dam, string $bucketId): array {
      $draft = $dam->createMetadataSchema([
          'name' => 'Product media', 'fields' => [[
              'key' => 'sku', 'label' => 'SKU', 'type' => 'text',
              'required' => true, 'searchable' => true, 'max_length' => 80,
          ]],
      ]);
      $published = $dam->publishMetadataSchema($draft['id'], $draft['revision']);
      $policy = $dam->bucketGovernance($bucketId);
      return $dam->saveBucketGovernance($bucketId, [
          'revision' => $policy['revision'], 'enabled' => $policy['enabled'],
          'schema_id' => $published['id'], 'schema_revision' => $published['published_revision'],
          'require_other_reviewer' => $policy['require_other_reviewer'],
          'allow_ar' => $policy['allow_ar'], 'channels' => $policy['channels'],
      ]);
  }
  ```
</CodeGroup>

## HTTP operations for this workflow

Management requests use Authorization: Bearer with your server-side Assets credential and X-Asset-Space-ID for the selected space. API-key scopes and space/collection permissions both apply. Successful JSON responses use \{success: true, data: ...}; the SDK methods return the unwrapped data. Paths shown outside /api/v1 use their dedicated short-lived credential.

| Operation                                                   | Authorization | Concurrency           | Success |
| ----------------------------------------------------------- | ------------- | --------------------- | ------- |
| `GET /api/v1/assets/metadata-schemas`                       | assets:read   | —                     | 200     |
| `POST /api/v1/assets/metadata-schemas`                      | assets:admin  | —                     | 201     |
| `PUT /api/v1/assets/metadata-schemas/{schemaID}`            | assets:admin  | Quoted If-Match       | 200     |
| `POST /api/v1/assets/metadata-schemas/{schemaID}/publish`   | assets:admin  | Quoted If-Match       | 200     |
| `GET /api/v1/assets/buckets/{bucketID}/governance`          | assets:admin  | —                     | 200     |
| `PUT /api/v1/assets/buckets/{bucketID}/governance`          | assets:admin  | revision in JSON body | 200     |
| `GET /api/v1/assets/{assetID}/metadata-editor`              | assets:read   | —                     | 200     |
| `POST /api/v1/assets/buckets/{bucketID}/governance/migrate` | assets:admin  | Quoted If-Match       | 200     |

For If-Match, quote the revision number, for example If-Match: "7". On 412 assets.revision\_conflict, reload the relevant resource and reconcile the edit before retrying. Use the revision of the resource being changed: asset, collaborator, schema, publication or gallery. A bucket-policy save instead places its current revision in the JSON body.

## Request and response field reference

Schema create/update accepts name and the complete fields array. Update replaces the draft definition; preserve fields you intend to keep. Publishing returns the schema with published\_revision. List returns \{schemas: \[...]}. The policy is a separate resource with its own revision.

Metadata writes use PATCH /api/v1/assets/\{assetID}/metadata, the selected-space header, quoted asset revision and \{metadata: \{...}}. To avoid removing values from other fields, merge edited values with metadata-editor.metadata before saving. The editor’s metadata\_revision records the metadata snapshot; use its revision field for If-Match.

| Field                         | Meaning and constraints                                                                                                               |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                        | Nonblank schema name, at most 120 bytes.                                                                                              |
| `fields`                      | Up to 100 typed definitions; each supplies key, label, type, required and searchable as needed, plus type-specific options/bounds.    |
| `schema_id / schema_revision` | Set both to a published schema ID and revision; omit both to keep the bucket without a bound schema. Never bind an unpublished draft. |
| `enabled`                     | Whether the bucket uses governed publication delivery. Enabling a populated bucket requires the explicit migration flow.              |
| `require_other_reviewer`      | Require the reviewer to be different from the uploader/editor.                                                                        |
| `channels`                    | 1–20 unique channel keys using the metadata-key syntax; every publication must choose one.                                            |
| `allow_ar`                    | Enable AR authorization for eligible model publications.                                                                              |
| `revision`                    | Current bucket policy revision in a save request; returned policy carries its next revision.                                          |
| `migrating`                   | Read-only migration state. Continue checkpointed migration until false and enabled=true.                                              |

```json Example request body theme={null}
{
  "revision": 7,
  "enabled": false,
  "schema_id": "YOUR_PUBLISHED_SCHEMA_UUID",
  "schema_revision": 2,
  "require_other_reviewer": true,
  "channels": [
    "web"
  ],
  "allow_ar": true
}
```

## Expected result

<Check>
  The bucket uses a specific published schema, asset metadata retains JSON types, and governed delivery requires reviewed references rather than exposing originals.
</Check>

## Common failures

<Warning>
  * Binding an unpublished schema draft.
  * Sending numeric/boolean values as strings.
  * Assuming required metadata makes an incomplete draft impossible to save.
  * Starting governance migration without accounting for existing public embeds.
</Warning>

## Related guides

<CardGroup cols={2}>
  <Card title="Library, spaces, collections and sharing" href="/assets/library-spaces-and-sharing">
    Find and organize media, switch Assets spaces, invite collaborators with explicit permissions, and handle access changes safely.
  </Card>

  <Card title="Publications, review and usage rights" href="/assets/publications-review-and-rights">
    Select immutable renditions, capture metadata and rights, submit editorial review, publish by channel, and withdraw delivery.
  </Card>

  <Card title="AI assistance and asset versioning" href="/assets/ai-dam-and-versioning">
    Use configured asset AI jobs, moderation, transcripts, derived images, collections, saved searches, and branching versions with explicit readiness and spend controls.
  </Card>

  <Card title="Assets SDKs: JavaScript, Go, Python and PHP" href="/assets/sdk-media-workflows">
    Configure space-scoped clients and use native video, governed DAM, model rendering, galleries and CMS capabilities in all four SDKs.
  </Card>
</CardGroup>
