Skip to main content
Deprecated. This page documents the v2 storefront API, which is being retired for external callers. Use Add and diagnose inventory and the Source diagnostics Page in V3 instead.
When your storefront uses a third-party AdCP-compatible sales agent as an inventory source, Apostra can help you answer two different questions:
  • Is the source set up correctly? Check source status, credentials, advertised capabilities, and compliance.
  • What happened on recent buyer traffic? Inspect recent AdCP calls, responses, task statuses, and failures for that source.
Use both. A source can be configured correctly but still fail a specific buyer request, and a failed buyer request can come from a valid business rejection rather than a broken connection.

Start with the connection

The normal Inventory source page starts with the Source name, its connection status, the saved HTTPS endpoint when it is safe to display, whether required authentication is configured, and when it was last checked. It gives one clear next action: refresh the connection when the page asks for fresh evidence. A completed connection refresh is the newest connection evidence on the page. If it passes, the Summary says that it passed even when an older stored health record reported an error. If the older record is all that is available, Summary labels it as stale connection evidence, shows when it was checked, and asks the named owner to refresh it. Summary shows one next action and its owner. Use Technical details for product-path, wholesale, reconciliation, failure, and trace explanations. The page never shows a saved secret. It also does not treat an active Source as provider approval, Agent certification, or permission to transact. Those are separate decisions with their own status and next action. Use Technical details when you need source tests, protocol calls, transport evidence, tool declarations, or a diagnostic handoff. Those details help a developer investigate a problem; they are not routine publisher setup. Technical details also shows Request signing, read from the request_signing block in your agent’s last get_adcp_capabilities response. It lists each category your agent declares: the operations it requires signed (required_for), the ones it checks without rejecting unsigned calls (warn_for), and the ones it verifies when a signature is present (supported_for), including the matching protocol_methods_* names. An operation listed in more than one category appears only in the strictest, following AdCP’s precedence. When the agent states a covers_content_digest policy, the row also shows whether signatures must, must not, or may cover the request body. If the agent declares no signing, the row says so. Request signing is not a credential and has no setting on the Source. Apostra signs the operations your agent lists. See Verifying our signed requests for the keys and the identity to trust. After you change the declaration, refresh capabilities so the page shows it. Setup, request eligibility, and health are separate. Apostra sends a discovery request when the Source is set up, not paused, mapped to an eligible Agent, and compatible with that request. Degraded or erroring health remains visible but does not automatically stop requests. A serious health problem may lead an operator to record a separate, reasoned ineligibility decision.

Source health

Choose Source health on the Inventory source page to see the source health object for that source, one section at a time:
  • Source: the seller account, the source and its connection type, the Agent that powers it and its observed revision, and whether buyer requests reach it, with the reason codes when they do not. Incomplete setup stops requests only until a source is first activated, so an activated source can show Receives buyer requests and Setup incomplete together; the page says so when it does. When setup is incomplete, the steps still to finish are listed under the Setup incomplete badge, and each opens Seller Setup, where you finish it.
  • Health by operation: one row for each AdCP operation Apostra performs against the source, with its status, call counts when they were read, the last success, the last failure, and who acts on it. Who acts appears only when at least one operation needs someone to act, and stays blank on rows that do not. An operation nothing has observed reads Not observed. An operation the seller’s own AdCP capabilities do not support reads Not supported by seller and names the seller as owner, so you can tell it apart from one that has not been seen yet. The seller’s capabilities say so when they declare no media_buy protocol (which covers creating, updating and reporting on media buys) or no creative library (which covers creative sync), or when the live list of tools the agent returned leaves the operation out. A seller whose capabilities Apostra built from configuration rather than read from the agent, or whose tool list came back empty, never reads Not supported by seller, and an operation Apostra has observed or written is never marked unsupported. When a health check rather than a call marks an operation degraded or erroring, for example a stale cache, and the window counted no calls, the row gives the check’s finding as the reason instead of call counts. An operation with no calls in the window says so. An operation that a Blocking or Needs attention finding in Diagnosis is about is at least degraded, even when every call succeeded, because the calls work and what they return is wrong. For example, product discovery reads Degraded while its calls succeed when products declare creative formats their creative agent does not define. The row lists those findings in findingIds, most severe first, and the most severe finding’s summary is the reason for the status. The widget shows a degraded row with findingIds as Needs attention with that summary, whatever its call count. An Advisory finding is informational and leaves the operation’s status alone. When no failing call set the owner, the row’s owner is the most severe finding’s owner. A call that failed keeps its own status and condition. An operation with a finding is never marked unsupported.
  • Diagnosis: each finding with its severity, the operation it is about, who acts, the next step, a troubleshooting link when one exists, and Show calls, which expands the source’s calls for that operation under the finding, within the window the finding covers, with See all calls to open the full list of calls filtered to that operation. A finding that products declare creative formats their creative agent does not define also lists those products under Products affected, with each product’s id and the formats it declares. Those values come from your agent, so the page shows them as data and never inside the finding’s sentence.
  • Open operations: each operation still waiting at this source, who it is waiting on, since when, the subject it is about, and the exact thing it is waiting for. When that thing has its own widget, such as a media buy waiting for its creatives, a button opens it. A creative you have not yet approved is not listed here, because no source receives it before your review; it waits in your creative reviews instead. A buy the source holds for creatives waits on the buyer only while no creatives are attached; with creatives attached it waits on Apostra until they reach the source, then on the source’s review. A buy whose flight has ended and whose buyer’s buy is completed, archived or canceled is not listed. When the diagnostics response does not include open operations, the section says they are not available rather than showing an empty list.
The page does not show write outcomes, because the seller diagnostics read does not return them. The window selector at the top of the page sets the window the counts cover. Times show the time zone they are in.

A timeout is not an error response

A typed timeout means the call did not finish inside the observer’s budget. It does not mean the Source returned an error, is unreachable, or is currently unhealthy. For example, a get_products call can exceed a 30-second buyer window and then complete successfully in 35.7 seconds when observed with a longer 45- or 60-second budget. A fresh test with the same 30-second budget may simply reproduce that budget mismatch; compare it with a 60-second observation before diagnosing reachability. The response exposes the mechanism as condition: "timeout", separately from the diagnostic impact (status: "degraded"). A completed error response uses condition: "error_response"; a connection failure uses condition: "transport_error". Reporting has its own health axis, so an inventory observation timeout does not make healthy reporting unhealthy. None of these health observations changes request eligibility by itself. Human-facing Source Health surfaces use one vocabulary: Healthy, Degraded, Erroring, and Unknown. The sourceHealth object in the response uses the same words: each operation’s status is healthy, degraded, or erroring, and null (shown as Unknown) when nothing has observed it. The deprecated source.health and source.capabilityHealth fields keep the older unhealthy value for wire compatibility; present that value as Erroring. Do not infer the failure mechanism from that status. Read condition: timeout is observer-budget exhaustion and is degraded evidence, while error_response means the Source actually returned an error. Diagnostics keep three evidence types separate:
  • A source test is an explicit sandbox test against the selected source.
  • A source call is an observed protocol exchange with the selected source, such as get_capabilities or an Agent-supplied get_products request.
  • A storefront buyer-path or composition run describes work at the storefront layer. It is not evidence that the selected source was called unless a source exchange is attached to it.

Physical transport evidence

transportRequests is the bounded, redacted record of outbound protocol attempts. It is separate from logical recentActivity and the health verdict: one logical operation can create several physical requests. Requests to every sales agent Apostra calls are eligible for this record, including the one Apostra runs for an ad server source. For an ad server source, destinationOrigin is null because the request goes to an Apostra-internal service rather than a public endpoint. The record can be incomplete. When Apostra’s diagnostics storage is under heavy load, it drops transport rows rather than slow down live requests, so a missing row does not prove that no request was sent. When the record cannot be read at all, unavailable is true and the zero counts are not a measurement. A get_products timeout is recorded as the seller’s only when Apostra’s client opened a request to the source before the deadline. If it never did, the time went on Apostra’s own client setup, and the timeout is recorded as Apostra’s, not the seller’s. Check the request’s dispatchState, latencyMs, and failureDomain before treating a timeout as a seller problem. A seller can answer a call as a stream (text/event-stream): the response headers arrive first and the answer follows, or never comes. outcome, httpStatus, and latencyMs describe those headers, so such a request is stored as outcome: "succeeded" with a latencyMs that is the time to response headers, not how long the call took. An HTTP success on a tool call is therefore not proof that an answer came. When Apostra’s client reports how the stream finished, the same request also carries:
  • streamCompletion is how the stream ended: ended (the seller finished it), errored (the stream failed), aborted (Apostra stopped waiting, for example at the call’s deadline), or cancelled (Apostra closed a stream it had finished with, including after a successful answer). A stream Apostra aborted reads aborted here even though its httpStatus is 200.
  • streamCompletedAt, streamDurationMs, and streamBytes are when the stream ended, how long after the request started, and how many body bytes had arrived by then.
These are null (or absent) for a request with no stream to finish, such as a reply that arrived in one piece or a request that failed before any headers, and for requests recorded before Apostra started recording stream ends. A stream that is still open has no end recorded yet. Read the rest against the call itself:
  • logicalOutcome is what this exact call recorded (matched by its operation or task id, never by timing): its terminal status (null when no terminal result was recorded), whether it timedOut, and its durationMs. It is null when no matching call is in the sampled window.
  • summary reads an HTTP-success request whose call did not answer in plain words. When the stream’s end is recorded as aborted or errored it says so directly: “Response headers arrived after 200ms (HTTP 200); the stream was aborted after 25.9s with no answer.” Otherwise, and for older requests, it reads the call: “Response headers arrived after 200ms (HTTP 200), but the call timed out after 25.9s with no answer.” It is null when the call answered or there is nothing to add. A call younger than two minutes with no result yet is not called unanswered, because it can still be running.
  • sessionTerminateMissing is true for such an unanswered call whose session was never closed by a successful session terminate while another session in the page was. This is the signature of a call that hung after its headers arrived. A terminate that never completed, or that failed, does not count as a close.
logicalOutcome, summary, and sessionTerminateMissing are derived when you read the page. The stream fields are recorded as the stream ends. outcome keeps its four values. For media-buy creates and updates, groupId identifies the application operation and each SDK session keeps its own debugId. Local stages show setup and whether mutation dispatch was attempted. physicalRequestCount excludes local stages; logicalOperationCount counts accepted operation envelopes. If mutation_not_dispatched is present, Apostra stopped locally and no remote mutation request or response was recorded. This does not establish that the remote media buy is unchanged. An authoritative readback is required. This distinction matters for no_ingredients: a composition run can stop at the storefront before it calls the external sales agent. That failure must not be shown as a failed source call or source test. On the Agent-supplied product path, the storefront sends the brief to the external agent’s get_products; that call uses the agent’s finished products and does not require ingredients.

Read seller readiness without combining unrelated results

For a source linked to an Agent, Source Diagnostics shows three separate results:
  • Availability on Apostra says whether the storefront can currently transact. Resolve any listed blocker in Seller Setup.
  • Agent certification applies to the Agent’s current revision. If it needs attention, open the Agent page and complete its certification work. A stale certification does not by itself change the availability result shown here.
  • Source health is the latest observed condition for this exact source. When it is degraded, erroring, or unknown, open Setup and refresh capabilities before changing the source. That refresh checks the endpoint, credentials, and declarations; it does not prove product discovery or a media-buy lifecycle.
Use validation when you need new no-spend evidence for the exact source. A successful source check is not a replacement for the storefront’s own availability decision or for current Agent certification. If the source has no organization-owned Sales Agent, Source Diagnostics offers the Create Agent Task for a self-operated replacement and an Apostra support route for a Partner connection. A revoked Partner mapping cannot be restored from this page: Apostra coordinates that cross-organization connection. For external sales-agent sources, also separate live source health from component-cache health. Live health answers whether Agent-supplied discovery or media-buy calls reached the source and completed. Component-cache health answers whether Apostra can read the source’s raw products/components for merchandising without calling the source during buyer-request composition. Cache complaints usually mean the source does not support a usable component response, returned invalid/missing product metadata, has an errored or cold cache state, or cannot refresh because of auth or reachability. Stale-but-usable cached rows are not automatically a failure; they are valid composition inputs when the storefront allows hard-stale OK cache reads. Soft-refresh age is handled automatically by Apostra’s stale-while-revalidate cache path; it does not create a seller action item. A cache complaint does not by itself mean live the Agent-supplied product path is down. Apostra does not make wholesale product or signal catalog requests to every external sales agent. They run only when the Source’s explicit product contract includes the Storefront-built path and the Agent reports AdCP 3.1+ support. An Agent-supplied Source focuses on live buyer-request activity instead of component-cache readiness. Only wholesale products are composition inputs; ordinary finished products are not reclassified as ingredients. Managed ad-server Sources have a fixed wholesale contract because their raw inventory must be turned into products. Neither product path is separately monetized. When a seller-operated source stays unhealthy long enough to trigger a proactive Apostra alert, the alert links directly to this diagnostics surface with the affected source selected. Open that link first, then use the recent activity and developer handoff sections for the partner-safe debug ID, task ID, operation ID, and correlation headers behind the failing calls.

Endpoint

GET /api/v2/storefront/inventory-sources/{sourceId}/diagnostics Returns seller-facing diagnostics for one third-party sales-agent or modular inventory source. The response combines the current source setup, recent AdCP activity, test-run evidence, latency rollups, error rates, discovery participation, demand impact, and developer handoff identifiers. New outbound calls include a partner-safe x-scope3-debug-id that also appears in the diagnostics handoff. Besides the credentials and headers you configured for the source, those calls carry only x-scope3-debug-id, x-request-id, and traceparent. Apostra does not send a buyer’s session or workflow identifiers to your agent. If a source configuration repeats one of those header names, the Scope3-generated correlation value takes precedence; quote the debug ID from the diagnostics handoff when contacting support.

Buyer exclusion-list impact

discoveryParticipation reports a bounded, source-owned account of products removed from anonymous buyer discoveries because one of that product’s properties matched the buyer’s exclusion list. When present, advertiserPropertyExcludedProductCount is the number of products removed in the selected window and advertiserPropertyExclusionRequestCount is the number of affected discovery requests. advertiserPropertyExcludedProducts lists up to 25 recent product IDs, names, and matching domains. advertiserPropertyExclusionDataAvailable is false when this auxiliary projection cannot be read. Treat its counts as unavailable rather than zero in that case. This derived evidence is retained for 31 days. That preserves the maximum 30-day diagnostics window while keeping the seller-visible observation store bounded. The retention worker removes observations after that window. Observations also cascade away immediately when their inventory source is deleted, but not when a discovery session is deleted. This is diagnostic evidence, not a routing or eligibility decision. It does not reveal the buyer or advertiser, any other entries in the buyer’s list, or other sellers’ products. The matching domains are only the intersection with this Source’s own product properties. demandImpact repeats the bounded affected-request count as buyerRequestsAffectedByAdvertiserPropertyExclusions for operational impact reporting.

Canonical reporting evidence

The response’s reporting field is an independent projection of the canonical reporting ledger for this exact Source. It does not derive reporting health from generic Source activity or copy one Source’s result onto its Agent.
  • availability says whether canonical reporting evidence is supported or unsupported. It is null when no value was observed.
  • health uses waiting, healthy, delayed, action_required, and complete.
  • sourceProvenance: acquired appears only when an immutable acquisition plan proves the Source attribution. null means that proof was not observed, not that the Agent lacks reporting capability.
  • obligations preserves period, expected time, required finality, coverage, missing-first-report state, issue ownership, and latest revision evidence.
  • lastSuccessfulRevision, dataThrough, nextExpectedAt, and freshness describe the most recent usable reporting state.
  • Truncation flags mark bounded obligation and related-media-buy collections. A response that exceeds the obligation-row limit returns nullable aggregate reporting state instead of projecting health from partial evidence.
A reporting-ledger read failure does not erase otherwise-valid Source diagnostics or change inventory health. In that case reporting returns a nullable projection. The action descriptors are read-only integration seams and remain unavailable with reason: adapter_pending until their typed adapters are connected.

Local synthetic Demo evidence

A code-owned demo-* source under the authenticated Storefront’s active, unexpired Demo lease uses local synthetic verification. Its persisted recipe is the source evidence, so Seller Setup reports all nine required source steps ready and Source Diagnostics labels it Local synthetic. No vendor endpoint, credentials, protocol trace, capability refresh, or live source test exists for this path. This classification is read-only. It does not create, extend, or reactivate a Demo lease, change capabilities, or publish synthetic inventory. A missing or expired lease, a different tenant, or a source without a demo-* recipe uses the ordinary live-source verification model. To run a direct source test, use the shared storefront operation run_inventory_source_discovery_test, backed by: POST /api/v2/storefront/inventory-sources/{sourceId}/tests/discovery This source-scoped operation calls the selected external sales agent’s get_products with a read-only brief. It creates no media buy, spends nothing, and does not test reporting or webhooks. If your agent’s get_adcp_capabilities declares account.required_for_products with buyer-declared accounts (require_operator_auth: false), the test buys as a test persona shaped like real demand: the brand acmeoutdoor.example, the agency pinnacle-agency.example as operator, and Apostra as the agent. It first calls sync_accounts for that account, using a billing mode from your supported_billing, then sends the account with get_products. If the sync_accounts call itself fails (for example, it times out), the test still sends the account so the result reflects your agent’s own get_products answer, and the result says the registration failed. The test is marked as test traffic the way AdCP marks it: sandbox: true on the account, when your capabilities declare account.sandbox: true. AdCP has no request-level test marker, so if your agent doesn’t support sandbox accounts, the test uses a live account and its result says so. Either way it sends only a no-spend get_products. If your agent assigns its own account IDs (require_operator_auth: true), the test sends no account and reports that one is required, because it has no operator credential to use. Live buyer requests always carry the buyer’s own brand and the operator acting for it. Its outcome, product count, step, and available debug identifiers are recorded as a durable source test run, so the result remains available in Source Diagnostics.

Refresh current health evidence

If Source Diagnostics shows stale or conflicting health evidence, ask Apostra to refresh the affected inventory source, or call refresh_inventory_source_health from an MCP client with that source’s sourceId. No refresh is needed when diagnostics does not request verification. The operation sends one no-spend get_products request to that source. It does not change source configuration, create a media buy, or test reporting or webhooks. Its response returns the timestamped outcome, recorded run, task, operation, debug, and correlation IDs, and refreshed storefront readiness. The same evidence appears in Source Diagnostics. MCP hosts open the same Source Diagnostics app through the typed open_source_diagnostics tool. The widget and its actions use shared MCP contracts rather than sending a new chat message to Apostra.

Run the full Sales Agent validation

Test & certify keeps a selected Source check separate from protected validation. When you select a Source, the validation checks that Source’s connection and discovery path. It still requires an authorized target and the normal account checks. Selecting a Source does not create an isolated provider fixture or make a blocked Source runnable. Leaving the Source selector empty uses the protected validation path. That path first needs a current registered Agent deployment before it can prepare its test target. It runs the brief-only profile for your account; certification runs automatically on live traffic, so there is no separate transaction test. A missing deployment is shown as setup work; a fixture is checked only after that target is available. A separate Buyer Account is optional. If you omit it, validation stays with the owning Seller Account. Omitting it does not create a Buyer Account or remove target ownership, authorization, or test-setup checks. If you select a reachable Buyer Account, validation continues to use that account. Choosing No auth completes credential setup for a public endpoint; there is no secret to submit. Apostra inspects the saved Source and continues to diagnostics instead of reopening the credential form. A validation window with no qualifying calls remains untested, not failed. If your sales agent later starts requiring a credential, you can add one to the same Source without creating a new one. Open the Source’s diagnostics, expand Connection details, and choose Add credentials. If a connection check fails with an authentication error, the same action appears at the top of the page. In the form, pick Bearer token or API key and enter the secret; saving switches the Source from No auth and stores the secret in one step. The Source keeps its status, and you can run Check connection afterwards to confirm the agent accepts it. To do the same through the API, send authenticationType and auth together to Update inventory source.

Request

Parameters

Response

The most important response blocks are: diagnosis.owner tells you who must act:
  • seller — a generic external sales agent you operate, or an adapter whose credentials need re-authorizing. The topCause and nextSteps are addressed to you.
  • scope3 — Apostra must act. This includes an official Apostra-hosted adapter (Pinterest, Reddit, Snap, …) whose runtime is failing, and an initial-response timeout where Apostra did not capture the Source task ID before its deadline. topCause says which one happened. There is nothing for you or the Source operator to change; nextSteps directs the investigation to Apostra.
diagnosis.topCause is the primary explanation to show first. Use diagnosis.issues[] when more than one signal is present in the same window: for example stale async callbacks, caller-deadline timeouts, slow responses, non-timeout failures, input-required responses, business rejections, skipped diagnostic attempts, missing auth, or inactive sources. Each issue includes a stable mode, severity, affected count, and human-readable summary. Issue mode values use the same source failure vocabulary as the diagnosis model, such as stale_async, timeout_degraded, auth_missing, source_runtime_error, business_rejected, and input_required. When the Source answered with a typed AdCP error, diagnosis.topCause and diagnosis.failure.summary name its code (for example AUTH_MISSING) and what that kind of code asks for, and diagnosis.developerHandoff.latestError carries the Source’s own error text unchanged apart from redacted secrets. Only codes in the AdCP standard set are recognized, and only from the error’s code, never from the wording of a message. A code outside that set, and an error with no code, read “could not classify” and still carry the Source’s text. See Source returned an AdCP error. When the request Apostra sent contradicts the capabilities your agent declared in get_adcp_capabilities, diagnosis.owner is scope3 (apostra on sourceHealth), and diagnosis.topCause states what we sent against what you declared. We compare media_buy.buying_modes, account.required_for_products, account.require_operator_auth and adcp.supported_versions. The comparison reads only what your agent declared, and is skipped when we hold no capabilities for it. See Our request broke the Source’s capabilities. comparison.trend is a quick movement signal, not an uptime or SLA claim. It compares sampled source activity in the requested window with the immediately previous window of the same length. not_enough_data means neither window has enough source activity to compare; latency remains unknown when request and response rows cannot be paired. Zero calls in the selected window means the operations are untested in that window. It is not a failed call and cannot, by itself, support an Erroring verdict. Likewise, missing validation assertions mean no qualifying run has assessed them yet; read the captured health observation or failed exchange before attributing a failure to the Source. The latest source-health check can be older than the selected activity window. When an older failed check has no newer traffic to confirm or clear it, the overview labels it as stale evidence and asks for current verification. It does not claim the source is currently down, and it does not say that no action is needed. A healthy completed get_products result takes precedence over an unhealthy sync_creatives observation only when it is at least 24 hours newer and both observations remain inside the seven-day freshness window. Other current unhealthy observations stay visible and can still affect readiness.

The connection verdict

connection.state folds the Source’s status, credentials and stored health into one answer: A health check is stale once it is more than seven days old. That is the same bound Apostra applies when it compares health evidence from different checks, and it does not depend on the windowHours you choose: the window sets which activity the counts cover, not how long a health check stays trustworthy. A source that has only a stale check reads needs_check, whatever that check found.

Capability health observations

source.health and source.capabilityHealth are deprecated; read sourceHealth.health instead. They are still returned, unchanged. source.health is one collapsed cell for the inventory axis: the same worst-of-fresh collapse described below for capabilityHealth.inventory, not whichever check happened to write last. Its own status field is healthy, degraded, unhealthy, or unknown (unknown when nothing has ever reported) — a survivor of the per-source column this field has always read, kept as its own four-value field for compatibility. Present unhealthy as Erroring in human-facing output. Two of its other fields follow the same “worst check wins, not last check wins” rule, which matters when more than one check is reporting on the same source:
  • lastSuccessAt is the last time the currently worst-reporting check saw a healthy result, not the most recent success across every check. If that worst-reporting check has never once succeeded, lastSuccessAt reads null even though a different, healthy check has a success history.
  • lastErrorAt clears back to null as soon as the collapsed status is healthy, rather than continuing to show a past error that no longer reflects current state.
source.capabilityHealth is the fuller picture behind that cell, and it uses a different convention for “nothing has reported”: null instead of the string unknown. It has two axes, inventory and reporting, and each axis carries both a derived status and the observations[] evidence behind it: Each entry in observations[] has: Three things follow from that shape:
  • Different checks watch the same source and can disagree. A source can have one check confirming get_products connectivity while a separate reporting check is failing, and both readings stay visible in observations[] at once: for example, one check reporting the connection is fine while another reports the reporting feed failing. The old single cell was last-writer-wins: whichever check happened to run most recently silently overwrote whatever the others had reported, and the disagreement was lost. status restores that visibility without giving up a single verdict: it is computed across every reporting check, not just whichever wrote last.
  • An empty observations array means no check has reported on that axis yet, and status reads null accordingly. A source with no reporting checks configured shows "reporting": { "status": null, "observations": [] }; read that as “not observed,” not as “reporting is fine.”
  • A reading can go stale, and status already accounts for that. A check that stopped running leaves its last reading in place indefinitely; status discounts a reading old enough to no longer describe current state in favor of fresher evidence on the same axis. The same rule this page already applies to source.health applies here: a failure with no newer activity to confirm or clear it is stale evidence that needs current verification, not a confirmed current outage.
  • Each axis’s observations[] array is capped at 20 readings, newest first. A source realistically reports through a handful of named checks per axis, so this is not a limit you should expect to hit in practice.

The source health object

sourceHealth is one typed answer for this source, in the order you would ask the questions: which source this is, whether each operation is healthy, whether the writes Apostra sends it are landing, what is wrong and who acts, what exactly happened, and whether ads served. Apostra’s own Source Health tools return the same object, so you and Apostra support read the same answer in the same words. Each row in sourceHealth.health has: sourceHealth.writes is part of the object so every reader gets the same shape, but sellers cannot see write outcomes yet: this read always returns null for it. Apostra support reads the same object with the writes filled in, so when support tells you a write to your source is open or never answered, these are the fields they mean. Each entry covers one kind of write over the window: An empty writes list means Apostra sent this source no writes in the window. null means the writes are unknown, not absent. sourceHealth.operations answers “what is still waiting at this source, on whom, and since when”. An operation is one intended change at this source: a media buy created or updated, a creative synced, or an account request decided. It is listed while it is open, and it is open because of what the source, you, the buyer or Apostra has not done yet, never because of how old it is: an operation waiting for three days is visible from its first minute, with its age in since. On this read, and on the read of an Agent operator you authorized, every listed operation is open, its attempts list is empty, and operationsTruncated is always false: operations that have finished are not part of it. Apostra support reads the same source health object with more in it. After the open operations, their read lists the operations the source finished in the window, newest first and at most 50: each one succeeded, failed or never_answered, how it settled, and every try in attempts with its error code and recovery class. operationsTruncated is true there when the list may be missing finished operations: more than 50 finished, or more began in the window than the read could check. Writes sent before 6 October 2026 carry no operation id, so they still count in the write totals support sees but are not listed as operations. The operation id you quote to support is the one they see. Behind Apostra’s own managed sales agent there is no separate operator: a change it is holding is waiting on your own review in your ad server, so it reads waitingOn.party: "seller". The operator of an Agent you have authorized to power a source can read that source’s health object. They see that an operation is waiting on you and since when, but not what you have to decide: target and nextAction are null on those entries. They see no actions, generic text in place of findings you or Apostra own, and no request or response bodies unless you invite them to debug that source. See Read one client source’s health. A source that pauses a task for more input (the AdCP input-required status) is waiting on its caller, Apostra, not on you or its operator. Apostra cannot supply that input yet, and checking the task again would not move it, so the operation fails as soon as Apostra sees the pause rather than staying open: Neither is listed in sourceHealth.operations. In sourceHealth.writes the write counts as failed, and topFailure.errorCode reads source_input_required with recovery: "structural". When a source refuses an account request in a way that sending it again cannot change, the attempt is recorded with the source’s own error code and recovery: "structural". For example, a source that lists agent in supported_billing but answers BILLING_NOT_PERMITTED_FOR_AGENT is refusing the billing mode it declared. Apostra then doesn’t send the same account request to that source again for 24 hours. Fix the cause in your agent, for example by accepting the billing mode you declare or removing it from supported_billing. The next request after the 24 hours tries again. A credential error such as AUTH_INVALID is not held, because reconnecting the source can fix it; the next account request goes out as usual. Each entry in sourceHealth.diagnosis.findings has: sourceHealth replaces source.health, source.capabilityHealth, diagnosis.severity, diagnosis.owner, and diagnosis.issues. Those fields are deprecated but still returned, unchanged, so existing integrations keep working. Their ok, warning, critical, unknown, and unhealthy values never appear in sourceHealth.

Errors

  • 400 BAD_REQUEST — windowHours is not a positive integer or is greater than 720.
  • 401 UNAUTHORIZED — missing or invalid API key.
  • 404 NOT_FOUND — no inventory source with this sourceId exists for the storefront.
See Errors for the full error contract.

Open the diagnostics surface

Source diagnostics overview showing a sales-agent source that needs attention, error counts, late callbacks, and the next investigation action
Diagnostics open from the source you are looking at, scoped to that source:
  • From the source itself. In the ad-server or sales-agent source app in chat, use Open full diagnostics. It opens the Source Diagnostics app already focused on the connection you had selected. (The in-app View details / View diagnostics buttons are different — they switch to the advanced tab inside the same source app.)
  • From Pending operations. When a source you operate degrades, it appears in the Sources degraded group of the Pending operations view with its severity and a one-line summary. Each row’s Open full diagnostics opens the Source Diagnostics app scoped to that source.
  • By asking Apostra. Ask “why is <source> failing?” or “show me diagnostics for <source>” — Apostra opens the Source Diagnostics app scoped to that source. Naming the source focuses the app on it; otherwise it opens on the source you last had selected.
  • From the Help menu. Open Ask Apostra, choose Help, then choose Diagnostics for the storefront-wide diagnostics view (change history, Calls, source tools, test runs).
Choose Show me from a Source Diagnostics announcement, or choose ? in the diagnostics surface, to start the two-minute quick tour. The tour teaches the same operating sequence in every supported chat host:
  1. Start with the question: is this source healthy, what changed, and who acts?
  2. Choose the evidence window. Start with 24 hours, narrow for an active incident, or widen it to confirm a pattern.
  3. Read the overview verdict before inspecting individual calls.
  4. Use Evidence for recent calls and partner-safe handoff identifiers.
  5. Take the smallest diagnosed next action, then refresh or run a focused test.
The surface has three primary views: Summary, Evidence, and Setup. Run discovery test calls the selected external source’s get_products directly with a read-only, no-spend brief and records the result as a durable source test run. It does not create a media buy. Refresh capabilities is the secondary setup check: it calls the source capability endpoint directly and shows the result in the app; it does not send a synthetic question to Apostra. Capability refresh verifies endpoint reachability, credentials, and declared tools only—it does not prove discovery. The refresh is scoped to the selected Source’s own connection identity, so it works even when that Source has no legacy standalone Agent record. A buyer-path result appears under the selected source only when diagnostics have an observed source exchange to attribute to it. A storefront intelligence run that stopped before source dispatch remains storefront evidence, not a failed call against the external agent. In Setup, diagnostics show the selected Source’s provider-declared mode, effective product path, and readiness. That authority is read-only in Apostra. To remediate an incomplete wholesale path, correct the Agent’s reported product, property, format, pricing, or execution declaration, then ask Apostra support to refresh or reconcile the capability. Do not change a mode to hide a readiness failure. For an Agent-supplied source, Connected means its source connection and required credentials are ready. Setup does not wait for an Apostra wholesale catalog, and a null reporting status (no reporting check has reported yet) does not become setup work. A subsequently observed degraded or unhealthy source is shown as Needs attention instead.

Powered by

When the selected Source is backed by a Sales Agent, Setup shows a Powered by card naming that Agent. Every storefront account sees this card for its own Sources. It does not depend on the Agent page, which belongs to the Developer account. The card’s Open Agent button opens the Source you are already viewing, because a storefront account has no Agent page. The card’s heading reflects who holds the Agent, not just that one exists: The tour changes tabs and highlights controls, but it does not change source configuration. Any product action keeps its normal safeguards. You can also open Source Diagnostics directly by replacing <account-id> with your account id:
To focus the app on one inventory source, include diagnosticsSourceId:
Task-level filtering is available inside the app’s recent-call search after it loads; there is not currently a task-id deep link. Slack cannot render the MCP app inline. When Apostra diagnoses a sales-agent source from Slack, it provides this same account- and source-scoped link as a Open sales-agent diagnostics button. Open it, choose Run discovery test, then reply done in the Slack thread. Apostra looks up the recorded source test and interprets it; you do not need to copy the result between apps.

Start with the inventory source

Open the storefront inventory source in the app, or call Get storefront readiness. The sourceDiagnostics[] entry for each source gives the current operating picture: If the source is not active, credentials are missing, or the source does not advertise the tool you need, fix that before debugging individual buyer requests. Treat a failed capability refresh and failed source traffic as separate signals. A refresh can fail while a recent get_products call succeeds, and that successful call is direct evidence that the source responded. Planning can continue from a clearly labeled last-known capability document; when no capability document is available, it reports the probe failure separately instead of treating missing metadata as proof that the source is unreachable.

Understand product paths

The productMode.paths[] diagnostics report each Source’s effective runtime path and its readiness. Storefront-built means Apostra can use ready wholesale inputs from the Source when composing products. Agent-supplied means the connected Agent returns complete buyer-ready products. An Agent that supports both reports each path independently; a problem on one path does not silently reclassify the other. Use the Source’s normal detail page for connection and operating actions, and use productMode diagnostics to identify whether the Source, Apostra, or the connected Agent owns a readiness problem. Storefront readiness also includes Interpretable product formats when canonical-format compliance is enabled for your storefront. Direct canonical format_options[] and projectable legacy {agent_url, id} references both pass, regardless of source age. If a legacy product contains several format references, at least one usable canonical projection keeps the product available; an unsupported sibling reference is omitted from the buyer-facing format options and remains visible as an advisory diagnostic. A product with no usable canonical option is withheld on its own, while the Source and its other products remain request-eligible. Seller Setup prefers public catalog evidence when a source has it, so an older buyer-account or sandbox-test observation does not override a corrected public declaration. Buyer discovery and purchase still validate the relevant account context before products can serve. The readiness check names unresolved products and their affected Source as repair guidance; it does not make the storefront transaction-ineligible. Replace an unsupported custom reference with an exact shared-catalog reference or publish an interpretable custom canonical declaration. The Source’s separate connectivity health can remain healthy because a successful response with a partial catalog warning is not a reachability failure. This compatibility policy is distinct from observed Source health. A health error does not automatically make the Source ineligible or suppress its next request; stopping traffic requires a separate explicit Source or Agent control. See Connect your sales agent for the product-format policy. Storefront readiness also includes Product channels declared. It counts, for each connected third-party sales agent, the products that declare no AdCP channels: neither the product’s own channels nor applies_to_channels on any of its format options. Buyers filter discovery, route proposal requests, and report delivery by channel, so a product without one is missing from channel-filtered discovery and its delivery reports with no channel. A format type on its own, such as a video or audio format, does not count as a declared channel. This check is a warning. It never blocks the storefront, and the Source keeps receiving requests. Ask the agent’s operator to add channels (for example display, olv, ctv, or streaming_audio) to each product the check names. We do not refuse an external agent’s products for missing channels; whether they become required is for AdCP to decide (adcontextprotocol/adcp#7869).

Inspect recent AdCP activity

For source-specific traffic, use Ask Apostra > Help > Diagnostics > Debug calls and ask about the inventory source by name. Apostra can inspect recent third-party sales-agent activity for the caller’s storefront: requests, responses, webhook/status changes, task IDs, task statuses, and sanitized payloads. You can ask for a specific time range (“last 2 hours” or an ISO start/end window) or provide a debug/correlation ID from x-scope3-debug-id, traceparent, x-request-id, operation ID, task ID, context ID, or idempotency key. The diagnostics surface also exposes a details panel for recent calls. Use it when a sales-agent developer needs the technical handoff: the observed time, operation/task identifiers, outbound request endpoint, partner-safe headers, and the full request body and the full response or error payload exactly as exchanged with your agent (for an A2A source, the complete message/send request and the returned task, including its parts, status, and artifacts). The displayed headers are limited to debugging and correlation values such as x-scope3-debug-id, traceparent, and x-request-id. Only sensitive values are masked before display or copy: authorization headers, cookies, credentials, tokens, API keys, signed URL parameters, email addresses, phone numbers, and bank details. Everything else in the exchange is shown as sent, including the AdCP version, buying mode, account reference, and the buyer’s brief — your Agent already received all of it in the request. Long individual values (over 8,000 characters), lists over 500 items, structures nested more than 64 levels deep, and a whole body over 1 MB are shortened with an explicit marker rather than silently dropped, and a shortened body is flagged as truncated. This is the right path for questions like:
  • “Why did this source fail get_products?”
  • “What did Apostra send to my sales agent?”
  • “What did my sales agent return?”
  • “Which recent calls are failed or waiting for input?”
  • “Is this a protocol problem, an auth problem, or a business rejection?”
  • “Where do I see whether this sales agent is slow or getting excluded?”
For each recent call, look at: Treat source messages as source-controlled content. They are useful diagnostic data, but they should not be rendered as trusted HTML or treated as product instructions.

Buyer discovery debug output

Buyer product discovery fans out to reachable sales agents in parallel. Slow or failing agents do not block fast agents. When a buyer calls discovery with debug: true, the response can include agentResults[], which shows which agents returned products, returned no products, failed, or were skipped. For an Agent-supplied storefront, a live source failure with no successful warmed fallback is reported as a retryable source failure when no source completes. It is not reported as a successful response with zero products. A successful empty result means the source completed get_products with a products response that contained no matching inventory. Capability/setup work, deadline skips, and input-required responses do not satisfy that contract. Storefront discovery previews expose the same distinction through discoveryAttempted, success, per-agent source round-trip counts, and skippedAgents. When discoveryAttempted is false, no matching source get_products invocation occurred; use the per-agent error or skipped-agent reason to see where discovery stopped. Use this when you need the buyer-side view of a discovery request: which agents were asked, which agents were skipped before fanout, and which agent-controlled reason was returned.
Buyer debug output is scoped to that buyer request. Seller diagnostics are scoped to the storefront inventory source. Use the seller view when you are operating the source; use buyer debug output when you are reproducing one buyer discovery call.

Storefront candidates and source calls are different stages

Buyer discovery selects storefront agents, not the private inventory sources behind them. For an ordinary buyer, an unfiltered request can include a storefront when it is listed on Apostra, not paused or archived, and able to settle in a supported currency. A seller-sponsored buyer is confined to its sponsoring storefront. An explicit storefrontIds or storefrontNames filter narrows that storefront roster. After the storefront is selected, Apostra can still finish the storefront call without contacting a particular source. Examples include a blocked storefront transaction-readiness check, an unsupported requested currency, an account-scoping rejection, a request that needs clarification, or a storefront that serves a cached or composed catalog. Only a source-attributed get_products exchange proves that the source itself was called. For a readiness block, use the failing check’s guidance object as the canonical explanation. It names why the check failed, where it stops the buyer path, the exact fix, implicated sources, and typed evidence when available. This means an all-zero discoveryParticipation block does not prove that no buyer demand existed, and it does not prove that the storefront was absent from every candidate set. It means diagnostics observed no attributable source call or source-test skip in that window. To answer whether a storefront participated in a particular buyer request, use that request’s agentResults[] with debug: true. If the original buyer request is unavailable, Apostra support can correlate the storefront’s buyer-path records with source activity. Two setup fields are commonly misread as routing gates:
  • Empty storefront-card regions or channels do not by themselves remove a storefront from get_products fanout. Requested-channel prefiltering uses the agent’s advertised channel capabilities; unknown coverage remains eligible. Country constraints are passed in the brief for the storefront or source to evaluate.
  • linkedStorefront on an inventory-source response is retained for response compatibility and is always null. It does not attach an AGENT source to its owning storefront and is not a discovery-routing requirement.

Empty responses and latency do not change future ranking

returnedNoProductsCount and latencyStatus are diagnostic observations. They are not inputs to later storefront eligibility, source selection, or product and proposal ranking. There is no rolling empty-response or slow-response penalty window. A source that returns no products contributes no candidates to that individual response, so there is nothing from that source to rank for that request. When a buyer explicitly enables managed screening and ranking, Apostra evaluates the products and proposals returned for the current request against the buyer’s instructions. Without that requested ranking, Apostra does not apply a historical-performance ranking system.

How source latency is measured

The latency fields and a source-test run’s durationMs measure different intervals:
  • Each diagnostics latency sample is one attributable get_products exchange, measured from its recorded request activity to its final recorded response activity. p50LatencyMs, p95LatencyMs, and p99LatencyMs are percentiles across those paired samples in the selected window.
  • latencyStatus is over_threshold when any paired exchange is greater than latencyThresholdMs, or when the window contains a timeout. It is not a comparison between the window’s p95 and the threshold. When no recorded exchange supplies an explicit timeout or deadline, the current diagnostic default is 5,000 ms.
  • A direct source test’s durationMs is wall-clock time for the complete test probe, from source-client setup through completion or failure of get_products. It can include connection/session setup, capability negotiation performed by the client, the get_products exchange, and asynchronous-task handling. It is calculated before trace lookup and test-run persistence. Separate capability calls do not enter the get_products latency percentile, even though they can increase the test’s total duration.
Use the source exchange and transport timeline to split seller-handler time from Apostra/client overhead. Do not compare the run’s total durationMs directly with latencyThresholdMs.

What this does not answer yet

The diagnostics show source state, recent protocol activity, latency percentiles, timeout counts, and source-level skipped/excluded evidence for the selected lookback window. For broader business impact analysis, combine:
  • sourceDiagnostics[].health for the latest known source health.
  • Apostra diagnostics for recent ADCP call details.
  • Buyer discovery agentResults[] with debug: true for one discovery request.
If a seller asks for missed product opportunities, lost buyer demand, revenue impact, or longer-term trends, treat that as a product/documentation gap unless a tool returns that exact rollup. The actionable answer should explain what exists today, link to the UI surface above, and call out the missing diagnostic that should be added.

Empty catalog from a version mismatch (VERSION_UNSUPPORTED)

If a source’s products vanish from your catalog and its recent Calls show a VERSION_UNSUPPORTED payload — e.g. AdCP version '3.1' is not supported. Supported: ['3.0', ...] — the source’s AdCP server is rejecting the protocol version Apostra pins instead of serving a compatible one. This is almost always a source-side issue, not a problem with your storefront. Per the AdCP spec, a source that supports the same major version must downshift to its highest supported release and serve the request; VERSION_UNSUPPORTED is reserved for a genuine cross-major mismatch. A source that returns it for a same-major minor (it supports 3.0 but rejects 3.1) is running a non-conformant or stale AdCP server build. At the current 3.1 pin, Apostra does not retry component warming at 3.0 and does not reinterpret a 3.0 response as a component catalog. A one-shot retry hook can become active only for a future pin above 3.1 when the source advertises a compatible same-major GA release that is still at least 3.1. A 3.0-only source may still answer live buyer briefs through the pass-through path, but its components stay absent until the source operator upgrades the server. Share the source name and the sanitized VERSION_UNSUPPORTED payload (from the Calls tab, Details panel) with the sales-agent operator, and point them at AdCP versioning & negotiation for the rule their server must follow.

Practical checklist

When a source is not showing up or a buyer request did not return products:
  1. Confirm the inventory source is active.
  2. Confirm credentials are configured if the source requires auth.
  3. Confirm the source advertises the needed capability, especially get_products for product discovery.
  4. Check source health for the latest error, success, and check timestamps.
  5. Ask Apostra to inspect recent Calls for that source.
  6. If reproducing a buyer discovery request, run discovery with debug: true and inspect agentResults[].
  7. If the failure is still unclear, share the source name, task ID, timestamp, and sanitized error with your sales-agent operator or Apostra support.

Inventory sources

Register and manage the sources behind your storefront

Get storefront readiness

Inspect readiness and per-source diagnostics

Product discovery

Understand buyer discovery and agentResults

Errors

Shared Apostra error contract

AdCP versioning & negotiation

Why a version mismatch empties a catalog, and the downshift rule