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

# Creative

> Manifest-based creative assets, nested under campaigns, with format coverage and per-product propagation

## Overview

A **Creative** in v2 is a **creative manifest** — a structured bundle containing uploaded assets (images, video, HTML, VAST tags), an optional brief, brand info auto-resolved from the campaign's advertiser, and a target ADCP format. Delivery is campaign-scoped: a manifest reaches a media buy through the campaign it is attached to.

<Note>
  **New in v2**: the campaign is the default home for a creative. Creatives are **created directly under a campaign** at `/api/v2/buyer/campaigns/:campaignId/creatives/...`, rather than being maintained as an advertiser-level catalog that campaigns reference. The model is also fully manifest-aligned with the [AdCP creative manifest spec](https://adcontextprotocol.org/schemas/latest/core/creative-manifest.json).
</Note>

<Note>
  **Advertiser-level creatives still exist**, as a staging and reuse shelf rather than the delivery model. You can [create a master under an advertiser](/v2/buyer/creatives/tasks/create-advertiser-creative-manifest) before any campaign exists, list an advertiser's creatives, and promote a creative to the advertiser library — see [Advertiser-scoped creative listing](#advertiser-scoped-creative-listing) and [Advertiser library: assign and unassign](#advertiser-library-assign-and-unassign) below. An advertiser-level master carries `advertiser_id` with no `campaign_id` and never delivers until it is assigned to a campaign.
</Note>

<Note>
  **Field-name exception**: Creative manifests use **snake\_case** field names (`creative_id`, `campaign_id`, `format_id`, `format_kind`, `format_option_ref`, `template_id`, `brand_domain`, `target_format_ids`) because they are passed through verbatim to the ADCP wire-format. This is an intentional exception to v2's general camelCase convention — see the [migration guide](/v2/setup/migration-guide) for context.
</Note>

## Why this changed

V1 treated the advertiser-level catalog as the primary home for a creative, reused across campaigns. V2 makes the campaign the default home so:

* Each campaign sees exactly the formats its selected products require (`creativeFormats.required`)
* Asset uploads happen in a single multipart request with the manifest metadata
* Format and template auto-detection runs against the campaign's product mix
* Tracking macros and brand info are resolved from the campaign + advertiser context

The result is fewer mismatches between selected products and uploaded creatives, and a cleaner per-campaign creative library.

## Key fields

| Field               | Type   | Notes                                                                                                                                                                                                                                                                                                                         |
| ------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `creative_id`       | string | Manifest ID                                                                                                                                                                                                                                                                                                                   |
| `preview_revision`  | string | Exact opaque revision of the buyer-preview projection                                                                                                                                                                                                                                                                         |
| `campaign_id`       | string | Owning campaign                                                                                                                                                                                                                                                                                                               |
| `name`              | string | Human-readable name                                                                                                                                                                                                                                                                                                           |
| `message`           | string | Optional creative brief / direction text                                                                                                                                                                                                                                                                                      |
| `template_id`       | string | Creative template (`display_image`, `display_html`, `video_standard`, vendor tags, etc.)                                                                                                                                                                                                                                      |
| `format_id`         | object | `{ agent_url, id }` — **legacy** (v1 named-format) selector. New create input uses either this field or canonical identity, never both. A persisted imported manifest may retain it as provenance alongside `format_kind`; in that case the canonical identity is authoritative and the URL is not dispatched.                |
| `format_kind`       | string | **Canonical** (AdCP 3.1) format selector — a bare enum (`image`, `video_vast`, `video_hosted`, …) with **no `agent_url`**. It may be accompanied by `format_option_ref`. Video/audio duration rides in `params` (`duration_ms_exact` / `duration_ms_range`), not on `format_id`. See the [glossary](/v2/concepts/glossary#f). |
| `params`            | object | Validated parameters retained with canonical identity. A URL-free `image` response includes positive whole-number `width` and `height` here and omits a synthetic `format_id`.                                                                                                                                                |
| `format_option_ref` | object | Product- or publisher-scoped canonical option `{ scope, format_option_id, publisher_domain? }` that accompanies `format_kind`. It contains no agent URL and is mutually exclusive with a legacy outbound `format_id` selector.                                                                                                |
| `target_format_ids` | array  | Formats from campaign products this manifest can serve                                                                                                                                                                                                                                                                        |
| `brand_domain`      | string | Auto-resolved from the campaign's advertiser                                                                                                                                                                                                                                                                                  |
| `assets`            | array  | Uploaded assets (id, type, MIME, public URL, source)                                                                                                                                                                                                                                                                          |
| `preview_url`       | string | Manifest-local processed preview URL; provider placement previews use the separate preview action below                                                                                                                                                                                                                       |
| `tracking`          | object | Auto-generated impression and click tracker URLs                                                                                                                                                                                                                                                                              |
| `sync_status`       | object | `{ synced, agent_count, last_synced_at }` across sales agents                                                                                                                                                                                                                                                                 |
| `frequencyCaps`     | array  | Buyer-defined caps on this creative                                                                                                                                                                                                                                                                                           |

## Asset types

V2 assets cover the full ADCP type spectrum:

| Type                                  | Purpose                                                                      |
| ------------------------------------- | ---------------------------------------------------------------------------- |
| `IMAGE`                               | JPEG, PNG, GIF, WebP, SVG                                                    |
| `VIDEO`                               | MP4, WebM, QuickTime                                                         |
| `AUDIO`                               | MP3, WAV, OGG                                                                |
| `HTML`                                | Inline content or hosted URL                                                 |
| `JAVASCRIPT` / `CSS`                  | Companion script and style assets                                            |
| `VAST`                                | VAST tag (URL or inline XML) — **renamed from v1's `VIDEO_VAST`**            |
| `ZIP`                                 | Inspected HTML5 bundle, stored intact with its entry point and declared size |
| `URL`                                 | Clickthrough, tracker pixel, or VAST URL with explicit `url_type`            |
| `FONT` / `LOGO` / `DOCUMENT` / `TEXT` | Supporting assets                                                            |

URL assets are typed via `url_type`: `CLICKTHROUGH`, `TRACKER_PIXEL`, `TRACKER_SCRIPT`, or `VAST`. Webhook assets enable server-side dynamic content rendering (DCO) with HMAC or API-key signing.

### Uploaded assets and assembled Creatives

The upload Task and the Creative library hold different things. The Task puts private JPEG, PNG, or MP4 bytes into managed storage. During transport it uses an expiring opaque `asset_ref`; successful advertiser-scoped finalization requires and returns a durable `asset_id` for the advertiser's asset library and retains the original filename as its display name. After finalization, the Task records a conversation receipt with the asset ID when one exists, saved name and tags, content type, byte size, digest, and any verified video facts. For a provider-scoped upload without a reusable Library asset ID, the digest is byte identity for the receipt only: it is not an `assetId` or `sourceUid`, cannot be passed to `save_creative` or provider adoption, and does not imply that later attachment is possible. On external hosts, that Task stays open after the receipt so its private continuation reference remains available for any separately requested follow-up; close the Task when no follow-up is needed. The receipt reports the upload only; creating or attaching a Creative, syncing data, contacting a provider, and delivering media still require a separate request. The transport `asset_ref`, provider identities, signed upload URLs, storage paths, `delivery_url`, and `source_uid` are excluded from the receipt.

### Browse an advertiser's Creative Library

In Apostra, select an advertiser and then choose **Manage → Creatives**.
You can also ask Murph to **show me my creatives** after selecting the
advertiser. Both paths open the same Creative Library and show the advertiser
name and ID at the top. The Library stays within that advertiser if the
surrounding workspace changes or you reload the page.

The Creative Library opens in a list grouped by Creative format, with
Creatives that do not have a format in **Uncategorized** at the end of the
list. Choose the grid toggle to switch layouts for the current browser
session. If no loaded Creative has a format, the Library shows one ungrouped
list and does not offer format grouping.

Select a Creative or Asset to open a previewer beside the list on wide layouts
and in a sheet on narrow ones. The list keeps its search, filters, grouping,
and scroll position while the previewer shows the available preview, summary
facts, and attachment choices. Close it with **Escape** or **Back to creatives**
to continue browsing.

Opening Creatives from a campaign or from a link shared by your agent opens the
same Library with that campaign selected. Existing Campaign Creative Assets
bookmarks also move to this shared Library instead of opening the retired
screen. Apostra restores the advertiser and campaign when you reload a
valid link. A campaign or Creative that does not belong to the selected
advertiser, or that you cannot access, is refused rather than opened under a
different advertiser.

Campaign focus lists the Creatives currently attached to that campaign and
supports text search. Choose **All advertiser Creatives** to browse reusable
Creatives, open one, and attach it to the selected campaign. **Creative
requirements** opens the campaign's current format requirements and mapping
task. Those requirements come from the signed format declarations on the
campaign's selected Products. Uploading a file still creates an independent
Library asset; you can then assemble a Creative from that asset and attach the
saved Creative to the campaign. Preview remains tied to the exact saved
Creative revision.

Use **All Creatives** for every visible, non-archived Creative owned by the
advertiser. Use **Promoted shelf** for the smaller set marked for reuse. The
current view, search text, number loaded, and total remain visible. If more
results are available, the Library says the list is incomplete and lets you
load the next page.

Opening a Creative loads its exact saved revision through Apostra's preview
origin. If the Creative changes, refresh the Library before opening the new
revision. **Upload asset** starts the existing upload Task; completing that Task
adds an asset, not an assembled Creative. Product selection remains required
before assembling a new Creative.

The **Catalogs** tab lists the advertiser's connected catalog feeds beside
Creatives and Assets. Each card shows its item count, source, most recent sync,
and whether the feed is Healthy or Stale (with the reason). **Connect a
catalog** and **Open** take you to the existing catalog manager; **Build
creatives** is shown as a forthcoming workflow and does not start work yet.

For an object response instead of the visual Library, use V3
`search({kind: "creative", filter: {advertiserId}})`.

Use V3 `search({kind: "creative_asset", filter: {advertiserId}})` to find active assets, optionally filtering by text, media kind, or tags. Use `get({kind: "creative_asset", id: assetId, sourceId: advertiserId})` for exact readback. Promoted MP4 records include `verifiedMedia` with the verified container, video codec, audio codec, duration, width, and height for format compatibility checks. Promoted audio records include the verified container, audio codec, bitrate, channel count, duration, and sample rate. They do not expose storage locations or delivery URLs. The first release is scoped to the same authenticated buyer principal and advertiser that finalized the upload; another teammate does not inherit access merely by belonging to the customer. A finalize accepts up to 50 tags of 64 characters each. Images remain capped at 30 MB, WAV and MP3 audio at 50 MB, and MP4 video at 2 GB. Audio is available as an independent Library asset, but not yet as a `save_creative` source.

Use V3 `search({kind: "creative_format", filter: {advertiserId, productId}})`
before assembly to list canonical format options on the selected product. Pass
the exact opaque Product ID returned by marketplace discovery, including its
`sf2:` prefix. Apostra resolves its storefront, inventory source, and
seller-local product identity without falling back to a matching raw ID.
`productId` is required to keep the catalogue read bounded. You can narrow the
read further with `sellerId` or `formatKind`. The
result includes a signed opaque `id`, the exact `formatOptionRef`, and each declared slot's
identity, required and repeat cardinality, accepted asset kind, and authored
constraints. It also identifies the seller by display name and id. The read
does not require a campaign or destination and makes no provider call or setup
request. Seller-owned product declarations are available without a provider
connection; direct-adapter products keep their existing active-connection
visibility rules. Use the returned opaque id with
`get({kind: "creative_format", id, advertiserId})` to read the same declaration
again. The read excludes legacy global format catalogue entries.

V3 `save_creative` turns asset-library records into one format-bound Creative. For campaign-independent assembly, send `advertiserId`, the selected format `id` as `creativeFormatId`, and `sourceAssets`; each source binding supplies `assetId` and names its required image or video slot. Add `mode: "draft"` to save a partial advertiser-owned assembly before every required slot is ready. Revise it with the same `creativeId`, the current `stateRevision` as `expectedRevision`, the signed `creativeFormatId`, and the complete replacement slot state. Use `mode: "complete"` when the assembly is ready for full format validation. Drafts and completed advertiser assemblies stay outside campaigns and do not contact a provider.

Apostra revalidates the signed format and derives its canonical kind and option on every save. Supply copy and destination slots through `social` and `clickUrl`. A stale revision fails without changing the Creative, and a replacement never silently merges with its prior state. For a campaign-scoped create, send `campaignId`, `formatKind`, and the campaign product's exact `formatOptionRef`; that legacy save also attaches the new Creative to the campaign. Calls that omit `mode` retain complete-save behavior. Apostra rejects expired or foreign assets, stale or mismatched format selections, undeclared slots, wrong media types, duplicate slot values, and missing required slots at completion.

The saved result is the Creative manifest. An advertiser-level assembly appears once under **All creatives** and its authoritative V3 search and readback contain durable asset rows, `state`, the current numeric `stateRevision`, and `membership.campaignIds`. To associate a completed advertiser Creative before a seller package exists, call `save_creative` with its `creativeId`, `advertiserId`, and `campaignId` only. Repeating that call is safe. The association records campaign membership; it does not assign a package, create provider delivery, or create spend. Those are separate later states. To set the full set of campaigns an advertiser Creative runs on, call `save_creative` with `creativeId`, `advertiserId`, and `campaignIds`: the list replaces membership, so a campaign missing from it is detached and a new one attached. Omitting `campaignIds` never detaches. Attaching checks the campaign's sellers: when the campaign has media buys and no seller's products accept the Creative's format, the call fails and names those sellers; a campaign with no media buys yet accepts any complete Creative, while a media buy whose products declare no accepted formats cannot be checked, so the save fails closed. A Creative live on an active or paused media buy cannot be detached until that buy ends. Every way of changing membership, including the Library picker and the singular `campaignId` association, shares one lock protocol, so concurrent membership writes to the same Creative or campaign do not overlap: a write that finds another one in progress, or whose target set changed while it was being prepared, returns `CONFLICT` and should be retried with the full set. The same edge is set from the campaign side with `save_campaign.creativeIds` and from the Creative Library picker; readback is `membership.campaignIds` on the Creative and `creatives` on the campaign. The existing opaque `revision` remains the buyer-preview projection digest. The same completion view can load that exact saved state through **Show preview**. Creative collections contain assembled Creatives, not the independent uploaded assets. V2 MCP sessions can open the same upload Task, but asset search/get and `assetId` assembly are V3-only.

### Preview a saved Creative

In the Creative Library, select **Show preview** to run the current saved
revision in an isolated frame. The frame uses Apostra's render origin. It
does not embed a provider preview or run the Creative in the Library page.

V3 clients can request the same read with
`get({kind: "creative", id, advertiserId, include: ["preview"]})`, or use the
Creative's campaign as `sourceId`. The `preview` field contains only
`renderUrl`, `expiresAt`, the opaque preview `revision`, and the exact current
numeric `stateRevision`. If the saved format
has no first-party renderable asset, it contains a `reason` instead. Existing
`get` calls omit `preview` unless it is included, but every Creative read now
returns the whole-state `stateRevision` and no longer forwards the legacy V2 provider
preview URL or tracker fields.

The buyer preview renders direct image, video, and audio source assets. HTML,
VAST, DAAST, and ZIP sources return a not-renderable reason because their
nested documents can load provider resources or measurement trackers that are
not safe to project into this read.

Preview URLs expire after 15 minutes and stop working sooner if the Creative
changes. Request a new preview after expiry or whenever the returned revision
or `stateRevision` differs from the Creative read. Storage URLs, provider preview HTML, and
measurement trackers are not returned by the V3 Creative read.

A `VAST` url asset also takes `vast_version` — one of `2.0`, `3.0`, `4.0`,
`4.1`, `4.2`, `4.3`. Declare it: a managed ad-server destination checks the version
against the ones it holds delivery evidence for, so a tag without one is not
eligible for those routes and fails creative sync. See
[video creatives](/v2/concepts/video-creatives#declare-a-vast-tags-version).

### HTML5 bundles

Canonical `html5` creatives use one ZIP asset in the `html5_bundle` slot, with
an optional `backup_image`. Buyer chat inspects these packages without running
their JavaScript, preserves the ZIP, and carries the declared dimensions into
seller matching. Generic multi-creative ZIPs continue to expand into separate
creatives. Direct multipart ZIP creation and reclassification are rejected;
use the inspected buyer-chat upload path.

An imported bundle is not automatically delivery-ready. Media-buy sync
requires an explicit landing-page URL, and compatibility fails closed when a
seller declares an HTML5 runtime constraint that inspection cannot verify.

## Lifecycle

<Steps>
  <Step title="Discover required formats">
    `GET /api/v2/buyer/campaigns/:campaignId/creatives/templates` returns available templates plus the selected products' required formats. V2-native declarations are returned in `campaign_format_options`; legacy format references remain in `campaign_format_ids` for compatibility. Each product's requirements are also broken out under `campaign_products`. The legacy named-format registry across agents is available at `GET /api/v2/buyer/formats`.
  </Step>

  <Step title="Create the manifest">
    POST a multipart request with files + JSON metadata. Apostra auto-classifies assets, processes HTML to rewrite local references to CDN URLs, inserts ADCP macros, and generates a preview. If the campaign already has non-draft media buys, a complete new manifest is automatically propagated to their active sellers; no separate `sync_creatives` call is required.
  </Step>

  <Step title="Validate coverage">
    `GET /api/v2/buyer/campaigns/:id` returns `creativeFormats: { required, covered, missing }`. Upload more manifests until `missing` is empty.
  </Step>

  <Step title="Propagate to media buys">
    On campaign execute, the manifests sync to each sales agent for the formats they support. A ready manifest is registered for delivery in the same transaction as its assets, so it cannot disappear between upload and assignment. Sync status is reflected in `sync_status` on each manifest.
  </Step>

  <Step title="Update or replace">
    `PUT` the manifest with new files, deletions, or reclassifications. Updates flow back to media buys.
  </Step>
</Steps>

## Media-buy compatibility results

When a media buy inherits creatives from its campaign, Apostra checks each
creative against the selected products before delivery. An inherited creative
that cannot serve those products is omitted and reported in
`creative_compatibility_warnings` with a status of `incompatible` or
`unresolved` and a reason. Older creative records that do not contain a usable
format are reported in `legacy_creatives_excluded`.

Historical static-image labels such as `display_300x250`,
`banner_300x250`, and `display_300x250_nongenerative` can serve a product's
same-sized `display_300x250_image` placement without retyping the saved
creative. Apostra sends the product's advertised format reference and still
checks the creative's asset type and dimensions before delivery. Other
legacy-format differences remain incompatible rather than being widened by
format family.

Apostra preserves an imported legacy format label for traceability, but a saved
creative's canonical `format_kind` is authoritative for campaign and product
matching. New saved creatives must resolve a canonical kind. If an older
creative's assets, media type, dimensions, and import provenance do not identify
one format kind deterministically, Apostra quarantines it for explicit upgrade
instead of guessing. Seller-specific format references are applied only when
the creative is adapted for that seller; they do not change the saved master.

If a create-media-buy request explicitly names an incompatible or unresolved
creative, the request is rejected instead of creating a buy that silently
drops it. Integrations should surface these response fields so a buyer can
replace or correct the creative before launch.

For creatives your integration creates, use the canonical `format_kind` field.
It never contains an agent URL. Apostra only preserves a URL-bearing
`format_id` when an external third-party system explicitly supplied that
legacy reference.

## Conversational creative sessions

Creative sessions are the agent-native path for buyers who want to generate, compare, refine, validate, and save creatives without handling manifest details directly.

### Composer drafts

For a campaign, the Creative Library can open a Composer Task without Creative
Engines. Choose Snap Story, Meta Reels, Display 300×250, or Audio :30, then
fill its copy and uploaded-asset slots, name the draft, add its click-through
URL, and save it. The Task shows which slots are filled and a live preview,
then saves the assembly draft in the campaign's Creative Session without
selecting or calling an engine.

Use the host chat to request changes. The Task does not provide a second change
bar and reloads the session after chat updates it. Saving a composer draft keeps
work in review; it does not create a campaign Creative, attach it to delivery,
or make it live. When Creative Engines is not enabled, the Task keeps assembly
controls available and labels generative controls unavailable. Ask your Apostra
account team to enable it for the Buyer Account; generation also needs a
connected engine and applicable provider setup.

The intended user flow is:

1. **Idea** — the buyer describes the channel, audience, goal, and source assets.
2. **Draft gallery** — a creative adapter returns multiple draft variants for human review.
3. **Refine** — the buyer gives natural-language feedback like "make the book more prominent" or "less busy."
4. **Evaluate** — draft checks are advisory; final hard/blocking checks stop finalization.
5. **Finalize** — the approved leaf is saved as a campaign creative manifest.

When Apostra generates directions in separate calls and the request includes
`request.idempotency_key`, give each direction a distinct short label. Provider
keys use the first 40 characters after lowercasing, replacing characters outside
`a-z` and `0-9` with hyphens, and trimming edge hyphens. Labels such as
`Summer sale` and `summer-sale` therefore collide. Apostra rejects that batch
before calling a generator; change the labels before retrying. Existing keys
for unambiguous directions remain unchanged.

Use `POST /api/v2/buyer/campaigns/:campaignId/creative-sessions` to start a session. The response is designed for MCPUI and includes the draft `variants[]`, `asset_store`, `renderer_capabilities`, `rendering_model`, staged evaluator badges, and `format_renders[]` once a final is approved.

When refining a variant, send only the `request` settings you want to change for
that refinement. Omitted settings retain the session's saved values. Nested
objects merge; arrays and scalar values replace the supplied field. Explicit
`null` values in `config` are forwarded to the provider for validation. A string
`creative_brief` replaces the brief used for that refinement. Provider setting
overrides do not change the session defaults, and the selected variant remains
the parent.

If a finalization response is lost, read the session before retrying. Once the
session reports `finalized`, repeating the same selected variant, name, and
message returns its saved result without creating another campaign creative.
Retries still require current campaign write access. Different inputs return a
conflict; use the saved `creative_id` to read or update the campaign creative.
For older finalized sessions that did not retain the original request, retry
without name or message overrides.

This retry behaviour applies after the finalized session has been saved. It
does not guarantee recovery from an interruption between creative creation and
session persistence, or from concurrent finalization requests. A session that
does not report `finalized` is not proof that no creative was created.

<Note>
  Creative sessions are review workflows. They do not replace campaign creative manifests. Finalization is the transition point where a selected draft/refinement becomes a normal campaign creative with `creative_id`, legacy or canonical format identity, assets, preview, and sync status.
</Note>

### Required placement shapes

When a campaign has required placements, a `format_renders[]` tile is produced per placement at its real size. A placement with an extreme aspect ratio — a wide leaderboard or a tall skyscraper — only renders at its true shape if the generator can compose that ratio natively. Gemini and FAL do; OpenAI generates a fixed set of shapes and crops to fit.

If your session's connected generator would crop such a placement and you have also connected a native-ratio generator (Gemini or FAL) for that advertiser, that one placement is rendered on the native-ratio generator instead — on your own connected key for it, billed to that vendor account, the same as any generation on that provider. Other placements in the session stay on your session generator. When no native-ratio generator is connected, the placement still renders on the session generator with an advisory note that it may crop; connect one to fix the shape.

## Asset lifecycle

Creative production uses three related but distinct asset layers:

| Layer              | What it is                                                                                                             | Stored as                                                                     | How to use it                                                                                                                                           |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Source asset**   | Buyer-supplied input such as a book cover, product photo, logo, DAM URL, catalog image, audio file, or reference image | `source_asset` and `asset_store.assets[]` inside the creative session         | Treat as raw input. Preserve locked assets exactly; track role, provenance, rights, checksum, dimensions, and render crop/mask metadata when available. |
| **Draft variant**  | A generated or composited creative direction for review                                                                | `variants[]` with preview URL, quality, status, lineage, and evaluator checks | Use for exploration. Drafts may be discarded, branched, or refined. They are not campaign creatives until finalized.                                    |
| **Final creative** | The approved execution saved to the campaign                                                                           | Creative manifest under `/campaigns/:campaignId/creatives`                    | Use for delivery. It carries the final render, linked assets, text assets, ADCP format identity, tracking, preview, and sync status.                    |

For locked product, package, logo, or book-cover assets:

* Set `locked_asset: true`, `can_transform: false`, `role: "product"` (or the closest role), and specific `preservation_notes`.
* Keep provenance separate from rights. `source: "upload"` or `source: "dam"` says where the asset came from; `rights.status` says whether it is cleared.
* `rights.status: "unknown"` is a non-blocking review warning for final approval. `restricted` or `expired` blocks finalization.
* If the source image includes studio background or whitespace, provide `render_crop`, `subject_bounds`, or a transparent cutout/mask when available. Cropping is a preservation-safe fallback; a real cutout produces cleaner final compositions.

### Asset URLs must be publicly fetchable

Every asset URL on a finalized creative manifest is forwarded verbatim to the seller's ad server, which fetches it from its own infrastructure with no Apostra credentials. So a manifest asset URL has to be a publicly reachable `https://` (or `http://`) URL — those are the only two schemes accepted when you submit one.

Apostra-internal storage locations are not deliverable, even though they resolve for you while you are authoring. That includes `gs://` object URIs, `scope3-<kind>://` internal handles, and `https://storage.googleapis.com/...` URLs that address an Apostra-managed private bucket. These are refused wherever they appear.

Schemes other than `http(s):` — `tel:`, `market://` and the like — are refused too, both at upload and whenever the creative is **rebuilt** from its manifest for delivery. A manifest row that already stores one is not re-refused on a path that forwards the stored payload as-is, so an existing deep link keeps working there. Those refusals name the scheme, so you can tell them apart from a storage refusal.

The check runs when the creative is projected for delivery, so it can surface on `sync_creatives`, on media-buy execution, and on a media-buy update that assigns creatives — not only at upload time.

If an asset is not deliverable you get a `422 INVALID_REQUEST` naming the refused creative:

```json theme={null}
{
  "error": {
    "code": "INVALID_REQUEST",
    "field": "cr_9f2a",
    "message": "Creative cr_9f2a references storage that cannot be delivered to a destination. Publish the offending asset at a public HTTPS URL and retry."
  }
}
```

The subject is the creative when the refusal comes from syncing or executing an
existing creative, and the individual asset (for example `hero-2024`) when it
comes from building the creative in the first place. If your id contains
characters we do not echo back — a dot or a space, say — the message says
"A creative on this request…" instead of naming it, and `field` reads
`creative`.

**To fix it**, publish the asset at a public HTTPS URL you control (or a CDN), then update the manifest asset to point at that URL and retry.

<Note>
  The message deliberately does not say *which* storage location was detected, and on the sync/execute path it names the creative rather than the individual asset slot. If you cannot tell which asset in a multi-asset manifest was refused, contact support — Apostra retains the exact slot and the specific reason in server-side logs and can tell you which one to fix.
</Note>

## Seller-specific versions

The campaign's selected products determine which seller formats need coverage. Use the creative template/format endpoints before and after finalization:

1. `GET /api/v2/buyer/campaigns/:campaignId/creatives/templates` shows required campaign formats and available templates.
2. Start a creative session against the best matching creative adapter and target format.
3. Finalize the chosen direction into a creative manifest.
4. Re-check `creativeFormats` on `GET /api/v2/buyer/campaigns/:campaignId`.
5. If `creativeFormats.missing` still contains seller formats, generate or adapt seller-specific manifests for those formats.

Seller-specific versions should keep a common creative direction and source-asset lineage, but each saved manifest must match the actual seller format it covers. For example, the same "Wimbledon book hero" direction may need separate square image, portrait story, audio, video, or HTML executions depending on the selected products.

`format_renders[]` is the review surface for publisher/format previews. It helps a buyer inspect what the final creative looks or sounds like in a particular canonical renderer. The actual delivery object remains the saved creative manifest and its synced seller-specific format coverage.

## Performance loop

Creative work does not end at finalization. The full performance cycle is:

1. **Create or update campaign goals** — use `performanceConfig` on the campaign when optimizing toward events or metrics.
2. **Launch with format coverage** — execute only after required creative formats are covered.
3. **Collect delivery and outcome data** — use reporting plus conversion/measurement ingestion (`log_event`, measurement data sync, or measurement records depending on integration).
4. **Compare creative performance** — inspect performance by campaign, media buy, package, and creative where reporting dimensions are available.
5. **Iterate** — duplicate or start a new creative session from the winning direction, then refine the creative based on observed performance and seller feedback.

<Note>
  The Anjali/The Shift prototype exercises the creative build, preview, evaluator, and finalization loop. A complete performance cycle also needs live delivery, measurement ingestion, reporting by creative, and an optimization decision that feeds a new creative iteration.
</Note>

## Evaluator strategy

Creative evaluators answer different questions at different moments. Treat them as a selected set of checks, not a single score.

| Evaluator type | Question it answers                                                  | Typical stage |
| -------------- | -------------------------------------------------------------------- | ------------- |
| Core readiness | Is there a renderable preview and valid manifest structure?          | Draft, final  |
| Asset fidelity | Did locked assets stay intact, and are rights/provenance usable?     | Draft, final  |
| Brand/brief    | Does the direction fit the campaign brief and brand guidance?        | Draft, final  |
| Format/seller  | Does this execution satisfy the selected seller or publisher format? | Final         |
| Policy/safety  | Is there policy, claims, legal, or safety risk?                      | Final         |
| Performance    | Did it actually work after delivery?                                 | Postflight    |

Draft evaluator warnings should guide refinement. Final hard/blocking failures prevent finalization. Non-blocking final warnings, such as unknown source-asset rights, should be shown as review notes. Performance evaluators should only run after delivery/reporting data exists; pre-flight checks cannot prove a creative will win.

Longer term, evaluator selection should come from the creative session context:

* source assets and lock/rights metadata
* modality and renderer type
* campaign brief and brand guidance
* selected seller formats and publisher policies
* available adapter/seller evaluator capabilities
* postflight reporting and measurement data

## Common operations

### Create a creative manifest (multipart)

The endpoint accepts a `metadata` JSON field plus `files[]` form fields:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.apostra.com/api/v2/buyer/campaigns/cmp_987654321/creatives/create \
    -H "Authorization: Bearer $SCOPE3_API_KEY" \
    -H "Content-Type: multipart/form-data" \
    -F 'metadata={
      "name": "Q2 hero video",
      "message": "30s product reveal — premium tone, 2-second hook",
      "template_id": "video_standard",
      "url_asset": { "url": "https://acme.com/promo", "url_type": "CLICKTHROUGH" },
      "assets": [
        { "filename": "hero-30s.mp4", "asset_type": "VIDEO", "label": "main_video" }
      ]
    };type=application/json' \
    -F "files=@hero-30s.mp4" \
    -F "files=@companion-banner.png"
  ```

  ```bash With a VAST tag theme={null}
  curl -X POST https://api.apostra.com/api/v2/buyer/campaigns/cmp_987654321/creatives/create \
    -H "Authorization: Bearer $SCOPE3_API_KEY" \
    -H "Content-Type: multipart/form-data" \
    -F 'metadata={
      "name": "VAST premium",
      "template_id": "video_vast",
      "url_asset": {
        "url": "https://ad.doubleclick.net/ddm/vast/...",
        "url_type": "VAST",
        "vast_version": "2.0"
      }
    };type=application/json'
  ```
</CodeGroup>

<Note>
  Creative creation is a single multipart request: a JSON `metadata` field plus zero or more `files` parts (`-F "files=@..."`). The server accepts up to the configured `MAX_FILE_COUNT` files per request and pairs each file with the matching `assets[].filename` in the metadata.
</Note>

<Tip>
  File limits: 50 MB per file, 20 files per request. Allowed MIME types include images (JPEG, PNG, GIF, WebP, SVG, HEIC), video (MP4, WebM, QuickTime), audio (MP3, WAV, OGG), HTML/CSS/JS, fonts, PDF, and XML/markdown.
</Tip>

### List manifests

```bash theme={null}
curl "https://api.apostra.com/api/v2/buyer/campaigns/cmp_987654321/creativeManifest?take=50" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

Filters: `search`, `includeFrequencyCaps`. Pagination: `take`, `skip`.

### Get / update / delete

```bash theme={null}
curl https://api.apostra.com/api/v2/buyer/campaigns/cmp_987654321/creatives/cm_abcdef \
  -H "Authorization: Bearer $SCOPE3_API_KEY"

curl -X PUT https://api.apostra.com/api/v2/buyer/campaigns/cmp_987654321/creatives/cm_abcdef \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  -F 'metadata={
    "delete_asset_ids": ["asset_old"],
    "new_assets": [{ "filename": "v2.mp4", "asset_type": "VIDEO" }]
  };type=application/json' \
  -F "files=@v2.mp4"

curl -X DELETE https://api.apostra.com/api/v2/buyer/campaigns/cmp_987654321/creatives/cm_abcdef \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

### Render an ADCP preview

```bash theme={null}
curl -X POST https://api.apostra.com/api/v2/buyer/campaigns/cmp_987654321/creatives/cm_abcdef/preview \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

Calls the configured ADCP creative agent or platform adapter's
`preview_creative` tool. Provider-backed renders are returned as external links
and are never executed inline. Availability and fail-closed behavior vary by
platform; see [Creative preview support](/v2/guides/ad-platform-compatibility/creative-preview).

## Advertiser-scoped creative listing

`GET /api/v2/buyer/advertisers/:advertiserId/creatives` returns **all creatives scoped to the advertiser** — both promoted library creatives (`evergreen`/`reference` role) and flight-specific creatives attached to any campaign under this advertiser. A creative inherits the campaign's `advertiser_id` at creation time, so it appears in this list regardless of campaign attachment.

Use `?promoted=true` to filter to library creatives only (those explicitly saved with a role). Without this filter, the full set is returned — including creatives uploaded via the campaign-level creative dashboard that have not been promoted.

The advertiser creative listing also accepts `?search`, `?role`, `?source`, `?format_kind`, `?asset_type`, and `?dimensions` filters alongside standard `?take`/`?skip` pagination.

## Advertiser library: assign and unassign

Creatives can be saved to an advertiser library (promoted to `evergreen` or `reference` role) and then attached to campaigns via the advertiser-scoped endpoints:

| Method   | Path                                                                                  | Purpose                                                                                                                          |
| -------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `POST`   | `/api/v2/buyer/advertisers/:advertiserId/creatives/create`                            | [Create a master directly under the advertiser](/v2/buyer/creatives/tasks/create-advertiser-creative-manifest), with no campaign |
| `POST`   | `/api/v2/buyer/advertisers/:advertiserId/creatives/save-to-library`                   | Promote creatives to the advertiser library with a role                                                                          |
| `POST`   | `/api/v2/buyer/advertisers/:advertiserId/creatives/:creativeId/campaigns`             | Assign a library creative to a campaign                                                                                          |
| `DELETE` | `/api/v2/buyer/advertisers/:advertiserId/creatives/:creativeId/campaigns/:campaignId` | Remove a library creative from a campaign                                                                                        |

### Ownership validation

Both assign and unassign enforce advertiser ownership: the creative and the target campaign must belong to the same advertiser. If they belong to different advertisers, the endpoint returns **HTTP 422** with error code `INVALID_REQUEST`.

```json theme={null}
{
  "error": "Creative belongs to a different advertiser than the campaign.",
  "code": "INVALID_REQUEST",
  "field": "advertiserId"
}
```

A creative with no `advertiser_id` (not yet scoped to an advertiser) can be assigned to any campaign, and the backfill sets its `advertiser_id` to match the campaign's.

### Collection fan-out

When you assign a collection to a campaign, members whose `advertiser_id` conflicts with the campaign's advertiser are silently skipped. The response includes an `ownership_excluded` count so you can detect this. Check that all expected creatives appear in the campaign after a collection assign, and promote any skipped creatives to the correct advertiser before retrying.

## Advertiser creative collections

An advertiser can group its saved creatives into named collections that live
with the advertiser rather than with a campaign. A collection is
non-executable: it holds creatives, it does not deliver them. Attach a
collection from a campaign to make its creatives available there. Changes to a
collection carry a revision check so concurrent edits fail loudly instead of
overwriting each other. In the Creative Library page, creatives and collections
show in a grid or a list, switchable from the toolbar, with the same rows,
search, and filters in both. See
[Manage advertiser creative collections](/v2/buyer/creatives/tasks/manage-advertiser-creative-collections).

## Format coverage on the campaign

When you `GET /api/v2/buyer/campaigns/:id`, the response includes:

```json theme={null}
"creativeFormats": {
  "required": [{ "format_kind": "video_hosted" }],
  "covered":  [{ "format_kind": "video_hosted" }],
  "missing":  []
}
```

`missing` is the prompt to upload more creatives. Coverage is computed against the formats every selected product needs.

## Tracking macros

When the format supports it, Apostra auto-injects:

* An **impression tracker** URL as a `TRACKER_PIXEL` URL asset
* A **click tracker** URL that wraps the original clickthrough — the original is preserved in the `rurl` query parameter

Apostra resolves identity macros when the creative is synced for a media buy.
It fills `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, and `{CAMPAIGN_ID}` in Apostra tracker
URLs and in the [click-URL parameters](/v2/object-guides/advertiser#click-url-parameters)
resolved from the advertiser, campaign, and creative layers. In tracker URLs
`{PACKAGE_ID}` resolves to an empty value because one synced creative can be
assigned to multiple packages. In click-URL parameters the media-buy value is
the storefront transaction id used for attribution; a parameter whose identity
macro has no value for that sync is omitted instead of carrying a different id.

Serve-time environment macros such as `{CACHEBUSTER}`, `{DEVICE_TYPE}`,
`{COUNTRY}`, and consent strings remain literal in the outbound asset for the
publisher/ad server to fill when the impression serves. Self-contained HTML is
the exception: its embedded Apostra tracker fires directly in the browser, so
Apostra injects an HTML-safe URL with no unresolved identity placeholders. The
`tracking.supported_macros` array on the manifest response lists the macros the
format supports.

<Warning>
  Only one clickthrough URL per manifest. If multiple URL assets are flagged `url_type: CLICKTHROUGH`, manifest creation fails.
</Warning>

## Measurement endpoint and EU consent

The injected impression and click trackers fire to `ping.apostra.com` —
Apostra's own measurement domain in production (`/agentic/imp` for impressions,
`/agentic/clk` for clicks). Because it is Apostra's domain rather than a
registered third party, Google Ad Manager doesn't recognize it as a known
ad-tech vendor (see below). It powers impression counting, frequency capping,
emissions measurement, and conversion attribution.

### What the tracker carries

The impression URL carries identifiers and tokens — never raw PII:

| Param   | Meaning                                                                                                            |
| ------- | ------------------------------------------------------------------------------------------------------------------ |
| `dsid`  | Dataset ID — links the impression to the advertiser's conversion-attribution dataset                               |
| `cid`   | Creative ID                                                                                                        |
| `camp`  | Campaign ID                                                                                                        |
| `advid` | Advertiser ID                                                                                                      |
| `mb`    | Media buy ID, resolved when the creative is synced to the media buy                                                |
| `pkg`   | Package ID — left blank on synced creatives, which can map to multiple packages                                    |
| `axem`  | Emissions measurement token, filled at serve time                                                                  |
| `tmpx`  | HPKE-encrypted exposure token for frequency capping, filled at serve time (see [TMP](/v2/storefront/tmp/overview)) |
| `cb`    | Cachebuster                                                                                                        |

The click tracker carries the same identifiers and preserves the original
clickthrough in the `rurl` parameter.

<Note>
  `axem` and `tmpx` are filled by the **TMP Router** and are populated only when
  the publisher has connected [TMP](/v2/storefront/tmp/overview) — without it they
  resolve to empty, and the pixel still counts impressions but carries no
  emissions or frequency-cap signal.
</Note>

### Consent signals

Where the format or advertiser tracking config enables them, the tracker URL
includes consent macros as ad-server placeholders — `gdpr` (whether GDPR
applies), `gdpr_c` (the TCF consent string), and `us_p` (the US Privacy string).
The ad server fills them from the user's consent state at serve time and
forwards them to the endpoint. These are the only consent signals the pixel
carries today; there is no GPP (Global Privacy Platform) string.

### Clearing the Google Ad Manager "unknown ad technology" warning

When a creative is trafficked through Google Ad Manager, this clearing step is
the **publisher's** action (a GAM admin), not the buyer's. GAM scans the
creative for ad technology URLs and flags any domain it doesn't recognize on
Google's Ad Technology Providers list. Because `ping.apostra.com` is an Apostra
measurement domain, it surfaces under **Associated ad technology providers** as
an unknown URL with the note *"We found unknown URLs that may affect serving
eligibility."*

When the network has **Check reservation creatives for user consent** enabled,
an unrecognized domain blocks the creative from serving — including
non-personalized requests — in the EEA, the UK, and Switzerland; the Brazil
(LGPD) block applies regardless of that setting. The creative serves normally
everywhere else.

To clear it, the publisher adds `ping.apostra.com` as a custom ad partner URL
in the network's **EU user consent settings** (reachable from the **Go to
settings** link on the warning; the page requires specific GAM permissions, as
the warning notes). Selecting "No ad technology providers to declare" does *not*
clear a URL GAM has already detected in the creative.

## Format catalog

To inspect formats required by the campaign's selected products:

| Endpoint                                                      | Purpose                                                                                                                           |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `GET /api/v2/buyer/campaigns/:campaignId/creatives/templates` | List the deduplicated V2 `format_options`, legacy format IDs, per-product requirements, and available templates for the campaign. |
| `GET /api/v2/buyer/formats`                                   | Cross-agent legacy named-format registry (all legacy format IDs discovered from sales agents).                                    |
| `GET /api/v2/buyer/formats/:agentUrl/:formatId`               | Format details for a specific format on a specific agent.                                                                         |

## Propagation to media buys

At campaign execute, the manifest is synced to each sales agent that supports its selected legacy or canonical format identity. Creating a complete manifest after the campaign already has non-draft media buys also propagates it to their active sellers, without a separate `sync_creatives` call. The same manifest can serve multiple media buys; later updates re-sync. Use `sync_status` to confirm publishers have received the latest version. `synced` is true only when every current sales-agent link succeeded; `agent_count` counts successful agents, and `last_synced_at` comes only from successful links. Failed platform links include `rejection_reason` when the seller supplied one. The Creative Library detail shows the same links as a Seller approval table: a seller's Rejected or Changes requested verdict stands until it reports a new one; otherwise a row reads Sync failed when the current version could not be delivered, Approved once the seller approved a delivered version, and Reviewing until then. The seller's note appears only under a rejected or changes-requested row. When a Creative has a click URL, its collapsed Destination section shows the authored URL, the delivered URL after effective additions, and whether each addition came from the advertiser, campaign, or Creative.

### Creative transport modes

Apostra chooses the creative transport mode per sales agent:

* **Creative library sync** — sellers that advertise a creative library through `creative.has_creative_library: true` receive manifests through `sync_creatives`, and packages reference them with `creative_assignments`.
* **Inline creative delivery** — sellers that advertise `creative.has_creative_library: false` receive the creative manifest inline on each package that needs it.

For legacy or unknown capabilities (`creative.has_creative_library` is `null` or absent), Apostra falls back to the seller's advertised tools and uses `sync_creatives` when available.

During initial media-buy execution, Apostra reuses creatives that have current-revision evidence in the same seller account's library. Those creatives remain `creative_assignments` on retries instead of being re-sent as inline creates. If `sync_creatives` fails for the remaining new creatives and the seller allows inline creatives, only those unsynced creatives fall back inline during execution; existing library creatives remain assignments. Media-buy updates retain the seller-wide inline fallback when sync fails. If the seller is library-only and a required new creative fails to sync, the create or update request fails synchronously instead of silently continuing.

## Related concepts

<CardGroup cols={2}>
  <Card title="Campaign" href="/v2/object-guides/campaign" icon="rocket">
    Owning campaign — formats come from its products
  </Card>

  <Card title="Brand" href="/v2/object-guides/brand-story" icon="book-open">
    Brand info auto-resolved from the advertiser
  </Card>
</CardGroup>
