> ## 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.

# Publications, review and usage rights

> Select immutable renditions, capture metadata and rights, submit editorial review, publish by channel, and withdraw delivery.

## Goal

Publish the exact media and metadata that were approved while keeping originals private.

## Prerequisites

* A governed bucket, clean source version, complete required metadata and ready renditions.
* Explicit edit, review and publish permissions for the people performing those steps.

## Workflow

<Steps>
  <Step>
    Create a publication draft from the current asset and ready rendition references.
  </Step>

  <Step>
    Set its channel, rights owner, attribution and optional UTC rights window.
  </Step>

  <Step>
    Submit the snapshot, review it, and publish only after approval.
  </Step>

  <Step>
    Resolve fresh delivery credentials on demand; withdraw when use must stop.
  </Step>
</Steps>

## What a publication contains

An asset identifies the library item. A version identifies specific source bytes. A rendition identifies a derived output. A publication pins those identities together with metadata, schema/policy revisions, caption references, channel and usage rights. A public URL is a temporary delivery result, not the publication’s identity.

A new source upload or metadata edit does not silently update a reviewed snapshot. Native video validation/review and publication editorial review are separate gates: a playable video is not automatically approved for every channel.

## Create the draft in the dashboard

Open the asset’s publication workflow and select New publication draft. Choose a ready main rendition; originals are never selected automatically. Add approved caption tracks and, for models, a supplied poster and optional validated USDZ companion. A rendered spin must include its complete 24-frame set.

Enter Channel, Rights owner and Attribution. Rights start and Rights expiry are entered in UTC. Save draft captures the current source and metadata. If no rendition is available, finish processing and refresh instead of pasting an arbitrary delivery URL.

## Review and publish

* draft → Submit for review → in\_review. Editing a draft captures current inputs; it does not preserve approval of superseded content.
* in\_review → Approve snapshot → approved, or Request changes → draft. A change request needs a nonblank review note.
* approved → Publish to the selected channel → published. Approval alone does not make the publication deliverable.
* published → Withdraw publication → Confirm withdrawal → withdrawn. Subsequent delivery checks reject this publication, including unexpired credentials.
* The reviewer sees source, metadata/schema/policy revisions, rights and the actual preview. If the bucket requires another reviewer, the uploader/editor cannot self-approve.

## Permission and revision requirements

The API key needs assets:write for draft edits and submit, assets:admin for approve/request-changes, and assets:publish for publish/withdraw. The acting account must also have the corresponding space/collection actions. Read/resolve requires assets:read and current read authority.

Send If-Match with the publication revision for changes and transitions; the SDK does this for you. Use the returned revision after each successful transition. If another editor or a source/policy change invalidates the snapshot, reload and reconcile the draft rather than blindly replaying approval.

## Create a snapshot from returned references

Use publicationRenditions to obtain valid, immutable references. Do not construct rendition IDs, output checksums or render\_set\_id values yourself. The example assumes the user selected the primary and companion references from this response.

```ts theme={null}
const asset = await sdk.assets.get(assetId)
const { renditions } = await sdk.assets.dam.publicationRenditions(assetId)
const primary = renditions.find(ref => ref.rendition_id === selectedRenditionId)
if (!primary) throw new Error('Selected rendition is no longer available')
const draft = await sdk.assets.dam.createPublication({
  asset_id: asset.id, asset_revision: asset.revision, channel: 'web',
  renditions: [primary, ...selectedCompanionReferences],
  rights: { owner: 'Example Studio', attribution: 'Photography: Example Studio' },
})
const submitted = await sdk.assets.dam.transitionPublication(draft.id, draft.revision, 'submit')
// Separate authorized review action after inspecting the exact snapshot:
const approved = await reviewerSdk.assets.dam.transitionPublication(submitted.id, submitted.revision, 'approve')
// Separate authorized publishing action:
const live = await publisherSdk.assets.dam.transitionPublication(approved.id, approved.revision, 'publish')
```

## Rights fields and delivery window

rights.owner is required and at most 240 bytes; attribution is at most 2,000 bytes. Optional starts\_at and expires\_at are timestamps; use ISO-8601 UTC values in API requests. When both exist, expiry must be later than start. Review notes are limited to 2,000 bytes.

A future rights start or expired rights window prevents ordinary delivery even if status is published. Delivery credentials last at most five minutes and can expire earlier with the rights window. Rights metadata is your authorization assertion; entering an owner name does not obtain a license.

## Resolve a publication through your backend

Your application stores the allowed publication ID with its product/post. Before resolving, authorize the viewer and verify the publication belongs to that saved product/post. Do not expose a server account credential through an unrestricted resolve-any-ID endpoint.

GET /api/v1/assets/publications/\{id}/resolve returns publication\_id, expires\_at, attribution, ar\_allowed and renditions containing immutable reference objects and temporary URLs. HLS can include mp4\_url/poster\_url. Preserve the response’s intended media grouping and captions.

```ts theme={null}
// After checking the viewer and the product's saved publication reference:
const delivery = await sdk.assets.dam.resolvePublication(savedPublicationId)
return Response.json(delivery, {
  headers: { 'Cache-Control': 'private, no-store' },
})
```

## Preview, replace and withdraw

The authorized preview endpoint is /publications/\{id}/preview; the SDK uses resolvePublication(id, true). It exists for editorial inspection and must not be used as public delivery to bypass publication or rights checks.

When replacing media, create or refresh a draft from current source/metadata, review it and update the consuming product/gallery to the approved publication revision. A gallery’s saved references do not automatically follow asset edits.

Withdrawal, quarantine, asset deletion, policy changes or access revocation can invalidate subsequent delivery. Buffered/downloaded bytes cannot be recalled. Consumers must display an unavailable state or a separately authorized fallback instead of retrying the original public upload URL.

## Troubleshoot a blocked publication

* No ready renditions: inspect source scanning and processing. Proof renders are preview-only and cannot be published.
* Validation failure: complete required metadata, use the allowed channel, inspect the rights window and include the required model poster/valid companions.
* Revision conflict or stale approval: read current asset/publication/policy, compare changes and resubmit the corrected snapshot for review.
* Review denied: check assets:admin, review authority and the different-reviewer rule.
* Published but unavailable: check current rights, source/quarantine state, permissions, selected publication revision and any gallery withdrawal. Do not bypass the resolver.

## Go, Python and PHP: create the editorial snapshot and submit it

Read publication-renditions for the primary asset and each selected companion. Pass the selected immutable reference objects, unchanged, as references below. Exactly one reference must have role=media and belong to the primary asset’s current version. Use owner and attribution values that describe your actual license; the example strings are not a license grant.

The metadata editor supplies the current asset revision in the selected space. A concurrent source or metadata update causes a revision failure instead of creating a snapshot from stale data. These functions stop at in\_review. An authorized reviewer inspects the snapshot and calls the transition method with approve; the publisher then uses the returned revision with publish. Never approve automatically as part of upload.

Persist the draft ID as soon as creation succeeds. If submit fails, fix the existing draft or retry its transition with a freshly read revision; do not create a duplicate publication. List publications with asset\_id to recover saved drafts.

<CodeGroup>
  ```go Go theme={null}
  func submitPublication(ctx context.Context, dam *stackshift.AssetDAMClient, assetID string, references []map[string]any) (*stackshift.AssetPublication, error) {
  	editor, err := dam.MetadataEditor(ctx, assetID)
  	if err != nil {
  		return nil, err
  	}
  	draft, err := dam.CreatePublication(ctx, map[string]any{
  		"asset_id": assetID, "asset_revision": editor["revision"], "channel": "web",
  		"renditions": references,
  		"rights":     map[string]any{"owner": "Example Studio", "attribution": "Photography: Example Studio"},
  	})
  	if err != nil {
  		return nil, err
  	}
  	submitted, err := dam.TransitionPublication(ctx, draft.ID, draft.Revision, "submit", "Ready for editorial review")
  	if err != nil {
  		return draft, err
  	}
  	return submitted, nil
  }
  ```

  ```python Python theme={null}
  def submit_publication(dam, asset_id, references):
      editor = dam.metadata_editor(asset_id)
      draft = dam.create_publication({
          "asset_id": asset_id, "asset_revision": editor["revision"], "channel": "web",
          "renditions": references,
          "rights": {"owner": "Example Studio", "attribution": "Photography: Example Studio"},
      })
      # Persist draft["id"] in your editorial record before submitting.
      return dam.transition_publication(draft["id"], draft["revision"], "submit", "Ready for editorial review")
  ```

  ```php PHP theme={null}
  function submitPublication(\StackShift\AssetDAMClient $dam, string $assetId, array $references): array {
      $editor = $dam->metadataEditor($assetId);
      $draft = $dam->createPublication([
          'asset_id' => $assetId, 'asset_revision' => $editor['revision'], 'channel' => 'web',
          'renditions' => $references,
          'rights' => ['owner' => 'Example Studio', 'attribution' => 'Photography: Example Studio'],
      ]);
      // Persist $draft['id'] in your editorial record before submitting.
      return $dam->transitionPublication($draft['id'], $draft['revision'], 'submit', 'Ready for editorial review');
  }
  ```
</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/publications`                           | assets:read                                                               | —               | 200     |
| `POST /api/v1/assets/publications`                          | assets:write                                                              | —               | 201     |
| `PUT /api/v1/assets/publications/{publicationID}`           | assets:write                                                              | Quoted If-Match | 200     |
| `POST /api/v1/assets/publications/{publicationID}/{action}` | assets:write submit; assets:admin review; assets:publish publish/withdraw | Quoted If-Match | 200     |
| `GET /api/v1/assets/publications/{publicationID}/resolve`   | assets:read                                                               | —               | 200     |
| `GET /api/v1/assets/{assetID}/publication-renditions`       | assets:read                                                               | —               | 200     |
| `GET /api/v1/assets/publications/{publicationID}/preview`   | assets:read                                                               | —               | 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.

## Snapshot fields and resolver response

POST creates a draft; PUT edits an existing draft using the publication revision in If-Match and the current asset\_revision in the body. The service captures version\_id, metadata, metadata\_revision, schema and policy revisions. Do not send a made-up output URL in place of a rendition reference.

The returned publication includes id, asset\_id, version\_id, channel, status, revision, metadata, metadata\_revision, schema\_id/schema\_revision when bound, policy\_revision, renditions, rights, created\_by, reviewed\_by/reviewed\_at when reviewed, review\_note, published\_at/withdrawn\_at when applicable and creation/update timestamps.

Resolve and preview return publication\_id, channel, expires\_at, attribution, ar\_allowed and renditions. Each delivery rendition contains reference and url. Native video also contains mp4\_url and poster\_url; a rendered turntable contains mp4\_url. Standard resolution omits USDZ. The explicit AR request returns authorized AR resources when policy and companions allow it. Refresh through your backend when expires\_at approaches; do not persist the short-lived URLs in CMS content.

List publications with asset\_id, status and cursor. Consume publications and next\_cursor; continue with the returned cursor until it is empty. Keep the same filters on every page.

| Field                                  | Meaning and constraints                                                                                                                                                                  |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `asset_id / asset_revision`            | Primary asset UUID and its current revision. A source replacement requires a new current snapshot.                                                                                       |
| `channel`                              | One channel allowed by bucket governance, for example web.                                                                                                                               |
| `renditions`                           | 1–30 immutable references returned by publication-renditions; unique rendition IDs and exactly one primary role=media. Companion roles: poster, fallback, caption, usdz and spin\_frame. |
| `rights.owner`                         | Required, trimmed, nonblank owner/license-holder name, at most 240 bytes.                                                                                                                |
| `rights.attribution`                   | Attribution text, at most 2,000 bytes; resolver passes it to the embedding application.                                                                                                  |
| `rights.starts_at / rights.expires_at` | Optional RFC 3339 timestamps; expiry must follow start. Normal delivery is available only within this window.                                                                            |
| `note`                                 | Transition body field, at most 2,000 bytes. request-changes requires a nonblank explanation; submit/approve/publish/withdraw may use an empty note.                                      |

## Expected result

<Check>
  Consumers receive short-lived delivery for the exact reviewed publication, with rights and revocation checked on subsequent requests.
</Check>

## Common failures

<Warning>
  * Treating approval as publication.
  * Reusing an old revision after editing the source or policy.
  * Saving delivery URLs as permanent CMS content.
  * Publishing a proof or incomplete spin.
</Warning>

## Related guides

<CardGroup cols={2}>
  <Card title="Typed metadata and bucket governance" href="/assets/metadata-and-bucket-governance">
    Create and publish metadata schemas, bind exact revisions to buckets, validate asset fields, and migrate existing delivery into governance.
  </Card>

  <Card title="Embed the headless asset picker" href="/assets/headless-asset-picker">
    Add JavaScript or React media selection using expiring capabilities, save stable references, and resolve approved media from your backend.
  </Card>

  <Card title="Create and embed media galleries" href="/assets/media-galleries">
    Arrange published images, video, spins and models with locale, alternate text, approved fallbacks and stable published revisions.
  </Card>

  <Card title="Upload, validate and publish 3D models" href="/assets/models-ar-and-galleries">
    Prepare supported GLB and supplied USDZ files, inspect validation reports, add posters, review the model and launch authorized device AR.
  </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>
