This alpha V3 MCP contract is available only to enrolled Buyer Accounts. It
adds buyer-owned engine discovery and connection setup, campaign- and
advertiser-scoped session reads, durable session saves, and explicit
generation or refinement. Provider qualification and the end-to-end buyer
journey remain separate work.
partial_success is not false.
A customer-key image engine keeps one direction unless the request supplies its
own variant_axis; an explicit axis keeps its supplied number of directions.
Refining a named parent is also one direction. Audio, video and every other
non-preview branch stay one direction by default. The response returns one leaf
per direction, and each preview leaf uses one daily-cap unit. For an engine-less
draft, Apostra uses the buyer’s compatible active connection first, including a
connected voice provider that holds their voice clones. If there is no compatible
connection, it uses the Scope3-funded engine for the kind when the account is
eligible. Otherwise, the save says whether an image, video, or audio connection
is missing and directs the buyer to Settings → Connections. A failed customer-key
request never switches a generation already bound to that connection to the
preview credential.
For an image draft, set request.variant_count to an integer from 1 through 8
when you want a specific number of directions without writing each one. The
service creates distinct composition directions, adding lighting and mood
variation after the first three. An explicit request.variant_axis.values
always takes precedence. Customer-key engines honour variant_count too. For
the Scope3-funded preview, the service checks that the daily account and global
caps currently have room for the whole set before any provider request is sent;
if not, it returns a cap error and creates no partial set. That check does not
reserve capacity: each direction is reserved individually immediately before
its provider request. Concurrent preview use can therefore leave an admitted
batch with fewer generated directions than requested, but a provider request
is never sent without its own reserved cap unit. variant_count cannot be
combined with partial_success: false: that atomic single-image mode remains
one generation. Complete ad concepts differ: they make
three concepts by default with any engine, and a limit reached part way keeps
the concepts already made.
Omit engine unless the buyer explicitly asks to use their own connected
engine. Put the buyer’s exact visual request in
request.creative_brief.prompt. If the buyer asks for a specific number of
variants, put that number in request.variant_count.
For the first 24 hours after a variant_count release, the Creative Engines
on-call owner reviews preview usage and provider-dispatch records every four
hours. The check passes when every provider dispatch has its own daily-cap
unit, and a refused precheck has no provider dispatch. An admitted batch may
contain fewer provider dispatches than planned during concurrent use. After six
clean checks the manual review
ends. A mismatch or unexplained cap error pauses the Scope3 preview binding
while the owner investigates and rolls back the release if needed.
Bring your own provider connection
Use Settings → Connections when the buyer needs their own provider account, such as a voice clone. Complete the secure setup there; never put a provider token into tool arguments or chat. A connection is the buyer’s authorization to a provider and can have its own provider accounts and advertiser mappings. The service uses a compatible active connection automatically when a draft does not name an engine. In Apostra, select Connections, then Creative tools. This section shows providers for which your account needs to connect its own credential. A provider is omitted only while your account is currently covered by a configured, Scope3-funded preview. Existing buyer credentials remain listed so you can manage or disconnect them. Availability depends on your enrolled preview and provider qualification. In an MCP Apps host, callopen_creative_engines_page to open the Creative
Engines Page. You can ask Apostra to find a registered provider and open
its connection setup there; you start the secure connection yourself.
Discover an engine
Callsearch with kind: "creative_engine". Use query to match an engine or
provider name, filter.ids for up to 50 exact engine IDs, and limit plus the
returned cursor to page results. IDs are positive integer strings.
capabilities
declaration lists modalities, transformer IDs, supported format IDs and
build/preview support when the registered adapter supplies them. get with
the same kind and an engine id returns one record.
Declarations describe support; they are not a live provider health check or
proof of access to every model. Grant status describes your connection state.
pricing: null means this read has not obtained a quote, not that generation
is free. The current supportedFormatIds are adapter format identifiers, not
canonical AdCP format declarations or executable capability IDs.
Discovery currently covers registered Creative Engines visible to the active
buyer. Arbitrary external MCP endpoints cannot be added through this preview.
Paused engines
An engine’s operator can pause it to hold new business. While an engine is paused:- It is hidden from
search,getand the Creative Engines Page, as if it were not listed. save_connectioncannot begin or renew authorization for it.save_creative_session,generate_variantsand refinement against it return theSERVICE_UNAVAILABLEerror code withdetails.reason: "engine_paused"anddetails.retryable: true. Try again later or choose another engine.
Authorize and manage a connection
Useopen_creative_engines_page to open Creative Engines in an MCP Apps host. Its
Creative tools section supports secure connection repair, account selection,
advertiser mapping and disconnect for providers that require your credential.
It does not expose provider credentials, accept an arbitrary MCP URL, quote a
price or start generation. Engines currently funded for your account do not
appear as new connection targets.
Call save_connection with exactly one change. To begin authorization:
target names the engine by its Account id,
engineAccountId. A target.id for a creative engine is refused with a
VALIDATION_ERROR that names engineAccountId. It is a different number from
the engine’s own id: never copy an engine id into engineAccountId. Read
engineAccountId from the engine on any read that returns one, as in the list
below. An Account id that owns no live engine, or that owns something other than
a creative engine, is refused with a VALIDATION_ERROR.
An engine’s own id stays its Storefront id, and every reference to an engine
uses its Account id. So search and get with kind: "creative_engine" return
each engine with its id and engineAccountId beside it, and every other read
names the engine by engineAccountId alone:
searchandgetwithkind: "connection"for a creative engine grant, on the grant’stargetand itsengine, which carry noid;- a Creative Session’s
engine, as{engineAccountId, connectionId}; - the result of
open_creative_engines_page, which names the engine byengineAccountIdand no longer returnsengineId.
missingIds is unchanged.
The result contains a secure authorization handoff. Open it to complete OAuth
or enter a bearer credential, according to the engine’s supported modes. Never
put a provider token into tool arguments. The tool does not return token values.
The browser handoff identifies the registered provider. In the OpenAI
customer-key form, it asks for an OpenAI API key and links to the official
OpenAI API key dashboard. This is an
explicit customer-key connection only; it does not configure a platform-managed
credential or authorize generation spending.
To list your creative grants:
get({"kind":"connection","id":"77"}). It includes safe
credential status, selected account, advertiser mappings and a bounded account list. Use
connectionAccountsOffset with the returned accountsPage.nextOffset for
another account page. Provider metadata and secrets are omitted.
Use the existing save_connection fields with connectionId to reconnect
(authorization), refresh accounts (refreshAccounts: true), select a provider
account (selectedAccountId), map an advertiser (advertiserMapping), or disconnect (state: "removed"). A connection
cannot change its target. Disconnect retries remain available for owned grants
after catalog delisting or preview enrollment is removed.
Creative connections do not accept seller selection, advertiser activation,
media billing, buying policy or Enhanced Reporting.
Signup-restricted credentials cannot use the creative preview.
AudioStack and ElevenLabs require an advertiser mapping before generation. This
selects the provider organization or workspace that pays for the request. Use
advertiserMapping: {state: "mapped", advertiserId: "42", accountId: "88"}
with the connection ID. Read accountMappings to find the link ID for unmapping;
use connectionMappingsOffset with accountMappingsPage.nextOffset for another
mapping page. Replacing the provider key requires remapping the advertiser.
For a voice draft, include the exact spoken words in straight or curly
quotation marks in request.creative_brief.prompt, or provide a
creative_manifest.assets.script text asset. A tone or duration instruction by
itself is not voice copy. When preparing a draft, write a short script in
quotes from confirmed buyer intent, or ask the buyer for the words to speak
before you call generate_variants.
Generation and funding
This alpha provides discovery, setup, session reads and Creative Session operations.save_creative_session saves a brief, selected output, approval or
finalisation, and save_creative with fromCreativeSessionOutput saves an
exact approved output into a reusable Creative Asset revision. Neither starts
generation. generate_variants
explicitly prepares and executes one saved session revision. These are
generation operations and require Creative Engines. Do not choose a
Scope3-funded engine: the service resolves the compatible buyer connection or
funded engine for the media kind when it saves the draft. A connection alone
does not authorize spending. The Scope3-funded preview is the limited exception
described above; its platform-managed engines are paid by Scope3 rather than
the customer’s provider account.
The operations do not provide a request estimate or turn an unknown catalog
price into a quote. For accounts and clients with the existing V2 generation
tools, use the generative creative workflow.
Use the generation workflow’s applicable funding and price information before
requesting paid work. The current catalog’s pricing: null cannot be used as a
quote. Customer-key eligibility and provider account requirements still apply;
see provider setup. A failed
customer-key request does not authorize switching to a platform-funded request.
During iteration, preserve the brief, reference assets, target format and
parent variant. Retain returned session and task IDs so an interrupted request
can resume without submitting another build. Review the exact output that will
be finalized, then read back the saved creative. Content acceptance remains
separate from seller review and campaign launch.
Video generation, and a provider build expected to run for longer, returns a
submitted leaf promptly instead of keeping the tool call open. Use the exact
poll get request returned by generate_variants; it refreshes the same
session and returns the completed variant when ready. Do not submit another
generation action while that leaf is still generating. A task that later fails
becomes a terminal generation failure rather than a fresh provider request.
Complete ad concepts
Complete concepts are in beta. Your Apostra account team enables them for
enrolled Buyer Accounts.
format_option_ref facebook.com / meta_image_feed), the first
generate_variants action makes complete concepts instead of separate parts.
Each concept is one generated image and every text slot written for that image:
primary text, headline, description and call to action for a Meta post.
- How many. Three concepts by default. Set
request.variant_countfrom 1 through 8 for another number, or give one direction per concept inrequest.variant_axis.values, up to 8. - One direction per concept. Every concept has a direction. Its copy and its image are written from the same direction, so the text fits the picture it is shown with.
- One copy call. One language-model call writes the text of every concept
together, so the concepts differ from each other. The writer reads the brief
(
request.creative_brief.promptand the objective), the advertiser’s brand profile (voice, dos and don’ts, disclaimers) and the format’s slot limits. The brief and brand text are sent to a language-model provider to write the copy. - Your text stays as written. Text you supply for a slot is copied unchanged into every concept, and the writer writes only the other slots. When you supply every text slot, no copy is written and each concept differs only in its image.
- Checked, never cut. Apostra measures every slot against the format’s limits. Copy over a limit is rewritten once and otherwise refused; it is never shortened. A call to action comes from the format’s allowed list. The copy never contains a link; the click-through URL comes from your request or the brand’s website.
get and
kind: "creative_session": each entry in variants has its direction, its
image preview and text_assets, the text slots as AdCP text assets keyed by
the format’s asset ID:
generate_variants with variantAction: "feedback"
and that concept as parentVariantId, as described in
Read a creative session. The refined variant keeps
the concept’s text; only its image is regenerated.
Choose a concept in the composer
Choosing a concept is part of the complete-concepts beta. It is available only
to Buyer Accounts your Apostra account team has enrolled in it. Other accounts
do not see these controls, and the tools refuse the request.
get with kind: "creative_session" returns it as
selected_variant_id. Choosing approves nothing; you approve the finished ad in
the composer. You can choose a concept on a campaign’s draft or on an
advertiser’s draft with no campaign.
After you choose a concept:
- Edit the ad. Changes to slot text, images and the click-through URL are saved. The brief, the format, the concept directions and the draft’s name stay as they were when the concepts were generated; start a new draft to change them.
- Rewrite one slot. Generate on a text slot rewrites only that slot, with the chosen concept’s direction and your other slots as context. It replaces text you wrote only when you choose Generate on that slot, and it uses one copy call from the daily copy limit.
- Replace the image. Pick a Library file to replace the concept’s image.
Limits and partial results
During the preview, copy is free. A Generate action uses one copy call, and copy has its own daily limit: 100 calls per account and 1,000 across all accounts per UTC day. Each concept’s image uses one unit of the image limits described above.- Copy limit reached. No concept is made and no image is generated. The error names the limit and the time it resets, the next UTC midnight.
- Copy could not be written. If the language model fails or does not answer in time, no image is generated and the error says why. Sending the call again starts over and uses another copy call. While a call’s copy is still being written, repeating that same call is refused and asks you to try again in a minute, and a new action on the session is refused as still running.
- Image limit already reached. No copy is written and no concept is made. The error names the limit and the time it resets.
- Image limit reached part way. Apostra keeps the concepts whose images
were made and stops the rest. The session’s
noticessays how many of the requested concepts are ready and when the limit resets. A concept without its image is never shown as a finished ad. If no image could be started, the action returns the same message as an error. - Finish the rest. After the reset, repeat the same
generate_variantscall with the sameactionKeyandexpectedRevision. Each waiting concept’s copy is already saved, so the retry generates their images and writes no new copy. A new action on the session leaves the waiting concepts unfinished.
When an engine is ready for buyers
A Creative Engine is ready for buyers when it is listed, not paused and not archived. Nothing else is required. Unlike a sales storefront, an engine does not need inventory, products, a brand domain or a settlement currency, because it generates creative and does not sell media. An engine never appears in media-buying (sales) discovery, and it is never admitted to a media buy. Usesearch with kind: "creative_engine" to find it;
get_products and seller connections list sales storefronts only.
Existing seller integrations
search({"kind":"connection"}) continues to list seller grants by default.
Seller grants carry sellerAccountId, the seller’s Account id. Send
sellerAccountId, or target: {kind: "seller", sellerAccountId}, to
save_connection. sellerId and a seller target.id are refused with an error
that names sellerAccountId. See
Naming a seller by Account id.
You can continue making creative directly with a provider and
bring that creative into Apostra.
This alpha does not change that workflow or start a generation job without an
explicit generate_variants call.
Read a creative session
A creative session holds work in progress: the brief, plan, variants, evaluation, selection and any finalized creative. Reading it does not start generation. Creative sessions are where generated variants live; they are separate from product variants in a catalog and from saved Library creatives. To find a session by name, list the scope below and match the session title in the results; text queries are not supported. Then open its variants withopen_variant_gallery.
List sessions within exactly one campaign or advertiser Library:
filter.advertiserId instead:
filter.status to choose drafting, refining, evaluating or finalized.
The limit is at most 100. Each result identifies its scope with either a
campaignId or an advertiserId, alongside its session ID, title, status and
revision. When nextCursor is present, pass it as cursor with the same
campaign-or-advertiser scope and status filter. Each page reflects current
state; the list does not promise a fixed snapshot or a total count. Text
queries and other filters are not supported for sessions.
Open a session by ID alone. Apostra resolves its saved owner and checks your
current access again:
sourceId for a
campaign or advertiserId for a Library session. The object includes its
persisted revision, plan, variants and evaluation.
truncated: true and omitted identify the
bounded fields; use the existing V2 workflow for the complete durable record.
When the session has a saved engine connection, engine contains its
engineAccountId and connectionId. Credential material is not included. A
draft names its engine the same way: send engine as
{engineAccountId, connectionId}, so the engine object from a read can be
sent back unchanged. A draft that sends engine.engineId is refused with a
VALIDATION_ERROR that names engine.engineAccountId.
Hosted image, audio and video plans can identify their format with
format_kind (image, audio_hosted or video_hosted) and params, without
an agent URL. Image dimensions use params.width and params.height;
an exact audio duration uses params.duration_ms_exact. The declared modality
must match the selected engine. A canonical declaration cannot also specify a
legacy target format. Explicit legacy format requests retain their existing
compatibility path.
Video on the Veo engine is generated by Google’s Gemini Omni Flash model.
The requested video duration is a target rather than an exact output length;
the generated media reports its actual duration when it is available.
When you do not set config.resolution, video generation uses the requested
height or short side: 360 pixels or less selects 360p, 1080 pixels or more
selects 1080p, and 2160 pixels or more selects 4k. Other sizes select 720p. Set
config.resolution to 360p, 720p, 1080p, or 4k to choose a different
output resolution explicitly.
Use the canonical fields directly in a draft request. For an image draft:
agent_url in this declaration. It is a legacy-format field.
For common image requests, aspect_ratio is also accepted either beside
format_kind or inside params; it is saved as canonical dimensions. For
example, "9:16" becomes { "width": 1080, "height": 1920 }. Use explicit
dimensions for a ratio that is not supported. A draft save rejects an
unmappable ratio before it can be generated.
An image draft that names neither dimensions nor an aspect ratio is planned as
a square 1024 by 1024 image, and the saved plan’s params shows that size.
Every funded image engine accepts it. A draft that names only a width or only a
height keeps what it named, and its generation is refused until it names both.
Drafts saved before this default existed keep their saved request and are
refused the same way; save a new draft with a size to generate from them.
When preparing image placement renders, the session uses each placement’s
resolved dimensions. A placement without dimensions returns a warning instead
of generating at the original image size. These renders still need the
applicable seller compatibility checks before delivery.
Session reads do not support include. Both reads require current access to
the campaign and advertiser, and expired sessions are unavailable.
save_creative_session saves a campaign or advertiser brief and locked
references, selects or approves one exact output, or finalises that approved
output. Saving does not start generation. generate_variants is the separate
explicit action for one saved revision. To save an approved output into a
reusable Creative Asset revision, call save_creative with
fromCreativeSessionOutput. Reuse its actionKey only for an identical retry; name the
parent output and feedback when refining. Both operations require the Creative
Engines account capability; the service saves a compatible connection or funded
binding with the draft. Generation can
remain submitted or uncertain while its original task or receipt is recovered;
it never silently submits a replacement request.
A session runs one generation action at a time. To refine a variant after a
generation, send variantAction: "feedback" with a new actionKey, the
session’s current expectedRevision, the variant as parentVariantId and
your feedback. The refined variant records its parent in
parent_build_variant_id and has status refined. The parent becomes the
session’s selected_variant_id, and the new variant its refined_variant_id.
A refined Meta image post keeps its parent’s text and click-through URL; only
the image is regenerated. A new action is refused while a variant of the
previous action is still generating; poll with get and send it once that
finishes. An actionKey the session has already used is refused, so a retry
never buys the same work twice. The one exception is the session’s current
action: repeating its call exactly, with the same expectedRevision, returns
that action. Once a later action replaces it, its key is refused for the rest
of the session; use a new key for new work. A session takes at most 256
generation actions; create a new session after that.
Every save_creative_session request names its owner with exactly one of
campaignId, for a campaign session, or advertiserId, for an advertiser
Library session. The other fields say what the request saves:
Each request makes one change: send exactly one of
draft, selectedVariantId,
approvedVariantId or finalization. To select an output and then approve it,
make two requests, sending the expectedRevision the first response returns to
the second. A request that sends more than one of them, or a field that
belongs to a different change, is refused with an error naming the fields, and
saves nothing.
Each response includes the current session, revision, and, when present,
sessionGeneration. A draft response also includes
nextGenerateVariants: a copy-ready generate_variants call with the saved
revision and session generation plus a fresh action key. Use that key for the
first request and reuse it only if that same request needs a retry.
Generation returns its actionId and each leaf’s status, task ID, and variant
IDs. For every completed variant, completedVariants includes an asset URL,
MIME type, preview and, when the completed asset reports them, its dimensions,
so an MCP client can use the image without a second lookup. poll is returned
only when a submitted leaf has a task ID. A leaf marked uncertain or
not_dispatched is terminal for that action: it is not retried automatically.
An uncertain leaf may already have incurred a provider charge. The response
states this terminal condition and, when no leaf completed or was submitted,
is an error result; create a new generation action after correcting the
request instead of polling.
A not_dispatched leaf was refused before any provider was contacted, so no
provider charge occurred. When the refusal is one Apostra can name, the leaf
carries a failureReason: creative_pre_provider_invalid_format when the
requested format cannot be generated, for example an image with no size, or
creative_pre_provider_invalid_request when another part of the request is
invalid. When every leaf was refused for one of these reasons, the error
message says so and says how to fix the request, for an image by setting a size
or aspect ratio such as params.width and params.height. Without a
failureReason, the message stays general.
Approval and finalisation controls remain in the session. Session detail is bounded in these responses. When
sessionTruncated: true is present, sessionOmitted identifies omitted
detail, while the returned action and retry controls remain exact. The durable
session history is not paginated by these operations; use the existing V2
creative-session workflow to retrieve the complete record.
If already-persisted action controls alone cannot fit in one response, the
operation returns a bounded recovery error instead of clipping those IDs. Read
the durable session before retrying the same saved action; this does not create
a replacement generation request.
Promote an approved output
Usesave_creative with fromCreativeSessionOutput after the buyer has
approved the exact session variant. Supply advertiserId (the Library that
receives the asset), campaignId (the campaign that owns the session), an
idempotencyKey, and fromCreativeSessionOutput with the sessionId,
approved variantId, sessionRevision that recorded the approval, and the
outputHash of the approved output. Send nothing else: this request saves the
approved output on its own, and it does not attach anything to the campaign. The service resolves the approved rendition from the durable
approval, so callers do not pass an internal rendition ID: the image for an
image output, or the audio track for a voice or audio output. A companion image
generated beside a voice track is not the approved deliverable and is not
promoted in its place. The response has action: "promoted" and contains the Creative Asset ID,
immutable revision ID, SHA-256 digest and approval receipt ID.
Approved audio joins the advertiser’s Library through managed audio storage,
the same inspection an uploaded WAV or MP3 file receives. Until that inspection
finishes, the response has action: "promoting" and a
processingState of quarantined, inspecting or approved; no Creative
Asset exists yet. Repeat the same call with the same
arguments, usually after about a minute, to receive the Creative Asset. A
repeat never stores the audio twice. If storing the audio for inspection takes
longer than 30 seconds, the call fails with a retryable SERVICE_UNAVAILABLE
error; repeat the same call. If inspection rejects the audio, promotion
fails and no Creative Asset, revision or receipt is created. Audio approved
before this behaviour existed carries no audio evidence and is refused; approve
the output again, then promote it.
Repeat an uncertain request with the same key and the same input while the
named approval is still current to receive the same receipt without creating
another revision. Reusing a key with a different approved output or advertiser
fails. The service checks the buyer’s
current advertiser access before it saves the immutable evidence and rejects a
stale, replaced or unapproved output.
Promotion retains the approved bytes as an advertiser-owned Creative Asset. It
does not finalise or create a campaign Creative, export the bytes, contact a
publisher, launch a campaign or approve seller inventory. Finalising a Creative
does not promote or export it.
When a buyer promotes an approved output, the revision records where it came
from: the Engine, the provider, and whether it ran on the buyer’s own provider
account or the Scope3-funded preview. That record comes from the generation
call that produced the output, so outputs generated before provenance was
recorded can still be promoted but carry no provider record. Reuse rights follow
that provider’s terms; Interchange does not verify them. Check the provider’s
terms before using the asset outside Interchange. Its current inspection is strict JPEG/PNG
container and active-content validation for images, and the managed WAV/MP3
container inspection for audio. It records
container_validated for that work; no malware scan runs during promotion
today, and clean is reserved for a scanner verdict. The service does not
create immutable evidence when inspection cannot complete.
Promotion uploads the deterministic private copy before its final database
write, but it has no Creative Asset catalog entry until the current advertiser
grant is checked and locked. If that check fails, no catalog asset, revision or
approval receipt is created.
Generate an asset from the Creative Library
When Creative Engines is enabled for your Buyer Account, open an advertiser’s Creative Library, choose the Assets tab, then select Generate. Choose an image, video, or voice asset and enter a short brief. Apostra saves an advertiser-scoped Creative Session, starts generation, and opens the generated variants gallery. You do not choose an engine. The request uses the account’s existing engine-resolution support for its asset type; if it cannot start, it explains the required setup and does not create a Draft. Choose one completed output in the gallery and select Use selected asset. The chosen output is saved as a Library Draft for that advertiser. Review or discard the Draft in the Assets tab before using it elsewhere. Choosing an output does not attach it to a campaign or start another generation request.Review generated variants in an MCP App host
Whengenerate_variants returns a Creative Session with generated outputs, its
MCP App result identifies the ui://agentic-api/variant-gallery/mcp-app.html
resource. To show an existing session after search or get, call
open_variant_gallery with its sessionId; the optional campaignId or
advertiserId asserts the owner, but the operation resolves and checks the
saved owner when omitted. This is the canonical gallery operation. Compatible
hosts render the Variant gallery beside the conversation; hosts without MCP App
support retain the normal text result and asset URLs.
The checked gallery field contains the bounded session state the widget needs:
variants, previews, the selected output, the current revision and the
advertiser identity required to save an approved output.
Each completed image variant is also checked against the exact saved brief.
The gallery shows whether the rendered image passed, needs review, or missed
the brief, with a short reason. A warning does not stop draft review. Ask to
refine a flagged variant when the result identifies a missing requested object
or an unrelated dominant object. A failed brief-match check blocks finalising
that output. The check uses
only the generated image and the saved brief; a temporary evaluator failure is
shown as a warning for human review.
Selecting a variant does not start a generation. Refinement is a separate,
explicit paid action: enter feedback for a selected parent, then choose
Refine selected. The gallery calls generate_variants with that parent and
feedback and renders a cap-exhausted RATE_LIMITED error without retrying it.
Save to library is also explicit. It selects and approves the chosen output,
then saves it to the Library with the exact approval revision and output
digest. That creates an advertiser-owned Creative Asset revision and
returns its identity. A retry uses the same deterministic idempotency key, so
it replays the saved result instead of creating another revision. While
approved audio is still being inspected, the gallery says so and asks the buyer
to choose Save to library again in a minute.
The gallery confirms Saved to your Library. and offers the Creative Library
link without exposing the returned identifiers. It records one bounded,
best-effort completion activity; if that activity cannot be recorded, the
saved state remains visible and the gallery does not retry it.
Saving to the Library does not use the output in a campaign. Use in campaign
is a separate, explicit action that finalises the same exact approved output as
a campaign Creative. If promotion is refused because access, inspection or
rights checks fail, or the approval is stale, the gallery shows the reason and
keeps the approval available. It never auto-generates or auto-saves an
alternative.