Skip to content

feat: creative library protocol — list_creatives, sync_creatives, library retrieval, platform mapping#1339

Merged
bokelley merged 16 commits intomainfrom
bokelley/flashtalking-api
Mar 12, 2026
Merged

feat: creative library protocol — list_creatives, sync_creatives, library retrieval, platform mapping#1339
bokelley merged 16 commits intomainfrom
bokelley/flashtalking-api

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented Mar 6, 2026

Summary

  • Move list_creatives and sync_creatives from media-buy to creative protocol — all creative library operations now live in one protocol. Any agent hosting a creative library implements the creative protocol for both reads and writes.
  • Extend build_creative with library retrieval modecreative_id + target_format_id + optional macro_values returns a manifest with ad-serving tags as assets. Supports both creative-level tags (Flashtalking, Celtra) and placement-level tags (CM360 via media_buy_id/package_id).
  • Add creative agent interaction modelssupports_generation, supports_transformation, has_creative_library capability flags in get_adcp_capabilities so buyers can determine how to interact with a creative agent.
  • New creative-variable.json schema for DCO variable definitions with type support (text, image, video, color, etc.)
  • Platform mapping guide for implementers wrapping existing ad servers (Flashtalking, CM360, Celtra)
  • Universal macro handling — callers always use universal macro names; creative agent translates to platform syntax

Test plan

  • All 323 tests pass (npm test)
  • OpenAPI spec up to date
  • TypeScript typecheck passes
  • No broken links (Mintlify validation)
  • Schema examples validate against their schemas
  • No stale media-buy/sync-creatives or media-buy/list-creatives references in source files
  • Redirects in place for moved doc pages
  • Expert reviews: code reviewer, ad tech protocol expert, docs expert

🤖 Generated with Claude Code

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented Mar 6, 2026

Spec feedback from proxy agent analysis

Analyzed Flashtalking (full OpenAPI spec), Celtra (alpha REST API), CM360 (Google REST v5), and Innovid (limited public docs) against this PR to assess what works for building proxy agents.

1. Rename library_idconcept_id

Across platforms, the browsable creative grouping is not the top-level workspace:

Platform Workspace (= accounts protocol) Browsable grouping (= this field)
Flashtalking CreativeLibrary (advertiser, brand, mediaBuyer) Concept (theme across sizes)
Celtra Account Campaign folder
CM360 Advertiser CreativeGroup

The workspace/account is already handled by the accounts protocol (sync_accounts/list_accounts). What buyers browse is the creative concept — an industry-standard term for a cohesive creative idea across sizes/formats.

Changes: Rename library_idconcept_id, library_nameconcept_name, library_idsconcept_ids in filters, "library""concept" in fields enum. Keep has_creative_library capability name as-is (describes the feature).

2. Add media_buy_id + package_id to build_creative

Most creative agents (Flashtalking, Celtra, Innovid) generate tags at the creative level — no trafficking context needed. But CM360 generates tags at the placement level. When the creative agent is also the ad server, it needs to know which placement the tag is for.

"media_buy_id": {
  "type": "string",
  "description": "Media buy or placement identifier for tag generation. When the creative agent is also the ad server, this provides the trafficking context needed to generate placement-specific tags (e.g., CM360 placement ID). Not needed when tags are generated at the creative level (most creative platforms)."
},
"package_id": {
  "type": "string",
  "description": "Package or line item within the media buy. Used with media_buy_id when the creative agent needs line-item-level context for tag generation."
}

Both are optional strings. Only CM360-style platforms (where creative + trafficking coexist) need them today.

3. Do NOT add html to variable_type — richloads are formats, not variables

Flashtalking's "Richload" variables look like HTML slots, but they're actually asset references (assetId in VersionVariable). Passing raw HTML between agents would be platform-specific.

  • Variable types should stay semantic and agent-producible: text, image, video, audio, url, number, boolean, color
  • Richload/HTML5 packages are creative formats, not variable values → formal format spec (separate work)
  • FT richload variable slots map to image or video in AdCP (asset references) or aren't exposed as variables

Platform readiness for proxy agents

Platform list_creatives build_creative (library retrieval) DCO variables Build now?
Flashtalking GET /creative-libraries/{id}/creatives s3publish at version level GET /creatives/{id}/variables (Text, Image, Video, Audio) Yes
Celtra Gap — no list endpoint for created creatives GET /tags with universal macros Template object properties Yes, with caveats
CM360 creatives.list (excellent filtering) Needs media_buy_id (placement-level tags) dynamicFeeds (lossy mapping) Yes, after spec tweak
Innovid No public API No public API No public API Blocked — need API partnership

@bokelley bokelley changed the title feat: stateful creative protocol — library retrieval and list_creatives move feat: creative library protocol — list_creatives, library retrieval, platform mapping Mar 6, 2026
Comment thread docs/creative/task-reference/build_creative.mdx Outdated
Comment thread docs/creative/task-reference/list_creatives.mdx Outdated
Comment thread docs/creative/implementing-creative-agents.mdx Outdated
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented Mar 8, 2026

Follow-up: sync_creatives on creative protocol + buyer_ref as the creative handle

After further analysis of the end-to-end workflow across Flashtalking, Celtra, and CM360, we think sync_creatives belongs on the creative protocol too — not just media-buy. Here's the reasoning.

The full workflow

1. sync_creatives → creative agent (FT/CM360/Celtra)
   "Here are my assets. Host them for ad serving."
   Each creative_manifest carries buyer_ref, concept_id, assets.

2. list_creatives ← creative agent
   "What's in my library?" Filter by concept_ids, formats, variables.

3. build_creative → creative agent
   creative_id: "abcd"  ← same buyer_ref from step 1
   target_format_id, macro_values
   "Give me a tag for this creative with universal macros resolved."

4. sync_creatives → sales agent (DV360/TTD/Snap)
   "Here are the tagged creatives for this media buy."
   Now carries the tag output from step 3.

5. create_media_buy → sales agent
   "Traffic it."

Why this works

sync_creatives is "push creatives to an agent." The receiving agent's role determines what happens:

  • Creative agent receives it → ingests assets, organizes by concept_id, makes them available for list_creatives and build_creative
  • Sales agent receives it → associates creatives with media buys/packages for trafficking

The schema is the same creative manifest in both cases. No new task needed — just add sync_creatives to the creative protocol task list.

buyer_ref is the stable handle

The buyer sends sync_creatives with buyer_ref: "abcd" on the creative manifest. Later, build_creative(creative_id: "abcd") resolves that same ref. The creative agent maintains the mapping to its internal ID. list_creatives returns both the platform's creative_id and the buyer_ref if one was provided during sync.

This matches how buyer_ref already works in the media-buy protocol — it's the buyer's stable identifier for anything they've synced.

Concepts are just grouping labels

No separate concept CRUD needed. When the buyer sends sync_creatives with concept_id: "holiday_2026" on a creative, the creative agent files it accordingly — auto-creating the concept if it doesn't exist. list_creatives with concept_ids filter handles discovery. Concepts map to platform-specific groupings:

Platform concept_id maps to Auto-created on sync?
Flashtalking conceptId Yes — FT creates concepts when creatives are assigned
Celtra campaignFolderId Yes — POST /campaigns
CM360 creativeGroupId Implicit from creative metadata

What changes in this PR

  1. Add sync_creatives to creative protocol task list in docs.json, specification.mdx, and the task reference
  2. Schema reuse — the existing sync-creatives-request.json / sync-creatives-response.json work as-is. The creative manifest already carries buyer_ref, format_id, and assets. Adding concept_id to the manifest (or using ext) handles the grouping.
  3. Document the buyer_refcreative_id resolution in build_creative docs — when creative_id matches a previously synced buyer_ref, the agent resolves it from its library.

Platform mapping for sync_creatives → creative agent

Platform Asset ingestion Creative creation Concept assignment
Flashtalking POST /images/upload, POST /videos/upload POST /creative-libraries/{id}/creatives POST /concepts/with-assets
Celtra POST /assets (base64) POST /creatives (from template) POST /campaigns
CM360 creativeAssets.insert (multipart) creatives.insert Creative group metadata

@bokelley bokelley changed the title feat: creative library protocol — list_creatives, library retrieval, platform mapping feat: creative library protocol — list_creatives, sync_creatives, library retrieval, platform mapping Mar 8, 2026
Comment thread docs/creative/task-reference/list_creatives.mdx Outdated
Comment thread docs/creative/task-reference/list_creatives.mdx Outdated
Comment thread docs/creative/task-reference/list_creatives.mdx Outdated
@simonsafhalterceltra
Copy link
Copy Markdown

simonsafhalterceltra commented Mar 9, 2026

Celtra | Gap — no list endpoint for created creatives
We recently made our build_creative async since it can take some time to produce creatives.
As a consequence we added a new task get_build_creative_result where the input is a task_id returned from build_creative. We could rename this to list_creatives if that fits the purpose?

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented Mar 9, 2026

@simonsafhalterceltra Great question — you don't need a separate get_build_creative_result task. AdCP handles this at the transport layer.

How async works in AdCP: Any task (including build_creative) can return a working status instead of completed. The caller polls for updates using the context_id from the response — this is built into the MCP and A2A transports. There's no need for a task-specific polling endpoint because the protocol already provides a generic one.

The flow looks like:

  1. Caller sends build_creative request
  2. Agent returns { "status": "working", "message": "Generating creative...", "context_id": "ctx-abc" }
  3. Caller polls context_id for updates (or receives a webhook if push_notification_config was provided)
  4. Agent eventually returns { "status": "completed", "creative_manifest": { ... } }

This is documented in the Task Lifecycle and Async Operations guides.

On list_creatives: This serves a different purpose — it's for browsing/querying an existing creative library (filtering by status, format, concept, tags, etc.), not for retrieving the result of a build. If Celtra maintains a library of created creatives that buyers should be able to browse, then list_creatives is the right fit. If creatives are only produced on-demand via build_creative, you wouldn't need list_creatives at all — the async response from build_creative already returns the manifest.

bokelley and others added 11 commits March 9, 2026 06:04
…val to build_creative

Move list_creatives from media-buy to creative protocol so any agent
hosting a creative library can implement it (creative agents and sales
agents). Extend build_creative with creative_id, library_id, and
macro_values for resolving library creatives into delivery-ready
manifests with ad-serving tags (HTML/JS/VAST).

New schemas:
- core/creative-variable.json (DCO variable definitions)
- creative/list-creatives-request.json
- creative/list-creatives-response.json

Extended:
- build-creative-request.json (creative_id, library_id, macro_values)
- creative-filters.json (library_ids, format_ids, has_variables)
- get-adcp-capabilities-response.json (has_creative_library)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shows how CM360 and Flashtalking concepts map to the creative protocol,
covering tag generation models, macro handling, and which tasks to implement
per platform type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds Celtra template/property model alongside CM360 and Flashtalking.
New "Variable models" section explains how named slots, template object
properties, and rule-based asset selection each map to the protocol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… to build_creative

Based on proxy agent analysis of Flashtalking, Celtra, CM360, and Innovid:

- Rename library_id → concept_id across schemas and docs. The workspace/account
  is handled by the accounts protocol; what buyers browse is the creative concept
  (Flashtalking concepts, Celtra campaign folders, CM360 creative groups).

- Add media_buy_id and package_id to build_creative for platforms like CM360 where
  the creative agent is also the ad server and needs placement context for tag
  generation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Sentence case all headings in build_creative.mdx (~20 headings)
- Fix message parameter description (not the creative brief)
- Trim marketing language from list_creatives opening/overview
- Expand DCO acronym on first use in list_creatives and implementing guide
- Move account requirements before examples in list_creatives
- Fix "Which tasks to implement" table: Required → Core/Recommended to match spec
- Add media_buy_id/package_id mention in CM360 tag generation section
- Remove "centralized" from creative-filters.json description
- Rename "Lightweight list for UI" → "Field-limited query"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dback

Add supports_generation and supports_transformation capability flags to
get_adcp_capabilities so buyer agents can determine the correct interaction
model without trial and error. Document interaction models (transformation
agent, generative agent, creative ad server) in the specification.

Also addresses PR review comments:
- Clarify media_buy_id/package_id are buyer-side references
- Soften accounts protocol language (same protocol, not a separate version)
- Clarify Celtra template terminology in platform mapping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix 6 stale links to media-buy/list_creatives across media-buy docs
- Remove platform-specific macro allowance from macro_values schema
  (callers always use universal macros, agent translates)
- Fix macro table to use bare names (CLICK_URL not {CLICK_URL})
- Fix list_creatives response example (returned count matches array)
- Fix heading casing to sentence case in specification.mdx and
  implementing-creative-agents.mdx (19 + 16 headings)
- Note supports_compliance is orthogonal to interaction models

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…iew feedback

Move sync_creatives schemas and docs from media-buy to creative protocol,
completing the principle that all creative library operations live in one
protocol. Fix conformance numbering, add response time entries for
list_creatives and sync_creatives, fix .mdx link extension in
build_creative, and update changeset description.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix fields enum mismatch: "format" → "format_id" in list-creatives-request.json
to match the response schema field name. Add filter reference table to
list_creatives.mdx. Clarify include_performance (historical metrics) and
include_sub_assets (multi-asset creatives) descriptions. Fix 15 Title Case
headings to sentence case in sync_creatives.mdx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Spec gaps resolved:
- Unknown macro_values keys MUST be ignored (not an error)
- creative_id SHOULD be globally unique; concept_id REQUIRED when not
- delete_missing invalid when combined with creative_ids
- Cross-agent handoff workflow documented in build_creative

Doc improvements:
- Add list_creatives stub in media-buy (consistent with sync_creatives stub)
- Note schema path legacy for build_creative/list_creative_formats in spec
- Document delete_missing + creative_ids conflict in sync_creatives doc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bokelley bokelley force-pushed the bokelley/flashtalking-api branch from b699ae4 to 6ec992b Compare March 9, 2026 10:05
Replace include_performance/performance with include_snapshot following
the get_media_buys snapshot pattern. The snapshot provides operational
data (lifetime impressions, last_served) — detailed analytics belong
in get_creative_delivery.

- Remove performance_score from response, sort enum, and Addie tools
- Rename has_performance_data filter to has_served (names the intent)
- Add snapshot object with as_of, staleness_seconds, impressions, last_served
- Add snapshot_unavailable_reason per creative
- Add errors array to response (consistency with get_media_buys)
- Update docs with delivery snapshot section and library health check example

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@simonsafhalterceltra
Copy link
Copy Markdown

@simonsafhalterceltra Great question — you don't need a separate get_build_creative_result task. AdCP handles this at the transport layer.

How async works in AdCP: Any task (including build_creative) can return a working status instead of completed. The caller polls for updates using the context_id from the response — this is built into the MCP and A2A transports. There's no need for a task-specific polling endpoint because the protocol already provides a generic one.

The flow looks like:

  1. Caller sends build_creative request
  2. Agent returns { "status": "working", "message": "Generating creative...", "context_id": "ctx-abc" }
  3. Caller polls context_id for updates (or receives a webhook if push_notification_config was provided)
  4. Agent eventually returns { "status": "completed", "creative_manifest": { ... } }

This is documented in the Task Lifecycle and Async Operations guides.

On list_creatives: This serves a different purpose — it's for browsing/querying an existing creative library (filtering by status, format, concept, tags, etc.), not for retrieving the result of a build. If Celtra maintains a library of created creatives that buyers should be able to browse, then list_creatives is the right fit. If creatives are only produced on-demand via build_creative, you wouldn't need list_creatives at all — the async response from build_creative already returns the manifest.

Thank you, the first section makes sense.

I have a follow up question about the second part: list_creatives vs list_creative_formats
At Celtra at the moment our Creative Formats are actually templates that users can browse, they are reusable across campaigns so users can replace various components with their own, but also creatives that users can see.

Would in this case list_creative_formats just return the format IDs, and list_creatives the same formats on a public link where the user can browser and see them?

bokelley and others added 2 commits March 9, 2026 20:14
Based on Celtra feedback: platforms support multiple tag types depending
on campaign context, not just one model per platform. Document universal
tags (agency/programmatic), single-placement tags (publisher templates),
multi-placement tags (multiple sizes), and placement-level tags (CM360).
Add decision table mapping use cases to build_creative parameters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add descriptions for each variable_type value. Boolean is a conditional
data flag (e.g., show_discount, is_raining), not an asset toggle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented Mar 9, 2026

@simonsafhalterceltra Great question — you don't need a separate get_build_creative_result task. AdCP handles this at the transport layer.
How async works in AdCP: Any task (including build_creative) can return a working status instead of completed. The caller polls for updates using the context_id from the response — this is built into the MCP and A2A transports. There's no need for a task-specific polling endpoint because the protocol already provides a generic one.
The flow looks like:

  1. Caller sends build_creative request
  2. Agent returns { "status": "working", "message": "Generating creative...", "context_id": "ctx-abc" }
  3. Caller polls context_id for updates (or receives a webhook if push_notification_config was provided)
  4. Agent eventually returns { "status": "completed", "creative_manifest": { ... } }

This is documented in the Task Lifecycle and Async Operations guides.
On list_creatives: This serves a different purpose — it's for browsing/querying an existing creative library (filtering by status, format, concept, tags, etc.), not for retrieving the result of a build. If Celtra maintains a library of created creatives that buyers should be able to browse, then list_creatives is the right fit. If creatives are only produced on-demand via build_creative, you wouldn't need list_creatives at all — the async response from build_creative already returns the manifest.

Thank you, the first section makes sense.

I have a follow up question about the second part: list_creatives vs list_creative_formats At Celtra at the moment our Creative Formats are actually templates that users can browse, they are reusable across campaigns so users can replace various components with their own, but also creatives that users can see.

Would in this case list_creative_formats just return the format IDs, and list_creatives the same formats on a public link where the user can browser and see them?

Creative formats would be a list of the templates you support and would be public. Creatives are the instantiated templates with the user's assets, so not public.

bokelley and others added 2 commits March 9, 2026 22:00
Rename sub-asset.json to creative-item.json (avoids collision with catalog
item schemas). Rename sub_assets → items and include_sub_assets →
include_items throughout schemas and docs. Fix platform_creative_id →
platform_id in sync_creatives docs. Add missing account required field
to sync_creatives docs. Fix response example summaries in list_creatives.
Add assets and tags to per-creative fields table. Clarify fields enum
concept value returns both concept_id and concept_name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ild_creative

Rename formats filter to format_types in creative-filters.json for clarity
(distinguishes high-level type strings from structured format_id references).
Add date variable type to creative-variable.json for countdown timers and
offer expirations. Add default_value encoding guidance per variable type.
Add async support for build_creative (working, input-required, submitted
schemas) — generation and transformation can take 10-30+ seconds. Add
format_summary key construction guidance. Mark assignment-related filters
as sales-agent-specific in schema descriptions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bokelley bokelley merged commit f460ece into main Mar 12, 2026
8 checks passed
@bokelley bokelley deleted the bokelley/flashtalking-api branch March 12, 2026 20:18
bokelley added a commit that referenced this pull request Mar 12, 2026
After rebase onto main (which merged #1339 creative library), resolve
conflict in list_creatives to keep main's include_snapshot property.
Remove sync_creatives and list_creatives from drift test map (no
protocol schemas yet). Add creative_id, concept_id, media_buy_id,
package_id, and macro_values to build_creative tool per protocol schema.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bokelley added a commit that referenced this pull request Mar 12, 2026
…1317)

* fix(addie): sync tool input_schemas with protocol and add drift test

Celtra reported Addie passing brand inside creative_manifest instead of
as a top-level parameter. Root cause: hand-maintained tool input_schemas
had drifted from protocol JSON schemas across all 9 media-buy/creative
tools.

Adds missing protocol properties to every tool (brand, quality,
item_limit on build_creative; account, buying_mode, pagination on
get_products; etc.) and introduces a drift detection test that compares
tool properties against static/schemas/source/ on every test run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve review findings in tool schemas and drift test

- Remove duplicate brand property in build_creative (second silently overwrote first)
- Fix sync_creatives assignments type (object → array of structured items per protocol)
- Add missing time_budget to get_products, disclosure_persistence to list_creative_formats,
  push_notification_config to sync_catalogs
- Remove non-protocol fields from get_media_buy_delivery (granularity, date_range, media_buy_id singular)
- Remove format_types from list_creative_formats (protocol uses type singular)
- Add sync_catalogs to drift test TOOL_SCHEMA_MAP
- Add bidirectional drift detection (tool→protocol, not just protocol→tool)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve rebase conflicts and add missing build_creative properties

After rebase onto main (which merged #1339 creative library), resolve
conflict in list_creatives to keep main's include_snapshot property.
Remove sync_creatives and list_creatives from drift test map (no
protocol schemas yet). Add creative_id, concept_id, media_buy_id,
package_id, and macro_values to build_creative tool per protocol schema.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants