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

# Bring your own creative

> Upload creative in chat and route it to a campaign — no separate upload screen

## Overview

Start with **Add creative** in the Creative Library when you have a finished
file. When the upload finishes, the Library opens the appropriate creator with
the file in its first slot. Complete the creative with a name, click-through
URL, and format. From a campaign Library, this is the composer; from an
advertiser Library, it is the inline creator. This assembly path uses your
uploaded assets and does not require Creative Engines.

You can still hand Murph an image, video, or ad tag in a conversation. Murph
asks which campaign should receive it. HTML5 bundles also need their
landing-page URL before delivery.

<Note>
  This is the chat-first path for creative you already have. To generate creative from a brief instead, see [Creative](/v2/object-guides/creative) and the creative manifest tasks.
</Note>

The advertiser Creative Library also has a **Catalogs** tab for checking the
feeds that can later support catalog-based creative work. Connecting or
managing a feed opens the existing catalog manager; catalog-based creative
building is not available from this tab yet.

## Previewing a creative

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

<Note>
  Promoted managed audio has a capability-gated API adoption contract. It is
  available only when both the account capability and bounded origin are
  explicitly enabled, and this change enables neither. See [Adopt a managed
  audio source](/v2/buyer/creatives/tasks/adopt-managed-audio-source). Even when
  enabled, it records a creative and optional campaign association only; it
  never initiates seller or destination delivery, attaches to a media buy, or
  makes anything live.
</Note>

## The flow

<Steps>
  <Step title="Upload the asset">
    Attach an image, video, tag, or tag sheet in the conversation.
    Murph recognizes the media kind automatically.
  </Step>

  <Step title="Answer “which campaign?”">
    Murph opens a short picker showing the advertiser and that advertiser's
    recent campaigns. You choose where the creative should live:

    * **An existing campaign** — pick it from the list.
    * **A new campaign** — describe it in plain language and Murph creates it.
    * **Save to the advertiser** — park the creative at the advertiser level to
      assign later. The API and agent equivalent is
      [Create advertiser creative master](/v2/buyer/creatives/tasks/create-advertiser-creative-manifest).
  </Step>

  <Step title="Murph maps it to placements">
    Once the creative is on a campaign, Murph maps it to that campaign's
    placements. If an asset lands in the wrong slot, you can remap it. For an
    HTML5 bundle, add the advertiser destination in the creative's
    click-through URL field before media-buy sync.
  </Step>
</Steps>

## Uploading to the Creative Library

On the Buyer V2 or shared V3 connector, ask to upload a creative and select its
advertiser. Both expose the same `upload_creative_asset` Task and storage flow;
no destination connection or provider account is required. Enrolled Buyer
Accounts can save WAV and MP3 files up to 50,000,000 bytes as durable assets in
that advertiser's Creative Library. Accepted WAV MIME types are `audio/wav`,
`audio/x-wav`, and `audio/wave`; accepted MP3 MIME types are `audio/mpeg` and
`audio/mp3`.

Audio upload is available to accounts your account team has enabled for it.
If it is unavailable, the upload Task does not offer WAV or MP3; ask your
account team to turn it on.

The Task also accepts an MP4 up to 2,000,000,000 bytes when managed-video
ingress and inspection are configured. Managed video does not depend on the
private JPEG/PNG upload option. Eligible connections accept JPEG and PNG files
up to 30,000,000 bytes.

You can select or drag up to 25 files into the upload Task at once. The Task
shows one progress list and runs no more than three private uploads at a time.
Each file can be queued, uploading, verifying, done, or failed; retrying a
failed file does not re-upload files that already finished. Before the batch
starts, enter Library tags once to apply them to every file, or edit tags for an
individual queued file. The same per-file limits still apply: JPEG and PNG up
to 30,000,000 bytes, MP4 up to 2,000,000,000 bytes when enabled, and WAV or MP3
up to 50,000,000 bytes when enrolled.

If a client can call tools but cannot render the embedded upload Task,
`upload_creative_asset` returns a `fallback_url`. Open that HTTPS link to
continue in Apostra with the authenticated customer and advertiser already
selected. Without campaign composition, it opens that advertiser's Creative
Library and its upload flow; no campaign is created or selected. When campaign
composition was supplied, the link instead opens the **Add creative** upload
flow directly with the campaign and any supplied creative name selected; the
user only needs to choose the local file and finish the form. The link never
contains file bytes, a signed storage capability, or provider credentials.

The browser calculates the file's SHA-256 while streaming it, then uploads the
bytes directly to private object storage with a create-only capability. File
bytes never enter model or chat context. The signed PUT URL is returned only to
the embedded Task app; it is excluded from model-visible results, chat/activity,
logs, and persistence. Every Task call rechecks the buyer, advertiser, and
exact authenticated principal.

MP4 upload preparation must finish within 30 seconds. If managed-storage
validation or secure upload setup does not finish in time, the Task returns a
retryable `SERVICE_UNAVAILABLE` error with the failed `preparePhase` and creates
no upload reservation. Reservation creation uses a bounded database commit and
then checks the durable outcome if its acknowledgement is lost. A confirmed
commit returns the upload capability, and a confirmed abort returns the same
retryable error. If the outcome cannot be confirmed, the Task returns
`COMMIT_OUTCOME_UNKNOWN`; do not retry that preparation, and contact support
with the returned trace. Every error response withholds signed upload details.
JPEG, PNG, WAV, and MP3 preparation is unchanged.

The upload capability expires after two hours. Finishing the Task verifies the
stored object's declared type and exact size, pins its immutable storage
generation, and waits for asynchronous digest and media inspection. When the
Task is saving an advertiser-owned Library asset, you can add up to 50 optional
Library tags, each no more than 64 characters. After successful finalization,
Task activity gives the assistant the durable asset ID, saved name, tags,
content type, byte size, SHA-256 digest, and available verified media facts.
Video facts include container, codecs, dimensions, and duration. Audio facts
include container, codec, bitrate, channel count, sample rate, and duration.
Signed upload URLs, private storage paths, publication URLs, video frame rate,
and provider identity remain excluded.

After an advertiser-scoped Library upload succeeds, the confirmation card shows
the asset preview (or a video or audio icon), its name, media kind, byte size,
and the Library tags that were saved. The raw upload receipt is available behind
an **untrusted receipt** disclosure. Select **See it in your Library** to open the Assets lens
on that exact asset, or **Upload another** to start again. Hosts that cannot
render the card receive the same facts in words and an authenticated Apostra
chat link to the Library.

The assistant can find promoted audio with `search(kind: "creative_asset")` by
passing the advertiser ID in `filter.advertiserId`. To read one with `get`, pass
the asset ID as `id` and the same advertiser ID as `sourceId`. Every read
rechecks the customer, principal, and advertiser; the asset ID grants no
authority. You can save an advertiser-scoped draft with
`formatKind: "audio_hosted"`, its URL-free canonical `formatParams`, and the
uploaded asset in `sourceAssets`. Finalization does not create or attach a
Creative. Product fit is checked later when you attach the draft or use it in a
media buy. Existing provider-managed audio adoption is unchanged. Rejected or
abandoned quarantine objects never become public delivery assets.

<Warning>
  Upload finalization is not campaign association or destination delivery.
  Assemble a promoted image, MP4, or inspected audio asset through
  `save_creative`. Before choosing a product, name its URL-free canonical
  `formatKind` and `formatParams`; do not substitute a legacy format reference.
</Warning>

Managed MP4 upload is generally available to authenticated Buyer Accounts where
its upload and inspection services are configured. Advertiser-owned WAV and MP3
upload remains an alpha available only to enrolled Buyer Accounts. Enrollment
can be disabled without changing ad-platform credentials. JPEG and PNG
eligibility remains independent.

## Add creative in the Creative Library

Open the advertiser or campaign **Creative Library** and select **Add creative**.
The upload flow opens inside the Library. When it finishes, the Library opens
the appropriate creator with the file in its first slot. Add a name,
click-through URL, and format, then save the creative. Use **More** for
**Upload** when you only want to add a reusable file, or **New creative** when
you want to start from a file already in the Library.

An authorized buyer can also assemble an advertiser-owned Creative before a
campaign exists. A finished upload keeps its original filename as its display
name.

To save a draft, either name a canonical format such as image, hosted video, or
hosted audio, or pick a product to use its seller format.

The **Assets** tab contains reusable files uploaded directly to the Library.
Files inside existing Creatives are not listed there. The **Catalogs** tab lists
connected catalog feeds; it is separate from Creative collections.

Files already inside existing Creatives stay with those Creatives and do not
appear in **Assets**. A file you upload stays private to you until you use it
in a Creative.

In the Library's **New creative** flow, select an uploaded file first. The
Product picker defaults to **Compatible with this asset**, with **All products**
and search by product or seller available when you need to compare options.
Each row names the seller and canonical format, and lists the hard requirements
that matter for the file: media type, hosted-file or VAST/VPAID delivery, file
type, dimensions, duration, container or codec, and file-size limit where the
seller declares one. A row explains why the selected file fits or does not fit.
Selecting a product collapses the list and moves focus to its Format and slots
step; choose **Change product** to return to the list. If no product accepts the
file, choose **Upload another asset** to return to the Library uploader, or
**View all products** to inspect every available product and its constraints.

The Library reads each product's buyer-visible canonical format options and
verifies the selected option again before showing its slots. Select a compatible
asset and place it with **Place selected** or drag it into the slot. **Done**
makes one advertiser-scoped `save_creative` call and shows the authoritative
Creative readback. You can select **Show preview** there to load the exact saved
revision, then return to **All creatives** to find the Creative once. It does
not attach the Creative to a campaign or contact a provider.

Name the format in one of two ways. Before choosing a product, select the
canonical format family and its parameters, such as `image` with a size or
`video_hosted` with a duration. If you have already chosen a product, enter its
exact opaque Product ID, including the `sf2:` prefix, and the Library resolves
that product's buyer-visible format option and slots. Select a compatible asset
and place it with **Place selected** or drag it into the slot. **Done** makes
one advertiser-scoped `save_creative` call and shows the authoritative Creative
readback. It does not attach the Creative to a campaign or contact a provider.
When you later attach the Creative or use it in a media buy, Apostra checks
that its format kind fits the selected product. It does not yet compare format
parameters such as image size or duration at that step.

The Library checks file type and size from the durable asset record. For a
promoted MP4, it also checks the verified container, video and audio codecs,
exact or ranged duration, width, and height before enabling placement. A
mismatch or missing inspection fact is rejected before save. Formats that
require text, audio, HTML, nested groups, aspect ratio, or another unsupported
property remain visible with the reason they cannot be saved there. Use the
agent workflow for those formats and supply every required canonical slot
explicitly. The Library never falls back to the legacy global format catalogue.

## Compose a campaign draft

Choose **Compose** in a campaign's Creative Library to open the Creative
composer Task. Pick uploaded assets, fill the format's slots, set the draft
name and click-through URL, then select **Save creative**. The preview updates
while you work, and the save keeps the draft in that campaign's Creative
Session. This assembly flow is available whether or not Creative Engines is
enabled.

Ask for copy or other changes in the host chat. The composer has no separate
change bar, so it reloads the session after chat updates it. A seller's
rejection or requested change can open the composer with that note pinned.
Saving this draft does not create delivery, attach it to a campaign, or make it
live. Use the existing **Attach to campaign** picker from the saved Creative's
**Used in** section when the draft is ready.

Agents on the shared V3 connector can open the same Task with
`open_creative_library` using `lens: "composer"` and the campaign ID. A
composer launch always requires `campaignId`; add `sessionId` only to resume a
specific editable Creative Session. This lens is buyer-only; seller accounts do
not receive it. Without a session ID, Apostra creates a durable assembly
draft with the campaign's single supported composer format. For an entitled
Buyer Account with a connected engine, the draft records that connection;
otherwise it has no engine binding. It does not call a Creative Engine.
If the campaign has no single supported composer format, the draft starts as a
Snap Story and the Task says to change the format in chat. Hosts that do not
render MCP apps receive an Apostra chat link with `creativeAction=compose`,
the campaign ID, and the session ID when one was selected.

The composer leaves asset assembly and save controls available when generation
is unavailable. If Creative Engines is off, it tells you to ask your Apostra
account team to enable it for the Buyer Account. If Creative Engines is enabled
but this draft has no connected engine, it tells you that generation needs a
connected Creative Engine on the draft. Generation also needs any applicable
provider setup described in [Creative Engines](/v2/setup/v3/creative-engines).

## Turning an uploaded image into a creative

Pass the finalized JPEG or PNG `scope3-asset://` reference verbatim as
`sourceAssetRef` to `save_creative`, plus the matching `advertiserId` or
`campaignId`. Campaign-scoped adoption derives the owning advertiser from the
campaign.

For an eligible reference:

```json theme={null}
{
  "campaignId": "cmp_123",
  "name": "Loetje image hero",
  "sourceAssetRef": "scope3-asset://v1/123e4567-e89b-42d3-a456-426614174000"
}
```

Use either `campaignId` or `advertiserId`, not both. The upload must be
finalized and owned by the same customer, authenticated principal, and
advertiser. The server copies the reverified JPEG or PNG bytes into the
governed Creative asset store and persists only that durable asset plus a
one-way source fingerprint. It never stores the opaque source reference,
private object path, or upload/delivery signed URL on the Creative.

For a campaign, creation happens in the advertiser library first and the
existing campaign-membership operation runs second. Retrying the same source
and name returns the Creative already created and completes a missing campaign
attachment, without replaying campaign-scoped create side effects. The private
upload can then expire without emptying the Creative.

<a id="turning-an-uploaded-video-into-a-creative" />

## Turning an uploaded media asset into a creative

Call `save_creative` with either the advertiser or a campaign and one
`sourceAssets` binding for every required slot. There are two ways to name the
format: use a signed, product-bound `creativeFormatId` when a product has
already been selected, or, for an advertiser-scoped Creative before choosing a
product, use the URL-free canonical `formatKind` and optional `formatParams`.
The
second path has no product binding. When you later attach the Creative or use
it in a media buy, Apostra checks the canonical format kind and parameters
against each product. The result is per product: the Creative either fits,
fits with named adaptations (`resize`, `letterbox`, `trim`, or `transcode`),
cannot be confirmed because facts are missing, or cannot fit. A Creative without
the required size, duration, codec, or container in `formatParams` needs an
inspected uploaded asset that establishes that fact before Apostra can
confirm its fit. A failed fit names the closest declared option and the difference,
such as a 3-second audio file against a 15–30-second requirement. An attachment
declares the required adaptations but does not perform them. Supply required copy
and destination slots through `social` and `clickUrl`. For a composition that uses
`sourceAssets`, ordinary `assets` are unbound and cannot satisfy the selected
format's required slots.

## Format parameters

For an advertiser-scoped Creative that names `formatKind` directly, pass only the
URL-free `formatParams` keys listed for that kind. An unknown key is rejected.
The list comes from the same format constraints that validate the save.

* `image`: `width`, `height`, `sizes`, `min_width`, `max_width`, `min_height`, `max_height`, `aspect_ratio`, `max_file_size_kb`, `image_formats`, `ssl_required`
* `html5`: `width`, `height`, `sizes`, `min_width`, `max_width`, `min_height`, `max_height`, `max_initial_load_kb`, `max_polite_load_kb`, `host_initiated_subload`, `max_animation_duration_ms`, `max_cpu_load_percent`, `mraid_required`, `mraid_version`, `om_sdk_required`, `clicktag_macro`, `backup_image_required`, `backup_image_max_size_kb`, `ssl_required`
* `display_tag`: `width`, `height`, `sizes`, `min_width`, `max_width`, `min_height`, `max_height`, `supported_tag_types`, `max_redirect_depth`, `backup_image_required`, `om_sdk_required`, `ssl_required`
* `image_carousel`: `min_cards`, `max_cards`, `card_aspect_ratio`, `allowed_card_media_asset_types`, `card_image_max_file_size_kb`, `card_video_max_duration_ms`, `ssl_required`
* `video_hosted`: `duration_ms_exact`, `duration_ms_range`, `aspect_ratio`, `orientation`, `min_width`, `max_width`, `min_height`, `max_height`, `video_codecs`, `audio_codecs`, `containers`, `max_bitrate_kbps`, `max_file_size_mb`, `captions`, `om_sdk_required`
* `video_vast`: `duration_ms_exact`, `duration_ms_range`, `aspect_ratio`, `orientation`, `vast_version`, `vpaid_enabled`, `vpaid_version`, `simid_supported`, `linear_required`, `skippable_after_ms`, `max_wrapper_depth`, `ssl_required`
* `audio_hosted`: `duration_ms_exact`, `duration_ms_range`, `audio_codecs`, `audio_sample_rates`, `audio_channels`, `max_bitrate_kbps`, `loudness_lufs`, `companion_image_required`
* `audio_vast`: `vast_versions`, `vast_version`, `media_file_requirements`, `duration_ms_exact`, `duration_ms_range`, `skippable_after_ms`, `max_wrapper_depth`, `ssl_required`, `companion_image_required`
* `audio_daast`: `duration_ms_exact`, `duration_ms_range`, `daast_version`, `linear_required`, `max_wrapper_depth`, `companion_image_required`, `ssl_required`
* `sponsored_placement`: `supported_catalog_types`, `min_items`, `max_items`, `fanout_mode`, `required_catalog_fields`, `supported_id_types`, `hero_asset_supported`
* `native_in_feed`: `title_max_chars`, `body_text_max_chars`, `cta_max_chars`, `cta_values`, `main_image_sizes`, `icon_size`, `max_image_file_size_kb`, `image_formats`, `ssl_required`
* `responsive_creative`: `headlines_min`, `headlines_max`, `headline_max_chars`, `long_headlines_min`, `long_headlines_max`, `descriptions_min`, `descriptions_max`
* `agent_placement`: `platform_extensions`
* `coordinated_placements`: `platform_extensions`
* `seller_rendered_stateful_display`: `platform_extensions`
* `custom`: `format_shape`

For a repeatable group, bind every member with the same zero-based instance:
`cards[0].media`, `cards[0].headline`, then `cards[1].media`,
`cards[1].headline`. Put uploaded image, video, or audio members in
`sourceAssets[].slot` and text or URL members in `social.components[].slot`.
Every required member must be present for each instance, and indices must be
contiguous from zero.

Meta-style hosted video with a required thumbnail:

```json theme={null}
{
  "advertiserId": "900",
  "name": "Loetje video",
  "formatKind": "video_hosted",
  "formatOptionRef": {
    "scope": "product",
    "format_option_id": "meta-feed-video"
  },
  "sourceAssets": [
    {
      "assetRef": "scope3-video-source://v1/123e4567-e89b-42d3-a456-426614174000",
      "slot": "video",
      "makePrimary": true
    },
    {
      "assetRef": "scope3-asset://v1/223e4567-e89b-42d3-a456-426614174000",
      "slot": "thumbnail"
    }
  ]
}
```

Use an exact `formatOptionRef` from the buyer-visible product catalogue when a
product is already selected. Otherwise name the canonical `formatKind` and
its parameters directly; this does not invent a product or a legacy format
reference. The
MP4 must be promoted, every source must belong to the same customer,
authenticated principal, and advertiser, and every required slot must be
present. With `advertiserId`, the save writes an advertiser-owned Creative
without attaching it. To attach that existing Creative later, open it in the
Creative Library from the advertiser view. Its **Used in** section lists the
advertiser's draft, active, and paused campaigns and marks the ones that can
take it. A campaign can take the Creative when at least one of its sellers
accepts the Creative's format. Campaigns whose flight has ended, campaigns with
no sellers yet, and campaigns whose sellers could not be checked are listed but
cannot be selected. In chat, asking to put a Library creative on a campaign sets
the same membership through `save_creative` with `campaignIds`, or through
`save_campaign` with `creativeIds`; the V2 `attach_creatives_to_campaign`
operation performs the same attachment outside the Library. With `campaignId`, `save_creative`
additionally requires the option on that campaign's products and attaches the
Creative. A Library MP4 remains
`destination_required` until the governed V2
`attach_creatives_to_campaign` operation finds a compatible package whose
selected destination is Meta. At that point Apostra rechecks the current
buyer, advertiser, Meta account authorization, Library asset, and source
revocation before Meta receives the existing MP4. Other destinations remain
unsupported for this Library bridge and require no second upload.

The same uploaded source can be used in multiple named Creatives while it is
retained. Retrying the same name and exact composition returns the existing
Creative. Images are copied into governed Creative storage. A hosted MP4
capability expires 90 days after source approval. Meta delivery renews that
capability from the durable Library asset only while the exact source is still
active and has not been revoked. Private source references, private storage
URLs, and signed upload capabilities are never returned as provider input.

## Legacy provider-bound video adoption

An MP4 finalized by the shared V3 buyer Task is owner-bound and must use the V3
composition path above. The legacy V2 operation below is not a fallback for it.

For a video uploaded through the legacy provider-bound Task, finishing returns
the `source_uid` for the source that passed inspection. That same provider
scope can adopt it without assembling a creative around a hosted URL by hand:

```http theme={null}
POST /api/v2/buyer/creatives/adopt-managed-video-source
```

```json theme={null}
{
  "source_uid": "0f8b7a2e-6cf4-4db4-9a7f-09db4430ae91",
  "advertiser_id": "900",
  "storefront_id": "7",
  "name": "Loetje hero cut",
  "campaign_id": "cmp_123"
}
```

`campaign_id` is optional. Include it and the creative is attached to that
campaign in the same call; omit it to keep the creative advertiser-scoped. The
campaign and source must match the same advertiser and provider ownership.

Attaching also pushes the campaign's live media buys to their sellers. That push
is best-effort and is not part of what the response confirms: a success means the
creative is attached to the campaign, not that every seller has accepted it into
its packages. Check the media buys themselves to see where the creative is live.

The V2 response returns `creative_id`, `source_uid`, `delivery_url`,
`publication_uid`, `name`, and `created`. A success that named a `campaign_id`
means the creative is attached to that campaign whether or not this particular
call attached it, so a retry safely finishes an interrupted attachment.

**You name the source, not a URL.** The hosted HTTPS URL is issued server-side
and resolves the exact file that passed inspection, so the private storage
location is never something you supply or can substitute.

**Adopting the same source twice returns the creative that already exists**,
with `created: false`, rather than making a second one. If you include a
`campaign_id` the second time, the existing creative is attached to that
campaign — so a call that was interrupted after the creative was made can be
retried safely. Retry with the same `name`: adoption never renames an existing
creative, so asking for a different one is refused rather than silently
returning a creative that does not have the name you asked for.

Names compete among an advertiser's creatives that are not currently scoped to a
campaign — which is how adoption always creates them. If one of those already
holds the name, adoption is refused rather than silently reusing it. A creative
created directly under a campaign is outside that set while it stays there, but
detaching it moves it in: detaching does not free a name, it claims one. Attaching an adopted creative to
a campaign afterwards does not release its name either: archiving it does, and so
does renaming it with a separate `update_creative` call. If the creative holding
the name is already attached to a campaign, rename it instead — renaming frees the
name without disrupting a creative that's live in a media buy. `update_creative` is campaign-scoped, so a creative attached to no
campaign can only be archived. Note that archiving is the advertiser-scoped
delete (`archive_creative`); the campaign-scoped `delete_creative` only detaches
the creative from that campaign and does not release its name. Duplicating an
adopted creative with `duplicate_creative` copies the video, not the adoption:
the copy is not the adoption of record for that source, so it neither blocks nor
lets you re-adopt the source through it.

A `CONFLICT` on `name` does not always mean "pick another name". Each one
carries a `details.reason`: `name_creation_in_progress` means another call is
still writing that exact name, and retrying the identical request is the right
move; `name_creation_abandoned` means that writer died mid-way, so archive the
incomplete creative first; `name_taken` means the name belongs to a finished
creative and you need a different one. The conflicts on `source_uid` work the
same way — `adoption_in_progress` is the only one worth retrying.
`adoption_name_mismatch` means the source already has a creative under another
name; use that name or rename the creative separately. `source_delivery_revoked`
and `source_deletion_requested` are terminal for that source: upload a new video
before adopting again. Branch on `details.reason` rather than on the message text.

Two rules here are enforced by the database, not just checked: a managed video
source can back only one live creative, and advertiser-scoped creative names are
serialized across both adoption and ordinary creation. Both hold no matter how
many callers race, and archiving frees the name.

<Warning>
  The `delivery_url` is not permanent. It stays valid while the source is
  retained and its delivery has not been revoked. Once the retention window
  ends, or once the delivery has been revoked with nothing live replacing it,
  re-adopting that source is refused — with a message telling you what
  happened, not a name collision. Requesting deletion of the source refuses it
  too, by taking the source out of the adoptable state. None of these can be
  refreshed in place: upload a new video and adopt that new source, usually
  under a new name, since the existing creative still holds its old video until
  you archive or rename it. A deletion request also stops a call that was only
  trying to finish an interrupted attachment — a source you have asked us to
  erase is not attached to anything further.
</Warning>

If the source is already held by a creative but adoption can no longer confirm
that creative still carries the video — because its asset was replaced or
removed directly on that creative — adoption refuses with `CONFLICT` rather than
creating a second creative for the same source. Archive the creative holding it
and adopt again, or upload the video again and adopt the new source.

A second `CONFLICT` also tells you to archive, for a different reason: an earlier
adoption of this same source left its creation marker uncleared and has been
stuck long enough (about fifteen minutes) that nothing is still writing it. An
asset row may already have landed, but the creative never finalized. Its
`details.reason` is `adoption_abandoned`; archive that incomplete creative and
adopt again.

Within these three source-identity conflicts, `adoption_in_progress` is the one
you should simply retry. It means another adoption is still finishing or the
blocking adoption changed while this request was classifying it; retrying lets
the request observe the settled state. The separate
`name_creation_in_progress` reason described above is also retryable. Branch on
`details.reason`, never on the message; the other reasons require archiving or
changing the request first.

Legacy adoption produces a creative and may enqueue existing V2 campaign sync
behavior; its success response proves neither provider creation nor acceptance.
Only exact provider readback confirms destination materialization. For the
production-dark managed-audio contract, see [Adopt a managed audio
source](/v2/buyer/creatives/tasks/adopt-managed-audio-source).

Adoption is available wherever direct video delivery is configured — it is part
of the generally available managed-video capability, not a separate account
opt-in. A request made while the delivery service is unavailable is refused
with `CAPABILITY_NOT_SUPPORTED`.

## Uploading HTML5 banner ZIPs

Some ad tools export one HTML5 banner as a ZIP containing `index.html` and its
scripts, styles, images, fonts, or video. The V2 buyer API keeps a
recognized package together as one HTML5 creative.

The entry point may be `index.html` at the ZIP root or, for a single-folder
export, `banner/index.html`. All creative files must be inside that one folder;
multiple entry points or multiple enclosing folders are rejected. Creative file
contents and relative asset paths are preserved, and the entry point is recorded
for delivery. macOS metadata (`__MACOSX`, `.DS_Store`) and hidden directories
such as `.vscode/` are ignored when identifying creatives. Retained files in hidden
directories must still use supported file types. `.DS_Store` and AppleDouble
resource-fork entries (`._*` under `__MACOSX/`) are removed from the delivered ZIP,
so their names cannot exempt retained content from the supported-type check.
ZIPs without those metadata entries keep their original bytes. SVG and MP4 files
are supported inside the bundle. All input metadata counts toward file-count
and decompressed-size limits before removal. The campaign file-upload UI also
ignores hidden directories when expanding files; ordinary non-hidden
subdirectories remain available.

A retained HTML5 bundle may contain at most 100 files. Both its ZIP bytes and
its total decompressed contents must fit within 10 MiB, and its entry HTML
must fit within 1 MiB. The V2 backend allows at most 200 total entries in each
ZIP, including folders. Metadata counts toward these limits even when it is
ignored for creative recognition or removed before delivery.

The entry HTML must declare exactly one unambiguous size:

```html theme={null}
<meta name="ad.size" content="width=300,height=250" />
```

Replace `300` and `250` with the banner's actual positive integer pixel
dimensions. Dimensions are not inferred from the ZIP filename, CSS, images,
video, or a separately supplied landing URL. Missing, invalid, or conflicting
`ad.size` declarations produce that specific requirement. A readable ZIP that
fails the creative contract is reported as an import rejection, not as a corrupt
or unreadable archive. Traversal paths, duplicate paths, symbolic links,
unsupported files, ambiguous entry points, and oversized bundles remain blocked.

V2 also recognizes wrapper exports containing one nested HTML5 ZIP, an exact
exploded copy, and an explicitly named same-size backup image. The nested package
keeps its creative files and relative paths. Its removable macOS metadata is
stripped using the same cleanup described above, so its ZIP container bytes may
change. In V2 Murph, an attachment used on a later turn is retrieved
from the account's saved attachment pool before inspection. If those bytes cannot
be retrieved, the error says the attachment was unavailable and that its archive
contents were not inspected. Inline files and saved attachments share the
existing 50 MiB total chat upload budget when V2 Murph retrieves a batch. A batch
that cannot fit returns that limit explicitly before creative inspection; use
fewer or smaller files in the call.

**Claude/Apostra connector availability:** attaching a ZIP to a Claude
conversation does not itself upload it to Apostra. The V3 `save_creative`
surface and managed-media upload Task do not currently adopt HTML5 ZIPs. Use the
existing V2 buyer upload flow for HTML5 ingestion; a connector with only V3 tools
cannot perform that upload. An unavailable upload path is not evidence of archive
corruption. Support should identify the connector surface and the actual upload
result before diagnosing the file.

Importing and delivering are separate checks. Before media-buy sync, an HTML5
creative needs an explicit landing-page URL in its click-through URL field. A
placeholder clickTag or missing destination is a delivery concern, not an archive
integrity failure. A seller option whose HTML5 behavior requirements cannot be
proved from inspection is rejected with the blocking requirement named.

## Uploading tag sheets

Murph accepts creative manifests in CSV, legacy XLS, and XLSX. CM360/DCM tag
sheets can include instructions before the data table and several alternative
tag implementations for one placement-tag row; Murph finds the actual header
and creates one creative per populated display row rather than attaching every
display alternative as a duplicate.

For display, Murph prefers the iframe/JavaScript implementation and uses
JavaScript or Standard as fallbacks. It retains every populated display, VAST
pre-fetch, and Internal Redirect alternative as inert source provenance. Inline
third-party markup is stored as a non-trafficable custom creative without
rewriting vendor macros. Murph does not mislabel it as canonical `display_tag`
(which requires a hosted tag URL) or as an HTML5 bundle. A sheet containing only destination-sensitive alternatives is
recognized but not converted into an arbitrary primary creative. VAST version
must match the destination player, while Internal Redirect requires separate
image and click URL fields in a compatible ad server. When modern and legacy
sheets contain the same placement name and size, the modern row takes
precedence; unmatched legacy rows are retained.

Declared dimensions such as `300x250` are retained for destination compilation.
Murph can also read dimensions from common tag markup, including HTML
attributes, CM360 inline styles, and `sz=` parameters. A dimension declared in
the sheet takes precedence over a size inferred from markup.

### Preview and improve the mapping

Use the [Inspect a Tag Sheet skill](https://api.interchange.io/skills/inspect-tag-sheet/SKILL.md)
to preview a CSV, XLS, or XLSX file locally before upload. Its offline inspector
returns the parser version, mapping-profile version, mode, creative-row count,
canonical role and rule IDs, and diagnostic codes. The receipt contains no
filename, worksheet name, source header, placement name, tag, URL, formula, or
cell value.

The mapping profile is versioned. When the production parser can read a
workbook but finds no usable rows, Apostra automatically records the same
content-free receipt. When that shape recurs across customers, it sends one
deduplicated, content-free signal for team review. Corrupt files and size-limit failures are recorded for diagnosis but do
not automatically become product work. Destination-sensitive rows are
recognized and require a compatible destination before trafficking. A team
member or authorized agent reviews the signal, reproduces it with synthetic or
explicitly approved sanitized data, adds a regression fixture, and publishes a
new profile version.

If local inspection finds a plausible but incorrect result, such as the wrong
row count, the skill can prepare the same sanitized receipt for support. It asks
for confirmation before filing and never sends the workbook or its contents.
Customer workbooks do not enter the shared test corpus by default.

## Uploading before you've named an advertiser

You don't have to know the advertiser first. If you drop a file at the start of
a conversation, Murph **stashes** it and holds onto it across turns. The moment
you name (or pick) an advertiser, the stashed upload is adopted into the
"which campaign?" picker — nothing is lost between the upload and the decision.

## What happens to your creative

* The asset is stored against your advertiser and, once routed, attached to the
  chosen campaign.
* A creative saved to the advertiser (rather than a campaign) waits there until
  you assign it — it is not sent to any seller until it's on a live campaign.
* Attaching to a campaign makes the creative eligible to enter delivery; it
  must still clear any [storefront and source-system approval gates](/v2/buyer/creatives/approval)
  before it can serve. Saving to the advertiser or stashing does not begin that
  process.

## Related

* [Creative](/v2/object-guides/creative) — the creative manifest model
* [Creative tasks](/v2/buyer/creatives/tasks/index) — manifest task reference
