Skip to main content
TARS uses internal REST endpoints and MCP tools for operator-only work. They are available only to authenticated Apostra SuperAdmins and are not part of the customer-facing Buyer or Seller API. In these tools a prospect is an organization that is unclaimed: nobody outside Apostra administers an account for it. There is one kind of organization, and whether it is claimed is worked out from its current accounts each time it is read, never stored as a separate label. The Marketing mandate at /tars/mandates/marketing reads from three of these REST endpoints. It combines search engine visibility, answer-engine visibility, website journeys, and the existing canonical buyer-lead view. The standalone marketing admin remains available until a later retirement slice.

Authentication and errors

All REST endpoints use the normal API session or bearer-token authentication and require the superAdmin role. Internal MCP tools are listed only for a directly authenticated SuperAdmin and re-check that access when called.
  • Invalid query parameters return 400.
  • A caller without SuperAdmin access receives 403.
  • An unavailable marketing database returns 503 without exposing connection details. The failure is logged and reported to Sentry.
  • Journey responses can be partial: each unavailable tile has { "status": "unavailable", "data": null }. Treat that state as unknown, not as a zero metric.
Dates use YYYY-MM-DD. Custom from and to values are inclusive, must be provided together, and take precedence over window.

Manual sales meeting notes and transcript intake

The internal TARS composer accepts pasted meeting or customer-interaction notes as well as TXT, Markdown, PDF, and DOCX meeting transcripts. A turn can contain at most five attachments, each standard document can be at most 5 MB, and all attachments in the turn can total at most 50 MB. The internal POST /api/v2/admin/linear-sync/tars/ask contract carries these files in its optional attachments field and applies the same limits. The tars_intake_meeting_transcript MCP action accepts the confirmed canonical orgRef and exactly one source: an attachmentRef from the current turn, up to 120,000 characters of pasted notes, or a notionUrl. Pasted notes may include a title. The Notion page must be shared with the read-only TARS integration. All three sources use the same private storage, extraction, duplicate detection, and human-review path. The approval-gated actions use two internal SuperAdmin REST endpoints:
  • POST /api/v2/admin/tars/meetings/transcript-intake accepts an exact canonical orgRef, exactly one attachment or notionUrl, and an optional conversationUid. An attachment contains filename, mimeType, and base64-encoded content. The route reads a Notion page as Markdown, including its meeting transcript when available, and rejects partial page responses. It returns the stable transcriptId, whether the intake was a duplicate, the resolved organisation, and every extracted commercial or operational claim with its review status.
  • POST /api/v2/admin/tars/meetings/transcript-intake/review accepts that transcriptId, the same orgRef, and one decision for every pending claim. Each decision identifies its claimUid and chooses keep or discard; a kept claim may also replace the bounded statement or correct its kind to commercial or operational. A request contains 1 to 50 decisions and the response reports the approved and rejected totals with the resulting claims.
Before intake, TARS resolves an existing canonical knowledge organisation and shows it for human confirmation. The approval-gated intake action then stores one selected transcript in private document storage, associates its content hash with that organisation, and extracts proposed commercial and operational claims. Uploading the same bytes to the same organisation is idempotent; those bytes cannot later be assigned to a different organisation. TARS presents every pending claim for a keep, discard, or edit decision. The approval-gated review action applies the complete decision set atomically. Kept claims become approved Customer Knowledge and discarded claims are retired. Extracted claims are never approved automatically, and Proposal Builder can consume only the approved claims under its existing source and authority rules. The original manual transcript remains access-controlled evidence in private storage. Meeting outcome reads preserve the existing claims-only response of transcript.stored: false with reason transcripts_not_stored_claims_only. Manual uploads also include an additive transcript.privateOriginal marker with stored: true and reason private_original_not_exposed, but never return the transcript body.

Notion rollout check

NOTION_API_TOKEN is supplied to the application container in staging and production. After deploying a change to this intake path, the deploy operator must share one non-sensitive test page with the TARS integration and submit its link against a confirmed test organisation. The check passes only when the response returns the expected organisation, a private manual: transcript, and claims in pending_review state. If the check fails, stop using Notion link intake and use paste or upload instead; those paths do not depend on Notion. Verify that the page is shared, then inspect Sentry events tagged area=tars-notion-meeting-notes and the failure_kind tag before retrying. Do not approve claims produced by the test page.

Marketing email creative library

The Email library stores reusable drafts, approved creative, uploaded assets, themes, previews, and immutable version history. It does not choose an audience, enrol recipients, send email, or show delivery and sent-email history. Those operations belong to separate sequence and delivery surfaces. Use the model-visible tars_open_marketing_email_library tool to ask a compatible TARS host to display the library. Its structured response includes template, asset, and theme counts plus a bounded summary of the latest template versions. A tool response confirms only that the host was asked to open the library; it does not prove that the widget rendered. The widget uses these app-only tools internally:
  • tars_get_marketing_email_library loads templates, assets, and themes.
  • tars_get_marketing_email_template loads one template and its version history.
  • tars_preview_marketing_email_version renders one saved version with sample merge-token values.
  • tars_create_marketing_email_copy_draft creates a reusable copy draft or saves a new version of an existing draft; older versions remain unchanged.
  • tars_approve_marketing_email_version approves a draft version. The author cannot approve their own draft.
A version’s block document has up to three bands. The optional header carries a title and may set its own background colour, background image and logo. The content is a list of paragraphs, subheads, images, buttons and dividers in any order, and an image may link somewhere. The optional footer carries paragraphs, links and linked images, with its own background colour. Every image, in any band, must be an active library asset. Colours a band does not set come from the version’s theme: tars-platform (neutral, the default) or apostra-marketing (the Apostra palette). The renderer always appends the legal footer below any authored footer, with the legal name, postal address and unsubscribe link, and no document can remove it. All six tools require a directly authenticated SuperAdmin. They are not advertised to delegated service principals. Models should call only the owner tool, tars_open_marketing_email_library; the widget owns the five app-only calls. The supporting REST surface is rooted at /api/v2/admin/marketing/email-library. It is an internal Admin UI contract, uses the same SuperAdmin boundary, and exposes library reads, template and version writes, preview rendering, approval, and bounded asset upload. Invalid input returns 400, unauthenticated calls return 401, and authenticated callers without SuperAdmin access receive 403.

Approved deck modules

The Approved deck modules page is Product Marketing’s maintained source for the copy, claims and image revisions TARS may reuse in a prospect deck. Open it in TARS chat with tars_open_approved_deck_modules, or use the internal staff page at /tars/mandates/marketing/deck-modules. Product Marketing writes a sourced claim, approves it separately, then links that exact claim to structured slide copy. An uploaded image also has a separate approval and current-rights check. The server combines the copy and approved image revision with an allowed layout to produce the Apostra HTML. Callers cannot submit HTML, CSS, fonts, colours or logos. Saving changed copy creates a draft revision. Editing an approved module starts its next draft while the current approved revision remains available. Before publication, the server re-reads every linked claim and asset. A revoked claim, an unapproved image or expired usage rights block publication, even when the draft originally referred to an approved source. The widget uses these app-only tools:
  • tars_get_approved_deck_modules loads modules, claims, assets and PDF imports.
  • tars_get_approved_deck_module loads one module’s revision history and HTML.
  • tars_create_approved_deck_claim, tars_approve_deck_claim and tars_remove_approved_deck_claim maintain sourced product claims.
  • tars_save_approved_deck_module_draft saves structured copy and renders a new immutable preview.
  • tars_approve_deck_module_revision publishes the latest draft after checking its live sources.
  • tars_remove_approved_deck_module deletes a never-published module or revokes a published one.
  • tars_remove_approved_deck_asset deletes an unused pending image or revokes a used or approved one.
PDF import accepts one private PDF and creates one draft module per page. The original page image is retained only for comparison and never becomes an approved module asset. Each draft starts with pending copy review and no claim; Product Marketing must rewrite the copy, choose approved claims and replace any visual reference before publishing. Deletion and revocation are different. A never-published module, unused pending claim or unused pending image can be permanently deleted. Anything approved or referenced is revoked for new work and retained so an existing deck can still resolve its recorded revision. Every removal records the actor and reason. Unused image deletion commits a cleanup outbox entry with the database removal; the worker then deletes the stored object and retries transient failures. The supporting REST contract is rooted at /api/v2/admin/marketing/deck-modules. It provides the same library, claim, asset, module-revision and PDF-import operations to the internal Admin UI. All REST and MCP operations require a directly authenticated human SuperAdmin and are not advertised to delegated service principals.

Prospect proposal preparation

tars_open_prospect_proposal_authoring is the read-only handoff from one TARS organisation to the existing Commercial Offer authoring view. Supply at least one of orgRef, orgName, or customerId. When a Harvian handoff must be checked, also supply its exact customerId, protected userRef, or both under harvian. The tool resolves the existing organisation and canonical billing customer. It returns one authoring URL when the match is safe. Otherwise it returns explicit not-found, ambiguous Harvian, or missing-billing-customer blockers. It never creates an account, research question, proposal, offer, Page, or customer, and does not return contacts, interaction text, or private evidence. The existing Commercial Offer service provides two matching preview routes:
  • POST /api/v2/admin/commercial-offers/preview returns the resolved preview snapshot as JSON.
  • POST /api/v2/admin/commercial-offers/preview/document.pdf renders that same snapshot as a downloadable PDF.
Both routes accept the same strict body:
effectiveAt is optional and selects the published plan effective at that ISO timestamp. The narrative is optional and bounded. Callers cannot submit evidence through these routes; trusted evidence must come from the existing Customer Knowledge path. billingCustomerId is optional. When it is present, the preview also checks that the selected plan covers every product that billing customer holds, which is the same check issuance applies (see Which plan holds which accounts). The JSON response then carries planCoverage: An unknown or inactive billingCustomerId returns 404. planCoverage does not change the preview’s contentVersion. Previewing is non-mutating. Pricing, payment choices, term, support and entitlements come from the selected published plan. The response is marked as unissued, uses Cache-Control: private, no-store, and creates no customer, offer, entitlement revision or acceptance record. Formal issuance continues through the existing Commercial Offer authority. Issuance (POST /api/v2/admin/commercial-offers, or the create_commercial_offer MCP tool) refuses a plan that does not cover the billing customer with 409. The error message is the coverage explanation, and its details carry gaps, coveringPlans, nextStep and nextStepTools as above. GET /api/v2/admin/commercial-offers/context (the get_commercial_offer_authoring_context MCP tool) returns the same facts before preview: whether the customer is an organization, a standalone account or a child account, the products plan coverage counts, its sales agent offering (null when it has none) and the covering plan families.

Prospect proposal save

A salesperson can save a prospect’s proposal version, with customised terms, from TARS. Two tools do it, both limited to a directly authenticated human SuperAdmin:
  • tars_preview_prospect_proposal_save is read-only. Give it the company name and, for customised terms, a customisation in the shape Commercial Offers are authored with (adjustments and packageOverrides). Leave the customisation out to save the published terms. It composes the proposal the way a save does, using the plan and currency of the organisation’s current proposal unless you name a plan. A first proposal names the plan by display name or plan code in plan (for example Listing + Distribution, case does not matter) and the currency; no Rate Card or plan id is needed, and the plan is looked up on the Rate Card in effect now. With no plan, or a name that matches none or several, nothing is reviewed or saved and the response lists that Rate Card’s plans (up to 50) to choose from. sourceRateCardId and sourcePlanId still work. It returns each customised term beside its published default, which the server works out from the source plan, the published publishedProposal to base a proposal override on, a contentDigest and the currentVersionId the save would replace. A price change must also carry a proposal override with payment choices that match the new price, or the review is refused with the reason. A caller-supplied default is refused.
  • tars_apply_prospect_proposal_save saves the version after a person confirms the exact review. It repeats the reviewed orgRef, plan, currency and customisation, with currentVersionId as expectedCurrentVersionId, contentDigest as expectedContentDigest and confirmed: true. Nothing is saved if a newer version was saved since the review or the proposal now composes differently. Saving the same content again records nothing new.
Saving approves nothing. A new version replaces the current one, so an approval or review of the older version no longer applies. A version with customised terms can be marked reviewed, and its signup link issued, only after a commercial approver approves exactly those terms (see Customised terms approval). Marking a version reviewed is the next step.

Prospect proposal review

Two more tools, limited to a directly authenticated human SuperAdmin, mark the organisation’s current proposal version reviewed:
  • tars_preview_prospect_proposal_review is read-only. Give it the company name. It returns the version, the narrative, every public source with its statement and URLs, the published terms in full and each customised term beside its published default, all shown in full, and whether an approved commercial exception covers exactly those terms. Customised terms with no matching approval are blocked, and the review points to tars_request_commercial_exception.
  • tars_apply_prospect_proposal_review marks the version reviewed after a person confirms that exact content. It repeats the reviewed orgRef and proposalVersionId, with the digest as expectedContentDigest and confirmed: true. It is refused if the saved content changed, a newer version replaced it, or customised terms have no approval. A proposal too large to show in full is refused rather than reviewed unseen. It issues nothing and sends nothing; the signup link tools are the next step.
A salesperson can ask TARS for a private signup link for a prospect without knowing any internal identifier. Two tools do it, both limited to a directly authenticated human SuperAdmin:
  • tars_preview_prospect_signup_link is read-only. Give it the company name (or an orgRef from an earlier ambiguous result), the recipient’s exact recipientEmail, and optionally the prospect’s domain and expiresInDays (1 to 30, default 14). It resolves the organisation, reads its confirmed domain, its latest saved and reviewed proposal version and its latest signup link, and returns one review: recipient, whether an account or billing organisation exists, proposal version, plan, price, term and renewal, and any warnings or blockers. A missing email is reported as a missing input, not an error. An ambiguous or unknown company returns resolved: false with the outcome.
  • tars_apply_prospect_signup_link creates the link after a person confirms the exact review. It repeats the reviewed orgRef, recipientEmail, expiresInDays, revision as expectedRevision and the review text as expectedConfirmationSummary, with confirmed: true. If the proposal, plan, recipient, organisation, account state or latest link changed since the review, nothing is created and a new review is required.
The saved proposal version decides the terms. A version on the published plan terms proceeds. A version that carries customised terms proceeds only when an approved commercial exception covers exactly those terms: the review then lists each customised term beside its published default and shows the approval (its request, approver, date and approved digest), and the link records that approval. Without a matching approval the review is blocked with the reason and a pointer to tars_request_commercial_exception. terms is optional and only states which terms the salesperson expects; the review is blocked if the version holds the other. A link whose proposal version is unreviewed or superseded, whose published plan moved after review, or whose organisation has a claimed link is also blocked. Applying issues the link through the existing signup-link service (POST /api/v2/admin/commercial-offers/prospect-signup-links). It does not create an account or billing organisation, does not mark the proposal accepted, paid or sent, and sends no email. The result carries the copyable URL, expiry and status with sent: false; the URL is shown once and cannot be read back. A new link supersedes the organisation’s previous link, so repeating a request never leaves two live links. Staff operate links through the internal prospect signup links guide in the engineering repository.

Customised terms approval

Four more tools, limited to a directly authenticated human SuperAdmin, let sales ask for approval of customised Commercial Offer terms and let a commercial approver decide.
  • tars_preview_commercial_exception is read-only. Give it the company name. It reads the customised terms saved on the organisation’s latest proposal version (save them first with tars_apply_prospect_proposal_save) and returns each changed term beside its published default, which the server works out from the source plan, with a commercialDigest. Terms passed as customisation must equal the saved ones, because an approval for different terms would never match the version; otherwise the review is refused. A caller-supplied default is refused.
  • tars_request_commercial_exception records the request after a person confirms the review. It repeats the reviewed orgRef and proposalVersionId, a reason, the digest as expectedCommercialDigest, and confirmed: true; the terms are the ones saved on that version, so none are entered again. Nothing is recorded if the terms no longer produce that digest, and nothing is approved by asking.
  • tars_list_commercial_exceptions lists requests, by default the PENDING ones, each with the organisation, proposal version, changes beside their defaults, reason and digest. Filter by status or company, or read one by requestId.
  • tars_decide_commercial_exception approves or rejects one request with a reason and confirmed: true. It is refused for anyone who is not a current approver, for the person who asked, for a request already decided and for a proposal version that has been replaced.
An approval covers only those exact terms on that proposal version. Once the version is reviewed, the prospect signup link tools above preview and issue the customised link and record the approval on it. Three more tools, with the same limit to a directly authenticated human SuperAdmin, manage who the approvers are.
  • tars_list_commercial_exception_approvers is read-only. It lists each approver with name, email, when they were added and by whom. includeRemoved adds the 20 most recent removals and reports when older ones are not shown.
  • tars_preview_commercial_exception_approver_change is read-only. Give it action (add or remove) and person, an email or a name. It shows exactly who would change and the approver count before and after. A name must match exactly one Scope3 staff member; if it matches several it lists them and chooses nobody. A person with no platform user must sign in once before they can be added.
  • tars_apply_commercial_exception_approver_change records the change after a person confirms the preview. It takes action, the person email from the preview, a reason and confirmed: true. It is safe to repeat: adding a current approver or removing someone who is not one succeeds and reports no change. Every change is audited with who made it and why.

Customer domain approval

An account that cannot prove its registered domain by itself asks Apostra staff to attest it, which raises a Domain attestation request: <domain> escalation. A SuperAdmin approves the domain from the Account Domain Approval page or with the tars_approve_customer_domain MCP tool. Both run the same approval: POST /api/v2/accounts/{customerId}/domain/approve
  • An unknown account returns 404, and an account with no registered domain returns 400 (MCP: NOT_FOUND, VALIDATION_ERROR). A caller without SuperAdmin access receives 403; the MCP tool is not listed for them.
  • A successful approval returns the account id, its customerDomain, and customerDomainApproved: true. The MCP tool also returns resolvedRequestCount.
  • Approving closes the account’s open attestation request for that domain. The request is closed after the approval is saved, so a failure to close it does not undo the approval; approving again closes anything left open. Login access and account membership do not change, and the Linear ticket behind the escalation is not closed.
  • Approving writes an audit entry when it is done through the MCP tool.

Product map verification

A SuperAdmin records that a product-map capability was tested with two MCP tools: a review, then a confirmed write. One record is one verification. Records are only added: the newest record for a capability wins and older ones are kept.
  • A passed newest record on a capability that reads built makes it read tested, with stageBasis: verification. A failed newest record leaves it at built, which drops a tested capability back. A hand-set beta or GA from a feature’s maturity stays above tested, so a record there moves nothing. A capability whose own ticket reopens reads what the ticket rules give, whatever record is on file.
  • tars_get_product_map returns the newest record of each capability as latestVerification (result, bar, evidenceUrl, who recorded it and when), passed or failed, with countsTowardStage saying whether it decided the stage.
  • A value that is not a node id in the product architecture returns VALIDATION_ERROR, as does a building block or an area: only a capability can be verified.
  • A capability that does not read built yet (no stage, planning, or in progress) cannot be verified: the review reports canApply: false with the reason, and apply returns CONFLICT.
  • Apply returns CONFLICT when the review is stale: the capability’s stage or newest record changed since the review, or someone recorded in the meantime. Review again and confirm the new review.
  • A caller without SuperAdmin access is refused and the tools are not listed for them. A caller with no signed-in person is refused on apply.
  • Applying writes an audit entry.

Buyer journey and meeting speaker identities

A SuperAdmin can read the buyer journey and meeting capture, and record buyer lead reviews and speaker identities, from the TARS pages or from these MCP tools. Each runs the same service as its REST route under /api/v2/admin/tars/.
  • The writes change records inside TARS only. They send no email, Slack message or Linear item and create no customer account.
  • Each write needs the caller’s email and writes an audit entry.
  • Company names, domains, contact details, form answers, meeting names and speaker labels are written by buyers or come from transcripts. The tools return them fenced as untrusted data.

Your TARS connections

A staff member asks which of their sources are connected, and what TARS reads from each, with the tars_get_my_connections MCP tool. It takes no arguments and only ever reads the caller’s own connections. It changes nothing and never returns a token. It returns six sources: GitHub, Outlook mail and Outlook calendar, which each person connects with their own account, and Slack, Linear and Roam, which are on for everyone. Each source has:
  • Mail and calendar status is the status last recorded. A sign-in finished in the last few minutes shows pending until the TARS connections page is opened, and a connection that has just stopped working shows its old status until the next background sweep.
  • Connecting for knowledge capture is offered only to staff in the TARS email pilot, and connecting a calendar only to staff in the TARS calendar pilot.
  • The actions listed are the only ones the write tools below accept. The TARS connections page does the same work for the web.
  • A connection’s health error comes from the provider and is returned fenced as untrusted data.

The Connections widget

Ask TARS chat, or Claude or ChatGPT with the TARS connector, to “show my TARS connections” and the tars_open_connections tool opens the Connections widget (ui://agentic-api/tars-connections/mcp-app.html). It is read-only to open, takes no arguments, and needs a SuperAdmin like the read above. The tool answers the model with a bounded readout of each source’s status, account, mailbox use, available actions and one-line summary, and leaves the full reads statement to the widget and to tars_get_my_connections. Only the host or the person can confirm that the widget rendered. The widget shows two sections:
  • Your connections: GitHub, Outlook mail and Outlook calendar, connected with your own account and disconnected any time.
  • On for everyone: Slack, Linear and Roam, which have no individual opt-out.
Each source is a card with its name, a one-line label, an info button, the summary, the account, and a footer. Hovering or focusing the info button lists every line of what TARS reads from that source. The footer shows one status word: Connected, Sign-in started, Needs reconnect, Not connected or Always on. A source that is not connected offers its connect actions as buttons. A source that needs a reconnect, or a mailbox that still needs sending allowed, shows that action beside its status. Every other action, including Disconnect, is in the card’s overflow menu. When a source cannot be connected, its unavailableReason is shown, for example a calendar outside the TARS calendar pilot. Dates are written like “3 Nov 2026”.
  • The widget decides no rules. It reads the view through the app-only tars_get_my_connections_view, which returns the same result as tars_get_my_connections, and offers only the actions each source lists. Unlike the model-visible read, it does not fence the provider’s health error, because the widget renders it as plain text for its owner.
  • Sign-in opens in a new tab through the host, never inside the widget. The widget reads the status again when you return to it, so a finished sign-in shows without reloading.
  • GitHub connects on the TARS connections page, which the widget opens in a new tab. The /tars/connections page itself is unchanged.

Acting on your connections

Four app-only tools on the TARS MCP server (_meta.ui.visibility: ["app"]) are what the TARS connections widget calls. They are listed for the widget, not offered to a model or to TARS chat. Each acts only on the caller’s own connection, requires a SuperAdmin, and is audited. Each returns source, action, and a result:
  • A tool is refused with a CONFLICT error that names the source, the action, the source’s status and the actions available now, unless actions from tars_get_my_connections lists the action. This also covers the pilots: knowledge capture is refused for staff outside the TARS email pilot, and a calendar connect for staff outside the TARS calendar pilot.
  • GitHub cannot be disconnected here, and the sources on for everyone have no actions.

Choosing orgs for a knowledge-capture mailbox

A mailbox connected for knowledge capture reads only the threads of the orgs its owner chooses, and it captures none until the owner chooses. When the mailbox is connected and used for knowledge capture, its actions include choose_orgs. The widget shows it as Choose orgs at the top of the Outlook mail card’s overflow menu, and it opens a Task in place of the sources. The Task has one Back to Connections button, and Escape does the same. The Task holds three things:
  • Orgs this mailbox captures. The chosen orgs, each with Remove. Find an org by name (two letters or more) and Add it. An org that a SuperAdmin has opted out of email capture is marked and is skipped whatever you chose.
  • Check a counterparty. A dry run of the routing decision for a domain or up to ten email addresses. It says whether the mailbox would capture the thread and, if not, why, for example: not chosen, opted out, not an active customer or watched org, ambiguous, or no match. It writes nothing.
  • What your inbox has shared. Lifetime totals and the claims the mailbox has minted, the newest 50 at most; the Task says when older ones are not listed. A pending claim can be approved, edited and approved, or rejected. A committed claim can be retracted, and stays in the ledger marked Retracted.
Seven more app-only tools on the TARS MCP server back the Task. Like the four above, they are listed for the widget and never offered to a model, act only on the caller’s own mailbox, and require a SuperAdmin.
  • Every one is refused with a CONFLICT error unless actions from tars_get_my_connections lists choose_orgs for Outlook mail. A mailbox connected for email sequences only, one that is not connected, and one whose sign-in is pending or needs a reconnect are all refused. Changing a mailbox to email sequences only, or disconnecting it, clears its chosen orgs.
  • tars_manage_mailbox_orgs behaves as before. In a SuperAdmin TARS session it lists, adds and removes the same chosen orgs by org name or reference. The widget is the way to do it from Claude, ChatGPT and TARS chat when you want to see the orgs and the ledger.

SEO overview

GET /api/v2/admin/marketing/seo The response contains:
  • freshness: latest loaded date, row count, and synchronization time;
  • window: the effective and available date bounds;
  • kpis: current and prior impressions, clicks, click-through rate, and average position;
  • trend: daily performance;
  • topPages, topQueries, and pageQueries;
  • opportunities: page/query pairs with at least 10 impressions whose impression-weighted average position is between 4 and 10, ordered by impressions.
Query rows include rank tier, tier movement, zero-click and AI-query flags, and an at-risk signal. The first contract intentionally excludes live metadata health and the model-generated digest from the legacy dashboard.

Answer-engine visibility

GET /api/v2/admin/marketing/aeo The response supplies its fetch time and effective range, source status, prompt/run totals, current and prior primary-brand mention rates, share of voice by brand, performance by model, and the most-used citation URLs.

Website journeys

GET /api/v2/admin/marketing/journeys The response supplies its fetch time and effective range, overall source status, current/prior traffic and conversion KPIs, source breakdowns, homepage and converting paths, preview/contact/storefront funnels, and storefront signup attribution. Each tile reports its own availability.

Data-source configuration

The API process owns all credentials. The browser never receives database or PostHog keys.
  • MARKETING_DATABASE_URL: isolated scope3_marketing connection. Use a role with SELECT-only grants; the pool also sets PostgreSQL sessions read-only.
  • MARKETING_DB_POOL_MAX, MARKETING_DB_IDLE_TIMEOUT_MS, MARKETING_DB_CONNECTION_TIMEOUT_MS, MARKETING_DB_STATEMENT_TIMEOUT_MS: optional pool limits and timeouts.
  • POSTHOG_API_KEY and POSTHOG_MARKETING_PROJECT_ID: marketing-site HogQL reads.
  • POSTHOG_API_KEY_INTERCHANGE and POSTHOG_INTERCHANGE_PROJECT_ID: Apostra signup-attribution reads.
Missing PostHog keys degrade the affected journey tiles instead of fabricating zero values. Marketing database access has no fallback to the application or ledger databases.

Announcement scheduling and threaded replies

The Announcements console in Apostra admin broadcasts a message as Apostra into customer Slack channels. Its draft endpoints under /api/v2/admin/murph/announcements carry two capabilities beyond a plain send: a draft can hold a send time, and it can hold replies that Apostra posts into the announcement’s own Slack thread. These are operator-only SuperAdmin contracts. No customer-facing API changes.

Scheduling a draft

A scheduled send is a draft with a send time — there is no separate object and no status field. scheduledAt is an ISO instant on POST and PUT /announcements/drafts: Recipients are resolved at send time, not at schedule time. An account that was deactivated or lost its Slack channel between scheduling and sending is not messaged. An account that turned announcements off is skipped only when the announcement’s kind is awareness; see below.

Kind: awareness or action required

Every announcement carries a kind. It decides whether an account’s announcements setting applies. The customer’s announcements setting covers product updates and news. It does not cover notices the account needs to act on, so mark those action_required. Leaving kind out means awareness, which keeps the behavior of callers that predate the field. kind is accepted on the send body and on POST and PUT /announcements/drafts, and read back on drafts and in the announcement history. A scheduled draft keeps its kind and is sent with it. The preview_murph_announcement and send_murph_announcement admin tools take it too: the preview shows the kind and the accounts an awareness send would skip, and the preview revision covers the kind, so a send must carry the kind that was previewed.

Sending is at-most-once

A Slack message cannot be recalled, so a scheduled send never retries once it has started posting. A send that fails before posting anything — or that posts to no channel at all — is retried, up to five attempts; after that the draft leaves the schedule with the reason recorded. Anything ambiguous is held for a person rather than re-sent. Four read-only fields report that state on a draft: A draft showing sendError with sendStartedAt set needs a person: it may have reached some channels. The message says whether anything was delivered.

Thread replies

threadReplies is an ordered list of up to ten markdown bodies. After the announcement lands in a channel, Apostra posts each one as a reply in that message’s own thread, in order. This lets an operator send a short summary and hang the detail underneath it rather than one wall of text. Replies thread against each channel’s own copy of the parent message. A channel whose parent failed receives no replies. A reply that fails does not stop the ones after it.

What the send response reports

repliesSent and repliesFailed are counted separately from the parent sent and failed, because an announcement can land while its detail replies do not — the operator needs to know the thread is incomplete.

How replies are counted in reporting

Delivery and reply-rate figures count parent messages only. Apostra’s own threaded replies are excluded, and so are Apostra’s replies as inbound activity, so reply rate continues to measure customer responses. A per-channel replyFailures reports replies that failed while their parent landed.

Mailbox sequences that stop when a requirement is met

A mailbox sequence sends up to ten emails, in order, from a staff member’s own Outlook, with a wait between them (tars_save_mailbox_sequence, or POST /api/v2/staff/mailbox-sequences). A recipient stops when they reply, when an email bounces, or when the emails run out. A requirement reminder can also stop when the seller actually meets the requirement. Each recipient can carry an optional stopWhen with two fields:
  • agentId: the sales agent’s public id, as the admin agent tools show it.
  • check: a dated capability check: canonical_creatives, reliable_reporting, compact_lifecycle or webhook_signing.
Before every email to that recipient, including the first, the sequence reads the agent’s stored dated gaps. When the check is no longer among them, the recipient ends with status completed and terminal reason requirement_met, and gets nothing more. An agent that has not been evaluated yet has no stored gaps, so the email still goes. If the stored gaps cannot be read, nothing is sent: the email is held and retried 15 minutes later, and the failure is logged. A read failure never ends a recipient. Saving refuses an agent that does not exist, is archived, or is not a sales agent, because only sales agents have dated gaps. The condition is part of the content the owner approves, and tars_get_mailbox_sequence shows each recipient’s condition and any requirement_met ending.

Recording an opt-out from a reply

A recipient who replies asking to be removed is recorded as an opt-out, not just as the end of the sequence. A model judges each reply and records the opt-out itself when it is sure. For a request it misses, the owner (or the person who drafted the sequence) marks the recipient as opted out with tars_mark_mailbox_sequence_opt_out (sequenceId and recipientId), or POST /api/v2/staff/mailbox-sequences/:id/recipients/:recipientId/opt-out, or Mark as opted out on the sequence page. Either way the person is unsubscribed from email sequences and from all Apostra marketing email, whatever the sequence was for. A recipient still waiting ends as opted-out; one who had already replied keeps their status. Repeating the call changes nothing, and tars_get_mailbox_sequence shows the model’s suggestion for each reply, with its confidence, as a suggestion only.