Skip to main content
These workflows require a Seller Account. In v3, “Seller Account” is the operator workspace; a Storefront is the buyer-facing selling surface it operates.

Inspect seller activity

Open Activity under Operate in Storefront, in the left navigation. The destination is available while the Seller Account is being set up and after it goes live. It shows the account’s Calls and Changes without sending a new chat message.

Add a Library document from Apostra chat

In Apostra chat, attach a PDF, PowerPoint, Word document, Excel spreadsheet, or CSV and ask the assistant to add it to the Library. A document larger than the chat attachment limit uploads directly from the browser through the same private-storage flow as the Library Page. Each document can be up to 100 MB and arrives as its own Draft. If a document exceeds 100 MB, choose a smaller file and try again. Open the Draft in the Library to review and save it. A file that names another publisher or an older brand still arrives as a Draft; the assistant records a review note there instead of declining the upload.

Operate sponsored self-serve client campaigns

When a client operates a self-serve advertiser sponsored by your Seller Account, select it in Managed campaigns and open Campaigns. You can edit the same campaign fields that the client can edit, including campaign details, flight, budget, and lifecycle actions available in Campaigns. The client remains the campaign owner. Each change is recorded in the campaign’s history and your Seller activity as Edited by <your seller organization> (operator), so both sides can see that your organization made the edit. This authority applies only to that active sponsored client and its linked Storefront; it does not apply to external, delegated, or System advertisers. System advertisers remain read-only.

Give a client access to an advertiser

To let a person at a client company run campaigns on an advertiser in your Seller Account, read the advertiser with get({"kind":"advertiser_access","advertiserId":"..."}). It lists the person with access, what you can do with them now (availableActions), and whether you can invite someone (invite). An agent does not change client access: the changes are made on the advertiser’s Advertiser settings page, so send the person there to invite, approve, suspend, reactivate, or revoke someone, or to change their role. Only an organization administrator signed in directly can make these changes, and the read is the only source of what is allowed. See Invite and manage a client’s access.

Begin with readiness

Call get_status before changing configuration. Its blockers explain why work is stopped before a buyer request reaches an inventory source and provide a typed tool or Page for the next action. Do not infer demand from source traffic alone. A Seller Account that is not ready may correctly have no source calls because work stops earlier.

Confirm a Sales Agent product mode

Use save_agent to confirm or correct how a Sales Agent supplies products. Send the Agent ID, one product mode (WHOLESALE, COMPOSING, or BOTH), and a reason:
An administrator of the Agent’s owning organization updates the Agent-wide declaration and omits sourceId. Supplying sourceId for an owner confirmation is rejected rather than changing the binding or Agent declaration unexpectedly. An administrator of a Seller Account may instead correct only that Seller’s own bound Source when the Agent is unclaimed. sourceId is optional when exactly one Source is bound. If several Sources use the same Agent, the refusal lists their Source and connection IDs; repeat the call with the exact sourceId returned by the inventory-source read. This prevents a correction from selecting another Source. That correction does not affect any other Seller. Once an Agent is claimed, its owner controls the declaration; Seller requests are refused and direct the caller to the owner. BOTH is valid for a Seller’s binding; its existing product-path selection remains the binding’s lane selector. If a previous seller correction selected one mode, changing it to BOTH keeps that mode as the lane selection. BOTH is refused when the binding has no selection to retain.

Read and configure Seller identity

The Seller Account is a singleton, so read it without an ID:
The projection distinguishes:
  • configuredCapabilities — the declaration you saved; and
  • capabilities — what buyers can use after source topology and approval policy are applied.
These values may disagree. capabilitiesLocked and capabilityDerivation explain why. save_seller writes the declaration under the same name the read returns it, configuredCapabilities; the effective capabilities are derived and cannot be saved. Send capability patches rather than replacing the whole set:
setupIntent describes how the Seller Account is intended to connect. It does not apply a capability preset or change the capability flags. Declare each supported capability explicitly through configuredCapabilities; then read the Seller again to see the effective values derived from source topology and policy. Changing operatorDomain may invalidate profile fields curated for the old identity. If populated fields would be cleared, the tool refuses the write and requires confirmOperatorDomainProfileReset: true after you review the impact.

Edit the listing

The listing is what buyers see on Apostra and in every channel: the description, the channels and countries you accept, a one-line subtitle, and your website, support, privacy policy and terms of service links. The ChatGPT app listing and its submission package read these values; they are not entered separately per channel. Send only the fields you are changing in save_seller’s listing:
Links must use HTTPS. Send null to clear any of these fields; a cleared website falls back to the one in your brand.json. ChatGPT shows the first 30 characters of the subtitle. Read the values back with:
Changing the description, subtitle, website or a link changes the ChatGPT app listing, so approve that listing again before you generate a new submission package.

Admit an external buyer and inspect delivery

The buyer declares its Advertiser footprint before it connects. brandCountries and channels are the countries and channels the Seller listing is qualified against; they are not display-only labels. The buyer then uses save_connection to connect that Advertiser to the selected Seller.
In the Seller Account, read the counterparty roster explicitly:
Each relationship carries relationshipState, the status the API decides for it: needs_review, waiting_external, buying, rejected or new, who owes the next step, and the first item that needs the Seller. Follow a returned relationship with get to obtain the current admission state and, when it is awaiting the Seller, the exact intake ID and version:
Only a seller-controlled pending intake can be decided. Admit it in a separate save_seller call, preserving the version from that read:
Use decision: "decline" with a non-empty message to refuse an intake. External-authority and delegated requests are reported as such and cannot be decided by this Seller. A successful seller decision can still await ready-to-invoice evidence before an active grant is available; re-read the relationship rather than assuming that it is immediately eligible. Open the existing relationship-scoped Media Buys Page to inspect the admitted buyer’s execution and delivery:
The relationship ID is checked against the current Seller Account before the Page opens. A foreign or unknown relationship opens nothing. For a buyer company that transacts through a company-owned seller account, pass that account’s sellerAccountMappingId instead of accountRelationshipId; the two are never combined. It is checked the same way, and a foreign or unknown account opens nothing. sellerAccountMappingId is the mapping’s own ID, not a seller’s Account id. Its earlier name, sellerAccountId, still works and is being retired. Review, source mapping and information requests for the buyer’s account happen in the Buyer Account Mapping Page.

Add and diagnose inventory

Use save_inventory_source for typed source configuration. For an existing modular source, pass moduleConfig with its seller-local moduleInstanceId to update one module’s non-secret configuration, sending config as a list of { key, value } entries whose keys are the module contract’s dotted field keys; save source fields in a separate call so each write has an unambiguous outcome. The object form of config is deprecated. Credentials and OAuth grants stay in the relevant Page rather than model-visible arguments. To connect to a partner’s shared sales agent, pass only its agentId. The call opens the partner connection form, where you enter the partner’s fields and credentials, and creates nothing itself. To connect an ad server, pass only adServer with its type. The call opens the Connect ad server form and creates nothing itself; for Google Ad Manager it also returns the service account email to add to your network. See Connecting an ad server. Read a source with connection and diagnostic evidence:
Connection and health answer different questions: usable credentials can exist while every call fails. Diagnostics follow the source’s actual connection. If the source has no supported diagnostic marker, the include is reported as unsupported instead of guessing another source or agent ID. For a modular source, request its module roster and lifecycle coverage with:
The response places a compact moduleRoster before the detailed modularReadiness projection. moduleRoster.modules is the complete attached module list and carries each module’s instance ID, kind, name, contract ID, and status; supportedStages lists the lifecycle stages covered by the attached modules. A null contract ID means the contract could not be resolved — it does not mean the module is absent. The detailed readiness projection can be large, so treat an explicit truncation notice as evidence that only a prefix of that detail is visible.

Testing an ad-server connection

get contacts no ad server. Every connection fact it returns — the adapter block, health, sync state — is state recorded earlier, and none of it is evidence that the connection works right now. For an ad-server-backed source the read makes that explicit in adServerProbe: when the connection was last probed, what that probe recorded, and null when nobody has ever probed it. A connection reporting connected with adServerProbe.lastTestedAt of null has never been checked against the live ad server. Probing is a live call with a real cost, so it belongs to a person rather than to an agent acting alone. Open ad_server_diagnostics and run Test connection there:
The Page reports the probe’s own result, and the probe updates the recorded state whether it passed, failed, or could not reach the ad server. On FreeWheel the same probe is what observes an execution currency, so a source that has never been probed cannot declare one. On AdsWizz the same probe reads your agency’s own currency (GET /agencies/{agencyId}). A source whose agency currency has never been read is withheld from buyers as Not buyable. Apostra runs that probe for you within fifteen minutes, and Test connection starts it now. The agency’s answer decides what the source can sell in; the currency typed on the connection is never treated as proof. Three outcomes need different action:
  • The probe fails. The Page reports the failure: AdsWizz rejected the API key, refused the agency, or could not be reached. Reissue the key or fix the agency it is scoped to. Nothing about the currency changes until the probe passes.
  • The probe passes but the agency names no currency. The key can list advertisers but cannot read the agency record. Ask AdsWizz to grant read access to it, and Apostra’s next probe records the currency.
  • The agency reports a currency your storefront does not settle. Change the storefront’s payout currency, or use an agency that books a supported one. Apostra never converts between them.
A currency limit left over from before your agency’s currency was read (for example a USD limit on a ZAR agency) withholds the source until it is removed. Apostra removes it for you within fifteen minutes, unless a live buy is still denominated in that currency; the limit then stays, so that buy keeps running, and the source stays withheld until the buy ends. On SpringServe there is no account-level currency to read, so the currency you declare is the evidence. Declare it with the source’s execution-currency action, which changes the declared currency without replacing your credentials, and choose one your storefront settles. Apostra carries that currency to your first booking, where it reads the demand tag’s own currency back; if the two differ, the buy is refused and its campaign taken down rather than sold at the wrong price. Until you declare a currency the source stays Not buyable. An ad-server-backed source has two identifiers — its sourceId and its ad-server connection id (managedSa.connectionId, used as esaId on Pages). get accepts either and tells you which it resolved from, so holding one is never a dead end. Common Pages include:
  • connect_ad_server for credentials and OAuth;
  • connect_sales_agent to choose one of your organization’s sales agents or a certified partner’s to connect;
  • ad_server_source for connection management;
  • ad_server_diagnostics for sync history, refresh, and the connection test; and
  • source_diagnostics for call-level investigation; see Source diagnostics Page.
For a modular source, modular_source_setup, modular_inventory_source and modular_inventory_feed open its setup, readiness workspace and feed commit; see Modular source Pages.

Create products and declare Prebid

Create, change, archive and delete a wholesale product on an ad-server source with save_wholesale_product. Declare that the Scope3 Prebid module runs on a product with its prebidIntegrationActive field. See Create and manage seller products and Declare the Scope3 Prebid module on a product.

Save instructions for a buyer and brand

save_advertiser_instructions writes notes, a country scope and a discount for one operator and brand pair. See Seller buyer instructions.

Manage publisher coverage

Coverage separates declarations from evidence:
  • declared domains and properties are operator claims;
  • discovered properties remain evidence from publisher sources; and
  • authorization is a verdict derived from the current roster and proof.
Read coverage with:
Each domain also carries status, the one-word roll-up of whether Apostra may sell it: authorized, unauthorized, pending (not resolved yet) or error (the latest adagents.json lookup failed). It is null when the lookup found no conclusive evidence either way, such as a missing or invalid adagents.json. It is the same value the setup checklist shows for the domain, so you do not need to work it out from adagentsStatus and authorizationStatus. Use save_coverage to add or remove one domain or property without rebuilding the entire roster. Domain declarations in one call are applied before property claims beneath them, so a new domain and property may be declared together. Removing publisher-origin evidence is refused; retract only claims you own. Each property is an AdCP Property, so it keeps AdCP’s field names on both the save and the read: property_id, property_type, name, identifiers, tags and, on a claim, publisher_domain. The read adds three fields of ours to the same object: property_key, the key a retraction names; provenance; and authorization.
The save and the read are shaped differently. domains on save_coverage is a list of domain strings, the read’s domains[].domain, and replaces every domain you declared. Claims in declareProperties are a flat list naming their publisher_domain; the read nests properties under domains[].properties. The read also lists domains the platform found rather than you declared; a domains save does not remove those.

Add and inspect seller material

Sellers can select Add material at the top of the Library page to upload a supported file directly. The Library uses the save_material upload lifecycle below and then reads the finished Material back to show its units, pricing marks, and reusable toggles. The other paths remain available: drop material into Apostra chat, or upload it while closing a specific Library request. Use save_material to register teaching material without requiring a first-party Page. Supported source kinds are url, site, upload, crawl_manifest, inline, and history. For an upload, provide sha256 with the declared byte count and media type. The first save returns an authorized, expiring PUT URL and an immutable assetRef. Upload the exact bytes with the returned headers, then call save_material again with the materialId, its expectedRevision, a new clientRequestId, and a source that carries that assetRef; it cannot be attached to a different Material or a later refresh. Each refresh creates a new source revision and retains the prior original and renditions. save_material states what you want in its fields, and one call declares one change. With no materialId it creates a Material. With a materialId it takes one of these, each in its own call:
  • source, with expectedRevision and clientRequestId, adds a source revision. metadata and labels may travel with it.
  • metadata, with expectedRevision, edits the Material. labels need metadata in the same call.
  • isArchived: true archives the Material and isArchived: false restores it.
  • candidateDecisions records decisions on extracted candidates.
  • unitReusable marks slides, pages, or sheets reusable.
  • reprocess: { "sourceRevision": N }, with clientRequestId, asks for a new rendition of that source revision.
  • commitPreview applies a previewed rate card or avails sheet.
If an upload create uses bytes that already belong to a non-archived Material in the same Seller Account, it returns that Material rather than creating a duplicate. The response has alreadyExisted: true with its materialName and materialState. Matching a Live Material changes nothing. Matching a Draft can apply only the request’s explicit displayName or reviewNote; a fallback attachment filename never renames the Draft. This check is isolated to the Seller Account, so another account can save the same bytes. The processing result has two complementary forms. Rendition blocks and visual assets preserve extracted text, page or slide structure, images, crops, captions, layout hints, and source locators for later human presentation. Typed candidates propose structured changes for their destination, such as a format, signal, business rule, or seller identity. The agent retrieves prose passages directly when it composes a response; prices never come from prose.
Read it back with:
Uploaded decks and one-sheets expose unit summaries. A unit summary says whether it is reusable and whether it containsPricing. Every unit starts with reusable: false. Only a unit from an uploaded deck or one-sheet can be made reusable; sites, URLs, crawl manifests, spreadsheets, and dictated Material do not yield reusable units. Use save_material with the materialId and unitReusable, a list of { "unitId": "UNIT_ID", "reusable": true } entries, after reading the Material and the units you intend to mark. Entries are applied in order, and an entry that fails names its position; the entries before it stay recorded. A unit that containsPricing cannot be marked reusable until its price figure is replaced with a rate-card value. A reusable unit keeps layout figures, while its prices resolve from the rate card.

Review and confirm extracted candidates

Extraction returns review candidates; it never changes live selling state. Each candidate names its source Material and revision, stable candidate ID, exact evidence locator and digest, relevance and confidentiality scope, and canonical owner. It contains either a schema-valid proposedMutation or an unwritable reason. Playbook instructions use save_playbook; AI Business Rules use save_business_rules; formats use save_wholesale_product; signals use save_signal; and Seller identity uses save_seller. Do not move a proposal to another tool or remove its materialCandidate provenance. Prose passages are cited directly from the current document revision rather than accepted as candidates. Visual assets pass a separate rights, confidentiality, expiry, media-type, and accessibility gate; accepting a text candidate does not authorize its images. Playbook and AI Business Rules content saves replace their complete active documents. Extraction therefore returns these suggestions as unwritable when it does not have the current complete destination document. Merge the evidence with the current document first, then save the complete result through the named canonical tool; do not confirm a one-line replacement. For example, a Seller-description candidate can return:
destinationOwner and the primary evidence locator remain available for clients built against the first v3 Material response. New clients should use proposedOwner and evidenceItems for the complete typed routing and evidence envelope. After a person or authorized agent explicitly confirms the proposal, call the named tool with those exact arguments. A successful save adds materialReceipt to the response. Reading the Material again projects the bounded receipt with the destination tool and destination identity. Replaying the same confirmation returns that receipt without writing twice. A conflicting or concurrent decision returns an error and makes no additional write. If the recorded destination save succeeds but receipt projection is interrupted, retrying the same confirmation resumes the receipt projection without running the destination save again. While a destination call is running, its dispatch fence is heartbeated and cannot be reassigned. If the process ends without a usable destination acknowledgment, the result becomes indeterminate: do not repeat the save. Verify the canonical destination and reconcile that application. Client cancellation after dispatch does not abandon the owner write; its heartbeat is bounded and stops when the owner settles or the application is marked indeterminate. save_material does not change a typed proposal. Confirm it by calling its tool with exactly the proposed arguments. To save different values, call the tool without materialCandidate and reject the candidate. For multi-part Playbook and AI Business Rules proposals, every requested part must be acknowledged before the candidate becomes accepted. If only some parts are saved, materialPartialReceipt records the completed and pending parts with the owner response and its applicationId. Do not replay the full proposal. Resume only the pending parts through the same typed owner, then call save_material with the materialId and a candidateDecisions entry that accepts that candidate at its sourceRevision, with the verified canonical record still in place. Accepting a destination-owned candidate whose typed save already ran reconciles its application: the server finds it, re-reads the named typed owner, and does not accept a caller-supplied destination, dispatch, or repeat an owner save. The entry reports reconciled: true. Exact concurrent or replayed reconciliation calls return the same receipt. A late owner acknowledgment is stored as destination_saved before Material projection. Projection failures retain bounded retry evidence and resume without calling the owner again. Use save_material with the materialId and candidateDecisions, each entry a candidateId, the sourceRevision that contains it, and a decision whose action is accept, reject, correct, or withdraw, to record review outcomes without applying a destination-owned proposal. A correct decision carries correction.correctedContent, the replacement title and summary, and applies to Material-owned candidates only. Decisions are recorded in order and are idempotent per candidate. An MCP call that still sends correction.proposedMutation keeps working until the next ChatGPT re-approval; it is deprecated. It is not in the published schema.
Advertiser-confidential evidence requires an exact advertiserRef. Any advertiser-scoped candidate is not writable to a Seller-wide record when that record cannot preserve the advertiser scope; confirmation returns an error without writing that record.
A source may carry supplied structure. Each entry of derivedStructure.nodes[].provenance is a { "key", "value" } pair (at most 25 per node, names unique, key up to 100 characters, value text up to 500). Each renditionRefs[].completeness is an object with any of text, structure, tables, visuals and styles, each complete, degraded, unsupported or failed; a facet you leave out is not stated. get returns a source’s provenance as stored, a map from name to text, and completeness as stored. To save a node back, send its provenance as entries. An MCP call that still sends provenance as a map, or a facet outside the five, keeps working until the next ChatGPT re-approval; it is deprecated. Material states are needs_upload, queued, processing, ready, partial, or failed. partial and failed include diagnostics and a safe next step. Unsupported facets are reported as unsupported; bounded or lossy facets are degraded. Neither state is presented as complete or ready. The Product Marketing Page is a v3 Material client. It lists Material with search({ "kind": "material" }), reads each visible record with get({ "kind": "material", "id": "MATERIAL_ID" }), and records Material-owned candidate decisions with save_material. Sellers can select a supported file in the Page; it verifies the type, size, and SHA-256 digest locally, reserves seller-private storage, uploads directly with the expiring PUT capability, and finalizes the exact reservation by adding its source revision. The signed URL and headers are used transiently by the widget and are never rendered, written to model context, or retained as retry state. Supported Page uploads are PDF, PowerPoint (.pptx), Excel (.xlsx), CSV, PNG, JPEG, GIF, and WebP files up to 100 MB. Each Material card shows the canonical processing state and whether original, extraction-diagnostic, and visual-asset extensions are available. Visual inspection loads only bounded descriptors (25 at a time); it never requests the original source-access URL or retains preview capabilities. Pending Material-owned candidates can be accepted, rejected, or corrected in the Page. Corrections are limited to the candidate title and summary and are fenced to the source revision shown. A destination-owned proposal is displayed as a handoff to its named canonical workflow; Teach never converts it into a generic Material write. The Page does not call a separate product-marketing corpus endpoint or infer inventory gaps locally. Candidate collections are bounded. When candidates.nextCursor is present, pass it back as options.pages.candidates.cursor; the cursor is scoped to that Material revision and cannot continue another record. get(material) returns bounded rendition summaries by default. Request large collections explicitly and page each collection independently:

Review live RFP demand

Use search({ "kind": "rfp" }) for the seller’s saved RFP ledger. The Demand Inbox Page applies that same read model: it shows bounded pages of saved RFPs, uses continuations from the search response, and treats missing metrics as unavailable instead of recalculating them in the Page. An imported historical brief appears with origin: imported. Read its RFP with get({ "kind": "rfp", "id": "RFP_ID" }) to see its pair state: whether a response is attached, its commentary, and whether the pair is endorsed. Open one response with get({ "kind": "rfp_turn", "id": "RFP_TURN_ID" }). Proposal Pass reads the immutable turn, including its request, response state, semantic response document, outcome, feedback, representations, receipts, and diagnostics. Feedback and release go through save_rfp_turn, and future revisions through save_rfp; sent turns are not mutated in place. Read aggregate seller analytics with get_rfp_performance. The tool owns metric population, freshness, disclosure, dimensions, and pagination. Per-RFP drill-in remains get({ "kind": "rfp" }), so the dashboard does not compute aggregate rollups locally. While the first rendition is still queued or processing, omit renditionRevision: requested collections return bounded empty pages until an active rendition exists. Selecting a rendition revision that does not exist is still an error. Blocks preserve page, slide, or sheet identity, reading order, tables, source locators, geometry, and extraction provenance. Visual assets preserve their digest, type, dimensions, crop and placement, caption/OCR/alt text, confidentiality, and reuse rights. Original and preview binaries remain out of band: source_access and asset access fields are tenant-authorized signed URLs that expire in at most 15 minutes. Extraction accepts at most 100 MB of source bytes, 5,000 archive entries, 10 MB per expanded archive entry, 50 MB across expanded archive entries, and 20,000 spreadsheet cells. Each rendition is also bounded to 500 page/slide/sheet units, 5,000 semantic blocks, and 500 visual assets. Compressed spreadsheets and PPTX use the same archive expansion ceilings. Reaching a bound produces an explicit failed or degraded rendition while preserving the immutable original. An asset marked requires_approval remains unusable until a durable approval receipt exists; a caller-supplied approval assertion is never authorization. Legacy Product Marketing projections retain their historical outer ready state for compatibility. Their rendition remains partial, with unsupported visual/table facets and a projection diagnostic. Requested rendition includes return bounded empty or diagnostic pages instead of silently disappearing. Upload reservations that are not finalized expire after 24 hours and are cleaned in bounded worker batches. Finalized originals are immutable and are retained with their Material revision; rendition refresh never overwrites or aliases an older object. save_material with reprocess: { "sourceRevision": N } creates a new rendition revision over the same original. Candidate decisions remain separate Material records and never update operational owners such as products, rules, or inventory sources.

Create and revise seller RFPs

Use save_rfp to create an RFP and automatically queue its first immutable request-and-response turn. Poll that turn with get({"kind":"rfp_turn"}) until it reaches ready, passed, needs_clarification, or failed. A ready turn contains a populated response composed from this Seller Account’s active, priced products and canonical creative formats; missing catalog facts produce explicit clarification instead of an empty proposal. See Seller RFP lifecycle for the fields that say what each save_rfp and save_rfp_turn call does, the idempotency contract, lifecycle states, response artifact, search filters, and failure behavior. To add the response you sent for a brief, use save_rfp with responseMaterialId. Send endorsed: true or endorsed: false on the pair to control whether it is offered as a good example on later compositions. Use save_rfp_turn with the typed grade, ledBy and commentary fields to save the pair’s grade and feedback; those fields are commentary on the pair rather than a rewrite of its response.

Answer library requests

Use search({ "kind": "library_request" }) to find gaps the agent recorded while composing, then read one with get({ "kind": "library_request", "id": "LIBRARY_REQUEST_ID" }). Open and close requests with save_library_request. Close a request by uploading the material that fills it or by telling Apostra the material in conversation; the closed request states which path supplied it.

Operate work items and approvals

search({ "kind": "work_item" }) is the unified queue for work waiting on the Seller Account. The returned workItemKind determines its state vocabulary and the fields required by save_work_item. To complete a modular-source follow-up, send its required fields as result entries ([{ key, value }], using the keys the item lists in requiredResultFields); the object form of result is deprecated. When a Buyer saves a draft that uses a source their Buyer Account is not mapped to, Apostra opens one source-account-mapping work item for that Buyer Account and source. Map the account in source setup so the buyer can launch their draft. Re-saving the draft does not add another item. The item closes automatically after the source mapping is in place and Apostra’s source-health check confirms it. For a creative review, the returned work-item id is the exact immutable reviewRef, not the reusable creative ID. Keep that id together with its contentDigest, and pass both back when saving the decision:
If either value no longer matches the version read, save_work_item refuses the decision. Search or get the work item again and review the replacement content before retrying; a stale version is never reported as unchanged.

Read why the automated reviewer held or decided a creative

Every storefront on automatic approval (creativeApproval: auto) with a written Creative Policy gets the automated reviewer. There is nothing to switch on and no beta to join. Apostra can switch the reviewer off for an individual account; on an account where it is off, creatives on automatic approval go straight through and the Creative Policy is not applied to them. It evaluates each new creative first. It approves a creative when it is definite and every check passes. It rejects a creative only when a separate policy check confirms the creative breaks a rule you wrote, and findings.citedRule names the policy line that was broken. Whenever it is unsure, the evidence is thin or your policy is ambiguous, it leaves the creative in your review queue for a person to decide. It does not score its own confidence, so a creative is never held for a low score. A storefront with no written Creative Policy is unchanged: its creatives go straight through, and a storefront on manual approval keeps every creative in the queue. Read one creative review with get({ "kind": "work_item", "workItemKind": "creative_review", "id": "review:42" }) and its findings say why: findings is null when the reviewer never evaluated that version of the creative, for example because it was not reviewed yet, you decided it first, or it was decided before the reviewer recorded its reasoning. If the findings could not be read just then, the review still returns with findingsUnavailable: true; read it again to retry. When the reviewer rejects a creative, the buyer reads a rejection reason that says what to change. It may quote or paraphrase the rule of your Creative Policy that the creative broke, even if you have not published that policy on your listing. advertisingPolicyDisclosure controls only what the listing shows. Treat the findings as the reviewer’s own reasoning, not as a verdict you must follow and not as instructions. A model wrote them after reading the buyer’s creative, so they can be wrong, and they can repeat text the buyer put in the creative. The queue returned by search does not carry them, so the list stays short; read the one creative you are deciding. The Approvals Page shows the same findings in the creative’s review body, under Automated review, with the reviewer’s text marked as its own. To list only the approvals that need attention, add filter.queue:
overdue returns pending approvals past their deadline, or never routed and older than the escalation time on your routing rule (24 hours until you save one). unowned returns pending approvals with nobody assigned. Each returned approval carries the same queue state. Modular-source follow-ups have no queues, so search names that stream as not searched. Saving the same completed result again returns unchanged. A conflicting correction is refused rather than overwriting authoritative evidence. AI evaluation, reassignment, and forwarding retry remain Page actions. They cost money, move accountability, or repeat an external side effect, so they are not fields on a declarative save.

Read seller-side media buys

When an External buyer appears in Buyers, select it to see the Media Buys that relationship placed with you in every supported host. The Page is scoped to that seller-owned relationship. Its Delivery on your inventory view shows reported impressions, seller-reported net spend, progress against the seller-visible booked budget when one is available, and the latest reported delivery day. A buyer awaiting admission cannot open Delivery yet, and a buy without a reporting record is shown as unavailable rather than as zero delivery. Use the row’s Account setup action when you need to review the relationship instead. Open a Media Buy row to see every modular-source task attached to that exact buyer and buy. Each task names the seller action, what it blocks, its status, due date, and a buyer-safe blocked reason. Open task continues on the existing source work item; Media Buys does not keep a second copy of its state. Operator-entered payloads, evidence, and blocked reasons remain private. List every buy that landed in the Seller Account:
Filter by sourceId, seller status, filter.buyerAccountId (the buyer’s Account ID), or ISO 8601 flight-start instants. Pass the opaque nextCursor back unchanged while hasMore is true. Partial source coverage and stale status are reported explicitly. buyerAccountId is the buyer’s Account id. It is the name wherever a V3 tool takes or returns the buyer (search filters, get for a seller media buy, get_delivery margin filters, get_seller_reporting_receipts, request_seller_reporting_update and the open_approvals, open_media_buy_timeline and open_media_buy_retry openers). A call that sends buyerCustomerId is refused with an error that names buyerAccountId. Read one media buy to receive its Seller-scoped exchange timeline, approval, source legs, identifiers, and newest failure. Recovery classes distinguish a transient failure from a correctable or structural one. Add include: ["diagnostics"] for the supporting observation trail. When the media-buy ID alone is ambiguous across buyers, add buyerAccountId.

Query delivery and margin

get_delivery supports two reports:
  • delivery — seller-reported delivery over an inclusive range of at most 90 days; and
  • margin — the cumulative seller spread ledger.
Delivery is seller-reported, not buyer-side measurement. Finality does not by itself make a result eligible for billing. Missing source values remain unavailable rather than being invented as zero. Reporting pages are live per call; a cursor anchors continuation but does not create snapshot isolation.

Review synthetic Demo Storefront delivery

An active, unexpired Demo Storefront can use the same get_delivery operation with report: "campaign_delivery" to review deterministic synthetic campaigns. This is available through MCP v3 and through the matching generated REST operation only after the global AI-10305 public-HTTP launch gate is enabled. This V3 reporting slice does not complete AI-10126: that parent retains Demo Storefront attachment and lifecycle delivery, including its daily canary. The caller does not send a demo flag or tenant ID. Apostra confirms the current Seller Account’s lease before it reads the scenario.
The response names all three synthetic campaigns and includes non-zero USD delivery. Request the immediately preceding complete period with the same dimensions to compare the improving, declining, and stable examples. Every synthetic response includes synthetic.scenarioId, the explicit notice, and authority: "synthetic_demo" on each row. The text response carries the same notice. Synthetic delivery is an Apostra-owned fixture. It does not contact a provider, use credentials, spend money, establish reporting finality, support billing, or justify a commercial decision. When the lease is missing, expired, revoked, or cannot be read, Apostra does not substitute synthetic delivery; ordinary seller campaign-delivery permissions and scope rules apply instead.

Query aggregate RFP performance

Use get_rfp_performance for grouped RFP quality, efficiency, and commercial metrics. It is the shared reporting contract that Seller Analytics will adopt. In this release, call it through /mcp/v3; the existing Seller Analytics page is unchanged. Individual responses, evaluations, feedback, outcomes, and receipts remain on get({ "kind": "rfp" }). The default population is live production RFPs. Drafts and evaluations require an explicit purpose filter, while commercial metrics remain live and released only. See RFP performance for metric formulas, dimensions, privacy thresholds, and immutable cursor behavior.

Test your own supply

Every Media Company can run the no-spend own-supply campaign workflow directly from its Seller Account. In Apostra, select Add advertiser from Advertisers or Campaigns. The shared Advertiser Setup Task creates through save_advertiser, refreshes the Advertiser roster, and can switch straight into the new Advertiser. Sandbox is available to every Media Company. The Task shows Live only when the account has live managed campaigns, which come with Merchandising. An own-supply Advertiser accepts any brand domain. The domain does not need to be registered with AgenticAdvertising.org: when no brand registry or enrichment source knows it, the Advertiser keeps the domain as its brand and takes its name from the Advertiser. A buyer Advertiser still needs a brand the lookup can find. Portable clients can use /mcp/v3 for the same contract. Create a sandbox Advertiser and Campaign, open the organization’s own Seller Products, stage a MediaBuy, add Creative, activate the sandbox Campaign, and inspect bounded delivery. You do not need a related Buyer account or a separately provisioned sandbox entitlement. The server binds the sandbox Advertiser to the organization’s exact Storefront. The sandbox workflow cannot select another Seller’s inventory, create a live Advertiser, or spend money. See Sandbox Mode for the complete sequence and authority boundary. Start with the public Test a Sales Agent skill. Version 1.5 uses the exact evidence names returned by ordinary V3 calls and selects only from the same completed Product result (resultsComplete: true). It keeps the selected Product ID, Seller, Source, Sales Agent, Product query, revision, and pricing option together. If the Product is absent or that provenance changes before save_media_buy, the validation stops without staging instead of replaying a stale ID. The skill creates its sandbox Advertiser with the markets (brandCountries) and channels of the brief it runs, because a Seller account is only set up for an Advertiser whose footprint is complete. After it enables your Seller for that Advertiser, it reads the Seller control plane back. If the readback shows a requiredAction for that Advertiser, or its Seller account setup failed, the run stops before creating its Campaign, reports what is missing, and cleans up without launching. While activation is still in progress the run continues; if a managed Sales Agent still has no Seller account for the Advertiser at launch, it refuses the buy with ACCOUNT_SETUP_REQUIRED and names the step.

Certify the headless demand loop

The versioned v3-seller-demand-loop scenario is the portable certification for Materials → RFP → reporting. It provisions a disposable Seller Account, uses only /mcp/v3 tools, uploads representative source types, confirms typed candidates, composes and revises immutable RFP turns, downloads every proposal representation, exercises Quick RFP caching, reads Sessions and Seller Analytics contracts, then destroys the fixture. The trusted observer reads the request audit ledger independently, rejects v2 MCP traffic, verifies bounded collections, and publishes a credential-free JSON artifact. Run the deterministic staging journey against the deployed canary revision:
The result contains the immutable evidence reference and digest. A failure to provision credentials, reach the canary, render an artifact, or validate an authorized exchange is reported as a failure; the harness never substitutes a local or synthetic pass. For an independent MCP-capable host, create an isolated Conductor workspace and give the host the same machine-readable task. Replace the project id and base commit with the deployed revision being certified:
The external-host prompt must use the supplied /mcp/v3 credential, reject any v2 fallback, record tool calls and artifact digests without tokens or signed URLs, and clean up the disposable fixture. Its transcript is exploratory product evidence; the deterministic artifact remains the certification result.