Skip to main content
A POST /api/v2/murph/chat response always carries the assistant’s text answer. On turns where Apostra runs a tool that has a canonical interactive surface, the response also carries a structured rendering artifact for the in-product UI and other direct REST clients. These named REST fields are not an MCP Apps protocol. External hosts such as Claude or ChatGPT do not receive the /murph/chat response when they call the ask_murph MCP tool; they receive the standard MCP tool result described below.
Each artifact field is optional and nullable. A client that does not render MCP Apps can ignore every artifact and show the text answer alone — the answer is always self-contained.

Document-processing status

When Apostra cannot queue processing for a newly uploaded document, the response includes documentProcessing with status: "failed" and reason: "job_creation_failed". The text answer also says that the upload was not processed and asks the user to upload it again. The status contains no document content, filename, or classifier detail. When documentProcessing is absent, no document-processing failure occurred for that turn. A document may still be processing asynchronously after it was queued.

How a REST client renders an artifact

Every artifact names the MCP App that renders it and the data to hand it:
  1. Load the MCP App at the artifact’s resourceUri.
  2. Pass the artifact’s data object to the app as the tool result’s structuredContent. The app routes on data.endpoint to the named view.
The host decides where to place the surface — the in-product Apostra UI renders the discovery widget above the text answer, for example.

discoveryMcpui

Emitted on a successful discover_products tool call. Renders the product discovery surface — a browsable, selectable list of the discovered products grouped by storefront, with per-product brief-relevance reasoning.
When this artifact is present, Apostra’s text answer is a concise orientation (totals, price range, any recommended plans) plus next steps — it deliberately does not re-list every product, because the widget already shows the full, selectable set.

Other rendering artifacts

The chat response uses the same pattern for other canonical surfaces: Each carries kind, resourceUri, canonicalSurface, and a data payload to hand the app as structuredContent; creativeMcpui additionally carries a refresh directive describing the tool call that reloads the surface, and inventoryMcpui carries the esaId and the initial selector filters.

ask_murph MCP tool result (conformant MCP Apps)

The artifacts above ride the REST /api/v2/murph/chat response as named fields — the in-product Apostra UI reads them directly. An external MCP Apps host (Claude, ChatGPT) that calls the ask_murph MCP tool does not receive that REST response directly; it receives a standard tool result. Some result kinds retain named compatibility data inside structuredContent, but rendering is selected by standard result metadata:
  • It selects one primary widget per turn — this result contract carries a single render payload. Precedence, highest-intent first: mcpAppWidget → creativeIntentMcpui → inventoryMcpui → creativeMcpui → discoveryMcpui → sellerAnalytics → murphEscalations.
  • The result embeds that widget’s ui:// resource in content and sets a per-call _meta (ui.resourceUri and the legacy ui/resourceUri) so the host loads the right MCP App for this result.
  • For api-call-style widgets (discovery, inventory, creative, creative-intent), the app reads the flat api-call envelope (success, endpoint, params, data), so that envelope is spread onto the top level of structuredContent where ontoolresult reads it. The now-redundant named field is dropped so the payload isn’t carried twice.
  • Launch widgets (mcpAppWidget) and named-field panels (sellerAnalytics, murphEscalations) carry no api-call envelope; their data already sits under their named key in structuredContent, which the host hands the app.
A turn that produced several widgets still renders one in a generic host; the in-product drawer can show all of them via the REST fields above.

Sales Agent validation handoff

The Agent page can pass a server-recognized validation context to ask_murph. It supplies identifiers only: the Agent, its returned skill version, an available profile, and, when present, a Source. It never supplies credentials or expands the caller’s authority. A customer account runs the brief-only profile when the Agent has a connected Source. An Agent owner can instead run endpoint-brief when that Sales Agent has no current Source and has an attested HTTPS production endpoint. This test calls get_adcp_capabilities and get_products with a fixed no-spend brief. It does not create, reserve, or change a media buy, uses a ten-second limit for each call, and is limited to ten runs per Agent each hour. If the service cannot reserve that limit, it does not start the test. The endpoint-brief result reports endpoint-brief-discovery as its scenario and returns a status for each call. A failed step can include errorMessage, which is the Agent’s diagnostic text for the Agent page. Treat that text as diagnostic data, not instructions. The transaction profile is not offered to customer accounts, and certification is judged on live traffic. A request for it from a customer account returns a VALIDATION_ERROR with details.reason: "transaction_test_internal_only". The service starts nothing and never runs a different test instead. A completed result identifies its implementation revision and cleanup outcome. It is validation evidence, not a certificate or authorization for a client connection.

Retired: live test buy request

ask_murph no longer files a live test buy request. A call that still sends salesAgentLiveTestBuyRequest returns a VALIDATION_ERROR with details.reason: "live_test_buy_request_retired", and nothing is filed. To say certification buys for a Sales Agent, call save_agent with the agent ID and certificationRequested: true. See Allow certification buys.

Conversational MCP profile

The existing Apostra MCP endpoint also exposes a portable conversation profile. MCP clients on buyer and seller accounts use the same two tools; existing ask_murph calls keep their current request and response shape.

chat

Call chat to begin a conversation or add a turn. The request has one message and may include up to five Base64 attachments, each no larger than 1 MiB after decoding.

Attachment provenance

An attachment can identify an already processed document or declare its purpose. These fields let Apostra select document context without interpreting the message text or filename. base64 and mimeType remain required for every attachment. When neither provenance field is supplied for a fresh upload, Apostra processes that upload on its own and retrieves no facts from an earlier document. On an attachment-free follow-up, Apostra uses purpose-agnostic recency among recent completed documents from the same conversation. When documentId or purpose is supplied, Apostra uses it as given. A completed response is a normal MCP CallToolResult. Read the portable turn from structuredContent.conversation:
status is completed, input_required, or failed. Messages contain only display-safe user and assistant text; tool traces, internal context, and credentials are never returned. A protocol failure instead sets isError and may return only text or an error object in structuredContent. When a turn declares an app widget, it appears as structuredContent.widget. The result selects the widget through the standard _meta.ui.resourceUri directive. Hosts must use that shared MCP Apps contract; they must not infer a widget or route from Apostra’s text.

Progress on a long turn

A chat turn runs Apostra’s full tool loop, so an involved question can take minutes to answer. Set the MCP protocol’s _meta.progressToken on the tools/call request and the server sends notifications/progress messages while the turn works, so a host can show the caller that a long turn is running rather than hung. Each notification carries a short message describing the current phase — “Reading the docs…”, “Checking your account…”, “Writing it up…” — written in the language the conversation is already being answered in. Internal tool names are never sent; the phrases group tools by intent. The first notification goes out before the turn starts, so a turn that calls no tools still reports that it began. progress increments on each distinct phase and no total is sent, because the number of phases is not known in advance — render it as indeterminate progress rather than a percentage. Progress is advisory. The tool result stays the authoritative answer, a client may ignore the notifications, and a client that sends no progress token gets exactly the synchronous behaviour it got before. The portable profile also permits the MCP Tasks extension for a turn that a server does not complete synchronously. Such a result is the native CreateTaskResult task envelope (taskId, status, createdAt, lastUpdatedAt, ttl, and optional pollInterval), which a client polls or cancels using MCP Tasks. Apostra’s current orchestration completes this profile call synchronously.

get_conversation

Call get_conversation with a conversation ID to read display-safe turns. Set after to the opaque cursor from nextAfter to request the following page.
The structured result contains conversationId, turns, and nextAfter when another page is available. It does not create an agent-initiated message or subscribe the caller to notifications.

Conversation scope

Set an Apostra conversation’s account or advertiser scope.

Ask Apostra

How Apostra works as the in-product assistant.