Skip to main content

Justify for agents: the MCP server

Justify runs a Model Context Protocol server: a headless copy of the platform that an agent can drive. Campaigns, creator discovery, Creative Testing, Brand Lift, the library, gifting, outreach, job postings and the Marketplace+ graph are all reachable as tools, over the same services the web app uses and under the same permissions.

This page is generated from the server itself. The tool list, the descriptions and the entitlement column all come from the capability manifest that ships with each release, so what you read here is what the server will answer with.

283 tools · 4 prompts · 4 resources

Connect a client

There is no key to create. Clients register themselves through Dynamic Client Registration and you sign in with your ordinary Justify account, so the agent acts as you, with your permissions.

Most of this page is written for a connection opened from an organisation, where the agent works inside one of your brands. A creator connects to the same endpoint with their own creator account instead, from /creator/settings in the creator portal: there is no organisation behind it and nothing to choose. The tools it is served are the creator's own, which is the last family below, plus the two tools every connection is served whoever opens it: the selector find_justify_tools and the access-status read get_mcp_access_status. Entitlements narrow that menu exactly as they narrow an organisation's: what each of the creator's own tools needs beyond the connection is in the table beside it, and a creator who does not hold it is not served that tool at all.

Endpoint
https://app.justify.app/api/mcp
Transport
streamable-http
Protocol versions supported
2024-10-07, 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25, 2026-07-28
Capability manifest version
2026-03-01

Claude Code, from your terminal:

claude mcp add --transport http justify https://app.justify.app/api/mcp

Or connect read-only, if you would rather the agent never ask:

claude mcp add --transport http justify-read-only https://app.justify.app/api/mcp/read-only

The catalogue holds 283 tools, and 120 of them write, send or spend, which is what your host asks about. The read-only address leaves those out and carries the other 163, so nothing on it asks. Both are counts of the catalogue rather than of any one menu: what your own connection is served is narrower, and the section below on client tool caps publishes what a fully entitled organisation connection reaches.

The ordinary address serves everything and your host asks before anything writes, sends or spends. The read-only address serves only the tools that read, so there is nothing to approve: they are absent from the menu rather than refused when called.

Connect both if you like. Use the read-only one for asking questions and the full one when you actually want the agent to do something.

One thing worth knowing if you use your host’s allow-list instead: allow the read tools by name, never the whole server. A rule that trusts the whole server also trusts the tool that releases escrowed funds.

  1. Add the endpoint above to your client. In Claude Code that is a single command; in Claude, Cursor and the rest it is the "add a custom connector" field. Every one of them gets the same tools and the same answers; what differs is whether the two interactive views draw, which the hosts section below sets out.
  2. Sign in with your Justify account when the browser window opens. An organisation sign-in then asks which organisation the agent should work in; a creator sign-in has none to ask about.
  3. Ask the agent what it can see. From an organisation it should answer with your brands, which is list_brands; from a creator account it should answer about your own storefront. Either way it means the connection is live.

What the sign-in asks for

The OAuth flow requests exactly three scopes: email, profile and offline_access. That is all it needs, because the scopes only identify you.

There are no Justify product scopes to ask for. A client that requests them at registration is refused with 400 invalid_client_metadata and cannot connect at all, so do not add them to your client configuration.

What an agent may actually do is decided inside Justify, per tool, from the entitlements the connecting account holds and your own role permissions. The table in each family below names the entitlement each tool needs. A tool the connecting account is not entitled to is not merely refused: it is never registered, so it does not appear in tools/list for that session.

email, profile, offline_access

400 invalid_client_metadata

Where the interactive views draw

Every tool answers on every client below, with the same data, the same permissions and the same entitlement gating. No host is blocked and none is missing a feature.

What differs is the two interactive views, the creator shortlist and the commerce performance panel. Two hosts draw them. The rest show the same result as text, which is the documented fallback rather than a fault: the view is never the only route to the answer.

Draws the interactive views
Claude, MCPJam
Shows the same answer as text
AgentCore, ChatGPT, Cline, Copilot, Cursor, Goose, Mistral, n8n, Notion, Perplexity, Slackbot, VS Code

If your client caps how many tools it will take

Most clients take the whole menu. One class does not: OpenAI's chat completions API accepts at most 128 entries in its tools array, and Justify serves 283, so the request is refused with array_above_max_length before the model sees it.

The cut that fits is a read-only CONNECTION with the progressive menu switched on, rather than a filter you apply afterwards. Connect at https://app.justify.app/api/mcp/read-only, with the command printed higher up this page, and declare the app.justify/progressive-tools extension in your client capabilities when it initialises, at the contract version the server advertises back. The server then lists an administrator of your organisation 4 tools, find_justify_tools and the session tools beside it, which that API accepts. Nothing is withheld to make it fit: every tool that reads your campaigns, creators, jobs, library and Brand Lift stays callable on that connection, and find_justify_tools finds it and hands you its full definition to add to your tools array when you need it. What the address leaves out are the tools that write, send or spend. There are no scopes to ask for, and a client that requests them at registration is refused before it can connect, so the address and the extension are how you take this cut.

Two counts on this page describe that one address, and they count different things. The 163 printed beside the connect command above is the widest it can ever serve: every tool that reads or computes, before your role's permissions and your organisation's entitlements narrow it. The 4 is the list measured at that address with the extension declared, for an organisation administrator holding every scope in an organisation entitled to everything, which is the connection you will actually open; everything else that administrator may call is one find_justify_tools search away. The wider figure is over the cap; the listed one is 124 under it.

Filtering the whole menu yourself on annotations.readOnlyHint selects that same 163 tools and no longer fits either. A client that would rather hold the whole surface should send find_justify_tools instead of taking a cut, and add the definitions it hands back as it needs them.

No other client has been measured for a cap of this kind, so no number is published for one. Anthropic’s Messages API, MCP native connectors and Cursor are not known to cap the count, and an absence of evidence is not a limit.

OpenAI chat completions API: 128 tools

connect at https://app.justify.app/api/mcp/read-only and declare app.justify/progressive-tools → 4 tools

Every tool, by family

One section per family. Each table gives the tool, the entitlement the connecting account must hold, whether the tool reads or writes, and what it does, in the tool’s own words, because those are the words the agent reads too.

  • Orientation(5)
  • Campaigns and the Campaign Wizard(12)
  • Creator discovery, your saved roster and the shared workspace(16)
  • Creative Testing(13)
  • Brand Lift(3)
  • Campaign Library(17)
  • Connected stores and product gifting(13)
  • Outreach(10)
  • Social inbox and community replies(8)
  • Job board(11)
  • Marketplace+ intelligence graph(13)
  • Public recommendation corpus(1)
  • Long-running operations(3)
  • Growth experiments(7)
  • Atlas operator library(45)
  • Brand Pulse(6)
  • Roster contracts and deals(6)
  • Payments(2)
  • Billing(2)
  • Content-rights agreements(2)
  • Creator lists(2)
  • Uncover(2)
  • Dashboard(1)
  • Demo workspace(1)
  • Help centre(1)
  • Settings: workspace, self and team(3)
  • Signed influencers and approved content(2)
  • Creator portal: a creator’s own storefront, rates, site and listings(32)
  • Advertising accounts, spend policy and boost eligibility(37)
  • Marketing Mix Model(7)

Orientation

Start every session here. Almost every other tool answers for one brand, so the agent needs to know which brands your organisation holds and which one is already selected. Skip this and a multi-brand organisation gets an ACTIVE_BRAND_REQUIRED refusal rather than a guess. Agencies and multi-brand organisations get two more: the whole estate with the state of each brand, and the switch that picks which brand the rest of the session answers for, the same choice the brand switcher makes in the web app.

Entitlements used by this family: brand_switcher, creator_agent_access or mcp_api, multi_brand_management

Try asking

  • Which brands can you see on my Justify organisation, and which one is active?
  • Show me our whole brand estate: which ones are still half set up, which is the default, and which have been retired.
  • Work on the Aurora brand from now on, and keep answering for it until I say otherwise.
ToolEntitlement neededEffectWhat it does
find_justify_toolsNone beyond a signed-in accountReadsFind the Justify tools you are allowed to use. This server keeps most of its catalogue out of your context until you ask, and this selector is the way in. Mode search ranks the tools you can call against a plain-language sentence and returns name, summary, risk, action, scope, resource and an opaque token v per match, plus total and truncated: tags only, never the callable definition. Mode describe returns the full definition of up to ten named tools in tools, and names in unknown any you cannot reach; call it before calling any tool you were not already holding. Mode facets lists the filter values available to you, with counts. A tool you are not licensed for is absent rather than refused, so a name in unknown means stop, not retry; watch action for paid, which spends credits.
get_mcp_access_statuscreator_agent_access or mcp_apiReadsReport the access this MCP connection itself holds, so a capability question can be answered by reading rather than by failing a call. Returns principal, which says which half of the payload is populated and reads organisation here; organizationId, tier, accountType, role, activeCustomer, apiVersion and protocolVersion; scopes (granted, and effectCeiling, which reads read-only when this connection was granted read scopes alone); features.entitled, the feature keys this session holds; credential (fingerprint, clientId, authMethod); and rateWindows (toolCallsPerWindow, windowSeconds, toolCallsPerDay, callBudgetMs). The two fields that belong to the other principal, profileState and creatorPro, are present and null on this arm rather than omitted. Call it when a tool you expected is missing from the catalogue, before planning work you are not certain this connection may do, or when a refusal blames an entitlement: a tool you are not licensed for is absent rather than refused, so absence is the symptom this answers. It reports only your own connection: no record is read, no other organization is named, and no credit is spent. For the brands this session can act for, call list_brands instead; for the tools themselves, call find_justify_tools.
list_brandsNone beyond a signed-in organisationReadsList the brands of the authenticated organization, the same brands the web brand switcher shows. Returns data (id and name per brand), activeBrandId (the brand already selected for this user, or null when none can be determined) and total. This call takes no arguments and always answers for the whole organization. Call it first on a new session: the brand-scoped tools — list_campaigns, list_influencers, list_outreach_campaigns, list_jobs and their siblings — each declare their own brandId argument, and each falls back to activeBrandId when it is left out. Answers which brands you are able to act for: the roster of brand identities this session stands on, purely orientation for the agent.
get_brand_estatemulti_brand_managementReadsRead the whole portfolio of brands an agency or multi-brand organization holds, in one call, with the state of each rather than only its name. Returns data (per brand: id, name, industry, domain, isPrimary, isArchived, setupComplete, campaignGroupCount, createdAt and isActiveBrand), plus activeBrandId, total, archivedCount and callerRole. Set includeArchived to true to bring retired brands into the answer; they are left out otherwise. Reach for it instead of list_brands when the question is about the estate itself — which brands are still half-configured, which is the default, how much campaign grouping sits behind each — and then pass a chosen id to set_active_brand or to any brand-scoped tool. Roles here are granted on the organization membership rather than per brand, so callerRole governs the entire portfolio, and Justify keeps no per-brand headcount: campaignGroupCount is the tally it does keep.
set_active_brandRequired arguments: brandIdbrand_switcherWritesChoose which brand this session stands on, so every later call that omits brandId answers for that brand instead of refusing as ambiguous. The choice is stored as the caller preference the Justify web brand switcher writes, so a selection made by an agent is the selection a colleague then sees in the browser, and it survives this connection. Returns activeBrandId, activeBrandName, previousActiveBrandId, changed (false when the brand was already selected) and brandCount. Call it once after list_brands when an organization holds several brands and you are about to make a run of calls for one of them, rather than repeating brandId on every call; call it again to move to another brand. It alters no campaign, creator, job or payment record — the only row it writes is the caller own brand selection — and naming a brand this organization does not hold, or one that has been retired, is refused with NOT_FOUND while naming the brands that are selectable.

Campaigns and the Campaign Wizard

Read your campaigns, and build new ones through the same Campaign Wizard the web app uses: create a draft, save its steps, attach creators and an asset, validate it, then submit. Submission is the gate: Creative Testing and Brand Lift only work from a submitted campaign.

Entitlements used by this family: campaign_canvas, campaign_wizard, campaigns

Try asking

  • List my campaigns, then show me the detail record for the most recently updated one.
  • Create a draft Campaign Wizard called "Autumn skincare launch", fill in the basics, the objectives and the audience, then tell me what still blocks submission.
  • Give me the whole intelligence brief for campaign 6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e in one call.
ToolEntitlement neededEffectWhat it does
list_campaignscampaignsReadsList marketing campaigns for the authenticated organization, ordered by most recently updated. Returns: id, name, status (one of draft, submitted, approved, active, paused, completed), startDate, endDate. Use this to get an overview of all campaigns before drilling into a specific one. For full details including assigned creators and budget, follow up with get_campaign. For performance metrics, use get_campaign_analytics. Pages with cursor and limit: when hasMore is true, call again with cursor set to the returned nextCursor. Use it to see everything running right now: the live, active campaigns currently underway, alongside paused, draft and completed ones. brandId is required for multi-brand organizations and restricts the page to one brand; limit is the maximum number of campaigns per page; cursor is the opaque nextCursor from the previous page, passed back verbatim; updatedAfter is an ISO timestamp and keeps only campaigns updated after it.
get_campaignRequired arguments: campaignIdcampaignsReadsGet the canonical detail record for a specific campaign by ID. Returns: name, status, brand, target platforms, date range, budget (starter and above) with stored amount and currency, business goal (enterprise and above), nullable primary KPI (starter and above), secondary KPIs (starter and above), demographics (professional and above), and assigned creators. "and above" is the subscription tier the field needs: below it the answer OMITS the key entirely rather than returning it null, so a missing field is a tier fact and never a fact about the campaign — read your own tier from get_mcp_access_status. Use this after list_campaigns whenever the requested budget, currency, KPI, or other field is absent from the list row. For engagement metrics (views, likes, comments, shares), use get_campaign_analytics instead. Use it to find the one campaign a person names in passing — the back-to-school push, the spring launch — and read back what was actually agreed.
get_campaign_wizardRequired arguments: campaignIdcampaign_wizardReadsGet campaign wizard lifecycle state for one campaign. Returns step completion flags, submit blocking issues, audio settings, normalized locations, linked asset readiness, and creator count. Use this before update or submit workflows to understand what the server considers complete. Read it when a submit was refused, when a draft is picked up again after a gap, or before editing a step somebody else may have filled in: it names precisely which questions remain unanswered, so no submit is attempted on an unfinished draft.
create_campaign_wizardRequired arguments: idempotencyKey, nameTakes an idempotencyKeycampaign_wizardWritesCreate a draft Campaign Wizard for the authenticated organization. Requires idempotencyKey; an exact retry in the same organization, actor, brand scope, and request payload returns the original draft rather than creating a second one. Returns campaign id, status, current step, completion flags, and blocking issues. Use this first in MCP campaign creation workflows, then call save_campaign_wizard_step, set_campaign_wizard_creators, link_campaign_wizard_asset, validate_campaign_wizard, and submit_campaign_wizard. The draft carries the campaign basics: campaign name, business goal, brand website, budget as currency plus total and social media amounts, ISO 8601 start date and end date, primary and secondary contacts, target social media platforms, and for AUDIO campaigns the ad read style, planned spot duration and brand sound logo. Setting up a new campaign starts here. Use it when somebody wants to start PLANNING a new push for a coming quarter and nothing exists yet: this is the step that opens the plan.
save_campaign_wizard_stepRequired arguments: campaignId, data, expectedVersion, idempotencyKey, stepTakes an idempotencyKeycampaign_wizardDestructiveSave one Campaign Wizard step through the same path the web app uses. Requires idempotencyKey; the response is recorded in the same transaction as the step update, so an exact retry returns the original result. Returns campaign id, current step, derived completion flags, submit readiness, and blocking issues. Use this after create_campaign_wizard to fill steps 1 through 3. The data property names every field this tool accepts and the steps each one belongs to, so there is nothing to guess. Link step 4 assets with link_campaign_wizard_asset, then call validate_campaign_wizard before submit_campaign_wizard. Steps 4 and 5 accept only their pointer and completion fields here. On conflictReason=version_mismatch, call get_campaign_wizard to obtain the current version and retry with a new idempotencyKey. Use it to keep what a person has filled in so far so they can come back to it later. Use it when a person has typed the brief, the budget, the dates or the creator picks and wants that draft kept exactly as entered.Saving is not submitting: the campaign stays a draft on the person's own desk until they choose to send it in.
set_campaign_wizard_creatorsRequired arguments: campaignId, creators, idempotencyKeyTakes an idempotencyKeycampaign_wizardDestructiveAdd or replace creators on a draft Campaign Wizard using saved Creator UUIDs from the marketplace roster or fresh searchReceipt values from a confirmed marketplace search. Requires idempotencyKey and returns counts for added, skipped, and removed creators plus updated wizard readiness. Use this after saving step 1 creator choices and before validate_campaign_wizard or submit_campaign_wizard. Think of it as editing the wizard’s creator line-up in one write: newcomers join, duplicates are skipped, and creators omitted from a replacement list are detached from the draft.
link_campaign_wizard_assetRequired arguments: assetId, campaignId, idempotencyKeyTakes an idempotencyKeycampaign_wizardWritesLink an existing brand-owned, durable-storage-ready library asset to a draft Campaign Wizard without accepting submission IDs or raw upload bytes. Requires idempotencyKey; an exact retry returns the original result rather than linking twice. Returns link (id, assetId, campaignWizardId, sourceContentType, sourceContentId, sourceVersionId, role, rationale, budget, budgetCurrency, associatedInfluencerIds, submissionId, createdAt, updatedAt) and the updated wizard state (currentStep, completion flags, blockingIssues, canSubmit, assetCount, assetsReady, version). Use this after list_library_assets and get_library_asset, then validate_campaign_wizard before submit_campaign_wizard. Each linkage attaches one creative to the wizard step-4 slot with its role and rationale. Use it to attach a video or an image to the campaign someone is building.
validate_campaign_wizardRequired arguments: campaignIdcampaign_wizardComputesValidate Campaign Wizard readiness using the same server-derived completion and submit blocking rules as the UI. Returns completion flags, canSubmit, blockingIssues, creator count, asset readiness, and normalized locations. Use this before submit_campaign_wizard and after any wizard write tool. It also returns currentStep, contentFormat, assetCount, version and an etag of the quoted campaign-wizard:id:version form; carry that version back as expectedVersion when you submit. Nothing is mutated, so it is safe to re-check between edits. Use it to answer whether anything is stopping the campaign going in: blockingIssues names each thing to be fixed before it can be put in. Use it to answer is this campaign ready to go: a yes or no with the list of what is still missing, the same check the web app runs when someone presses submit.
submit_campaign_wizardRequired arguments: campaignId, expectedVersion, idempotencyKeyTakes an idempotencyKeycampaign_wizardWritesSubmit a complete Campaign Wizard through the same transactional submit path as the UI. Requires idempotencyKey; the submission response is recorded in the same transaction as the submission itself, so an exact retry returns the original result rather than submitting twice. Returns submission (id, campaignName, description, startDate, endDate, timeZone, contacts, currency, totalBudget, socialMediaBudget, platform, influencerHandle, primaryContactId, secondaryContactId, mainMessage, hashtags, memorability, keyBenefits, expectedAchievements, purchaseIntent, brandPerception, primaryKPI, secondaryKPIs, features, submittedSnapshot, submissionStatus, createdAt, userId, alreadySubmitted) and the wizard final state (id, status, submissionId, version). Use validate_campaign_wizard first and pass the latest expectedVersion from get_campaign_wizard or validate_campaign_wizard; this refuses incomplete, cross-org, stale, or non-draft campaigns. Complete means steps 1 to 3 and a budget, at least one influencer, at least one creative, and every creative processed with a content type, why this, an associated influencer and confirmed source rights; a draft may lack any of these until it is submitted. Use it when someone says the campaign is finished and nothing is missing, and to put it in.
get_campaign_analyticsRequired arguments: campaignIdcampaignsReadsGet engagement analytics for a campaign. Returns: postCount, totals (views, likes, comments, shares), org-level stats (totalCampaigns, publicCampaigns), and paidMedia — what this campaign spent turning its creator posts into paid adverts, beside the organic figures of the same posts. Use this after get_campaign to understand how a campaign is performing. Combine with get_campaign for the full picture: get_campaign gives you the brief and creators, get_campaign_analytics gives you the performance numbers. Use it to answer whether a campaign beat the last one: these totals are what a straight comparison between two campaigns is built from. It is also what answers how the summer campaign actually did, once someone asks in those words. paidMedia carries spend, impressions, clicks, video views, conversions, the number of boosts that have delivered, the largest reach any single boost was reported on any single day, a day-by-day list per boosted post cut in the advertising account timezone, and a ledger reconciling each boost spend against the ceilings the network reports. Money is a fixed-scale decimal string with currencyCode beside it. Reach is never added up: it counts people, so no campaign-level reach exists. A campaign that has bought no advertising answers with the block empty rather than absent.
get_campaign_canvasRequired arguments: campaignIdcampaign_canvasReadsGet the full Campaign Canvas intelligence report for a campaign. Returns: campaign summary, performance metrics (reach, engagement, views, posts), creator list, AI-generated narrative, and platform breakdown. Brand Lift report evidence is returned only when the caller also has brand-lift:read and Brand Lift report entitlement. Every Brand Lift and Creative Testing figure is the Evidence Cube's, the number the report prints: when the cube cannot answer, the figure is null and the reason is stated (brandLiftEvidence, or a test's projectionEvidence), never read from a stored copy. This is the richest single-call data source for campaign intelligence. A campaign with no tracked posts answers zero totals and a null aiNarrative, not an error; one outside your scope answers NOT_FOUND.
generate_campaign_briefRequired arguments: campaignIdcampaign_canvas, campaignsComputesGet a comprehensive campaign intelligence brief in a single call. Combines campaign details, assigned creators, engagement analytics, and Campaign Canvas intelligence into one compact JSON digest optimised for AI context. This is the fastest way to understand a campaign end-to-end. Returns: name, status, platforms, budget, creators, performance totals, canvas verdict, and key insights. campaignId is the campaign UUID; brandId is the brand UUID, required for multi-brand organizations and omitted for a single-brand organization. Use it when someone wants the whole story on a campaign in one go.

Creator discovery, your saved roster and the shared workspace

Marketplace search runs in two halves on purpose. The prepare half spends nothing: it turns your sentence into canonical Justify filters and hands back the exact cost and a confirmation to show you. Only the confirm half runs the paid search. Around that sit the saved roster, Justify Score ranking, and the collaboration workspace your team shares.

Entitlements used by this family: campaigns, influencer_list, influencer_marketplace, risk_reports

Try asking

  • Find UK fitness creators on Instagram with 10k to 100k followers and strong engagement. Show me the costed plan and the confirmation text first. Do not run the paid search until I say yes.
  • Rank the creators already saved on our roster against campaign 6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e by Justify Score, and tell me who fits the brief.
  • Add a note on our saved creator @lauren.runs saying the rate card came in, and make Priya the owner of that creator.
ToolEntitlement neededEffectWhat it does
list_influencersinfluencer_listReadsList influencers saved to the organization's roster, ordered by savedAt descending. Returns data plus the canonical roster total, nextCursor, and hasMore. Each row includes: id, handle, name, platform, followersCount, engagementRate, location, category, avatarUrl, isVerified, savedAt. engagementRate is the provider's own ratio, stored exactly as sent — it is average likes divided by follower count, so it is USUALLY well below 1 but is not bounded there: a small account carried past its own audience really does engage at many times its follower count. engagementRatePercent is the same rate as a percentage; quote the percentage, never the fraction. Use this—not score_saved_influencers—when the question asks for the first or most recently saved roster row rather than a Justify Score ranking. For an ordinal position beyond the first page, request the largest useful page up to 100 and pass its nextCursor to the next call. This lists only creators the organisation has ALREADY saved. To DISCOVER new creators, use prepare_influencer_search then confirm_influencer_search — that pair searches the wider creator marketplace and costs credits. To inspect a saved influencer's bio or full profile, call get_influencer with the returned id after this list call. Pass the opaque nextCursor back with a limit up to 100 to page; multi-brand organizations name the brand with brandId. Use it to answer who we already work with: the creators saved to the roster, never the wider marketplace.
get_influencerRequired arguments: influencerIdinfluencer_listReadsGet detailed profile information for a saved influencer by their ID. Returns: handle, name, bio, platform, avatarUrl, followersCount, engagementRate, location, category, brandId, isVerified, createdAt, updatedAt. engagementRate is the provider ratio, stored exactly as sent: it is average likes divided by follower count, so it is USUALLY well below 1 but is not bounded there, because a small account carried past its own audience really does engage at many times its follower count. engagementRatePercent is the same rate as a percentage; quote the percentage, never the fraction. Only accessible for influencers saved to your organization and brand-scoped roster. Use list_influencers to find available IDs. To discover creators the organisation has not saved yet, use prepare_influencer_search then confirm_influencer_search. This tool returns no email address, phone number or contact object — that is this read’s own shape, not a rule about the wire: get_influencer_profile_analytics with include set to contacts returns the creator’s contact channels for a saved creator, each carrying its type, its value and whether the supplier vouched for it. To SEND rather than read, compose with draft_outreach_message and hand the message to the outreach queue, which looks the address up on the saved creator record itself at send time. To find out BEFORE saving whether a creator is reachable at all, read the contact census on confirm_influencer_search rows (contactMethodCount, hasEmailContact, hasSmsContact). Use it to tell a person about one creator before they commit any budget: the saved profile is the record a spend decision rests on. Use it to answer who is this creator: one profile, read on its own, with the audience figures the supplier recorded on the last refresh.
save_influencerRequired arguments: idempotencyKeyTakes an idempotencyKeyinfluencer_list, influencer_marketplaceWritesSave a marketplace creator to the authenticated organization's influencer roster. Provide a saved visible influencerId, or provide handle plus platform with a fresh single-use searchReceipt returned by a confirmed marketplace search. Requires idempotencyKey for every save. Returns saved record id, canonical influencerId, handle, platform, brand scope, savedAt, and alreadySaved. Use this before get_influencer when a searched creator should become part of the saved roster. Saving refreshes the creator RECORD but not profile ANALYTICS: get_influencer_profile_analytics reads a cache warmed only by a human opening the profile in the web app, so it will miss for a just-saved creator — that is expected, not an error. Use it when a person says to keep this one because they like them: the creator moves out of the search results and onto the roster.
add_influencers_to_campaignRequired arguments: campaignId, idempotencyKey, influencersTakes an idempotencyKeycampaigns, influencer_list, influencer_marketplaceWritesAdd one or more marketplace creators to an owned draft campaign using Justify's canonical campaign creator persistence path. Provide campaignId and either saved Creator UUIDs from list_influencers/save_influencer or fresh single-use searchReceipt values from a confirmed marketplace search. Requires idempotencyKey for every campaign add. Receipts prepared before the user chooses a campaign are accepted when the OAuth actor and brand scope match. Returns campaignId, brandId, added, skipped, removed, requested count, and the normalized creators that were submitted. Use this only after a user explicitly asks to add selected creators to a campaign. Use it when someone says to put those creators on the campaign.
get_influencer_profile_analyticsRequired arguments: influencerIdinfluencer_list, influencer_marketplaceReadsGet cached profile analytics for a saved influencer using the same profile analytics cache and view model as the web marketplace profile page. CACHE-ONLY, by design: this never calls the external provider. The cache is warmed only when a human opens the creator profile in the web marketplace (a paid provider enrichment, roughly $0.63-$1.88 per profile, which MCP has no credit type to charge for yet) and lives 7 days — so a creator saved moments ago is cold by construction and this tool will miss until someone opens their profile in the app once. Returns normalized profile fields, selected analytics sections, presence platforms, platform handles, analytics support flags, brandId, profileUrl and metadata. Only saved influencers in the authenticated organization and brand-scoped roster can be accessed. Use it to answer how a creator’s audience is actually made up — the age, gender, location, language, ethnicity and interest split sitting behind the follower number — what their posts earn, what their rate card asks, and how any of it has moved month by month. ANSWERS ARE SECTIONED. Every call returns analytics.summary: the handle, the display name, the audience size with the basis it was read on (a channel measured on subscribers says so), the content count, the unbounded engagement ratio, the account location and the report timestamps. Ask for the heavier sections by name through include — audience, pricing, content, reputation, contacts — and ask for the ones you will use, because a creator’s whole report is larger than one tool result may carry. Every audience figure is either measured or named in that section’s not_measured with the reason it is not, so never infer one that is missing. analytics.sections separates the three silences that look alike: not_requested is what you did not ask for, not_returned is what carried nothing for this creator and why, deferred_for_size is what was returned and did not fit beside the rest — ask for it on its own and it arrives whole. Follower-type shares are percentages that already sum to 100; audience credibility is a 0-1 ratio; the engagement ratio has no upper bound and is never rescaled. profileUrl opens this creator in Justify.
score_saved_influencersRequired arguments: campaignIdinfluencer_listComputesWhich of our saved creators fit this campaign, and why does the top one fit and the bottom one not: the Justify Score ranking of the roster with a reason per creator. Rank the influencers ALREADY SAVED to this organization by their Justify Score against a campaign, so you can answer "which influencers should I back for this campaign?" — not merely "who is on my roster?". The Justify Score is campaign-relative: pass campaignId (find one with list_campaigns) and the brief comes entirely from that campaign's completed wizard — platforms, locations, follower range and categories are never supplied by you. A campaign whose wizard has no target platforms yet cannot be scored against; the same rule the Justify app applies. Returns each influencer with rank, id, handle, name, platform, followersCount, engagementRate, category, location, avatarUrl, justifyScoreDisplay and fitLabel (the score and wording exactly as the Justify badge shows them, e.g. 8 and "Strong Fit" — quote these, never the internal 0-100 justifyScore), fitLevel, dataCompleteness, rationale (the scorer's one-line reason this influencer fits or does not), and why (the scorer's own per-dimension explanations), plus the campaign intent it scored against. Makes NO external API calls and consumes NO credits — unlike influencer SEARCH, which discovers NEW influencers and costs money. Use it to answer from the people we already have, who should front this campaign: no new faces, no spend, the roster ordered best fit first.Nothing outside the roster is looked at, no supplier is called and the bill does not move; the order comes from the wizard's own brief.
prepare_influencer_searchRequired arguments: platform, promptinfluencer_list, influencer_marketplaceSpends creditsFree: nothing is spent and no provider is called. This is the costed plan and the confirmation text a person approves before any paid search runs, so "show me the plan, do not run it" is answered by calling this and stopping. Convert a natural-language influencer marketplace request into canonical Justify filters without spending credits or calling provider search. Rejects unsupported filters before issuing a token; otherwise returns the exact filters set, estimated influencerSearch credit cost, confirmation text, and a short-lived confirmation token. Always show the confirmation text to the user before calling confirm_influencer_search. The confirmation text now carries two things the canonical filter list cannot show on its own: the marketplace defaults that were ADDED rather than requested, named individually (a 10,000-follower floor is injected when parsePrompt is true and you set no count bound of your own, and is never injected when parsePrompt is false), and the human-readable place names the resolver matched behind each opaque location UUID, so an approver can check "Greater London" instead of a hexadecimal id. Topic words are matched as free text over creator bios, not looked up in a category taxonomy: "lifestyle" reaches every profile whose bio says lifestyle, brand accounts and subscription boxes included, and asking for a topic is therefore a keyword request rather than a classification. To keep brand and business accounts out of a people-shaped search, set creator_account_type to ["CREATOR"] on instagram — a demographic filter such as creator_gender cannot do that job, because a brand account can carry the demographic the provider indexed it under. This tool publishes no parser confidence score and no extracted-entity list; judge the parse from filtersSet, warnings and the explanation instead. Use it for a people-shaped brief in the customer’s own words — women in the UK who post about skincare and actually get engagement — because that sentence is what this tool turns into filters: creator_gender for women, creator_locations for the UK, bio_phrase and topic_relevance for skincare, engagement_rate for creators who genuinely get engagement, and follower_count for reach.
confirm_influencer_searchRequired arguments: confirmationText, confirmationToken, idempotencyKeyTakes an idempotencyKeyinfluencer_list, influencer_marketplaceSpends creditsThis is the step that returns the shortlist: the influencers who match the brief and align with the brand and its audience, with their engagement rate and fit beside each one, ready to pitch. Run a prepared influencer marketplace search only after the user has reviewed and confirmed the exact filters returned by prepare_influencer_search. Consumes the short-lived confirmation token once and applies the same influencerSearch credit allowance as the web marketplace. Returns data rows with handle, name, platform, followersCount, engagementRate (the rate as a fraction of one, usually between 0 and 1 but not bounded there, because a supplier percentage above 100 lands above 1), engagementRatePercent (the same rate already in percent, e.g. 0.95 meaning 0.95% — quote this one to a person and never multiply it yourself), category, location, avatarUrl, isVerified, justifyScoreDisplay and fitLabel (the score and wording exactly as the Justify badge shows them, e.g. 8 and 'Strong Fit' — quote these, never the internal 0-100 justifyScore), fitLevel, dataCompleteness, rationale (the scorer's one-line fit reason when a campaign was scored against), and the signed searchReceipt required by save_influencer and add_influencers_to_campaign, plus total, offset, hasMore, searchRunId and operationId for resuming. Read resultOrder and resultOrderNote before reading the rows: they say what order the page is in, because scoring a campaign ranks the page by fit and that ranking REPLACES any sort you asked for, and requestedSort echoes the sort you sent. This tool takes no pagination argument: the offset is sealed into the confirmation token, so when hasMore is true the next page is fetched by calling prepare_influencer_search again with offset set to the returned nextOffset and confirming the token it returns. Every row also carries a contact census — contactMethodCount, hasEmailContact and hasSmsContact — which says whether a reachable email or phone route exists for that creator WITHOUT disclosing the address: a search ROW carries no contact value on any Justify surface, the web marketplace search included, so the census is what a has_contact_details or specific_contact_details filter can be audited against. The channels themselves are one call further in — save the creator with save_influencer, then read get_influencer_profile_analytics with include set to contacts, which returns each channel's type, its value and whether the supplier vouched for it. To SEND rather than read, use the outreach tools, which resolve the address server-side at send time. category echoes the provider's own topic label for the row and is null wherever the provider sent none — it is not derived from your topic words, because topic words are matched as free text against creator bios rather than looked up in a taxonomy. It is the step that actually runs the brief a person asked for in their own words (women in the UK who post about skincare with real engagement) once they have approved the filters. The confirmation text and the confirmation token are the exact pair prepare_influencer_search returned: the text is what the user approved, the short-lived token is what lets this call run it once.
list_risk_reportsRequired arguments: handle, platformrisk_reportsReadsList the paid brand-safety screenings your organisation has bought about one creator. Returns per screening: id, status (AWAITING_PAYMENT, AUTHORIZED, IN_PROGRESS, COMPLETED, FAILED), a plain verdict (unfunded, funded, screening, delivered, failed), the platforms screened, the keyword watchlist, timeframeDays, charge (what Stripe captured on delivery, as amountMinor and currency, or null when nothing was charged or no charge was recorded), requestedAt, updatedAt and downloadAvailable; the envelope names the subject creator and whether more screenings exist beyond this page. A creator nobody has ever screened answers an honest empty page, never an error, so this is the safe first call before vetting or contracting anyone. There is no cursor: hasMore true means raise limit, up to 50, and ask again. Buying a fresh screening is deliberately not an agent action: it is funded by Stripe checkout in the Justify web app. Take an id from here into get_risk_report for the safe summary. The subject handle comes with or without a leading @ and is matched case-insensitively within the platform you name.
request_risk_reportRequired arguments: handle, platformrisk_reportsReadsHand off a brand-safety screening request to a human: returns the Justify web page where a person opens the risk-report dialog for this creator and completes payment through the existing Stripe checkout. This tool never charges, funds, reserves or creates anything — requesting is a paid action and payment happens only in the browser, so what comes back is the requestUrl to open, a paymentNote saying exactly that, and existingReportId when your organisation has already screened this creator (check it first with get_risk_report before paying again). The subject is a creator handle plus its platform, the same pair list_risk_reports takes. A handle Justify has never seen still answers with the marketplace page URL — the person can review the profile before deciding — with existingReportId null. The screening is about one creator at a time: the handle is matched case-insensitively within its platform, with or without the leading @.
get_risk_reportRequired arguments: reportIdrisk_reportsReadsRead one purchased brand-safety screening as a safe summary — never the provider payload and never a download link. Returns: verdict (unfunded, funded, screening, delivered, failed), the eight screening sections with what each one covers, the keyword watchlist, the platforms screened, timeframeDays, charge (what Stripe captured on delivery, as amountMinor and currency, or null when nothing was charged or no charge was recorded), requestedAt, generatedAt (filled in only once the screening is delivered), a failureCode from a closed vocabulary when something went wrong, and downloadAvailable. The signed PDF, the screening vendor job and the Stripe payment identifiers are deliberately withheld: an agent gets the finding, a person gets the file from the Justify web app. A screening a colleague bought is refused, because the receipt belongs to whoever paid — exactly as the web app refuses it. Take the id from list_risk_reports. Use it to answer is this creator safe to work with: the brand-safety screening's findings section by section, without the raw file.Sections cover conduct, controversy, brand conflicts, audience concerns and the like; each carries a plain finding a person can act on.
get_collaboration_workspaceinfluencer_marketplaceReadsGet the marketplace collaboration workspace for the authenticated organization and brand: saved creators in focus (each with owner assignment, recent notes, and comment counts), the activity timeline (searches, saves, notes, assignments), and the team roster with pending invites. Returns: focus, timeline, team, pagination cursors. Use it to see what the team is working on before adding notes with add_collaboration_note or assigning owners with assign_collaboration_owner. Pass focusCursor or timelineCursor from pagination to fetch older pages. Use it to answer what everyone has been saying about a creator: the notes, comments and activity the team left behind.
add_collaboration_noteRequired arguments: idempotencyKey, influencerId, textTakes an idempotencyKeyinfluencer_marketplaceWritesAdd a shared team note to a saved influencer in the collaboration workspace. Requires the influencerId of a creator already saved to the roster (find them with get_collaboration_workspace or list_influencers) and an idempotencyKey for every write. Returns the created note id, text, createdAt, and the user id of its author. Notes notify the organization team and appear in the collaboration activity timeline. File attachments are not supported over MCP — use the web workspace for uploads.
update_collaboration_noteRequired arguments: noteId, textinfluencer_marketplaceDestructiveEdit the text of a collaboration note you authored. Requires the noteId returned by add_collaboration_note or listed in get_collaboration_workspace focus comments. Author-only: notes written by other teammates cannot be edited. Returns the note id, updated text, and updatedAt. Retrying with the same text is safe — the edit sets the same content again. The new text overwrites the note in place rather than appending a revision, so the earlier wording is gone once the edit lands.
delete_collaboration_noteRequired arguments: noteIdinfluencer_marketplaceDestructiveRemove a collaboration note you authored and hide that entry from every teammate at once: retract it. Takes the noteId of your own note; a note another teammate wrote is refused. Returns the note id with deleted true. The note is soft-deleted and disappears from the workspace; a retry after success returns not found. Removal is one-way from here: the entry vanishes for every colleague at once, any file attached to it is cleaned up behind the call, and this server exposes no restore. Reach for it when somebody says a comment was posted in error, or names a person who should never have been named on the record.
assign_collaboration_ownerRequired arguments: assigneeUserId, influencerIdinfluencer_marketplaceWritesAssign a team member as the owner of a saved influencer in the collaboration workspace, or clear the owner by sending assigneeUserId as an explicit null. Omitting it entirely is refused rather than treated as a clear, so a retry that drops the field cannot silently strip a creator of its owner. Requires the influencerId of a creator already saved to the roster; find creators and current owners with get_collaboration_workspace, and team member ids in its team.members list. Returns assignedAt and the user ids of the assignee and of who assigned them. Repeating the same assignment is safe: the assignment record is upserted per saved creator.

Creative Testing

Predict how one creative will perform before it runs. Check the campaign is ready, pick a linked asset, start the run, then read the prediction results and the immutable per-agent evidence behind them. A run costs money, so it needs a submitted campaign and an explicit retry key. The four result modalities read separately: the leaderboard for a batch launched together, the brand-track wave, the attention heatmap as geometry rather than as a picture, and the roster of static image tests.

Entitlements used by this family: creative_testing, creative_testing_batch, creative_testing_brand_track, creative_testing_heatmap, creative_testing_image, debug_tools

Try asking

  • Check whether campaign 6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e is ready for Creative Testing, then start a run on the asset linked to it.
  • Show me the results of Creative Testing run 2c6a7f11-5b2c-4d3e-8f90-1a2b3c4d5e6f, with the per-agent decision evidence behind them.
ToolEntitlement neededEffectWhat it does
get_campaign_workflow_statusRequired arguments: campaignIdcreative_testingReadsInspect a Campaign Wizard campaign before measurement work. Returns submission readiness, primary KPI, asset readiness, creator evidence, Creative Testing runs, and linked Brand Lift study status only. Use this before starting or interpreting Creative Testing so the workflow remains campaign-first. Every measurement figure here is the Evidence Cube's, the number the report prints: a run's predicted reach band and retention, and a study's overallLift. When the cube holds no citable fact the figure is left out (a run) or null (a study) rather than read from a stored copy, and projectionEvidence names why. Read get_creative_test_results or get_brand_lift_study for a figure with its receipt. Use it to answer is this campaign ready to be measured: what has been filled in, what evidence exists and which studies are already attached, read before spending anything.
list_campaign_creative_assetsRequired arguments: campaignIdcreative_testingReadsList creative assets linked to one Campaign Wizard campaign. Returns asset IDs, media type, readiness, duration, resolved URL when available, and whether each asset can start Creative Testing. Use this before start_creative_test. Creator attribution is the same pseudonymous reference the campaign asset picker shows: a stable ct-creator-v1- ref, a short label and the platform, which group a campaign's assets by creator without naming anybody; call get_campaign_canvas for the campaign's assigned creator roster. Attribution is null when the campaign has several assigned creators and the link does not say which one owns the asset. Each asset also carries a blockers array naming exactly what stops it, such as missing video dimensions metadata, an unfitted image reach regression, or a short side under 360px, alongside total and hasMore; deleted and archived assets never appear and the page is capped at 500 links.
list_creative_testscreative_testingReadsList creative testing runs (video predictions) for the resolved active brand. Returns: id, name, status, verdict (the GREEN/AMBER/RED label and headline, with the predicted reach band and retention read from the Evidence Cube), createdAt, and batchId, the batch a run was launched in (null for a run launched alone): group rows by batchId, take the newest createdAt in a group as the batch age, and pass the id to get_creative_test_batch to read whether it has settled and which creative won. Report-ready rows include reportReady=true and reportTrustEvidence; active rows return reportReady=false and reportTrustEvidence=null. Every figure on a verdict is the Evidence Cube's, the same number the report prints; when the cube holds no citable fact for a run, the figures are left out rather than read from the stored verdict, and projectionEvidence names why. The share rate and each figure's receipt (fact id, interval, base, claim tier) come with get_creative_test_results. Filter by campaignId or status. Answers up to 50 runs per call (default 10); multi-brand organizations pass brandId to choose the brand. Use get_creative_test_results with a specific run ID for full prediction details including stage-gate breakdown and demographic analysis. Use it to answer what we are currently testing: every Creative Testing run still in flight or already settled.
get_creative_test_resultsRequired arguments: runIdcreative_testingReadsGet the full prediction results for a report-ready Creative Testing run. Returns reportReady=true, reportTrustEvidence, verdict, metrics, insights, summary, target audience, campaign evidence payload, frame artifacts, saliency metadata, calibration context, fallback disclosures, and execution time. Use list_creative_tests or get_campaign_workflow_status first to find run IDs. Use it to answer which version of the ad performed best: the verdict names the stronger creative and the reason.
get_creative_test_agent_dataRequired arguments: runIdcreative_testingReadsFetch immutable per-agent decision evidence for a completed Creative Testing run. Returns the stored agent decision JSON used by the report, including agent-level traits and choices when available. Use this after get_creative_test_results for detailed audit evidence. This is the SIMULATED-PANEL layer beneath a headline verdict: one entry per synthetic respondent the simulation polled, each recording which creative that respondent preferred and what the model said about why, so a disputed headline can be traced back to the individual judgements that produced it. The payload is served exactly as the run archived it in immutable object storage, sanitised of internal model plumbing but otherwise unedited, so a rerun of the same identifier answers byte for byte the same evidence — which is what makes it citable in an argument about a spending decision. Only a COMPLETED run has this evidence at all; an unfinished or abandoned run is refused by name rather than answered with a partial panel. Use it to answer why a creative won rather than merely that it won, and to show a sceptical colleague the respondent-level workings behind a recommendation.
get_creative_test_batchRequired arguments: batchIdcreative_testing_batchReadsRead one Creative Testing BATCH: the roster of sibling runs launched together from a single campaign, and the leaderboard verdict on each. Returns batchId, the campaign every child belongs to, childCount, settled (true once no child is still running, so an agent stops polling), and children — one entry per run carrying runId, name, status and the Justify Creative Score alignment (score, band, category, evidence strength) that only a COMPLETED child has. A batch belongs to one brand in one organisation, exactly as the web leaderboard reads it: in a multi-brand organization the brandId names whose batch it is, and every other brand reads NOT_FOUND. Refuses NOT_FOUND when no run in this brand carries that batchId. Use it to answer which creative in the batch won, and whether the rest of the batch has finished.
get_brand_track_resultsRequired arguments: runIdcreative_testing_brand_trackReadsRead the "Track my brand" wave for one Creative Testing run: how long the brand was actually onscreen in the creative, and when it first showed up. Returns tracked (false when the run was launched without brand tracking, which is an honest answer rather than a zeroed wave), summary (brandDetected, firstAppearanceSec, totalPresenceSec, presenceRatio, avgAreaRatio, peakAreaRatio, framesAnalyzed, framesWithBrand, videoDurationSec), modelLabel naming the detector in customer-facing terms, and presenceTimeline — the per-second area share the report charts. Per-detection model confidence and the internal detector id never leave the server, and no sponsor label or raw frame is returned. Refuses NOT_READY while the run is still predicting and NOT_FOUND for a run outside this brand. Use it to answer whether anybody would remember whose ad this was. The runId is the simulation run id (a UUID) from list_creative_tests or list_creative_test_images, and in a multi-brand organization the brandId names whose test the run belongs to. Every brand-tracking result belongs to one creative test simulation run in one organization: the run UUID is the anchor, and in a multi-brand organization every brand reads only the runs it owns.
get_creative_test_heatmapRequired arguments: runIdcreative_testing_heatmapReadsRead the attention heatmap for a completed Creative Testing run as GEOMETRY, not as a picture. Returns regions — normalised boxes for the text, face, brand, product, cta and message areas the model measured, in normalized_original_frame coordinates, each with the share of attention that area took — plus scores (entropy, the focal point, the thumbZone reachability figure and the model version) and attentionTimeline, how tightly focus held over time. The overlay image, the frames artifact and the original creative are DELIBERATELY not returned: an agent reasons about where the eye went, it does not need a download link to the customer artwork. Per-box detector confidence and raw detector labels are stripped before the projection is built. Refuses NOT_READY until the run completes and NOT_FOUND for a run outside this brand. Use it to answer where the eye actually went on the creative, and whether the logo was anywhere near it. The runId is the completed simulation run id (a UUID) whose heatmap you want, and in a multi-brand organization the brandId names the owning brand. Every heatmap belongs to one completed creative test simulation run in one organization: the run UUID is the anchor, and in a multi-brand organization every brand reads only the runs it owns.
list_creative_test_imagescreative_testing_imageReadsList the STATIC creative tests for the resolved brand — the runs whose tested asset was a photo rather than a video — with the verdict reached on each still. Returns one row per tested image: runId, name, status, campaignId, createdAt, the public verdict (headline, rationale, recommendation, and the predicted reach band read from the Evidence Cube) and the Justify Creative Score alignment for completed rows, plus total and hasMore. The reach band is the Evidence Cube's figure, the number the report prints; when the cube holds no citable fact for a still, the band is left out rather than read from the stored verdict, and projectionEvidence names why. Image runs carry no cognition panel and no demographic split, so this roster is the whole of what the image modality answers; ask get_creative_test_results for one row in full. Filter by campaignId to compare the artwork of a single brief. Pages with an opaque cursor and limit (1 to 50, default 10): when hasMore is true, call again with cursor set to the returned nextCursor. Use it to answer how our still imagery scored, as opposed to the video cuts.
start_creative_testRequired arguments: assetId, campaignId, idempotencyKey, simulationMode, targetAudienceTakes an idempotencyKeycreative_testingSpends creditsStart one Creative Testing run from a submitted Campaign Wizard campaign and one linked creative asset. IT SPENDS MONEY: each accepted launch reserves exactly 1 creativeTesting credit from this organisation's pool, taken once the run is accepted and refunded only when the launch itself fails. Two videos compared is two launches and therefore twice that charge. Read the pool balance with get_billing_summary before you launch, and tell the person what a launch costs before you commit them to it. Requires idempotencyKey and records a durable operation before any credit is spent or background work begins. Returns runId, operationId, statusUrl, campaign evidence hash, and asset metadata. Use get_campaign_workflow_status and list_campaign_creative_assets first; poll get_operation_status with operationId after launch. This tool refuses draft campaigns and missing wizard evidence. The launch carries the target audience evidence for the simulated run: audience size, age range, gender mix, locale, platform and primary KPI, plus optional content-affinity dimensions and temporal modelling inputs. Two videos put head to head are two runs of this tool, one per creative asset; get_creative_test_results then says which of them performed best.
cancel_creative_testRequired arguments: runIdcreative_testingDestructiveCancel an active Creative Testing run owned by the resolved active brand. Returns runId and final CANCELLED status. Use list_creative_tests, get_campaign_workflow_status, or get_operation_status first; already-cancelled runs return CANCELLED, while completed and failed runs are refused. The underlying simulation stops where it stands and there is no resume, so testing that creative again means a fresh run. A machine token carrying no real user actor is refused. Takes the run UUID; multi-brand organizations pass brandId beside it. Cancelling is anchored to one simulation run UUID in one organization, and a multi-brand organization cancels only a run its own brand launched: stop the test, kill the run, abandon the simulation mid-flight.
submit_creative_test_verdictRequired arguments: idempotencyKey, runId, verdictTakes an idempotencyKeydebug_toolsWritesSubmit the verdict for a Creative Testing run that is waiting for it. A run stops at its verdict stage, its credit held, until the Claude agent writes the verdict (the /ct-finalise skill). Takes the run UUID, the verdict in Steer's own output schema (the schema the verdict model's reply is held to) and an idempotencyKey. A verdict off that schema is refused with each problem named, a run that is not waiting is refused, and a video run whose frames or transcript are not stored is refused naming what is missing. The verdict label, confidence and reach band stay the regression's. Returns ACCEPTED, the completion's run id and replacedPendingVerdict: the run then completes (persisted, facts minted, credit finalised, the customer notified). A corrected verdict, submitted on a new idempotencyKey before the earlier verdict's completion has started, replaces it and answers replacedPendingVerdict true; cancelFailed lists every earlier completion the platform would not cancel, which may still run and complete the run with the earlier verdict, so name such a run to the reviewer. A different verdict submitted once an earlier completion has started (EXECUTING or WAITING) is refused as a CONFLICT naming that completion (COMPLETION_STARTED), and nothing is enqueued; the same verdict submitted again joins its own completion, so a retry after a timeout is safe. Submissions for one run are serial. Once the run has completed a new verdict is refused. Exact retries replay the original answer. Super-admins only.
get_creative_test_pending_verdictRequired arguments: runIddebug_toolsReadsRead a Creative Testing run's readiness and evidence before writing its verdict (the /ct-finalise ready check and evidence read), for a run in any organisation. Takes the run UUID. Returns the run's status and stage (read it again to wait for COMPLETED), whether it is waiting for its verdict, whether its cube facts are minted (they are filed when the run completes), whether its frames, transcript and saliency are stored, and its batch (the batch id and every run in it with its state). While it waits, also its evidence (the kept frames as short-lived signed read URLs, the transcript text and the Campaign Wizard brief's words) and its pending-verdict.json (the stored steerInput the verdict is written from and the finalResult it completes). A run that is not waiting returns its state and no evidence or pending verdict, so an unready run is refused by name before any verdict is written. Super-admins only. Read-only.

Brand Lift

Read your Brand Lift studies and create new drafts against a submitted campaign. Reads never expose respondent links, panel identifiers or raw panel payloads. Launch, report generation and commentary stay in the web app: there are no tools for them, and an agent should not imply otherwise.

Entitlements used by this family: brand_lift, brand_lift_reports

Try asking

  • List the Brand Lift studies on this brand, and open the one attached to campaign 6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e.
  • Create a draft Brand Lift study for campaign 6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e.
ToolEntitlement neededEffectWhat it does
list_brand_lift_studiesbrand_lift, brand_lift_reportsReadsList Brand Lift studies for the resolved active brand without exposing respondent links, survey-panel identifiers, raw panel payloads, respondent-level metrics, or survey response data. Returns study status, campaign linkage, approval status, question and response counts, and safe report readiness summaries. A report summary's overallLift is the headline lift read from the Evidence Cube, the number the report prints; it is null, with projectionEvidence naming why, when the cube holds no citable fact, and get_brand_lift_study carries its receipt. Start here directly for a named Brand Lift study, score, or result; do not substitute the general list_campaigns tool. Use get_brand_lift_study for a specific returned study before discussing Brand Lift evidence; create_brand_lift_study creates a DRAFT study; review, launch, report generation and commentary stay operator-gated outside MCP. Pages with cursor and limit: when hasMore is true, call again with cursor set to the returned nextCursor. Use it to answer the question a brand really asks — did the ad change what people think of us? — by listing the studies that measured it.
get_brand_lift_studyRequired arguments: studyIdbrand_lift, brand_lift_reportsReadsHow far one campaign moved awareness, consideration or purchase intent among the people surveyed: the measured lift of one study against its primary KPI, with the sample size it rests on. Takes the studyId that list_brand_lift_studies returns and, when more than one brand is held, the brandId (a brand UUID). Get one Brand Lift study for the resolved active brand. Returns study status, campaign linkage, approval status, panel launch mode, safe report summary, and survey question/response counts. It never returns respondent URLs, survey-panel identifiers, raw report metrics, recommendations, respondent records, or survey answers; the only Brand Lift write offered over MCP is create_brand_lift_study (DRAFT only); review, launch, report generation and commentary stay operator-gated. The safe report summary carries overallLift, overallLiftReceipt, sampleSize, generatedAt, overlayPlatform and industry; the survey counts are questionCount and responseCount; approvalStatus and panelLaunchMode are returned as fields of the study itself. The study row also carries funnelStage, primaryKpi and secondaryKpis; the report summary overallLift is the headline lift (primary KPI, intent to treat, a fraction) read from the Evidence Cube, the number the report prints, overallLiftReceipt is its receipt (fact id, interval, base, claim tier), and sampleSize the number of responses the report was computed over. When the cube holds no citable fact, overallLift is null and projectionEvidence names why; it is never read from a stored copy.
create_brand_lift_studyRequired arguments: campaignId, funnelStage, idempotencyKey, name, primaryKpiTakes an idempotencyKeybrand_liftWritesCreate a DRAFT Brand Lift study attached to a submitted campaign. Requires idempotencyKey; exact retries replay the original response. Credits are checked at creation but only deducted when the report is generated. Returns the created study: id, name, status, campaignId, funnelStage, primaryKpi, secondaryKpis, createdAt. The study then progresses through the platform survey-design and approval pipeline (operator-gated — launch is not an MCP action). Use list_brand_lift_studies / get_brand_lift_study to monitor. Use it when someone wants to measure whether a campaign shifts perception: the draft study is where that measurement starts.

Campaign Library

The concepts and media your campaigns draw on. Browse ready, brand-owned assets and their detail before attaching one to a wizard, or add a new video or audio file: request an upload URL, upload the bytes directly to the media provider, then confirm the asset is durably stored. The library four generative shelves are readable here too. Audio reads: the script written against a brief, the narrative options behind it, which one somebody chose, and every recording made from the approved words. Characters: the invented presenters a brand puts on camera, their backstory and wardrobe direction, the topics they must avoid, how far their portrait training has got, and the films generated from them. Clip Studio: each long recording put through analysis, the moments the detector found in it with a score and an explanation against each, the short cuts rendered from those moments with their subtitle styling and reframing, and the review notes a team left. Localisation: which language markets a finished film has been sent to, how far each run has travelled, and whether the result can be watched or shipped. No rendered media crosses this surface on any of the four: not a voice recording, not a generated film, not a clip render, not a dubbed track. What you get instead is the readiness behind each one, measured against private storage rather than read off a status column, so an agent can say a thing is finished without ever holding a link to it. One generative action has now crossed onto the wire: a video concept sitting at script approval can have its script written again from the narrative already chosen, and it asks first: the call quotes the credit and hands back a sentence for the customer to read before anything is spent. Everything else that costs money or commits a brand publicly, approving a script for filming, minting a download, sharing a cut or importing somebody else footage, still stays with people. The tag index sits across all of it: every label anyone has typed onto a concept, an audio read, an uploaded file, a character or a localised cut, with how much work carries each one and, if you ask, the campaigns it appears under. Renaming a label everywhere and removing one from every shelf stay with people too.

Entitlements used by this family: campaign_library, campaign_library_assets, campaign_library_audio, campaign_library_characters, campaign_library_clipping, campaign_library_localisation, campaign_library_video

Try asking

  • List the ready library assets on this brand and show me the detail on the one I should attach to my draft campaign.
  • I have a new hero video to add to the library: give me an upload URL, then close the asset out once the file is uploaded.
  • Which audio reads on this brand are still waiting for somebody to approve the script, and what does the script actually say?
  • The script on that concept is flat, so write it again from the same narrative, and tell me what it will cost before you do.
  • Who can we put on camera for this campaign, and which of those presenters are ready to shoot with today?
  • Open that Clip Studio project and tell me which moment is worth cutting, what was already rendered from it, and what the team said.
  • Which language markets have we actually shipped this film into, and which failed?
  • What labels are we actually using across this brand library, which carry the most work, and are any of them near-duplicates we should merge?
ToolEntitlement neededEffectWhat it does
list_library_conceptscampaign_library_videoReadsList AI-generated video concepts from the campaign library (latest versions, archived excluded — the same population the web library shows). To learn which campaigns have no concept yet, read get_dashboard_summary (campaignsWithoutConcepts) in one call rather than listing concepts campaign by campaign. Returns: id, title, archetype, status, logline, campaign name, character name, thumbnail URL, duration, created date. Filter by campaign. Concepts are the campaign-library video-concept pipeline (campaign → concept → generated video) and are NOT the only source of AI video: AI character videos are generated from synthetic influencers and produce no concept row, so an empty concept list is normal for an organisation that only generates character videos. Pair with list_library_assets for media files (which excludes character videos too) and list_creative_tests for prediction results. Use it to show the ideas a team has written up: each concept is a logline and an archetype written before any video exists.
get_library_conceptRequired arguments: conceptIdcampaign_library_videoReadsGet full details of an AI-generated VIDEO concept from the campaign library: the written idea a moving picture was made from, and the primary cut selected for it. Returns: title, archetype, logline, status, script, campaign name, character details, thumbnail path, video playback path, duration, generation metadata. An archetype is the story pattern the idea follows and a logline is the single sentence that pitches it, both written before any footage exists; the script is what the finished cut actually says. Media facts here are VERSION facts, read off whichever cut was promoted to primary: a concept whose primary cut has not rendered yet answers null for the thumbnail, the playback path and the duration, and that is the concept still awaiting its picture rather than a broken record. The thumbnail and playback values are signed first-party paths that need the caller signed in and expire within the hour, so they are worth following and not worth storing. This is the video half of the library shelf. Audio reads are a separate lineage with their own tool (get_audio_concept), and a film generated straight from a synthetic character produces no concept row at all, so an organisation that only makes those finds nothing here. Use list_library_concepts to find concept IDs first. Takes the concept UUID; multi-brand organizations pass brandId to pick one brand scope. pageUrl is the Justify address of the library page for this concept, so a person in the conversation can be handed a link to open rather than an id to go and find. Use it to read the idea behind a video, to check which cut is the chosen one, and to see which campaign and which character the idea belongs to.
regenerate_concept_scriptRequired arguments: conceptIdTakes an idempotencyKeycampaign_library_videoSpends creditsWrite a fresh script for a Campaign Library VIDEO concept from the narrative direction already chosen, when the script that came back did not land. This is the generation half of the library on the wire: every other library tool reads what was made, and this one makes something. IT ASKS BEFORE IT SPENDS. Call it with conceptId alone and it changes nothing: it returns estimatedCreditCost, a confirmationText written for a person to read, a single-use confirmationToken and expiresAt. Show the customer that sentence, then call again with the same conceptId, the token, and the same confirmationText, and it reserves 1 library credit and starts the work. A call without a token never moves a credit and never starts a run. The concept must be waiting at AWAITING_SCRIPT_APPROVAL with a narrative already selected — that is the only point in the workflow where a script exists to replace. Anywhere else it refuses and says where the concept actually is. The old script is replaced, not versioned. It returns an operationId. Poll it with get_operation_status: the status is read off the concept itself, so it reaches awaiting_approval when the new script is ready for a person to approve, succeeded when the concept is finished, and failed when the generation broke. cancel_operation stops the run, releases the credit and lands the concept in FAILED, where the library workflow offers recovery. Use get_library_concept to read the script that comes back, and list_library_concepts to find concept ids.
list_library_assetscampaign_library_assetsReadsList READY, brand-owned, durable-storage campaign library media assets. Returns: id, title, description, type (video/audio/image/document), status, duration, file size, pixel width and height, thumbnail URL, conceptId, tags, created date. Optionally filter by campaign. Assets include videos, images, audio, and documents uploaded or generated by AI pipelines. Each row includes eligibleForCampaignWizardLink and linkBlockers so agents can avoid downstream processing blockers such as private storage or video processing not being ready. An ai_video source spans two pipelines: concept-derived videos carry conceptId, which get_library_concept opens; AI CHARACTER videos (from synthetic influencers) have no concept row and are NOT exposed over MCP at all, so this list plus list_library_concepts is not the complete AI-video inventory. Use get_library_asset for full detail, governance decisions, transcript text, creator summaries, campaign links, and action target metadata before linking. Pages with an opaque cursor and limit of 1 to 50 (default 20): when hasMore is true, call again with cursor set to the returned nextCursor; multi-brand organizations pick the brand with brandId. Never report a library total from one page: total is the whole scope, the rows are one page of it. Use it to answer what a brand has got saved in here: the media files already sitting in the campaign library.
get_library_assetRequired arguments: assetIdcampaign_library_assetsReadsGet one brand-scoped campaign library media asset by ID after using list_library_assets. Returns: title, media type, readiness, playback token URLs, thumbnail URL, campaign links, influencers, governance action decisions, approval state, job linkage, uploader, transcript text, and campaign-wizard link blockers without exposing raw storage keys or provider IDs. Takes the asset UUID; multi-brand organizations pass brandId to fix one brand scope. The record also carries the frame facts a person needs before placing the file: posterUrl, plus the width and height in pixels and the aspectRatio derived from them where the rendering recorded its dimensions, and null for all three where it did not — a rendering made before the ingest that stores them carries none, so read a null as unknown rather than as square. It also carries localisedCount and, where the asset came out of Clip Studio, clipMetadata with its clipOutputId. It also returns pageUrl, the Justify address of the library page for this asset, so a person in the conversation can be handed a link they can open rather than an id they have to go and find. Use it to tell someone everything about one file: the full record behind a single library asset.
request_library_asset_uploadRequired arguments: confirmedSourceRights, fileName, fileType, idempotencyKey, sourceCategory, titleTakes an idempotencyKeycampaign_library_assetsWritesCreate a Campaign Library video/audio asset and return a direct upload URL for the file bytes (browser/client uploads directly to the media provider — this tool never receives file content). Requires idempotencyKey. Returns assetId, a campaign_library_asset operationId for get_operation_status polling, and the uploadUrl. After uploading, call finalize_library_asset to confirm readiness. Assets left un-uploaded are reclaimed automatically. Every grant carries a rights attestation the caller fills in: confirmedSourceRights is the declaration itself and must come from the person or organization the upload is made for, never from the agent; sourceCategory says where the content rights come from (owned, client_provided, creator_authorized, licensed, public_domain or other); sourceDescription adds detail on where the content came from; and rightsNotes records the usage rights held. title is the library title the asset is filed under, description is the library description shown beside it, fileName, fileType and fileSize declare the file itself, and campaignWizardId allocates the asset to a campaign inside the same brand scope. Use it when you have a video or audio file and nowhere to put it: this is the tool that gives the file somewhere to go, opening the slot that brings it into the library in the first place, before anything can list it or use it.
finalize_library_assetRequired arguments: assetIdcampaign_library_assetsWritesConfirm an uploaded Campaign Library asset is fully processed and durably stored, and close its campaign_library_asset operation. Safe to call repeatedly: while processing it reports the live processing status without side effects; once the asset is READY with verified private storage it marks the operation succeeded. Returns assetId, finalized (true once the operation is closed), processingStatus, status, hasPrivateStorage, and operationId. Use after uploading bytes to the uploadUrl from request_library_asset_upload; poll again while finalized is false. Use it to know when an uploaded video is usable: it answers whether the file is ready to put into a campaign yet.
list_library_tagscampaign_libraryReadsList the whole tag index of one brand library with how much content carries each tag, exactly as the Manage Tags panel reads it: one row per distinct label somebody typed onto a video concept, an audio read, an uploaded asset, a synthetic character or a localised cut. Returns data (tag, contentCount, campaigns, taggedRecordIdsWithheld), total, brandId, scopeKind, scopeReason, shelvesCounted, shelvesNamingCampaigns and campaignNamesIncluded. Ordered most-used first, ties broken alphabetically, so the top of the answer is the vocabulary this library actually settled on rather than an arbitrary slice. contentCount sums all five taggable shelves at once, which is why it can exceed what any single listing shows; archived and deleted work is left out, so the figure matches the panel rather than the raw table. Set includeCampaigns to attach the campaign names each label appears under. Only three shelves can name one — video concepts, audio reads and uploaded files — because characters and localised cuts hold no such column, which shelvesNamingCampaigns states on every reply, so an unnamed label is never mistaken for an unused label. The tagged rows themselves are never identified: this counts without naming, and taggedRecordIdsWithheld says so as a value beside every count. Unpaged deliberately, because the count behind it takes no keyset and a second cursor-capable copy beside the route would fork the read: the index comes back in one call and total is the true number of distinct labels, so a shortened reply can never be reported as complete. A multi-brand organisation names brandId; a single-brand one may omit it, and an organisation whose account type never owns any reads org-wide, with scopeKind saying which happened. Read-only. Renaming a label everywhere and removing one from every shelf are the widest-blast-radius edits this feature has and stay with people. Use it to answer how a team labels its work, which labels are worth filtering on, and which near-duplicates somebody ought to merge. Pair with list_library_assets and list_library_concepts to open the content behind a label.
list_audio_conceptscampaign_library_audioReadsList the audio concepts of one brand from the campaign library, newest first, exactly as the audio shelf reads them: a voiceover script written against a campaign brief, the state its generation run has reached, and the scores it has been graded on. Returns data (audioConceptId, title, hookLine, state, version, isLatest, primaryVersionId, durationSeconds, voiceName, voiceRecorded, voiceFileWithheld, naturalSpeechScore, hookScore, listenThroughPrediction, tags, campaignId, campaignName, createdAt, updatedAt), total, hasMore, nextCursor, brandId and scopeReason. state runs QUEUED, GATHERING_CONTEXT, GENERATING_OPTIONS, AWAITING_NARRATIVE_SELECTION, GENERATING_SCRIPT, AWAITING_SCRIPT_APPROVAL, GENERATING_VOICE, QUALITY_CHECK, READY, FAILED and ARCHIVED; the two AWAITING states are where a person has to choose before the run moves. Archived rows are excluded unless state names them, and only the newest version of each lineage is listed unless latestOnly is false, so the count matches the shelf rather than the table. voiceRecorded is measured against private storage rather than read off the state, and the recorded file itself is withheld: no playback address and no storage key reach this wire, which voiceFileWithheld states on every row. Pages by cursor and limit (1 to 50, default 20): when hasMore is true, call again with cursor set to the returned nextCursor verbatim. total is the true count for the brand scope, so a capped page is never mistaken for the shelf. A multi-brand organisation names brandId; a single-brand one may omit it. Read-only: generating a run, choosing a narrative, approving a script, promoting a version and recovering a failed run all stay with people. Use it to answer which audio reads a brand has written, which are waiting on a person, and which already have a recording. Pair with get_audio_concept for the script and list_library_concepts for the video half of the same shelf.
get_audio_conceptRequired arguments: audioConceptIdcampaign_library_audioReadsOpen one audio concept from the campaign library and read the podcast ad read behind it: the narrative options generated for it, which one a person selected, the approved script, and every version recorded from that script. Returns data (audioConceptId, title, logline, hookLine, ctaLine, state, version, isLatest, targetPodcast, adStyle, narrativeOptions, selectedNarrativeIndex, script, scriptVersion, wordCount, estimatedDurationSeconds, durationSeconds, voiceName, toneGuidance, emphasisWords, pronunciationNotes, versions, versionCount, primaryVersionId, voiceRecorded, voiceFileWithheld, naturalSpeechScore, brandAlignmentScore, hookScore, ctaScore, listenThroughPrediction, processingError, tags, campaignId, campaignName, createdAt, updatedAt, pageUrl). Each entry under versions carries versionId, versionNumber, state, isPrimary, durationSeconds, voiceName, naturalSpeechScore, waveformPointCount and createdAt, so an agent can compare recordings without hearing any of them. narrativeOptions keeps the position each option was stored at and marks the selected one, because selectedNarrativeIndex points at that position; a malformed column yields an empty list rather than half an option. The recording is withheld on every version: the private-storage key and the playback address stay on the server, waveformPointCount replaces the drawn shape, and voiceFileWithheld states the rule as a value. A multi-brand organisation names brandId; a single-brand one may omit it. An id belonging to another organisation or another brand is refused as not found, never as denied, so a probe learns nothing. Read-only: regenerating a script, approving one, selecting a narrative, promoting a version and minting a download all stay with people. pageUrl is the Justify address of the library page for this read, so a person in the conversation can be handed a link to open rather than an id to go and find. Use it to answer what a read actually says, whether it is waiting on approval, and which recording is the chosen one. Find the id with list_audio_concepts first.
list_characterscampaign_library_charactersReadsList the synthetic characters of one brand from the campaign library, newest first, exactly as the character roster reads them: an invented presenter with a name, a handle, a trained portrait and a voice with an accent, built to front a brand video. Returns data (characterId, name, handle, bio, characterType, state, identityImageState, identityImageReady, identityArtifactStored, identityArtifactDurable, mediaWithheld, voiceName, voiceAccent, totalVideos, totalViews, avgEngagementRate, tags, campaignCount, campaigns, createdAt, updatedAt), total, totalIsExact, hasMore, nextCursor, brandId and scopeReason. characterType runs BRAND_AMBASSADOR, LIFESTYLE_INFLUENCER, PRODUCT_SPECIALIST, FOUNDER_PERSONA and COMPANY_MASCOT; state runs DRAFT, ACTIVE, PAUSED and ARCHIVED, and archived rows are excluded unless state names them. identityImageState runs PENDING, TRAINING, READY and FAILED, and the two booleans beside it separate a state that says READY from a stored file that actually backs it, which is the difference between a presenter a brand can shoot with and one it cannot. The trained picture is withheld: no image address reaches this wire, which mediaWithheld states on every row. Pages by cursor and limit (1 to 50, default 20): when hasMore is true, call again with cursor set to the returned nextCursor verbatim. total counts the rows this call served, which totalIsExact qualifies, because the roster read walks a keyset and takes no count. A multi-brand organisation names brandId; a single-brand one may omit it. Read-only: creating a presenter, training a portrait, generating a film and archiving all stay with people. Use it to answer who a brand can put on camera, which presenters are ready to shoot, and which are still training their portrait. Pair with list_character_videos for the films made from them.
get_characterRequired arguments: characterIdcampaign_library_charactersReadsOpen one synthetic character from the campaign library and read the whole invented person: the backstory written for them, the wardrobe and physical direction a shoot works from, the catchphrases and vocabulary they keep to, and the topics they must avoid. Returns data (characterId, name, handle, bio, characterType, state, identityImageState, identityImageReady, identityArtifactStored, identityArtifactDurable, mediaWithheld, identityImageError, referenceImageCount, backstory, personalityTraits, speakingStyle, catchphrases, topicsToDiscuss, topicsToAvoid, vocabularyNotes, physicalDescription, wardrobeGuide, voiceName, autoAddWatermark, autoAddDisclosure, disclosureText, watermarkPosition, totalVideos, totalViews, avgEngagementRate, tags, campaignAssignments, videos, videoCount, brandIdentityId, brandName, createdAt, updatedAt). Each entry under videos carries characterVideoId, videoState, generationMode, videoReady, durationSeconds, failureReason, label and createdAt, so a reader can see what has been shot from this person without any film reaching the wire. The disclosure fields are the compliance rails a generated film inherits: autoAddDisclosure and disclosureText say what will be stamped on it, and watermarkPosition where. Every picture is withheld. The trained portrait, the reference photographs and each rendered film stay on the server; referenceImageCount replaces the photographs and mediaWithheld states the rule as a value. A multi-brand organisation names brandId; a single-brand one may omit it. An id belonging to another organisation or another brand is refused as not found, never as denied, and an archived person is refused the same way, so a probe learns nothing. Read-only: editing a person, training a portrait, generating a film and archiving all stay with people. Use it to answer who this presenter is meant to be, whether they can be shot with today, and what they are not allowed to say. Find the id with list_characters first.
list_character_videoscampaign_library_charactersReadsList the films generated from a brand synthetic characters, newest first, exactly as the campaign library video shelf reads them: each run reuses a trained likeness, so one row is one attempt at putting that presenter on camera. Returns data (characterVideoId, title, videoState, tileState, generationMode, videoReady, mediaWithheld, durationSeconds, failureReason, tags, localisedCount, characterId, characterName, characterHandle, campaigns, createdAt), total, totalIsExact, hasMore, nextCursor, brandId and scopeReason. videoState runs IDLE, GENERATING_VOICE, GENERATING_VIDEO, COMPLETED and FAILED, and generationMode is SILENT or TALKING; tileState is what the shelf prints on the tile, which reads READY only once the render is playable. videoReady is measured against the durable stored copy rather than read off videoState, because a run can reach COMPLETED while its stored copy failed verification, and an agent told the film is finished would promise a customer something nobody can play. localisedCount says how many localised versions were made from a film; list_localisations is where those are read. Every render is withheld, which mediaWithheld states on each row: no playback address, no poster and no storage key leave the server. Pages by cursor and limit (1 to 50, default 20): when hasMore is true, call again with cursor set to the returned nextCursor verbatim. total counts the rows this call served, which totalIsExact qualifies, because the shelf read walks a keyset and takes no count. A multi-brand organisation names brandId; a single-brand one may omit it. Read-only: generating a film, retrying a failed run, cancelling one and archiving all stay with people. Use it to answer what has been shot with a presenter, which runs failed and why, and which films are ready to publish. Pair with get_character for the presenter behind them.
list_clip_studio_projectscampaign_library_clippingReadsList the Clip Studio projects of one brand, newest first, exactly as the studio shelf reads them: one project is one piece of long footage a brand put through analysis, together with the moments the analysis found in it and the short cuts made from those moments. Returns data (clipStudioProjectId, analysisState, sourceReady, mediaWithheld, sourceDurationSeconds, momentCount, topMomentType, topMomentScore, topMomentExplanation, briefAlignmentScore, cutCount, campaignId, createdAt, updatedAt), total, hasMore, nextCursor, brandId and scopeReason. analysisState runs PENDING, IMPORTING, TRANSCRIBING, DETECTING_HOOKS, READY_FOR_SELECTION, GENERATING, COMPLETED and FAILED, so a reader can tell a project still transcribing from one waiting on a person to choose moments. topMomentScore is the virality score of the strongest moment found, picked by the same reduction the shelf uses, and topMomentExplanation says in words why the analysis rated it. A brand owns a project through the campaign it is linked to, so a recording linked to no campaign belongs to no brand and appears in no brand scope here, which the browser does too; scopeReason names that as a cause of an empty page. Every render is withheld, which mediaWithheld states on each row: no playback address and no provider identifier for the source or its cuts reaches this wire. Pages by cursor and limit (1 to 50, default 20): when hasMore is true, call again with cursor set to the returned nextCursor verbatim. A cursor carries the filters it was minted under and is refused if replayed against different ones, rather than answering with rows from another question. total is the true count for the scope. A multi-brand organisation names brandId; a single-brand one may omit it. Read-only: starting an analysis, generating a cut, exporting and deleting all stay with people. Use it to answer which recordings a brand has analysed, which produced a strong moment, and which are still processing. Pair with get_clip_studio_project for one project in full.
get_clip_studio_projectRequired arguments: clipStudioProjectIdcampaign_library_clippingReadsOpen one Clip Studio project and read the analysis in full: every moment the detector found in the source recording, every short cut rendered from those moments with the subtitle styling and reframing applied to it, and every review note a colleague left against the project or against one cut. Returns data (clipStudioProjectId, analysisState, sourceReady, mediaWithheld, sourceDurationSeconds, sourceAspectRatio, sourceType, momentCount, moments, cuts, cutCount, notes, noteCount, briefAlignmentScore, campaignId, campaignName, sourceContentType, sourceContentId, executionTimeMs, failureReason, transcriptWithheld, createdAt, updatedAt). Each entry under moments carries index, startSeconds, endSeconds, durationSeconds, hookText, hookType, whyItWorks, viralityScore, hookStrength, contentQuality, completionPotential and improvements, ranked strongest first. Each entry under cuts carries cutId, momentIndex, renderState, renderReady, aspectRatio, startSeconds, endSeconds, captionStyle, reframeMethod, viralityScore, audioFormat and createdAt; renderState runs PENDING, PROCESSING, COMPLETED and FAILED. Each entry under notes carries noteId, body, resolved, timestampMarkerSeconds, cutId, authorName and createdAt, so a reader can see what a team decided without opening the editor. The word-level transcript is never served, which transcriptWithheld states as a value: it is the customer speech verbatim and the studio itself loads it only behind an explicit opt-in. Renders are withheld the same way, and the loose settings and score-breakdown columns on a cut are dropped rather than passed through. A multi-brand organisation names brandId; a single-brand one may omit it. An id outside the organisation, outside the brand, or linked to no campaign of that brand is refused as not found, never as denied, so a probe learns nothing. Read-only: analysing, generating, regenerating, translating, exporting, commenting and deleting all stay with people. Use it to answer which moment of a recording is worth cutting, what was already rendered from it, and what the team said about the result. Find the id with list_clip_studio_projects first.
list_localisationscampaign_library_localisationReadsList the dubbing jobs of one brand from the campaign library, newest first, exactly as the localisation shelf reads them: each job takes one finished film and produces it again for another language market, and one row is one market attempt. Returns data (localisationId, state, targetLanguage, targetRegion, sourceLanguage, sourceContentType, sourceContentId, sourceVersionId, sourceDurationSeconds, outputDurationSeconds, playbackState, downloadReady, mediaWithheld, providerPolled, assetId, campaigns, tags, processingStartedAt, processingCompletedAt, createdAt, updatedAt), markets, total, totalIsExact, hasMore, nextCursor, brandId and scopeReason. state runs QUEUED, UPLOADING, TRANSLATING, STORING, COMPLETED, STORAGE_FAILED and FAILED; STORING is the phase after the provider finished, while the result is being copied into private storage, so a job can sit there with nothing wrong. markets rolls the page up by language and region with a ready count against each, which is the question this shelf exists to answer: which markets a piece of content has actually reached. playbackState and downloadReady carry the readiness and the media does not: no playback address, no poster and no download link reaches this wire, which mediaWithheld states on every row. downloadReady is measured against the verified stored copy rather than read off state. This read contacts no provider, which providerPolled states as false on every row, so a job still in an active state may be a moment behind what a browser would show. Archived jobs are excluded by the same repository the screen reads. Pages by cursor and limit (1 to 50, default 20): when hasMore is true, call again with cursor set to the returned nextCursor verbatim. total counts the rows this call served, which totalIsExact qualifies, because the shelf read walks a keyset and takes no count. A multi-brand organisation names brandId; a single-brand one may omit it. Read-only: starting a job, retrying a failed one, archiving and minting a download all stay with people. Use it to answer which languages a brand has shipped into, which markets are still processing, and which failed. Pair with get_localisation for one job in full.
get_localisationRequired arguments: localisationIdcampaign_library_localisationReadsOpen one localisation job from the campaign library and read where a dubbed version of a film has got to: the market it is being made for, the original it came from, how far the run has travelled, and whether the result can be played or shipped yet. Returns data (localisationId, state, targetLanguage, targetRegion, sourceLanguage, sourceContentType, sourceContentId, sourceVersionId, sourceDurationSeconds, outputDurationSeconds, playbackState, downloadReady, mediaWithheld, providerPolled, assetId, campaigns, tags, processingStartedAt, processingCompletedAt, createdAt, updatedAt, pageUrl). state runs QUEUED, UPLOADING, TRANSLATING, STORING, COMPLETED, STORAGE_FAILED and FAILED; STORING is the phase after the translation provider finished, while the result is copied into private storage, so a job resting there is progressing rather than stuck. downloadReady is the verified stored copy, which is exactly what the download route resolves before it will mint anything, so an agent can say a market is ready to ship without ever holding a link. playbackState says the same for watching it. No media leaves the server: the stored key, the provider output address, the media identifier and the poster are all dropped, which mediaWithheld states as a value. This read contacts no provider, which providerPolled states as false, so an active run may be a moment behind what a browser would show. An archived job is treated as absent, exactly as the library screen treats it. A multi-brand organisation names brandId; a single-brand one may omit it. An id belonging to another organisation or another brand is refused as not found, never as denied, so a probe learns nothing. Read-only: starting a run, retrying a failed one, archiving and minting a download all stay with people. pageUrl is the Justify address of the library page for this localisation, so a person in the conversation can be handed a link to open rather than an id to go and find. It is carried by this read only; the page of jobs stays as it is. Use it to answer whether one market is finished, why it failed, and whether the result is shippable. Find the id with list_localisations first.

Connected stores and product gifting

Your connected WooCommerce, Shopify and TikTok Shop stores: the unified product catalogue, the organisation-wide performance report built from recorded actuals, and the gifting lifecycle. Gift orders that cross your spend-approval threshold park for a human, and a rejected order never reaches the store. The Manage section reporting shelf sits here too, because that is where its figures come from: ask which reports you hold, then read one of them as a summary instead of pulling the whole report.

Entitlements used by this family: integrations, manage_gifting, manage_reports, shopify_integration or tiktok_shop_integration or woocommerce_integration

Try asking

  • Which commerce stores are connected, and how has attributed revenue moved over the last 30 days?
  • Which reports have I got under Manage, and which of them actually have anything in them this month?
  • Send a gift parcel of our travel serum to the saved creator @lauren.runs from the Shopify store, and tell me if it parks for approval.
ToolEntitlement neededEffectWhat it does
list_commerce_storesNone beyond a signed-in organisationReadsList the connected commerce stores (WooCommerce, Shopify, TikTok Shop) for this organization. Returns: id (the connectionId every other commerce tool takes), provider, storeDomain, status, capabilities (e.g. giftOrders.write), lastSyncAt, syncStatus. Stores whose provider integration is not enabled for this organization are filtered out. Use this first to find the connectionId, then list_commerce_products to browse its catalogue or create_gift_order to ship a gift. total is the same store count get_commerce_performance reports as connectedStoreCount, for the same organization in the same session: a store on a provider Justify ships no integration for is still listed and still counted, marked actionable false with limitations naming provider_unsupported, rather than hidden here and counted there. Such a store cannot be browsed or gifted from — no connectionId taken by another commerce tool will work for it — but it is a real store the operator has, and its orders still feed the revenue figures. invalidCapabilityKeys names any keys the stored capability map carries that are not real capabilities, and limitations then carries capabilities_invalid: the row is malformed, which is stated rather than filtered into silence. One documented exception to the matching totals: a store on a provider Justify does integrate but this organization is not entitled to is filtered out here while get_commerce_performance still counts it, so a total BELOW that report’s connectedStoreCount means a plan-excluded store rather than a contradiction — say so instead of picking a number. Use it to answer which shops we have hooked up to Justify: every store connection, whether it is working or broken. Use it to answer which shopfronts are plugged in right now and which one to name when sending a gift or browsing products.
list_commerce_productsRequired arguments: connectionIdNone beyond a signed-in organisationReadsBrowse the unified product catalogue of a connected store. Returns products with providerProductId, title, sku, price, currency, imageUrl, productUrl (the live listing on the store itself), inStock, and variants (providerVariantId, title, sku, price, inStock, imageUrl). Server-side search matches title or SKU prefix. Use list_commerce_stores first for the connectionId; pass providerProductId (and a variant for variable products) to create_gift_order or to create_job commerceProduct for a GIFTING job. Once a variant is chosen it is the variant that decides the gift, so read price, inStock and imageUrl off the chosen variant rather than off the product: the value create_gift_order records and checks its approval threshold against is the variant price, and the picture kept with the order is the variant image, each falling back to the product-level key only when the variant carries none. Stock does not fall back at all — once a variant is named, the product inStock is never consulted, so a variant whose own inStock is absent reads as shippable. Pages by page number rather than by cursor, because the catalogue is served by the remote store and no stable keyset can be held across it: when hasMore is true, call again with page incremented by one. total is every product matching the search, not the number returned on this page. Use it to answer what we are selling right now: the live catalogue of one connected store.
get_commerce_performanceNone beyond a signed-in organisationReadsGet the organisation-wide commerce performance report: historical recorded actuals from connected stores (WooCommerce, Shopify, TikTok Shop), with day boundaries in the organisation's reporting timezone. Returns period plus previousPeriod (an equal-length baseline window immediately before it), totals and previous (each with totalRevenue, totalOrders, attributedRevenue, attributedOrders, averageOrderValue, commission, roas), a daily trend array (date, totalRevenue, totalOrders, attributedRevenue, attributedOrders), topInfluencers rows (displayName, revenue, orders, commission, roas — capped at 10, with topInfluencersTotal for the full count), promoCodes rows with discountAmount, per-platform breakdowns, and gifting metrics (giftsDispatched, giftsVerified, giftedValue, roi). Every figure is a recorded actual: Justify holds no ad-spend data and produces no forecasts. dateRange picks the reporting window — 7d, 30d (the default) or 90d, ending now — and currency picks the display currency code, defaulting to the dominant order currency, with hasMixedCurrencies flagging when others were present. connectedStoreCount counts exactly the stores list_commerce_stores returns as total, for the same organisation in the same session — including a store on a provider Justify ships no integration for, which that tool marks actionable false. The two numbers agree by construction; if they ever differ, report the difference rather than choosing one. The one documented cause of a difference is a store on a provider this organisation is not entitled to: this count keeps it and list_commerce_stores hides it, so connectedStoreCount above that tool’s total means a plan-excluded store. Use dateRange (7d, 30d, 90d) and an optional display currency to shape the window; use list_commerce_stores to see which stores feed the report and list_gift_orders for individual gifting parcels. Use it to answer whether any of this did turn into sales: revenue, orders and ROAS beside the gifting and promo-code rows.
list_integrationsintegrationsReadsList every commerce integration this organization can hold — the entitlement catalogue, not the connected stores — and the state each one is in — WooCommerce, Shopify and TikTok Shop, one row each, whether or not a merchant has connected anything. Returns per row: provider, featureKey (the entitlement key governing it), availability, entitled, switchedOff, health, connectedStoreCount, connectionCount, reconnectableCount, lastSyncAt and lastErrorAt. availability is one word for why the organization can or cannot use that integration: available; plan_excluded, when Justify ships the integration and this subscription does not carry it; switched_off, when a kill switch disables it for every tenant so no upgrade reaches it; or unsupported, for a platform some connection row names that Justify ships no integration for. health reads never_connected, disconnected, degraded, failing or healthy, taken worst-first across that platform: degraded means live but carrying a recorded failure, or running poll-only because its webhook subscription is off, and failing means a store in ERROR or a sync that ended in ERROR. Nothing is dropped: a switched-off or plan-excluded integration is reported with its state named, which is the signal to stop before calling a commerce tool that would refuse. connectedStoreCount counts the same live stores list_commerce_stores lists, so a positive count beside entitled false is the explanation for a store the performance report counts and that catalogue omits. Use it to answer which platforms we are wired into and whether any of them is broken, then get_integration_status for one platform in full.
get_integration_statusRequired arguments: providershopify_integration or tiktok_shop_integration or woocommerce_integrationReadsRead one commerce integration in full: whether it is connected, when it last synced, what went wrong, and what the connection is permitted to do. Takes provider and returns the estate row list_integrations gives (availability, entitled, switchedOff, health, connectedStoreCount) plus connections, one entry per store, each carrying connectionId, storeName, storeDomain, storeUrl, currency, status, health, syncStatus, webhookStatus, lastSyncAt, lastWebhookAt, lastError, lastErrorAt, retryCount, connectedAt, disconnectedAt, disconnectReason, reconnectable and grants. grants are the capability keys the connection actually holds, which is what it is permitted to act on; the OAuth scope strings themselves sit inside the encrypted credential and one platform alone records them, so they are not published. withheld names what is absent from every answer by design: providerAccessToken, consumerKeyAndSecret, webhookSigningKey and webhookCallbackUrl. No argument makes this hand over a credential. detailWithheld turns true when the organization is not entitled to that integration: the row and its reason still answer, the store entries do not, and a kill-switched or plan-excluded platform is never reported as merely having nothing connected. reconnectable marks a store Justify disconnected on its own that the merchant can still reinstate from settings. Use it to answer why our shop feed stopped or when it last talked to us, and list_integrations first to see which platforms are worth asking about.
list_manage_reportsmanage_reportsReadsList the reports on the Manage section reporting shelf for this organisation, with what each one covers and whether it holds figures for the period. The shelf holds six: revenue_summary, revenue_trend, creator_leaderboard, discount_codes, sales_channels and gifting_return. Returns: one entry per report with reportType, title, subject, generatedAt, rowCount and populated, together with total, period, timezone and currency. subject names what one row of that report describes, so its shape is known before it is fetched; rowCount counts across the organisation, and populated is false when there is nothing to show. The two can disagree, and only on revenue_trend: its rows are calendar days, one per day in the window whether or not anything happened, so a quiet month reports thirty rows and populated false. Trust populated — it is the one that asks whether any figure is non-zero. generatedAt is the instant this call computed the figures: Justify keeps no pre-built report, so nothing on this shelf is ever stale and no entry needs refreshing. It is a shelf, not a page: the six entries are the complete set for every organisation, so there is nothing to walk and no cursor to send. dateRange picks the window the figures are computed over — 7d, 30d or 90d, defaulting to the window the page opens on — and currency sets the display currency for every money figure, as a three-letter code. Treat it as an inventory rather than an answer: it carries no figures whatsoever, so reading the shelf end to end costs a fraction of one report and tells you which entries are worth fetching and which are skippable. Read one of them with get_manage_report, which returns that report headline figures and its leading rows; ask get_commerce_performance when the complete underlying report is wanted rather than a summary. Refuses when the organisation holds no entitled commerce provider, which is the same refusal the Manage reporting page gives a browser.
get_manage_reportRequired arguments: reportTypemanage_reportsReadsRead one report off the Manage section reporting shelf as a bounded summary: its headline figures and its leading rows, never the whole underlying dataset. reportType picks which: revenue_summary is the money headline against the previous window, revenue_trend the daily series, creator_leaderboard the creators carrying attributed revenue, discount_codes the codes customers redeemed, sales_channels the split across connected commerce providers, and gifting_return what gifted product earned back. Returns: reportType, title, subject, generatedAt, period, previousPeriod, timezone, currency, requestedCurrency, hasMixedCurrencies, rowCount, rowsShown, populated, a metrics array and a rows array. Each metric carries key, label, unit and either value or text: unit currency means money in the display currency, count a whole number of things, ratio a multiple such as return on spend, and label a word carried in text rather than a number. rowCount is the whole-organisation count and rowsShown is what this summary carries, so a leaderboard is never mistaken for the full list. A null metric value means the figure could not be computed for the period, not that it was zero: return on spend is null until commission has been paid, and gifting return is null until value has been gifted. dateRange picks the 7d, 30d or 90d window, defaulting to the one the Manage page opens on, and currency sets the display currency for every money figure, as a three-letter code. Use list_manage_reports first to see which reports hold figures, and get_commerce_performance when the complete dataset is wanted instead of a summary. Refuses when the organisation holds no entitled commerce provider, exactly as the Manage reporting page refuses a browser.
list_gift_ordersmanage_giftingReadsList the gift orders (product gifting parcels) for this organization, newest first. Returns id, status (AWAITING_ADDRESS, PENDING_APPROVAL, PENDING, ORDERED, SHIPPED, DELIVERED, VERIFIED, FAILED, CANCELLED), line snapshots (product, sku, value), providerOrderId, tracking fields, and job/campaign/creator attribution. Filter by connectionId, status, jobPostingId, or influencerId. Use this directly for organisation-wide gifting history or to establish that revenue is not available; connectionId is optional, so do not call list_commerce_stores first unless the user explicitly asks for one store. Use approve_gift_order or reject_gift_order on PENDING_APPROVAL rows and retry_gift_order on FAILED rows. Pages with cursor and limit: when hasMore is true, call again with cursor set to the returned nextCursor. Never answer "how many gift orders" from one page: total is every gift order matching the filter, the rows are one page of it. Use it to answer what a brand has sent out to people as gifts: every parcel, newest first. Use it to answer what have we sent, to whom and did it get there: the gifting log across every store and every creator.It reads the ledger only, dispatching nothing and pricing nothing; the newest entry sits at the top.
get_gift_orderRequired arguments: giftOrderIdmanage_giftingReadsFetch one already-placed gift order by id, read-only, with its full lifecycle state: status, line snapshots (product title, sku, value at creation), providerOrderId on the store, tracking number/url/carrier, ordered/shipped/delivered timestamps, retryCount and lastError. Use list_gift_orders to find ids; use approve_gift_order, reject_gift_order, or retry_gift_order to act on it. Use it to look at one gift request in full before deciding on it. Use it to answer where is the parcel now and did it arrive: the courier, the tracking link, the dates it was ordered, shipped and delivered, and what went wrong if it failed.A single parcel, start to finish, is the whole answer here.Ask it about one parcel by its id; it never lists, never sends and never retries.
create_gift_orderRequired arguments: connectionId, idempotencyKey, influencerId, linesTakes an idempotencyKeymanage_giftingDestructiveSend a product gift parcel from a connected store. A gift may only ever reach a Justify user, so influencerId is ALWAYS required and must be a real platform user. Provide connectionId (list_commerce_stores), one or more lines with providerProductId (+ providerVariantId for variable products, from list_commerce_products), and EITHER requestAddressFromCreator true (the creator confirms their own stored address — the gift waits as AWAITING_ADDRESS) OR an explicit shippingAddress belonging to that same user. Requires idempotencyKey; retries never double-ship. IT SPENDS REAL MONEY AND IT DOES NOT UNDO: the connected merchant is charged for the goods and a courier collects them, and once the courier has the parcel the shipment cannot be recalled from here. The only remedy after collection is a conversation with the merchant. Gifts over the org approval threshold park as PENDING_APPROVAL for approve_gift_order. Read giftApprovalEnabled, giftApprovalThreshold and giftApprovalThresholdCurrency from get_organisation_settings BEFORE calling this, because they decide whether a person ever sees this parcel: with approvals off, or with a parcel worth less than the threshold, it dispatches on this call alone and nobody signs anything off. Which moment is weighed depends on the address: a parcel with an explicit shippingAddress is weighed now, and one with requestAddressFromCreator true waits as AWAITING_ADDRESS and is weighed when the creator confirms their address. Price the parcel from list_commerce_products first, and say what it costs before you send it. Returns id, status, providerOrderId. Use it when a person says send them the product: pick the store, the item and the creator, and the parcel is ordered.The three usual questions before pressing go are which shop, which item and which creator; the fourth, who signs it off, is answered by the approval gate.It is the act of buying and posting, not of reviewing or listing.
approve_gift_orderRequired arguments: giftOrderIdmanage_giftingDestructiveApprove a PENDING_APPROVAL gift order (one parked by the org spend-approval threshold) and dispatch it to the store. The provider is only ever called after this approval. Returns the updated order with status ORDERED (or FAILED with lastError when the store rejects it). Conflicts cleanly when the order is not awaiting approval or a concurrent decision won. Use list_gift_orders with status PENDING_APPROVAL to find candidates. Use it when a gift request is waiting on someone and they say to sign it off: the parcel is approved and dispatched. This is a gifting decision, not a job decision: what is released is a PARCEL of physical merchandise from a connected storefront, never an escrowed creator fee, and once the courier has it the shipment cannot be recalled from here. The verdict written here is YES, and it is the only place that writes it: the parked spend clears, the basket is handed to the storefront, and a courier collects. Signing off opens the gate and edits nothing else — the recipient, the merchandise and the delivery address stay exactly as the requester left them. Read who is receiving what before you authorise, because there is no unwind on this side: the only remedy after collection is a conversation with the merchant.
reject_gift_orderRequired arguments: giftOrderIdmanage_giftingDestructiveReject a PENDING_APPROVAL gift order: terminal CANCELLED, kept as an audit row, and the store is never called — nothing ships. Conflicts cleanly when the order is not awaiting approval or a concurrent decision won. Returns the updated order: id, status (CANCELLED), provider, providerOrderId, retryCount, lastError. Use list_gift_orders with status PENDING_APPROVAL to find candidates; prefer approve_gift_order when the gift should ship. Use it when a reviewer decides a parcel shouldn’t go: turn the request down and nothing ships. Nothing is packed or posted: no supplier is contacted, no stock is reserved, and no shipping label is ever produced. What remains is the paper trail — the row stays readable in its terminal state, so a later audit sees that a refusal was made and by whom rather than inferring it from silence. Undo is not offered on this path: a turned-down request is replaced by raising a fresh one, never by reversing this call.
retry_gift_orderRequired arguments: giftOrderIdmanage_giftingWritesRe-dispatch a FAILED gift order to the store using its stored encrypted payload. Bounded by the retry limit; adapters adopt an orphaned provider order instead of creating a duplicate when the original create partially succeeded. Returns the updated order with status ORDERED, or FAILED with lastError when the store rejects it again. Use list_gift_orders with status FAILED to find candidates and get_gift_order to inspect lastError first. A retry is a second attempt at the SAME dispatch and not a fresh judgement: whoever signed the spend off signed it off once, the sealed basket is replayed exactly as it was stored, and nobody receives two parcels because an orphaned provider order is adopted instead of duplicated. Read lastError before you repeat, since a network timeout is worth another attempt and a storefront that has run out of the item is not.

Outreach

Compose and send creator outreach for a campaign, and watch what happens next: sender readiness before anything goes out, the redacted queue, delivery progress and the funnel analytics. Queueing a send is the same act as the send button in the app, through the same service. Text messages have their own read now: how the SMS channel is performing over a recent window, which handsets rejected a message and under which carrier error code, how many billable segments the copy consumed, and the register of individual messages behind those numbers. Recipient telephone numbers and carrier identifiers stay on the server, and nothing here contacts the messaging network itself: it reads what the send worker and the delivery-receipt webhook already wrote down.

Entitlements used by this family: outreach, outreach_sms

Try asking

  • Draft an outreach email for campaign 6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e grounded on our saved creator @lauren.runs, and check our sender is ready before anything is queued.
  • Show me the delivery funnel for outreach campaign 6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e: sent, opened, replied.
  • Are our text messages actually arriving this month, and which ones failed? Show me the error codes and how many segments we burned.
ToolEntitlement neededEffectWhat it does
list_outreach_campaignsoutreachReadsList campaigns available for outreach in the authenticated organization. Returns: id and campaignWizardId for the Campaign Wizard, name, status, brand, target platforms, start/end dates, and business goal. These are Campaign Wizard records with influencer outreach potential, not queued OutreachCampaign delivery records. Use get_outreach_campaign with this id for read-only campaign context; use list_outreach_queue campaignId values for delivery analytics/progress. The id returned here is also accepted directly by get_outreach_analytics and get_campaign_progress, which resolve it to the OutreachCampaign delivery record queued for that Campaign Wizard, and say so when a campaign has none queued yet or has several. Use it to answer what outreach pushes a brand has set up, whether or not anything has gone out yet. Use it for the plan rather than the sending: which campaigns are open for creator outreach and what each is trying to achieve.Nothing here has been queued or delivered; it is the menu of campaigns a push could be built around.
get_outreach_campaignRequired arguments: campaignIdoutreachReadsGet details of a campaign for outreach purposes. Returns: name, brand, business goal, target platforms, start/end dates, expected outcomes, and campaignWizardId. Use list_outreach_campaigns to find Campaign Wizard IDs for this context tool. This tool is read-only context. Sending is a separate human-gated step: drafts are created in the Outreach UI or automation, then queue_outreach_send approves and queues them. Use it to show the outreach a brand did for one launch — the trainers launch, the Christmas push — before asking get_outreach_analytics how many replied.
get_outreach_analyticsRequired arguments: campaignIdoutreachReadsGet delivery funnel analytics for an outreach campaign. Returns: total recipients, how many messages actually left the platform, counts and completion rates for each stage (queued, scheduled, sending, sent, delivered, opened, clicked, replied, failed, bounced, skipped), overall progress percentage, and whether the campaign is complete. Opened and clicked count the recipients who EVER did it, not the ones sitting in that status now, and their rates are over the messages sent rather than over every recipient; every other stage is a count of where recipients sit today, over total. This is the campaign-level performance tool. First use list_outreach_queue, then pass one returned OutreachCampaign delivery campaignId here. Campaign Wizard IDs from list_outreach_campaigns are accepted here too: this tool resolves a Campaign Wizard ID to the OutreachCampaign delivery campaign queued for it and reports that funnel, answering campaignWizardId alongside the delivery campaignId so both IDs are visible. When a Campaign Wizard has no outreach queued yet, or has several delivery campaigns, the refusal says which and lists the delivery campaign IDs to call back with. Answers who we have emailed and what came back from those creators. The read is resolved within one brand of a multi-brand organization: pass a valid delivery campaign UUID, and brandId when the organization holds several brands, since results are restricted to the brand scope that owns the campaign.
get_outreach_statusoutreachReadsGet read-only outreach operational status for the resolved active brand. Returns redacted queue totals, delivery status counts, deliverability gate status, and sender health for a recent window. Use for organisation-level operational health and sender readiness only, not for a request about individual outreach campaigns or campaign performance. For campaign performance, use list_outreach_queue then get_outreach_analytics. No campaign id is required: the report is restricted to one brand scope (brandId required for multi-brand organizations) over a day window that defaults to 14. Use it to answer whether our email is actually arriving or landing in spam: deliverability and sender health across the brand rather than one campaign.
get_sender_readinessoutreachReadsGet read-only outreach sender readiness for the authenticated organization. Returns redacted email sender gates, domain verification status, and aggregate deliverability health metrics. Use before queueing outreach workflows to explain whether sending is blocked or ready without exposing sender email addresses or provider credentials. Use it to answer can we send from this address yet: domain checks, warm-up state and the mailbox's standing, before a single message goes.
list_outreach_queueoutreachReadsList redacted outreach queue entries for the resolved active brand. Returns deliveryId, OutreachCampaign delivery campaignId, campaignName, channel, status, attempts, workflow step, and timestamps only. Use to inspect queued, scheduled, sending, failed, or manually required outreach without exposing contact values, message bodies, or provider identifiers. For a question about outreach campaigns and their performance, call this first, then call get_outreach_analytics with one returned campaignId. Pages with cursor and limit: when hasMore is true, call again with cursor set to the returned nextCursor. Never report a queue size from one page: total is the whole queue for the filter, the rows are one page of it. Use it to answer what is sitting there waiting to go out: the messages queued but not yet sent. limit defaults to 20 and takes a maximum of 50; cursor is the opaque nextCursor from the previous page, passed back verbatim; multi-brand organizations pass brandId to restrict the queue to one brand.
get_campaign_progressRequired arguments: campaignIdoutreachReadsGet read-only outreach delivery progress for one outreach campaign in the resolved active brand. Returns campaignId, campaignName, total delivery count, status counts, normalized progress, and completion state. Use after list_outreach_queue or a queued outreach operation when an agent needs campaign-level send progress without recipient contact data. Campaign Wizard IDs from list_outreach_campaigns are accepted here too: this tool resolves a Campaign Wizard ID to the outreach delivery campaign queued for it and reports that progress, answering campaignWizardId alongside the delivery campaignId so both IDs are visible. When a Campaign Wizard has no outreach queued yet, or has several delivery campaigns, the refusal says which and lists the delivery campaign IDs to call back with. statusCounts breaks the recipients into pendingApproval, queued, scheduled, sending, sent, delivered, engaged, opened, clicked, replied, failed, bounced, dropped, skipped, manualRequired and cancelled. Answers whether the outreach send you started earlier, perhaps yesterday, is finished yet: done when isComplete is true, still waiting to finish otherwise. Check progress with either a delivery campaign UUID or a Campaign Wizard ID; the read is restricted to the brand scope that owns the campaign in a multi-brand organization.
draft_outreach_messageRequired arguments: campaignWizardIdoutreachSpends creditsThe only way to write outreach copy about a saved creator: composes the subject and body from the campaign brief and the creator record Justify holds, so every figure in the copy is checked rather than invented. Compose outreach email copy for a campaign, optionally grounded on one saved creator. Returns: subject, body, wordCount, issues (content-quality findings against the copy), and grounding (whether creator intelligence anchored the draft or it was written from campaign context alone). Nothing is saved and nothing is sent — this is the composition step, and queue_outreach_send is the separate act that queues delivery. Creator figures are read from the saved creator record rather than taken as input, so any number appearing in the copy is checked against Justify data. This calls a paid model but consumes no outreach credits; credits are reserved per delivery at send time.
queue_outreach_sendRequired arguments: campaignWizardId, idempotencyKey, influencerIds, message, subjectTakes an idempotencyKeyoutreachDestructiveQueue an outreach email to saved creators for a campaign — the same act as the Outreach UI send button, through the same service. Requires idempotencyKey. Recipients are named by saved-creator id and their addresses are resolved server-side, so contact values stay redacted. Email only: SMS needs a human consent attestation an agent must not make. Sender readiness, campaign eligibility, personalisation-token validity, anti-harassment contact limits and credit reservation are all enforced before anything is queued. HUMAN APPROVAL IS REQUIRED: an email reaching a creator is consequential, so this call parks the request and hands back a single-use approval link rather than delivering. The link is bound to the organisation, to the named approver, to a hash of this exact payload and to a short expiry, and it must be opened by that person while signed in to Justify; an agent holds no authority to approve its own request. Hosts on protocol revision 2025-11-25 or later receive the link as a URL-mode elicitation; earlier hosts receive the same link in the error text. Track the parked request with get_operation_status and list_operations — it sits at rawStatus AWAITING_APPROVAL until a person acts, then completes with the queue transition. Once approved the stored result carries: campaignId, operationId, queuedEmail, skipped, skippedRecipients, unknownCreatorIds and idempotentReplay. Compose the copy first with draft_outreach_message; monitor delivery with get_outreach_status and list_outreach_queue. Use it when the drafts are approved and someone says to send them: automate the influencer outreach, one personalised message per recipient, then track responses and follow up from list_outreach_queue.
get_sms_outreach_statusoutreach, outreach_smsReadsGet the state of SMS outreach for this organisation: how the text-message channel is performing over a recent window, and the per-message delivery register behind those numbers. Returns a window block saying whether anything was actually measured, SMS totals (sent, delivered, failed, bounced, replied, delivery rate, failure rate, billable segments consumed, mean delivery time), sending readiness with named blocker codes, a failure register tallied against the numeric error code the mobile network returned, with the estate severity rating against each, and the message rows themselves — delivery id, outreach campaign, campaign name, state, attempts, cadence step, last attempt, timestamps, and whether a skip or a suppression was recorded. A recipient telephone number, the operator message identifier and the operator own error prose are held back: this reads the stored delivery ledger and never dials out. Use it to answer whether text messages reach a handset, which handsets rejected them and why, and whether a segment bill looks larger than the message count. Sending a text message is a different act entirely and no argument here starts one: queue_outreach_send owns that, and it is the tool that carries the human sign-off. For the email side of the same campaigns use get_outreach_status; for the queue across both channels use list_outreach_queue. Takes no brand argument, because the totals underneath are organisation-wide exactly as the outreach channel dashboard is. The register takes no cursor: when hasMore is true, narrow it with outreachCampaignId or status, or raise limit, which defaults to 20 and tops out at 50.

Social inbox and community replies

Inbound community messages, triaged under your active automation policy. A suggested reply is a proposal: it waits in the review queue until a person approves it, optionally with edited text, and anything that needs a human can be escalated instead. You can also read the rules the automation runs under, which rung of the ladder each policy sits on, the approval floor, the quiet hours, the daily budget and whether anything has been stopped, and the moderation queue itself, conversation by conversation, with how long each one has been waiting. The inbox those proposals come out of is readable in its own right: the comments and direct messages your brand has received on the channels it owns, with the platform each arrived on, where the conversation has got to, how it was classified for intent and sentiment, who owns it and how many hours it has been waiting. Open one and you get the exchange message by message, exactly as the inbox screen shows it. Where display rights forbid showing a message the text arrives as null, which means withheld rather than empty. Archived provider payloads, the platform own identifiers for a thread and the keyed hash that identifies an author never leave the server. Publishing a policy, promoting a rung, clearing a stop and sending a reply all stay with people.

Entitlements used by this family: community_automation, community_autopilot, community_moderation, outreach, social_inbox

Try asking

  • Show me the suggested community replies waiting for review, approve the two that read well, and escalate anything a person should answer.
  • Before you draft anything: is our community autopilot allowed to reply on its own, and what are its quiet hours and daily budget?
  • What is sitting in the moderation queue for this brand, urgent first, and how long has the oldest one been waiting?
  • What has come in on Instagram that nobody has answered? Sort by how long it has been sitting there.
  • Open that conversation and read me the whole exchange, oldest message first.
ToolEntitlement neededEffectWhat it does
suggest_community_replyRequired arguments: messageIdcommunity_automation, outreachWritesTriage one inbound community message and, when appropriate under an active automation policy, generate a brand-voice suggested reply (PROPOSED, awaiting human approval). Returns triageDecision, reasons (why the triage decided as it did), plus actionId and proposedText (the draft reply — both null when triage declines to draft); it never sends a reply. Follow with get_community_review_queue and approve_community_reply for the human-gated approval step. Use it when someone has messaged us and you want something written back in our voice: the draft is proposed, never sent, until a person approves it.
approve_community_replyRequired arguments: actionIdcommunity_automation, outreachWritesApprove an AI-suggested community reply for the authenticated organization, optionally supplying an edited final text. Records the approver and the human edit distance; the approved reply is dispatched by the executor, not by this tool. Returns actionId, approved (true on success), and editDistance (normalised 0-1 distance between the proposed and the approved final text). Find pending actionIds with get_community_review_queue first. Use it when a reviewer has read the draft, decided it is fine, and says to let it through.
get_community_review_queuecommunity_automation, outreachReadsList AI-suggested community replies awaiting human review for the authenticated organization. Returns items each with actionId, conversationId, messageId, actionType, actionStatus, proposedText (the editable draft reply — never sent by this tool), confidence, riskScore, safetyDecision, createdAt, conversation context (platform, status, priority, intent, sentiment, and the creator handle and avatarUrl when the author is a known creator) and inboundMessage (the rights-redacted text being replied to, direction, receivedAt). Read-only. Use it to surface what needs approval, then approve_community_reply to approve a draft (optionally edited) or escalate_community_conversation for human judgement; dispatch of approved replies is a separate human-gated step, never this tool. Use it to answer what replies are waiting on a person before they go back out to the public. limit caps how many suggestions come back and defaults to 25; conversationId narrows the queue to one thread. This is the APPROVAL queue for drafted replies, not the moderation queue of incoming threads that list_moderation_queue walks.
escalate_community_conversationRequired arguments: conversationIdcommunity_automation, outreachWritesEscalate a community conversation to human review for the authenticated organization (sets status ESCALATED and notifies reviewers). Returns conversationId and the updated conversation status. Use when a reply needs human judgement; find candidate conversations with get_community_review_queue first. A reviewer user id that is not a member of this organization refuses the escalation outright, so conversation context never reaches an outsider and nobody is notified. Use it when a message needs a real person rather than a machine and someone says to hand it up. Use it when a comment or direct message has turned into a complaint, a legal question or a press enquiry that must be handed to a named colleague.
get_community_autopilot_configcommunity_automation, community_autopilot, outreachReadsRead the community autopilot configuration governing this organisation: which rung of the automation ladder each policy sits on, and the guardrails that rung acts under. Returns one row per policy — policyId, brandIdentityId, name, channel, topic, mode, ladderRung, ladderRungCount, nextRungUp, sendsWithoutHumanApproval, status, killSwitchEnabled, approvalThreshold, maxActionsPerThread, quietHours (start, end, timezone), dailyBudgetUsd, safetyPolicyVersion, promptVersionId, configHash, publishedAt, updatedAt — beside every active stop (pauseId, scope, reason, detail, brandIdentityId, channel, policyId, createdById, createdAt), organisationPaused, organisationPauseId, ladder, total, brandId and configReason. The five rungs run DRAFT_ONLY, SUGGESTED_REPLY, HUMAN_APPROVED_SEND, LIMITED_AUTOPILOT then CHANNEL_AUTOPILOT, and only the last two dispatch a reply without a person approving it, which sendsWithoutHumanApproval states per row. A stop scoped ORG, raised as MANUAL_KILL_SWITCH or DRIFT_BREACH, halts every policy at once whatever rung it sits on, so organisationPaused is read before any mode. quietHours and dailyBudgetUsd read null until somebody sets them, and LIMITED_AUTOPILOT cannot be entered while either is null. Read-only, and no tool publishes a policy, promotes a rung or clears a stop: those remain human-only so an agent cannot widen its own authority. Read this before suggest_community_reply or approve_community_reply, so a draft is judged against the rails it will actually be held to. Use it to answer how far the automation may go unattended, what would halt it, and when it has to stay silent.
list_moderation_queuecommunity_moderation, social_inboxReadsList the community moderation queue for one brand, latest activity first, exactly as the inbox reads it: every conversation on the social ledger with the state it is in and how long it has been sitting there. Returns data (conversationId, brandIdentityId, channel, status, priority, intent, sentiment, lastMessagePreview, lastActivityAt, ageHours, ageLabel, externalConversationId), total, totalIsExact, hasMore, nextCursor, brandId and scopeReason. status runs OPEN, PENDING, SNOOZED, RESOLVED, ESCALATED, SPAM, ARCHIVED and priority runs LOW, NORMAL, HIGH, URGENT; both narrow server-side, as do channel and assignedToUserId. ageHours counts from the latest message on the conversation either way, so a large number is a thread nobody answered rather than one nobody opened, and ageLabel says the same in words. lastMessagePreview is rights-redacted and comes back null wherever display rights withhold the text; no message body is served in full by this tool. Pages by cursor and limit (1 to 100, default 25): when hasMore is true, call again with cursor set to the returned nextCursor verbatim. total counts only the rows this call served, which totalIsExact states, because the underlying read walks a keyset and takes no count. An organisation holding no brand identity receives an empty page whose scopeReason names that as the cause, never a silent one. Read-only: assigning, snoozing and resolving a conversation stay in the web app, and escalate_community_conversation is the only hand-up an agent may make. Use it to answer what is waiting to be moderated, which of it is urgent, and how long each thread has waited.
list_social_inboxsocial_inboxReadsList the social inbox: the comments and direct messages members of the public have sent the brand on its owned social channels, newest activity first. Returns one row per conversation carrying conversationId, platform (the platform and surface together, for example INSTAGRAM_COMMENT or TIKTOK_DM), state, priority, classified intent, classified sentiment, a truncated preview of the newest message, when that activity happened, how many hours ago that was, and the brand identity it belongs to. A conversation here is an inbound thread from a member of the public on a channel the brand owns, which is not an outreach delivery to a creator and not a campaign. Use get_social_inbox_thread to read one conversation message by message. Filter by platform, state, priority or assignee. Pages by keyset: when hasMore is true, call again with cursor set to the returned nextCursor, passed back verbatim. Never quote a backlog from one page — total counts every conversation matching the filter, the rows are one page of it. Every answer carries scopeReason naming the brand it read, so an empty inbox says so in words rather than coming back as a bare empty array. Provider payloads, provider thread identifiers and the hash that identifies an author are held back throughout. Replying is a separate, human-gated act that no argument here performs.
get_social_inbox_threadRequired arguments: conversationIdsocial_inboxReadsGet one social inbox conversation and every message inside it, oldest first, in the same projection the inbox screen itself renders. Returns the conversation summary line — platform, state, priority, brand identity — and one row per message carrying messageId, conversationId, platform, direction (INBOUND from a member of the public, OUTBOUND from the organisation), its moderation standing on the platform (PUBLIC, PRIVATE, HIDDEN or DELETED), the rights-redacted body, whether the author is a subject the ledger recognises, when it occurred, and whether it has been tombstoned. A null body means display rights forbid showing that text: it is held back, not empty, and must never be filled in from another source. Raw provider payloads, the provider identifiers for the thread and its messages, and the hash that identifies an author are all held back — this reads the inbox projection, never the archived payload behind it. Use list_social_inbox to discover conversation ids. A conversation belonging to another brand or another organisation is reported as not found rather than forbidden, so nothing is learnt about what exists elsewhere. Reading a thread sends nothing: composing a reply is suggest_community_reply and dispatching one is a separate human-gated step. A thread read returns the exchange itself rather than a page of the inbox: no scope count, no cursor and no next page, because one conversation is the whole answer.

Job board

Job postings attached to a campaign, and the applications they attract. Offers go to accepted applicants only. Closing a posting stops new applications while existing ones continue; cancelling is the hard stop. When a creator counters your fee you can accept or decline that counter here, and when their work arrives you can read the deliverables, see what the automated content check made of each one, and send one back for changes. Approving or rejecting a deliverable is not on this surface, because each of those moves the escrowed fee and money never moves on an agent word.

Entitlements used by this family: job_board, job_content_verification

Try asking

  • List the open job postings on this brand and show me the applications on the winter campaign posting.
  • Send the offer to the accepted applicant on job posting 4a1b2c3d-5e6f-4708-9a0b-1c2d3e4f5a6b.
  • The creator countered our fee on that offer, so accept their counter and lock the deal in at their figure.
  • Did the uploads on that job actually show the product, and was anything flagged as unsafe?
  • Send that deliverable back to the creator asking for a shorter opening and a clearer disclosure.
ToolEntitlement neededEffectWhat it does
list_jobsjob_boardReadsList job postings for the resolved active brand. Returns: id, title, status, description, requirements, campaign name, estimated reach, created date. Jobs are briefs posted to attract influencer applications. Use get_campaign for full details on the associated campaign. Pages with an opaque cursor and limit (1 to 50, default 20): when hasMore is true, call again with cursor set to the returned nextCursor. Closed and inactive jobs stay hidden unless includeInactive is true; multi-brand organizations pass brandId. Use it to answer what roles a brand has got open at the moment, and set includeInactive true to get every job posting, live or closed. Every ID it publishes opens with get_job, whatever state the posting is in.
get_jobRequired arguments: jobIdjob_boardReadsGet full details of a job posting by ID. Returns: title, description, requirements, expected deliverables, campaign name and platforms, status, estimated reach, application count, created date. Opens a posting in ANY lifecycle state, closed and expired ones included, exactly as the brand sees it on the web job page: status and isActive are how you learn which state this one is in, so read them rather than assuming the posting is still taking applications. Use list_jobs with includeInactive true to find the IDs of postings that are no longer live. Also returns the posting visibility (public or invite-only), its targetPlatforms and its estimatedReach figure. jobId is the job posting UUID from list_jobs; brandId is the brand UUID, required for multi-brand organizations.
list_job_applicationsRequired arguments: jobIdjob_boardReadsList applications for a specific job posting in the resolved active brand. Returns application identity, creator identity, application status, and nullable latest-offer compatibility fields such as amount, currency, sent date, expires date, and payment status. Applications are JobApplication records; offer fields describe the latest JobOffer when one exists. Use list_jobs or get_job to find job IDs first. Pages with an opaque cursor and limit of 1 to 50 (default 20): when hasMore is true, call again with cursor set to the returned nextCursor; multi-brand organizations pass brandId with the job posting id. Use it to answer who has applied so far: every creator who put themselves forward for one job posting. jobId is the job posting UUID from list_jobs; brandId is required for multi-brand organizations and defaults to the sole brand when one exists; cursor is the opaque cursor a previous list_job_applications call returned; limit is the maximum applications to return, 1 to 50, default 20. Use it to answer how many people have applied to this posting, who they are, and whether an offer has gone to each yet.The offer columns tell whether money has been proposed to an applicant, how much, in which currency, when it was sent and when it lapses.
create_jobRequired arguments: campaignWizardId, description, idempotencyKey, titleTakes an idempotencyKeyjob_boardWritesPublish an influencer brief as a job posting, so creators can apply to it and the applications can be collected, reviewed and shortlisted. Create a job posting attached to an existing campaign. Requires idempotencyKey; exact retries with the same idempotencyKey return the original response rather than repeating the action. Provide the title and description explicitly (the REST surface generates them with an LLM for human users; agents supply their own copy). Jobs start ACTIVE unless isDraft is true. Set jobType GIFTING with commerceProduct (connectionId + providerProductId from list_commerce_stores/list_commerce_products) for a product-gifting job — the creator receives the linked product on offer acceptance. Use list_campaigns/get_campaign_wizard to find campaignWizardId first; use send_job_offer, close_job, or cancel_job afterwards. Use it when a brand is looking for someone and wants to put the word out: the posting is what creators see and apply to.
send_job_offerRequired arguments: amount, idempotencyKey, jobApplicationId, jobIdTakes an idempotencyKeyjob_boardWritesSend an offer to an ACCEPTED applicant on a job posting. amount is in major currency units and an explicit 0 is valid (gift-only offers on GIFTING jobs — the creator receives the linked product, no cash component, no payout account needed). Requires idempotencyKey; exact retries replay the original response. Returns the created offer: id, jobId, jobApplicationId, amount, currency, status, sentAt, expiresAt. One offer per applicant; conflicts when an offer already exists, the applicant is not ACCEPTED, or all maxPositions slots hold live offers. Use list_job_applications to find jobApplicationId; the offer expires after 7 days.
close_jobRequired arguments: idempotencyKey, jobIdTakes an idempotencyKeyjob_boardDestructiveSoft-close a job posting: it stops accepting new applications while existing applications continue unchanged. Reversible bias — prefer this over cancel_job. Requires idempotencyKey. Rejects with CONFLICT when the job is already closed, cancelled, or completed. Returns the job id, its new status, isActive, and applicationsAffected (how many applications the transition touched — zero for a close, since existing applications continue). Use it to answer we have enough applicants, stop taking more: the posting comes down from the board but the people already in the running keep their place.
cancel_jobRequired arguments: idempotencyKey, jobIdTakes an idempotencyKeyjob_boardDestructiveHard-cancel a job posting. Blocked with CONFLICT while any application is accepted, contract-pending, or active — close_job is the safe alternative. On success the platform asynchronously rejects all remaining non-terminal applications. Requires idempotencyKey. Returns the job id, its new status, isActive, and applicationsAffected as counted at transition time (the asynchronous rejections that follow are not included in it). Use it to take a role posting down when the listing must come down entirely.
counter_job_offerRequired arguments: decision, jobId, offerIdjob_boardDestructiveAnswer a creator counter-offer on a job offer this organisation sent: accept the rate the creator counter-proposed, or decline it and leave the original offer standing. A counter-offer is the creator asking for a different fee than the one offered; this tool is the brand-side reply to that haggling, and an agent can never raise a counter-proposal itself because only the creator portal can. Returns: the offer record (id, amount, currency, status, expiresAt, createdAt, updatedAt) exactly as the web counter modal returns it, plus decision, jobId, jobApplicationId, counterAmount and counterCurrency. accept_counter rewrites the offer amount to the countered figure, marks the offer accepted, moves the application to active or contract-pending, and records the immutable payout destination snapshot. It is terminal: the negotiation is over and the figure is frozen. decline_counter clears the counter from the offer and keeps the original offer pending at its original amount, so the creator can still accept or decline it. No funds move on either decision. Depositing and releasing escrow are separate acts behind a human approval and are not reachable from this tool. Refuses with CONFLICT when the offer already left the pending state, when it has expired, or when no counter is outstanding on it; refuses with PAYOUT_NOT_READY when the creator has connected no payout account, in which case nothing at all is written. Use list_job_applications to find the offerId, and get_payment_status afterwards to see whether the accepted fee has been funded yet.
list_job_deliverablesRequired arguments: jobIdjob_boardReadsList the deliverables creators have uploaded against one job posting in the resolved active brand. Returns: deliverable id, title, description, file format, file size in bytes, duration in seconds, media processing status, a first-party playback address, approval status, approval notes, approval date, job posting id, job application id, uploader user id, uploader name, upload date and last-updated date. A deliverable is the uploaded work a creator submits to satisfy a job — the artefact a brand approves or rejects before money is released. It is not a job application and not a content asset in the campaign library. Use list_jobs or get_job to find job ids, and list_job_applications to see who was offered the work. Each row also carries how far the media pipeline got with the upload, so a file still transcoding is visibly not yet watchable. Pages with cursor and limit: when hasMore is true, call again with cursor set to the returned nextCursor. brandId is the brand UUID, required for multi-brand organizations and defaulting to the sole brand when one exists; jobId is the job posting UUID from list_jobs or get_job; cursor is the opaque cursor a previous list_job_deliverables call returned; limit is the maximum deliverables to return, 1 to 50, default 20.
review_job_deliverableRequired arguments: decision, deliverableId, idempotencyKey, jobIdTakes an idempotencyKeyjob_boardWritesRecord a review verdict on a deliverable a creator uploaded against a job posting, returning the work for changes with written feedback. A deliverable is the finished artefact a creator submits to satisfy a job; reviewing it is how a brand tells the creator whether the work is ready. Returns: reviewId, deliverableId, jobId, decision, approvalStatus, note, revisionDeadline, revisionRound and createdAt. The only verdict this tool serves is needs_changes, which marks the deliverable as changes-requested, records the feedback note against it, sets a revision deadline and consumes one revision round. Approving and rejecting a deliverable are deliberately absent here: each one moves the escrowed fee inside the same handler, approving releases it to the creator and rejecting refunds it to the brand, and money never moves on an agent word. Both stay with a person in the web app. Refuses when the revision rounds allowed on that deliverable are already spent, when the asset is not a deliverable of this job, and when the deliverable belongs to no job application. Requires idempotencyKey because recording a review is not a state machine: an unguarded repeat would spend a second revision round the creator never used. Exact retries replay the original response. Use list_job_deliverables to find deliverableId and to read the approval status a previous review left behind.
get_job_content_verificationRequired arguments: jobIdjob_board, job_content_verificationReadsDid the creator's uploaded draft show the product, match the job terms and pass brand safety: the automated verdict on every deliverable of one posting, before a person approves any of it. Read the automated content-verification register for one job posting: one verdict per uploaded deliverable saying whether the posted content matched the job terms. Content verification is the automated check Justify runs over a deliverable after a creator uploads it, and it is separate from the human review a brand records with review_job_deliverable. Each deliverable carries verdict matched, failed or pending, plus verificationId, checkedAt, confidence, productDetected, presenceMeasured, presence, brandSafe, brandSafetyIssues, quality scores, feedbackSummary, feedbackStrengths and feedbackImprovements. Returns: jobId, deliverableCount, reported, truncated, a tally of matched, pending and failed, and the deliverables array. pending means no check exists for that deliverable yet, and is never the same as failed: an upload nothing has looked at is not an upload that was rejected. presenceMeasured is the second honesty flag: the stored productDetected column defaults to false, so read productDetected only when presenceMeasured is true, and otherwise say that nothing watched the asset. presence carries the screen-time facts the tracker measured: onScreenSeconds, firstAppearanceSecond, assetDurationSeconds and framesAnalysed. framesAnalysed below the asset whole-second count means only the opening window was watched, so never state a share of the whole asset from it. The register is bounded: deliverableCount counts the whole job, truncated says whether rows were left out, and tally covers only the rows returned. Walk a job past the bound with list_job_deliverables, which pages. Use list_jobs or get_job to find jobId, list_job_deliverables to read upload and approval state, and review_job_deliverable to act on what you find here. The jobId is the sole required input, the job posting UUID; in a multi-brand organization brandId is required beside it and defaults to the sole brand when one exists — the same anchor every job read takes.

Marketplace+ intelligence graph

The events, talent and agencies catalogue behind the Marketplace+ surface: typed search across all three, the events calendar for a date window, the PR-desk read of which cities are busy, the trending talent feed, and the full entity profiles the web pages render.

Entitlements used by this family: debug_tools, marketplace_plus

Try asking

  • Which cities have catalogued events coming up, and which talent are linked to the ones in Milan?
  • Search Marketplace+ for beauty talent and the events they are attached to this autumn.
ToolEntitlement neededEffectWhat it does
marketplace_plus_searchRequired arguments: querymarketplace_plusReadsSearch the Marketplace+ intelligence graph: the same 3-way typed search the web surface runs across talent profiles, catalogued events and agencies, from this platform’s own curated catalog. Use it when the ask is anything-shaped — anything about a scene, a topic or a city, swept across the whole Plus catalogue in one query. Returns a discriminated result list (kind: talent | event | agency) with per-kind totals; structured queries ("events in London during August") surface the parsed intent so a misparse is visible, and a degraded flag marks results that must not be presented as a settled zero. Pass type to restrict to one entity kind, withinDays to bound event recency, and limit/offset to page: when hasMore is true, call again with offset set to the returned nextOffset. Use marketplace_plus_get_talent, marketplace_plus_get_event or marketplace_plus_get_agency to read a full profile for a result id.
marketplace_plus_events_in_windowRequired arguments: from, tomarketplace_plusReadsList the catalogued Marketplace+ events overlapping a date window — the same read that feeds the web events calendar, from this platform’s own curated catalog. Pass from and to as YYYY-MM-DD (inclusive, at most 100 days apart) and get back the count plus each event’s title, dates, venue, city, country, enriched event type and isSpanning flag. isSpanning true means a long runner — a sports season, a championship series, a theatre run or an award cycle that lasts longer than a calendar month and merely covers these dates; isSpanning false means an event someone attends on a day inside the window. Filter to isSpanning false when the question is what actually happens on a date (the web calendar shows the long runners in a separate ongoing strip above its month grid for exactly this reason), and keep the isSpanning true rows when the question is what season or series is running. Use marketplace_plus_get_event to read a full profile (roster, description, coordinates) for any returned event id, or marketplace_plus_whos_in_town to pivot the same window by city and attending talent. Use it to answer what is on over the next fortnight or month: from and to are the two ends of that window. Pages with cursor and limit: count is the total for the whole window and events is one page of it, so when hasMore is true call again with cursor set to the returned nextCursor. A busy quarter holds far more events than one page carries. Use it to answer what is happening between two dates: festivals, premieres, fashion weeks and award nights, listed by day.
marketplace_plus_whos_in_townmarketplace_plusReadsThe Marketplace+ PR-desk read: which cities have upcoming catalogued events (busiest first), and — once a city is passed — the talent linked to that city’s events inside the day window, exactly as the web Who’s In Town modal reads it. Pass days as 7, 14 or 30 (default 7) and optionally city to expand one city into its talent-to-event links, each carrying the talent id and name, the event id, title, dates and venue, and whether the appearance is announced or predicted. Use marketplace_plus_get_talent or marketplace_plus_get_event to read the full profile behind any link. Use it to answer who is going to be at the show in one city — Cannes in June, Milan in September — inside the next 7, 14 or 30 days. The links list pages with cursor and limit: totalCount is the total for the whole city window and links is one page of it, so when hasMore is true call again with cursor set to the returned nextCursor. Use it to answer which famous names will be in a city this fortnight: the PR desk's diary of who is expected where.The answer is a shortlist of arrivals rather than a calendar of shows: read it the morning a publicist asks who else is around.
marketplace_plus_get_talentRequired arguments: talentProfileIdmarketplace_plusReadsRead one Marketplace+ talent profile by talentProfileId — the same view-model the web talent entity page renders. Returns the identity (name, professional title, city, country, bio), upcoming announced and predicted event links — each carrying role, the source article’s own stated reason for naming this person beside that event (null when none was extracted), and a matchConfidence that grades only how confidently the name resolved to this profile and never whether the person will be there, so read role before repeating a link as an appearance — recent past appearances with the true total — every event summary carrying an isSpanning flag that is true when the entry is a long runner (a season, championship series, theatre run or award cycle lasting longer than a calendar month) rather than an appearance on a date — the latest literal-mention news digest, bookability (active promo cycle plus human-verified rep contacts), the agency representation graph, and an honest lastUpdatedAt timestamp of the latest real data mutation (null when nothing dated exists). Talent ids come from marketplace_plus_search, marketplace_plus_whos_in_town or an event roster. Returns NOT_FOUND for an unknown or merged-away profile.
marketplace_plus_get_eventRequired arguments: eventIdmarketplace_plusReadsRead one catalogued Marketplace+ event by eventId — the same view-model the web event entity page renders. Returns the event details (title, dates, venue, address, city, country, coordinates, enriched event type, description, official website, and an isSpanning flag that is true when the row is a long runner — a sports season, championship series, theatre run or award cycle lasting longer than a calendar month rather than something attended on a single day) plus the roster of talent linked to this event — known talent drawn from announcements and press coverage, not a verified attendee list — where every entry carries its announcement status (announced, historical or predicted), role, the source article’s own stated reason for naming that person here (null when none was extracted), and a matchConfidence that grades only how confidently the name resolved to that profile and never whether the person will be there, so read role before repeating a name as an attendee. Also returns an honest lastUpdatedAt timestamp of the latest real data mutation. Event ids come from marketplace_plus_search, marketplace_plus_events_in_window or marketplace_plus_whos_in_town. Returns NOT_FOUND for a missing, deleted or inactive event. Use it to look up everything the catalogue holds on one festival, awards night or conference.
marketplace_plus_get_agencyRequired arguments: agencyIdmarketplace_plusReadsRead one Marketplace+ agency by its canonical Agency entity id — the same view-model the web agency entity page renders. Returns the agency identity (canonical name, website, description, verified headquarters and other office locations), the verified booking route, the represented-talent roster with its exact total, the rep contact matrix (verified emails, phones and LinkedIn profiles per represented talent), the agency’s own team directory with leadership flagged, and an honest lastUpdatedAt timestamp of the latest real data mutation. Agency ids come from marketplace_plus_search or a talent profile’s representation entries. Returns NOT_FOUND for an unknown agency id. Use it to answer who represents this person and how do we book them: the firm behind a talent, its offices, its bookers and the people it looks after.
marketplace_plus_trending_talentmarketplace_plusReadsThe Marketplace+ default discovery feed for talent: the top profiles from the latest enrichment window, ranked by trend, as the same fully-enriched talent cards the web Plus landing grid renders — name, professional title, city and country, primary platform handle, follower count, and each talent’s next upcoming catalogued event with its announcement status. Takes no parameters. Use marketplace_plus_get_talent to read a returned talent id in full, or marketplace_plus_search when you have a specific query instead of wanting the trending feed. Use it to answer who everyone is talking about at the moment: the names trending in the latest enrichment window.
list_marketplace_plus_stagingdebug_toolsReadsRead the Marketplace+ staging queue, the review queue in front of the events catalogue every customer with Marketplace+ sees: status pending (the default) is the candidates awaiting review, oldest first; status reviewed is the approved and rejected history, newest first. Each item carries its id, action (NEW, UPDATE or CANCELLED), confidence, sourceUrl, feedId, reviewStatus, who reviewed it and when, the rejectionReason, the matched catalogue event and the proposedPayload (the event as it would be catalogued). Cursor-based: returns data, total, hasMore and nextCursor; the next page is the same call with cursor set to the returned nextCursor and the same status. A cursor counts places in the queue, so after approving or rejecting pending items list again without a cursor: the queue has shrunk, and an old cursor would step past items still waiting. Super-admin operators only. Read-only; approve, reject or edit an item with the staging write tools. Use it to work through the curation backlog.
get_marketplace_plus_staging_itemRequired arguments: itemdebug_toolsReadsRead one Marketplace+ staging item by its id, as list_marketplace_plus_staging shows it: its action, confidence, source, review status and history, the matched catalogue event, and the whole proposedPayload, so you can check a candidate before approving, rejecting or completing it. A missing id is refused with NOT_FOUND. Super-admin operators only. Read-only. Use it to look closely at one proposed event before it reaches the catalogue.
update_marketplace_plus_staging_itemRequired arguments: idempotencyKey, item, reasonTakes an idempotencyKeydebug_toolsWritesComplete a still-PENDING Marketplace+ staging item with what its source left out, the curator's edit on the staging page: any of title, venue, city, country, startDate and endDate (YYYY-MM-DD) and websiteUrl. The slug is derived from the title and an absent endDate falls back to startDate, as the page does. It never approves the item and never requires the event to be complete. An item already reviewed is refused with CONFLICT; a call naming no field is refused. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the item's id).
approve_marketplace_plus_staging_itemRequired arguments: idempotencyKey, item, reasonTakes an idempotencyKeydebug_toolsDestructiveApprove one PENDING Marketplace+ staging item, the greenlight the staging page's Approve gives: a NEW proposal becomes an event in the catalogue every customer with Marketplace+ sees, and an UPDATE or CANCELLED proposal changes the catalogue event it matched. The proposedPayload must already be a complete event (complete it with update_marketplace_plus_staging_item first); an incomplete one is refused with INVALID_REQUEST, an item already reviewed with CONFLICT, and a proposal whose matched event has since gone with CONFLICT. A NEW proposal that matches no live event exactly but sits beside a similar title in the same city and week is refused with CONFLICT, conflictReason NEAR_DUPLICATE and the live events in details.candidates, and nothing is written: judge them, then approve again (with a new idempotencyKey) passing mergeIntoEventId, the candidate it is, or confirmNew: true when it is none of them. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the item's id and the catalogue event's id, null for a signals-only item, which approves with no catalogue write).
reject_marketplace_plus_staging_itemRequired arguments: idempotencyKey, item, reasonTakes an idempotencyKeydebug_toolsDestructiveReject one PENDING Marketplace+ staging item as unsuitable, the same as the staging page's Reject: it is marked REJECTED with your reason as its rejectionReason and never reaches the catalogue. An item already reviewed is refused with CONFLICT. Requires idempotencyKey (exact retries replay the original response) and reason, your own reason for rejecting it (at least 10 characters), which is stored on the item and audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the item's id).
stage_marketplace_plus_candidatesRequired arguments: candidates, idempotencyKey, reasonTakes an idempotencyKeydebug_toolsWritesStage a round of Marketplace+ event candidates into the review queue, the marketplace-plus skill's stage step: each candidate (feedId, sourceUrl, normalizedUrl, headline, snippet, publishedAt as an ISO date-time or null, and the extraction the skill's Workflow produced) is checked against the candidate contract, and when any one fails, nothing is staged and every problem is named by index. Staging skips a candidate already in the queue (by normalizedUrl) or already in the catalogue, and writes one that can never become an event REJECTED at birth with its reason. Every staged candidate waits as PENDING for a person's approval; nothing reaches the catalogue from here. At most 100 per call. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (staged, skippedStaged, skippedCatalogued and endedAtStaging).

Public recommendation corpus

A read across the Justify public corpus of creator product recommendations, the same corpus behind the public creator profiles. Nothing here is scoped to your organisation, and nothing here writes.

Try asking

  • Search the public recommendation corpus for creators recommending running shoes.
ToolEntitlement neededEffectWhat it does
search_public_recommendationsNone beyond a signed-in organisationReadsSearch the Justify public corpus of creator recommendations. Returns per item: quote (structured body or post excerpt), stats (likes, comments, views — a count Justify does not hold is absent, never null or zero), sourcePostUrl, title, brand, price, goActionUrl (tracked /go/ redirect), disclosure (FTC/ASA visible text — always present on every item), creator (handle, platform, name), and trustTier (verified_creator | creator_claimed | observed_unclaimed — observed-unclaimed items have a redacted quote). Use when an agent needs creator-backed product or experience recommendations at answer time. Combines with get_influencer to fetch full analytics for a matched creator handle. Use it when a customer wants a good running shoe or a decent coffee grinder and you need what creators have actually said about one.

Long-running operations

Some acts do not finish inside one call. They return an operationId, and these tools are how an agent that lost the thread finds the work it already started, checks where it got to, and stops it if it should not finish.

Entitlements used by this family: creative_testing

Try asking

  • I lost the thread: what operations have I already started on this organisation, and what state are they in?
ToolEntitlement neededEffectWhat it does
list_operationsNone beyond a signed-in organisationReadsList resumable operations for the authenticated organization, so an interrupted agent can find work it already started. Includes Creative Testing launches alongside other long-running operation types. Returns data rows (id, type, title, status, rawStatus, progress, currentStage, cancelable, campaignId, brandId, creditState, error, errorCode, statusUrl, createdAt, updatedAt) plus total and hasMore. Provide brandId in multi-brand organizations. Use get_operation_status for one operation and cancel_operation to stop a cancelable one. This is the session's own ledger of slow asynchronous launches still in flight or recently settled, each addressed by an operationId: a housekeeping read for the agent itself. Filter by operation type or mapped status; limit caps the page. Use it to answer what we have still got running in the background: the agent’s own ledger of slow work not yet settled. Use it to answer is anything still running from earlier: the background jobs this session kicked off and where each got to. The tray of jobs still ticking over after the conversation moved on.
get_operation_statusRequired arguments: operationIdNone beyond a signed-in organisationReadsGet one resumable operation by operationId, as returned when the operation was started. Operation ids are prefixed by type, for example creative_test:<id>. Returns the operation record: id, type, title, status, rawStatus, progress, currentStage, cancelable, campaignId, creditState, idempotencyState, error, errorCode, statusUrl, createdAt, updatedAt. Provide brandId in multi-brand organizations. Poll this after start_creative_test or other long-running launches; use cancel_operation when cancelable is true. Answers where the long asynchronous launch this session kicked off has reached: one ledger entry, polled by the agent for its own housekeeping.
cancel_operationRequired arguments: operationIdcreative_testingDestructiveCancel a supported operation using the operationId returned when it was started, for example creative_test:<id> or campaign_library_generation:<id>. It stops the background run itself, not only the ledger entry: the work ends, and for a Campaign Library generation the reserved library credit is released and the concept lands in FAILED, where the library workflow offers recovery. Already-cancelled runs return the existing cancelled operation, while completed or failed runs return a conflict. Returns the operation record after cancellation: id, type, title, status, rawStatus, progress, currentStage, cancelable, creditState, error, errorCode, statusUrl, createdAt, updatedAt. Use list_operations or get_operation_status first to confirm the operation is cancelable. This is the tool to stop something you started by mistake: it stops the run that is still in flight and never rewrites a finished one. Use it when someone says stop that: the work halts mid-way and what it had reserved is released.

Growth experiments

The internal growth-experiment register: declared terms, lifecycle stage, wiring targets and minted tracked links. This family sits behind the internal debug-tools entitlement, so a customer organisation will not see it in tools/list at all.

Entitlements used by this family: debug_tools

Try asking

  • List the registered growth experiments with their declared thresholds and judge dates.
ToolEntitlement neededEffectWhat it does
list_growth_experimentsdebug_toolsReadsList every registered growth experiment with its declared terms, lifecycle stage, wiring references and minted tracked links, newest first. Returns data (code, name, lifecycle, channel, owner, metric, threshold, judgeDate, spendGbp, crmListSlug, sequenceId, linearIssueUrl and links with their share URLs) plus total. Optionally filter by lifecycle. Use the code field to address update_growth_experiment, promote_growth_experiment and complete_growth_experiment. GrowthExperiment rows are platform-global founder measurement rather than tenant data, so the list reads identically for every organisation and is gated to super-admins by the access_debug_tools permission.
update_growth_experimentRequired arguments: code, reasondebug_toolsWritesEdit one registered growth experiment, addressed by its immutable code. Requires reason, your own operator reason (at least 10 characters), which is audited with the write. Provide any of name, metric, threshold, judgeDate, spendGbp, crmListSlug, sequenceId, linearIssueUrl or hypothesis; setting a nullable field to null clears it and an omitted field is left untouched. Which fields may change depends on the lifecycle: completed records accept only linearIssueUrl and hypothesis, live records freeze metric and threshold, and clears of declared terms are drafts only. CRM list and sequence references are checked against the live provider first and a value the provider proves wrong is refused before anything is saved. Unknown input keys are refused rather than ignored. Returns data (the updated experiment) and verification. Find codes with list_growth_experiments.
promote_growth_experimentRequired arguments: code, destination, reasondebug_toolsWritesPromote a draft growth experiment: declare its channel, metric, threshold and judge date, move it to queued and mint its tracked links in one call. Requires reason, your own operator reason (at least 10 characters), which is audited with the write. Any term the draft already declares may be omitted (a supplied value wins over the row); destination is always required because a draft has no links to fall back on. The call converges on retry: a queued experiment holding fewer links than the requested count mints only the shortfall (terms never change on that path, and restating different term values refuses), a queued experiment already holding its links refuses cleanly so links are never minted twice, and live or completed records always refuse. Unknown input keys are refused rather than ignored. Returns data (the experiment) and mintedLinks (only the links this call minted, with their share URLs). Find drafts with list_growth_experiments. Use it when an idea graduates from a note to a scheduled test: the bet, the yardstick and the judgement date are fixed and the tracking links are minted.Promotion is the moment a hunch becomes a commitment somebody will be judged on.
go_live_growth_experimentRequired arguments: code, reasondebug_toolsWritesTake a queued growth experiment live: the moment it starts spending real attention against its declared threshold and the moment it joins THE LIST on the growth overview, which shows live experiments only. Going live happens once and cannot be reversed. Requires reason, your own operator reason (at least 10 characters), which is audited with the write: name what is now running (the campaign, the sequence, the placement) and since when. Allowed from queued only: a draft must be promoted first so its terms and links exist before anything runs, a live experiment refuses because going live happens once, and a completed record refuses because its verdict is on the record. Unknown input keys are refused rather than ignored. Returns data (the experiment, now live). Find queued experiments with list_growth_experiments. Use it when the team says start the clock: the experiment begins counting from today and its terms can no longer be edited.Going live is the starting gun: from here the experiment is measured, not planned.
complete_growth_experimentRequired arguments: code, reason, verdictNotedebug_toolsDestructiveWrite the verdict on a growth experiment and close the record: the verdict is written once, the lifecycle moves to completed and the declared terms freeze permanently. Requires reason, your own operator reason (at least 10 characters), which is audited with the write; the verdict says what happened, the reason says why you are closing the record now. Allowed from queued or live (an abandoned queued experiment is itself a result worth keeping); a draft cannot complete because it never declared what would judge it, and an already completed record refuses. Unknown input keys are refused rather than ignored. Returns data (the completed experiment carrying its verdictNote). Find candidates and check their judgeDate with list_growth_experiments. Use it to answer did the experiment work: write down the result as won, lost or inconclusive and shut the book on it.
get_growth_wiring_optionsdebug_toolsReadsList the wiring targets a growth experiment can point at: crmLists (the CRM lists outbound companies live in, as slug and name) and sequences (live outreach sequences with archived ones excluded, as id, name and active), plus crmConfigured and sequencesConfigured flags. Each side degrades independently to an empty list with its flag false when that provider cannot be reached. Use a returned slug as crmListSlug and a returned id as sequenceId in update_growth_experiment.
attach_experiment_pieceRequired arguments: code, idempotencyKey, pieceId, reasonTakes an idempotencyKeydebug_toolsWritesAttach a piece of copy (an email, advert, post or page) to a queued or LIVE growth experiment and mint its one tracked link, with utm_content carrying the piece id so its clicks attribute to the piece. Requires idempotencyKey (exact retries replay the original response), reason, your own operator reason (at least 10 characters), which is audited with the write, and record, the piece's experiment record: an attach without one is refused (piece_record_refused), because a link with no hypothesis, copy or receipt cannot be read as evidence when the verdict lands. One experiment holds one record: a NEW piece is refused once the row already carries a variantCopy (a control and a variant are two experiments), while re-attaching the SAME piece refreshes its record. The declared terms never change: this mints, it does not re-declare. Converges on retry: the same piece attached again returns the link it already holds with outcome already_attached and mints nothing. Drafts refuse (no channel to mint with) and completed records refuse. Omit destination to reuse the experiment's existing one. Unknown input keys are refused rather than ignored. Returns data (the experiment), link (the piece's link with its share URL) and outcome. Find experiments with list_growth_experiments; get the receipt from verify_copy.

Atlas operator library

The operator-side Atlas surface, re-homed from main at the 2026-08-26 merge: the append-only artefact library (versioned snapshots with their content hashes) and the governed messaging house with its deterministic copy verifier. Every tool here sits behind operator-only entitlements (debug_tools, brand_playbook), so a customer organisation will not see any of it in tools/list.

Entitlements used by this family: brand_playbook, debug_tools

Try asking

  • List the stored artefact snapshots with their versions and slugs.
  • Read the governed messaging house so I can draft from the approved arms.
  • Check this advert line against the messaging rules before it takes a slot.
  • Read a competitor's stored profile before enriching it, then write the merged profile back.
  • What have our competitors done this week? Show me the latest insights against them.
ToolEntitlement neededEffectWhat it does
upsert_artifactRequired arguments: contentType, idempotencyKey, reason, slug, titleTakes an idempotencyKeydebug_toolsWritesStore a Claude artefact snapshot in the artefact library as the next version of its slug, an upsert keyed on the slug: the bytes are preserved exactly as sent, an exhibit in the append-only store, and a slug not yet stored is created by this call. Send the document exactly one of three ways: html for anything under the 512 KB request-body cap; upload for a large document whose bytes are already in Justify private storage, the reference request_artifact_upload gave you, up to 100 MB; or files for a folder of files (a report with its images, PDFs and video), each file already placed through request_artifact_upload with files and index.html the entry page, where every file of the version you started from that you do not name is carried forward, so a version that changes one slide names one file. On the reference path the store heads the object, streams it once to hash it, and refuses the filing when the size or the hash is not the one you declared. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. The library is append-only: different bytes become a new version, and bytes identical to the current head return that head with created=false rather than a duplicate, whatever tags or provenance are sent. The content hash is computed by the store, never supplied. tags file the artefact on the shelf and provenance records the session that made it: each is carried forward from the previous version when absent and replaced when present; to change only the filing of an artefact already stored, its tags or its provenance, use refile_artifact. Stored bytes are display and archive only; the messaging-system JSON remains the single machine truth. Name expectedVersion, the version your change was made from (0 for a new document); it is required once the document is published, and a save from anything but the latest is refused with CONFLICT naming the latest. The title must pass the title check (what the document is about in words an eleven-year-old understands, sentence case, one clause, no internal words) unless titleWaiver gives your reason, and tags must come from the fixed folder list (sales, marketing, investment, legal, operations, product, engineering, handover, all hands, templates); machine values such as phone, list: and sequence: go in keys. Unknown input keys are refused rather than ignored, inside provenance too. Returns data (the stored version, with tags, hasProvenance and created).
request_artifact_uploadRequired arguments: slugdebug_toolsWritesGet a short-lived upload URL for filing a LARGE document on the artefact shelf, one too big to send as html in a request body: the bytes go straight from your machine to Justify private storage and never through a model or the MCP request body. Say which slug the document is for, how many bytes it is and its sha256, and this returns uploadUrl (a presigned PUT that expires in fifteen minutes), storageKey (the reference), checksumSha256 (the value the PUT must carry in its x-amz-checksum-sha256 header), expiresAt, maxBytes (the 100 MB ceiling) and version (the version number the filing is expected to take). Send the file with a single PUT to uploadUrl carrying that checksum header and a content-length equal to byteLength, then call upsert_artifact with upload: { storageKey, sha256, byteLength } instead of html, plus the title, the tags, your operator reason and an idempotencyKey. Nothing is stored on the shelf by this call: it grants somewhere to put the file, and upsert_artifact is what files it. A document under 512 KB needs none of this: send it inline as html. For a folder of files (a report with its images, PDFs and video), send files instead of byteLength, sha256 and contentType: this returns files, one entry per file (path, storageKey, stored, uploadUrl and checksumSha256), plus expiresAt and maxBytes. A file the shelf already holds for the document, under any path of any version, comes back stored with no uploadUrl, so a folder refiled with one slide changed sends one file; PUT each of the others to its uploadUrl with its checksum header and its content type, then call upsert_artifact with the same files.
refile_artifactRequired arguments: idempotencyKey, reason, slugTakes an idempotencyKeydebug_toolsWritesFile an artefact already in the artefact library under new folders, rename it, change the keys machines find it by, or attach the session that made it as provenance, without new bytes. The library is append-only, so a refile appends a version carrying the same bytes (the sha256 is unchanged) and the new filing; a filing identical to the head returns the head with created=false rather than a duplicate. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. A slug with no stored version is refused, and so is a call that names none of title, tags, keys and provenance. tags replaces the whole set: send every tag the document should carry, and an empty array unfiles it; provenance replaces the whole block. title renames the document without new bytes and must pass the title check unless titleWaiver gives your reason; tags must come from the fixed folder list (sales, marketing, investment, legal, operations, product, engineering, handover, all hands, templates); keys replaces the machine values (phone, list:, sequence:). Name expectedVersion, the version the new filing was decided on; it is required once the document is published, and a refile from anything but the latest is refused with CONFLICT naming the latest. Unknown input keys are refused rather than ignored, inside provenance too. Returns data (the resulting version with its tags, hasProvenance and created).
retire_artifactRequired arguments: idempotencyKey, reason, slugTakes an idempotencyKeydebug_toolsDestructiveRetire an artefact: its head leaves the shelf, the sidebar counts and list_artifacts (includeRetired lists it again), and every version stays readable through get_artifact while it is in the bin. Appends a version with the same bytes, tags and provenance, status retired and, when named, supersededBy, another stored document's slug; a head already retired as sent returns with created=false. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), audited with the write. An unknown slug is refused, and a published document is refused: unpublish it first. A retired document stays in the bin for thirty days, where restore_artifact brings it back, then the nightly purge deletes every version and stored copy. deleteNow true purges it at once, as Delete now in the shelf's Retired folder does, retiring an active document in the same call; nothing can bring it back. Unknown input keys are refused. Returns data (the version with status, supersededBy, tags, created and deleted). Use it when a document is out of date and should stop being offered.
restore_artifactRequired arguments: idempotencyKey, reason, slugTakes an idempotencyKeydebug_toolsWritesBring a retired document back from the bin, the same as the Restore button in the shelf's Retired folder: appends a version carrying the head's bytes, title, folders and keys with status active, so it is back on the shelf and is not deleted. A retired document stays in the bin for thirty days and is then deleted for good by a nightly task, every version and every stored copy of it, so restore it before then; list_artifacts with includeRetired shows what is in the bin. A document that is not retired is refused. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Unknown input keys are refused rather than ignored. Returns data (the restored version). Use it to undo a retirement: it reinstates the document as it stood, so it reappears in its folders.
set_artifact_folder_policyRequired arguments: idempotencyKey, reason, tagTakes an idempotencyKeydebug_toolsWritesSet the policy of one folder of the artefact library (a tag): who may read it, whether it is kept out of All, or both; give allowedUserIds, hiddenFromAll or both, and whatever you omit keeps its current value. allowedUserIds restricts the folder to named super-admins: after this call only those user ids can see, list, read or write the documents filed under the tag, and to everyone else those slugs answer NOT_FOUND on every door as if they did not exist. A restriction is permanent once set: there is no clear door and the list is never empty. Any named reader may call again to replace the whole list as long as they keep themselves on it; a caller outside a restricted folder is answered NOT_FOUND and nothing is written. hiddenFromAll true keeps the documents in the folder out of the All view on the shelf and out of an unfiltered list_artifacts (for a folder of many small pages, such as all hands); the folder is still listed and opens with every page, and a search and get_artifact still reach them. hiddenFromAll is not a restriction and can be set back to false. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters). Every call is super-admin only and audited at critical severity, with that reason. allowedUserIds must include your own id, carry user ids only (user_...), and name at most twenty readers. The folders restricted from the day this landed are investment and legal, each naming Ed and Cam. Unknown input keys are refused rather than ignored. Returns data (the policy with its allowedUserIds, hiddenFromAll, createdBy, updatedAt and created).
publish_artifactRequired arguments: idempotencyKey, reason, slug, versionTakes an idempotencyKeydebug_toolsDestructivePublish one version of a document on the artefact shelf: from now on every one of its public links answers with that version, the same as the Publish button in the viewer. Any version may be published, the latest or an earlier one (rolling back is publishing an earlier version); a retired document, and one in a restricted folder, is refused. Send version null to unpublish: the document's links stay and answer 404 until it is published again, which is also the way to retire a published document. Check the version in the viewer before you publish it. A document on an article address, justify.app blog/, news/ or case-studies/, is published only when its page passes the article rules: a title of at most 60 characters, a meta description of 51 to 159, a canonical link to its own address, og:title, og:description, og:image and a twitter:card, one h1, wording the messaging house allows, and BlogPosting, NewsArticle or Article structured data with author, datePublished and publisher, which an area's index page does not owe; a refusal names everything missing. A document on a paid landing page address, justify.app lp/<name>, is published only when its page has a <title>, one h1, a <body>, no partials.js of its own (the site header and footer it is served in bring the consent banner and the analytics), no email field in a form sent by GET, and wording the messaging house allows. Roll a live landing page back by publishing its previous version, never by unpublishing it: an advert points at it. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Unknown input keys are refused rather than ignored. Returns data (the published version or null, the version published before, whether anything changed, and the document's links).
set_artifact_linkRequired arguments: action, expiresAt, host, idempotencyKey, path, reason, slugTakes an idempotencyKeydebug_toolsDestructivePut a document on one of our public addresses, change the date a link expires, or take a link down, the same as the link controls in the viewer. action add puts the document at host and path (invest.justify.app, creds.justify.app, justify.app; path '' is the host's main link, and a name such as karen-betts is a link for one person); an address already serving another document is refused naming it, so a link is never moved by accident: remove that one first. action set_expiry changes the date on a link this document already has; action remove takes it down, and the address answers 404 from then on. expiresAt is an ISO 8601 date and time with an offset, in the future and within a year; a link for one person needs one, and only a host's main link or a permanent page may send null. A room for one prospect sits at justify.app with path for/<name> (for/karen-betts) and needs password as well as expiresAt: the reader is sent the password with the link, the page asks for it before it opens, and the shelf keeps only a salted hash of it and never returns it; no other address takes a password. Articles sit at justify.app blog/<name>, news/<name> and case-studies/<name>, need no expiry and are the only pages search engines index, and blog, news and case-studies themselves are each area's index page; putting a published document on one checks its page against the article rules publish_artifact applies. Paid landing pages sit at justify.app lp/<name> (lp/measure), need no expiry, are never indexed, are served inside the live site's header and footer, and are checked against the landing page rules publish_artifact applies. A link answers 404 until the document is published (publish_artifact). Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Unknown input keys are refused rather than ignored. Returns data (the link, its previous expiry for set_expiry, and every link the document has).
copy_artifactRequired arguments: idempotencyKey, name, reason, slug, versionTakes an idempotencyKeydebug_toolsWritesCopy one version of a document for one person, the same as the Copy button in the viewer: a new document, <slug>-<name>, whose version 1 is that version's bytes, filed under the same folders and carrying a record of where it came from. Change the copy for that person with upsert_artifact (expectedVersion 1), then give it its own link with set_artifact_link at suggestedPath and an expiry date, and publish it with publish_artifact. A name whose copy already exists is refused: open that copy, or use another name. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Unknown input keys are refused rather than ignored. Returns data (the copy's slug, version and sha256, its parent and suggestedPath).
get_messaging_housebrand_playbookReadsRead Justify's governed messaging house (messaging-system.json): brand core, approved messaging house, tone, language rules, claim guardrails, claims register, channel playbooks and every other section, as the machine truth every piece of public copy is drafted from. Returns data with sourcePath, sourceHash (sha256 of the file, report it as the version you read), buildGitSha, keys (every top-level key), houseJson (the house as compact JSON text, parse it rather than paraphrase it) and characterCount. Pass section to read one top-level key; omit it for the whole file (about 300k characters). Read-only; drafts must be checked with verify_copy before use.
verify_copyRequired arguments: textbrand_playbookReadsRun Justify's deterministic copy verifier over a draft: the kill list, the claims register's forbidden phrases, deprecated language, the structured validation rules and the channel's required elements, all read from the same messaging house get_messaging_house serves. Takes the text, the channel it is for (website, email, ads, social, signup or sales deck) and, when the piece takes a slot in a promise test or a style test, the promiseArm or styleArm it is written as. Returns data with ok (false when any blocking rule matched), channel, rulesLoaded, sourceHash, violations (ruleId, kind, matched, message, optional autofix), advisories (warn-level hits) and receipt (the gate run's receipt: pass it whole as record.receipt when registering the piece with attach_experiment_piece). A draft with violations is not usable copy: fix every named rule and verify again. This is the gate, not a judgement; tone and style are yours to assess afterwards.
propose_messaging_entryRequired arguments: id, idempotencyKey, kind, payload, reasonTakes an idempotencyKeybrand_playbookWritesPropose a new entry in Justify's governed messaging house: a line, rule, claim, capability, arm, section, buyer_type or voice_reference row, under a new dot-namespaced id. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write and stored as the log row's reason. The payload is judged by the kind's schema and a body of the wrong shape is refused naming the kind; an id already in use is refused, because ids are never reused, and a section whose order another section already holds refuses, because the house sha must not depend on row order. status defaults to in_test (a proposal is a candidate, never locked on arrival). Super-admin operators only. Nothing proposed is served until publish_messaging_house runs, and a projection row (rule, claim, arm, buyer_type, voice_reference) changes the exported house only when its section row is edited too. Unknown input keys are refused rather than ignored. Returns data (the stored entry) and nextStep.
edit_messaging_entryRequired arguments: id, idempotencyKey, reasonTakes an idempotencyKeybrand_playbookWritesEdit one messaging house entry in place: its payload, its status (locked, in_test or legacy) or its arm, any of them. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write and stored as the log row's reason. The payload is judged by the row's own kind and a body of the wrong shape is refused naming the kind. A superseded or retired row refuses: a rework is a new row, so propose one or supersede the row it replaces. status cannot be moved to retired or superseded here; retire_messaging_entry and supersede_messaging_entry log those transitions by name. Moving the status to locked is logged as promoted. A section payload whose order another section already holds refuses, because the house sha must not depend on row order. Setting arm to null clears it; an omitted field is left unaltered. Super-admin operators only. Nothing edited is served until publish_messaging_house runs. Unknown input keys are refused rather than ignored. Returns data (the updated entry), changedFields and nextStep.
retire_messaging_entryRequired arguments: id, idempotencyKey, reasonTakes an idempotencyKeybrand_playbookDestructiveRetire one messaging house entry: its status moves to retired and the row stays, because nothing in the house is deleted. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write and stored as the log row's reason. An already retired or superseded row refuses. A section row refuses: a section is a top-level house key, reworked with edit_messaging_entry on its payload; retire the lines inside it instead. Super-admin operators only. The retirement is not served until publish_messaging_house runs. Unknown input keys are refused rather than ignored. Returns data (the retired entry) and nextStep. Use it when a line of copy is no longer true or no longer wanted: it drops out of what the house serves while its history stays on record. Retiring is quieter than deleting: nothing is destroyed, the line just stops being served.
supersede_messaging_entryRequired arguments: id, idempotencyKey, reason, successorTakes an idempotencyKeybrand_playbookDestructiveRework one messaging house entry as a new row: the successor is created under its new id with the same kind, the old row points at it and moves to superseded, and both are logged in one transaction (proposed on the new row, superseded on the old). Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write and stored on both log rows. The successor payload is judged by the old row's kind; a successor id already in use is refused, and an already retired or superseded row refuses. A section row refuses: a section is a top-level house key, reworked with edit_messaging_entry on its payload; supersede the lines inside it instead. successor.status defaults to in_test. Super-admin operators only. Nothing is served until publish_messaging_house runs. Unknown input keys are refused rather than ignored. Returns data (the successor), superseded (the old row) and nextStep. Use it when a sentence needs rewriting rather than removing: the replacement takes the old line's place and the old line is kept as the record of what was said before.
publish_messaging_houseRequired arguments: idempotencyKey, reasonTakes an idempotencyKeybrand_playbookWritesPublish the messaging house the tables currently hold: the section rows are reconstructed, validated against the shared schema and hashed, and a snapshot carrying that sha is written so get_messaging_house, verify_copy and the playbook serve it on their next call. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write and stored as the snapshot's note. Idempotent by content: a house whose sha already has a snapshot returns it with created=false and writes nothing. A house that fails the shared schema refuses naming the section; fix it and publish again. Super-admin operators only. This does not regenerate the committed export file, which the build-time artefacts consume: the reply names the script to run and the parity check that proves it. Unknown input keys are refused rather than ignored. Returns data (sha, publishedAt, created, sectionCount, entryCount), export and nextStep. Use it to answer whether the copy everyone reads is current: it makes the approved wording live for the whole team in one go, the way pressing publish on a style guide does.
list_artifactsdebug_toolsReadsList the artefact library: every stored Claude artefact at its latest version, with slug, title, contentType, sha256, byteLength, author, createdAt, tags (what files it on the shelf), hasProvenance (whether the session that made it is recorded), status (active or retired), supersededBy, versionCount, hiddenFromAll, published (the version the public sees at the document's links, who published it and when, or null) and links (every public address it answers on, with its expiry, how many times it was opened and when it last was). Retired documents are left out unless includeRetired is true; every version of one stays readable through get_artifact. tag narrows the list to one folder (one tag, e.g. all hands). A folder kept out of All (hiddenFromAll, set with set_artifact_folder_policy) is left out of an unfiltered list so its many pages do not flood it; name the folder in tag, or search with q, to reach them. A document filed under a kept-out folder and an ordinary one is left out of the unfiltered list, because one kept-out tag is enough, and is still listed with tag set to the ordinary folder. Optional q searches the words of the title, slug, author, tags and the document text, and returns the documents that match, each carrying rank (its search score, higher is a closer match) and excerpt (about one line of the matched text); with no q both are null. sort orders by title (A to Z), newest or versions; omit sort and a search comes back best match first, an unsearched shelf in title order. Paged by cursor: returns data, total (the documents this call lists: the matches when q is given, the folder when tag is, otherwise the shelf without the folders kept out of All), libraryTotal (every visible head either way, those folders included, so no match reads differently from an empty library), hasMore and nextCursor; the next page is this same call with the cursor argument set to that nextCursor value and the same q, tag, sort and includeRetired. Read-only. Use get_artifact with a slug to read an artefact's bytes or an earlier version.
get_artifactRequired arguments: slugdebug_toolsReadsRead one artefact from the library: its metadata (tags included, so a refile_artifact can be verified here), its provenance (the session that made this version, whole, or null when none was recorded), its sha256, its byteLength and its stored bytes as content (text/html renders as it did on claude.ai; text/plain is a capture), plus versions, every version number the slug holds, published (the version the public sees at the links of the document, who published it and when, or null) and links (every public address it answers on, with its expiry, how many times it was opened and when it last was). Read the latest version before you change a document, and send its version as expectedVersion when you save. A document over 512 KB comes back with content null and readUrl instead, a signed link to the whole file that expires in fifteen minutes, because a hundred megabytes of markup belongs in a download and not in a tool result; byteLength and sha256 say what is on the other end of it. A version that is a folder of files also returns files, every file in it (path, sha256, byteLength and contentType), and its content is the entry page, index.html. Pass version to read an earlier one; omit it for the latest. Returns data. Read-only; the messaging-system JSON stays the single machine truth and these bytes are display and archive only. Use it to answer what does this document say: the page itself, as it was captured, with the history of every earlier capture.
list_competitorsdebug_toolsReadsList the competitor strategy estate: every enabled competitor record with its id (the value get_competitor and update_competitor take), name, website, whether a CompetitorV2 profile is stored and how many feature scores it carries, profileUpdatedAt (the freshness stamp the enrichment skill sets once, at the end of a run), the last monitor run and the count of pending signals. Sorted by name. Optional q narrows the list to competitors whose id or name contains the text, case ignored. Cursor-based: returns data, total (after the filter), competitorTotal (before it), hasMore and nextCursor; the next page is the same call with cursor set to the returned nextCursor and the same q. Read-only. Use it to answer who are we up against: every rival on file with how fresh its intelligence is.It is the roll-call, not the dossier: one row per rival, with the dossier read separately.
get_competitorRequired arguments: competitordebug_toolsReadsRead one competitor record from the competitor strategy estate: its id, name, enabled flag, the monitored sources, the stored profile JSON whole (the CompetitorV2 object: featureMatrix, pricing, customers, investors, headquarters, funding, justifyAdvantage, justifyGap, strategicPosition and the rest), profileUpdatedAt and profileUpdatedBy. Pass the competitor id from list_competitors, or the 12-character reference the debug tools print. Read-only: read it before an enrichment, merge into what is there, then write with update_competitor.
update_competitorRequired arguments: competitor, idempotencyKey, profile, reasonTakes an idempotencyKeydebug_toolsWritesWrite an enriched profile onto one competitor record in the competitor strategy estate: the whole CompetitorV2 object (feature matrix, pricing, customers, investors, headquarters, funding, justifyAdvantage, justifyGap, strategicPosition and the rest), merged by you from what get_competitor returned. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. markProfileFresh (default true) sets profileUpdatedAt, the freshness stamp the enrichment skill sets once at the end of a run; pass false for an intermediate write. The domain writer refuses the Justify row and any profile claiming isJustify, validates the profile shape, and appends every score or confidence change to the score history. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the stored row's summary) and nextStep.
find_company_registerdebug_toolsReadsRead the UK company register (Companies House) for a rival, live: search by name, or read one company by its number. With query: up to five matches, each with its companyNumber, name, status, incorporatedOn and address. With companyNumber: the companyName, the registry block in exactly the shape add_competitor_facts takes for section registry (registered office, status, SIC codes, directors and persons with significant control as names and roles only, latest accounts, the confirmation statement's last and next due dates, the next accounts due date, overdue flags, charges outstanding of total, the register link and checkedOn), and the ten latest filings with date, category, form type, a readable description and a link to the filed document. Pass exactly one of query or companyNumber. Read-only and free: to keep the block on a competitor, file it with add_competitor_facts.
list_competitor_insightsdebug_toolsReadsRead the competitor strategy estate's insights, newest first: every signal recorded against a rival (news found by google-alerts, intelligence a teammate reported), each with its competitor, title, summary, analysis, changeType, threatLevel, status (PENDING while a high or critical one waits for review, ACCEPTED, DISMISSED), source, sourceType (google_alert, sales_call, company_filing and the rest), sourceUrl, the capabilities it touches and when it was recorded. Optional filters: competitor (an id from list_competitors or its 12-character reference), since (an ISO date; insights recorded on or after it), changeType and status. Cursor-based: returns data, total, hasMore and nextCursor; the next page is the same call with cursor set to the returned nextCursor and the same filters. Super-admin operators only. Read-only. Use it to answer what have our rivals done lately: the weekly news and the roadmap engine read from here.
list_roadmap_ideasdebug_toolsReadsRead the competitor strategy page's roadmap ideas, highest composite score first: each idea the roadmap engine drew from the rivals' moves, with its title, thesis, the feature ids it touches, status (CANDIDATE, PROMOTED, DISMISSED, SHIPPED), gapScore, momentumScore (recomputed from how recent its evidence is), compositeScore, the market leader it names, where it came from, how many insights back it and its dates. Optional status narrows the list. Cursor-based: returns data, total, hasMore and nextCursor; the next page is the same call with cursor set to the returned nextCursor and the same status. Super-admin operators only. Read-only. Use it to answer what should we build next, as the rivals see it.
list_acquisitionsdebug_toolsReadsRead the competitor strategy page's acquisitions, newest deal first: each acquisition in the influencer marketing market the team has recorded, with the acquired company and the acquirer and their websites, the month (YYYY-MM), the amount when known, the strategic pillar, summary, keyHighlights, strategicImplications and the source. Cursor-based: returns data, total, hasMore and nextCursor; the next page is the same call with cursor set to the returned nextCursor. Super-admin operators only. Read-only; add one with add_acquisition. Use it to answer who has been buying whom in our market, and where its consolidation is heading.
record_competitor_insightRequired arguments: affectedCapabilities, analysis, changeType, competitor, content, idempotencyKey, reason, summary, threatLevel, titleTakes an idempotencyKeydebug_toolsWritesRecord one insight against a rival in the competitor strategy estate, with where it came from: sourceType google_alert (the default, the way the google-alerts pipeline records a news item from any outlet) or a sales call, a demo seen, customer feedback, a conference, a press release, social media, a company filing or other. Each is kept with its sourceUrl, title, the content read, its changeType and threatLevel, your summary, your analysis of what it means for Justify, and the capabilities it touches (an empty list records that it touches none). One insight per source URL and competitor: recording the same URL for the same competitor again returns the stored insight with created false and writes nothing. A finding with no address, such as a call, omits sourceUrl and is kept apart by its idempotencyKey; a google alert always carries its article's sourceUrl. A critical or high threat is stored PENDING, waiting for a person's review on the competitor strategy page; anything else is ACCEPTED. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the stored insight and whether this call created it).
add_acquisitionRequired arguments: acquiredCompanyName, acquiredCompanyWebsite, acquirerName, acquirerWebsite, amount, date, idempotencyKey, keyHighlights, reason, sourceUrl, strategicImplications, strategicPillar, summaryTakes an idempotencyKeydebug_toolsWritesAdd one acquisition to the competitor strategy page's list, the same as its Add acquisition form: the acquired company and the acquirer with their websites, the month (YYYY-MM), the amount (null when it was not disclosed), the strategic pillar (null when none fits), a summary, one to ten keyHighlights, the strategicImplications for Justify and the sourceUrl. An acquisition is known by the acquired company's name: adding one already on the list is refused with CONFLICT. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the acquisition's id, names and month, and how many the page now holds).
star_competitorRequired arguments: competitor, idempotencyKey, reason, starredTakes an idempotencyKeydebug_toolsWritesStar or unstar one competitor on the competitor strategy page: a starred rival is the one the team watches most closely, weighted up in the estate's own search. Send starred true or false and the competitor is left that way, so asking twice changes nothing the second time (changed false). Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the competitor's id, whether it is now starred, and whether this call changed it).
review_competitor_insightRequired arguments: idempotencyKey, insight, reason, statusTakes an idempotencyKeydebug_toolsWritesReview one insight on the competitor strategy page, as its Signals tab does: ACCEPTED keeps it as intelligence the weekly news and the roadmap engine read, DISMISSED sets it aside. A critical or high insight waits PENDING until someone reviews it, so list_competitor_insights with status PENDING is the review queue; a reviewed insight may be reviewed again, never sent back to PENDING. The verdict is stamped with when and by whom. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the insight, the status it held before and the status it holds now).
set_roadmap_idea_statusRequired arguments: idea, idempotencyKey, reason, statusTakes an idempotencyKeydebug_toolsWritesMove one roadmap idea through its lifecycle, as the competitor strategy page's Roadmap tab does: CANDIDATE, PROMOTED (the team means to build it), DISMISSED (kept on record with its evidence, so idea generation never resurrects it) or SHIPPED. Pass the idea id from list_roadmap_ideas; the move is stamped with when and by whom. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the idea's id and its status now).
add_competitorRequired arguments: idempotencyKey, name, reasonTakes an idempotencyKeydebug_toolsWritesPut a new rival under monitoring in the competitor strategy estate, as the page's Add competitor form does: its name, optionally its website (its homepage, pricing and careers pages are then watched) and the other names it trades under, so news naming it either way finds it. A name or alias already on file is refused with CONFLICT, so two spellings of one rival never become two records. Its id is minted from the name. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the new rival's id, which get_competitor and update_competitor take, and its name).
add_competitor_factsRequired arguments: competitor, facts, idempotencyKey, reason, section, sourceTakes an idempotencyKeydebug_toolsWritesAdd facts to one named section of a competitor's profile and leave every other field exactly as it was, so a finding never means resending the whole profile. A record section (registry, headquartersAddress, pricing, segmentFit, strategicPosition, aiProfile) takes an object whose fields are laid over the stored ones; a list section (investors, keyCustomers, uniqueFeatures) takes a list whose entries are appended unless already held, an investor known by its name; a single-value section (headquarters, totalFunding, employeeCount, founded, acquiredDate, website and the rest of the section list) takes the new value. The registry section is the company register block: company number, registered office, incorporation, status, SIC codes, directors and persons with significant control as names and roles only, latest accounts, the register's address and the day it was checked. The headquartersAddress section is where the company works, for the Territory map: line1, line2, locality, region, postcode, countryCode (ISO alpha-2), sourceUrl and checkedOn, with a street line or a postcode; it is never the registered office. source, the address or document the facts come from, is required and kept beside the section. A profile changed by someone else meanwhile is read again, never overwritten. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the competitor and the section written) and nextStep.
list_news_candidatesdebug_toolsReadsRead the candidates for the weekly newsletter at justify.app/news: the industry articles google-alerts stored in the last days (10 by default) with full content, relevance critical or high, a signal type other than other, and no competitor named at all, regulatory first and then newest, forty at most. Each carries only what an issue may cite: id, headline, source, author, date, url (copy it, never retype it), summary, signalType and the fullContent to judge it by; our own analysis, prospect matches and scores are never read. Cursor-based: returns data, since, total, hasMore and nextCursor; the next page is the same call with cursor set to the returned nextCursor and the same days. Super-admin operators only. Read-only. Use it to start the weekly newsletter issue.
list_sales_signalsdebug_toolsReadsList the Why now ranking of the sales signal log: every Tier 1 and 2 account with a live signal, highest signal score first, each with its accountKey (the value get_account_signals takes), CRM company id, name, tier, owner, score out of 100, whether it is hot (60 or more), its state, the hours left on the 48-hour touch clock, the play and drafted opener, and every signal with its points. A signal's points are its type weight, times the account's fit (Tier 1 counts in full, Tier 2 at four fifths), times its freshness over the type's shelf life; signals marked notreal or snoozed do not count. Filters: owner (an owner's first name, or none for accounts nobody owns), status (new by default; touched, booked, snoozed, notreal or all), q (company or person), minScore (20 by default), includeContext (also list accounts outside Tier 1 and 2, which never rank) and asOf (rewind the log to a moment). Cursor-based: returns data, total, hasMore and nextCursor; the next page is the same call with cursor set to nextCursor and the same filters. With view sources it lists instead the health of every source that writes to the log, in sources: each source's key, name, what it catches, how often it runs, its health (live, late when it has gone quiet for longer than its cadence allows, or not_connected when it has never written), when it last wrote, its signals and CRM matches in the last seven days, and realShare, the share of its judged signals in thirty days not marked notreal; data is then empty. Read-only. Super-admin operators only. Use it to answer what should I work this morning: the hot accounts, why now, and the opener.
get_account_signalsRequired arguments: accountdebug_toolsReadsRead one account from the sales signal log: its score, state, owner, tier, play and drafted opener, every signal it ever had (spent ones included, each with its points today, its source link, its evidence line and its state), and every mark an owner put on them, oldest first. Pass account, the accountKey from list_sales_signals: the CRM company record id, or name: and the company name for a signal that matched no CRM company. Pass asOf to rewind the account to a moment. Read-only. Super-admin operators only. Returns data, the account and its marks. Use it to read the storyline behind one account: what happened, when, and what the owner did about it.
record_sales_signalRequired arguments: companyName, evidence, idempotencyKey, occurredAt, reason, summary, typeTakes an idempotencyKeydebug_toolsWritesLog one sales signal: a dated, sourced change at an account that gives a true reason to write today, such as a buyer promoted, a raise, a rival closed or a reply. The one door every source writes through. Give its type (promoted, reply, raise, yearend and the rest of the signal types), the source that caught it (manual when omitted), when it happened, its sourceUrl or, with no address, the source's own sourceEventId, the evidence line, a one-line summary, the company name and, where known, its web domain, its CRM company record id and the person it happened to. The signal is matched to its CRM company by domain first and then by name, and to the CRM person at that company, and is scored with the account's other signals. The log is append-only: the same type and source address sent again returns the stored signal with created false and writes nothing. If the CRM cannot be read the call writes nothing and can be retried. A new signal at a matched company is copied to that company in the CRM as a note and three fields; crmCopy says whether the copy landed, and a failed copy never loses the signal. An opener, when given, carries no link. Requires idempotencyKey (exact retries replay the original response) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Returns data (the stored signal, its match and the account's score).
update_sales_signalRequired arguments: idempotencyKey, mark, reason, signalIdsTakes an idempotencyKeydebug_toolsWritesMark one or more sales signals the way an owner does on the signals page: touched (written to inside the 48 hours), booked (a call is booked; pass attioDealId to link the CRM deal), snoozed (off the list for seven days), notreal (the signal was wrong, which teaches the source) or undone (lifts the latest earlier mark off each signal). Signals are never edited: every mark is a new row, so the log can be rewound. Pass signalIds from list_sales_signals or get_account_signals, up to 50, usually every open signal on one account. Requires idempotencyKey (exact retries replay the original response; a retried key writes no second mark) and reason, your own operator reason (at least 10 characters), which is audited with the write. Super-admin operators only. Unknown input keys are refused rather than ignored. Every touched company has its three CRM fields refreshed, and crmCopy says whether that landed. Returns data, how many marks were written, crmCopy and the marks.
find_brand_creator_postsRequired arguments: brand, budget, idempotencyKey, reasonTakes an idempotencyKeydebug_toolsSpends creditsFind a brand's past creator campaigns for a prospect's demo, from Justify's creator-data supplier, within a call budget you set: reports on the brand's own handles and its sub-brand and venue accounts (their sponsored posts are collabs seen from the brand's side), a search for UK creators who mention the brand and have sponsored posts, then creator reports in rank order (tagged by the brand and mentioning it first). Returns the brand's accounts; every post naming the brand with its url, publishedAt, platform, evidence and tier (tier1: the creator's own post, labelled as paid, naming the brand; tier2: the brand's own collab naming the creator; lead: neither); each creator with posts here and their picture, name, followers, engagementRate and verified flag; the candidates not yet reported; reports still pending (ask again in twenty minutes); and callsSpent, cacheHits and stoppedBy. One call makes at most 40 live supplier calls and stops starting them after thirty seconds; call again with a fresh idempotencyKey to continue, and every answer already given comes from the cache, free. Requires idempotencyKey and reason, your own operator reason (at least 10 characters), audited with the calls spent. Super-admins on a justify.app address only. Use it to research a prospect brand before a demo, alongside the company record and a web search.
mint_demo_workspaceRequired arguments: brandName, days, reasondebug_toolsWritesMint a prospect's demo login: a sign-in on your own address with a plus tag from the brand's name (your name, a plus sign and the brand, at justify.app; verified, so no mail is sent), one organisation named after the brand with that login as its only member and admin, taken past onboarding with no Stripe customer, checkout or card, seeded with the fixture the prospect-demo skill wrote (the six files: brand, influencers, campaign, creativeTesting, outreach, brandLift), and ending in 7 or 30 days. Returns loginEmail, password, signInUrl and endsAt to paste to the prospect: the password is shown once and stored nowhere. A fixture is refused, with the path of each problem and nothing made, when a field or enum is wrong, a creator it names is missing, or a string still carries the Fernwick sample, a placeholder creator or an internal-only note. Called again for the same brand it moves the end date to 7 or 30 days from today, never earlier than it was, so it is also how a demo is extended: without a fixture it changes nothing else; with a fixture it refreshes the demo, checking it as a mint does (a refused fixture changes nothing) and reseeding the workspace from it, every row written again under its fixture id and any campaign post the fixture no longer carries removed, with the same login and the same password. resetPassword issues a new password. The campaign file may carry posts, the brand's real creator posts, which the campaign then shows as its tracked content. After every successful call each of those posts is read through the public content API: its media and thumbnail are stored on the campaign, and a video is filed in the library as an asset linked to the campaign and its creator, with its playback prepared; postMedia reports every post as stored, processing, pending, skipped or failed (with the reason), and a call that leaves any processing or pending is finished by calling again for the same brand (an extension with no fixture is enough). A post the fixture no longer carries loses its asset. For an agency, send brands (2 to 6 fixtures of the same six files, one per client brand, the first the brand the login lands on) with accountType agency instead of a fixture: one organisation named after the agency, made as an agency so the brand switcher shows, every brand seeded into it under its own ids; a refresh with brands reseeds every brand and removes any brand the list no longer names. Brands need distinct slugs, names and domains. A refresh whose seed fails puts the demo back as it was. Requires reason, your own operator reason (at least 10 characters), audited without the password. Super-admins on a justify.app address only. Use it after the prospect-demo skill has built and checked the fixture.
list_booking_pagesdebug_toolsReadsList Justify's own booking pages and the hosts who take bookings, as /debug-tools/scheduling shows them. Each page carries its id (update_booking_page takes it), slug, publicPath (the /book/<slug> link a booker opens), title, duration, slot step, buffers, minimum notice, horizon, daily cap, calendarId, purpose (SYNTHETIC is the Sunday night test page, never offered to bookers), active, and its hosts in round-robin order with their bookable weekly hours. Each host carries the id create_booking_page takes, its @justify.app email, name, IANA time zone and whether it is active. Super-admins on a justify.app address only. Read-only.
check_availabilityRequired arguments: slugdebug_toolsReadsThe free slots an active booking page offers a booker right now, read exactly as its public /book/<slug> page reads them: each host's weekly hours, Google Calendar busy time, live bookings on any page, buffers, minimum notice, horizon and daily cap. Give the page's slug and optionally a window (from and to, ISO 8601 with an offset; from defaults to now, to to seven days after from, at most 31 days). Returns the page's title, duration and hosts, the window read, and each slot's start and end in UTC. A page that is switched off, a SYNTHETIC test page or an unknown slug answers NOT_FOUND; Google failing answers SERVICE_UNAVAILABLE. Super-admins on a justify.app address only. Read-only.
list_bookingsdebug_toolsReadsThe upcoming bookings on Justify's booking pages that still hold a host's time (PENDING while Google makes the event, then CONFIRMED) and have not ended, soonest first, as /debug-tools/scheduling lists them. Each carries its id (cancel_booking takes it), status, startsAt and endsAt in UTC, the Meet link, the booker's name, email and time zone, the page's slug and title, and the host with whether the host is still active. A cancelled or past booking is not listed. It takes no cursor: it serves the soonest limit bookings (at most 100), the window the debug page shows. Super-admins on a justify.app address only. Read-only.
create_booking_pageRequired arguments: durationMinutes, hosts, idempotencyKey, reason, slug, titleTakes an idempotencyKeydebug_toolsWritesMake one of Justify's own booking pages, as the New booking page form on /debug-tools/scheduling does: a slug (its link is /book/<slug>, lower case letters, digits and single hyphens, never manage), a title, a duration and one or more hosts from list_booking_pages, each with weekly hours in the host's own time zone. Several hosts make a shared page whose bookings go round robin. Optional rules: slot step, buffers, minimum notice, horizon, daily cap, questions for the booker, a calendarId and purpose SYNTHETIC for the Sunday night test page. A taken slug is refused with CONFLICT and an inactive or unknown host with NOT_FOUND. Requires idempotencyKey (an exact retry answers the same page) and reason, your own operator reason (at least 10 characters), audited with the write. Super-admins on a justify.app address only. Returns data, the page with its id and publicPath.
update_booking_pageRequired arguments: idempotencyKey, pageId, reasonTakes an idempotencyKeydebug_toolsWritesChange one of Justify's booking pages by its id from list_booking_pages: its title, description, duration, rules, questions, calendarId, purpose or hosts, or switch it off with active false (its link then offers nothing) and on again with active true. Send only what changes; hosts, when sent, replaces the whole list in round-robin order. The slug never changes, because links in the wild name it. An unknown id answers NOT_FOUND and an inactive or unknown host NOT_FOUND. Bookings already made stand. Requires idempotencyKey (an exact retry answers the same page) and reason, your own operator reason (at least 10 characters), audited with the write. Super-admins on a justify.app address only. Returns data, the page as it now stands.
cancel_bookingRequired arguments: bookingId, idempotencyKey, reasonTakes an idempotencyKeydebug_toolsDestructiveCancel a confirmed booking on one of Justify's booking pages by its id from list_bookings, as staff: the event is deleted from the host's Google Calendar first, which tells the booker, then the booking is marked CANCELLED and the house cancellation email goes. If Google cannot delete the event nothing changes and the answer is SERVICE_UNAVAILABLE, worth retrying. A booking that is not confirmed, already cancelled or whose host is inactive answers NOT_FOUND. Requires idempotencyKey (an exact retry answers the same cancellation) and reason, your own operator reason (at least 10 characters), audited with the write. Super-admins on a justify.app address only. Returns data (the booking id and status CANCELLED).

Brand Pulse

Read a brand's Brand Pulse, the always-on monitoring pillar, its share of voice against tracked competitors, and the alerts it has raised, then acknowledge, snooze or resolve an alert from the conversation. Every tool is brand-scoped and answers honestly when a brand is not yet configured (a gate, never a fabricated series). Resolving is terminal; reopening a resolved alert stays a human act in the web app.

Entitlements used by this family: brand_pulse, brand_pulse_alerts

Try asking

  • How is our brand pulse looking this week, and what is our share of voice against the competitors we track?
  • Show me the open Brand Pulse alerts on this brand.
  • Acknowledge that alert, we are on it, and snooze the sentiment one until Monday morning.
ToolEntitlement neededEffectWhat it does
get_brand_pulsebrand_pulseReadsGet one brand pulse: whether Brand Pulse listening is configured for the brand and how completely it covers the brand today. Returns pulse (id, brandIdentityId, setupState, coverageState, createdAt, updatedAt) and coverage (status ok or partial_coverage, stateWrittenAt, connectedChannelCount, hasPublishedFingerprint, rightsConfirmed, and every enabled owned channel with its own coverage and freshness state). Read stateWrittenAt before you quote status: it is when the stored coverage verdict was last written, so a stamp weeks or months old means the verdict has not been re-derived since and status describes what somebody last recorded rather than what the listening is doing now. Say so when you report it. States are honest rather than invented: a brand with no Brand Pulse setup answers gate not_configured, and a configured brand whose sources have collected nothing yet answers gate coverage_unavailable, each with a gateMessage to relay. Call this before list_brand_pulse_alerts or get_brand_pulse_sov, because an alert count read from an unconfigured or partly covered brand means something different from one read on full coverage. brandId is the brand identity id from list_brands: required when the organisation holds several brands and no active brand is stored; a single-brand organisation may omit it. Ask it BEFORE trusting any figure that depends on listening: it says whether the listening is switched on for this brand at all, so a silence is never read as a finding.
list_brand_pulse_alertsbrand_pulse_alertsReadsList the Brand Pulse alerts raised for a brand, newest first. Use it to answer whether anything has flared up for the brand that somebody should know about. Each row carries id, alertType, severity, status (open, acknowledged, snoozed, resolved), the sourceWindow start and end the anomaly was detected over, sourceFreshnessState, campaignContextState, suppressionState, redactionLevel, snoozedUntil, and when it was acknowledged or resolved. Filter with status and severity. Pages with cursor and limit: when hasMore is true, call again with cursor set to the returned nextCursor, and never quote one page as the alert count — total is the whole brand for the filter. Use get_brand_pulse first, and read its coverage rather than only its configured flag: a brand can be configured and still have connectedChannelCount zero, in which case no source could have raised an alert and a total of 0 here means nothing was watched rather than nothing happened. Then acknowledge_brand_pulse_alert, snooze_brand_pulse_alert or resolve_brand_pulse_alert to act on a row returned here. It is the noticeboard itself, read-only; picking an item up, muting it or closing it are separate acts. Answers gate not_configured for a brand without Brand Pulse, gate store_dormant when the mention warehouse the alert detector reads is switched off by ruling, and gate coverage_unavailable when it is not wired. Under either warehouse gate no alert can be raised, so report an instrument that is not running, never a quiet brand.
get_brand_pulse_sovbrand_pulseReadsGet share of voice for a brand on the co-mention basis: the daily volume and sentiment split of conversations that mention a tracked competitor, alongside the brand's own daily mention series under you. sovBasis is always co_mention and is never an estimated or modelled market share, so never report this as a percentage of a market. Returns one competitors entry per tracked competitor with competitorId, name and a series of day, mentions, positive, negative and neutral counts; a competitor with nothing tagged keeps an empty series instead of vanishing. Answers gate not_configured for a brand without Brand Pulse, gate coverage_unavailable when the mention warehouse is unwired in this deployment, and gate store_dormant when the warehouse is switched off by ruling — never a fabricated flat line. store_dormant is NOT a quiet brand: no figure will arrive here for any brand until somebody switches the warehouse back on, so report it as an unavailable instrument rather than as an absence of conversation, and do not tell anyone to wait for it. Read get_brand_pulse first for whether coverage is complete enough to compare with. brandId is the brand identity id from list_brands: required when the organisation holds several brands and no active brand is stored; a single-brand organisation may omit it. With no competitors tracked the competitors list comes back empty and no series is invented; list and tag competitors in the Brand Pulse dashboard first, then read share of voice here. Use it to answer how much of the talk is ours set against the competition: our share of the voice in the category, measured as who gets mentioned beside whom rather than as a modelled slice of a market.
acknowledge_brand_pulse_alertRequired arguments: alertIdbrand_pulse_alertsWritesAcknowledge one open Brand Pulse alert: record that somebody has taken ownership of it and is looking, without closing it. Writes the same row the Brand Pulse dashboard acknowledge button writes, stamping the acting user and an audit event in one transaction, and returns alertId, the new status acknowledged, and the auditEventId. An alert that is already acknowledged, snoozed or resolved conflicts rather than being stamped twice. Use list_brand_pulse_alerts with status open to find candidates; prefer snooze_brand_pulse_alert to go quiet for a while, and resolve_brand_pulse_alert once the underlying issue is dealt with. alertId is the alert id from list_brand_pulse_alerts, not a fingerprint and not a brand id; brandId is the brand identity id from list_brands naming the brand that owns the alert, required when the organisation holds several brands and no active brand is stored. Use it when someone is picking an alert up and wants it marked as theirs while leaving it open: acknowledged means owned and still open, never closed. What this writes is a NAME against an alert that is still live and nothing else: the row keeps its place in the working set, and the dashboard shows a colleague has picked it up. Nothing is silenced and nothing is settled, so an acknowledged item still argues for attention — which makes it the safe first move when you cannot tell which of the three transitions the person actually meant. Use it to answer I have got this one: the alert is marked as being handled by a named person and stays on the board.Think of it as initialling a ticket: the initials say who is on it, and the ticket stays open until somebody resolves it.
snooze_brand_pulse_alertRequired arguments: alertId, snoozeUntilbrand_pulse_alertsWritesSnooze an active Brand Pulse alert until a wake time you choose, so it stops asking for attention without being closed as dealt with. Requires snoozeUntil as a future ISO 8601 timestamp; a past or malformed wake time is refused before anything is written. Writes the same row the dashboard snooze control writes, with the actor and an audit event in one transaction, and returns alertId, the new status snoozed, and the auditEventId. A terminal alert conflicts rather than reopening. Use acknowledge_brand_pulse_alert when somebody is acting on it now, and resolve_brand_pulse_alert when the issue is over. alertId is the alert id from list_brand_pulse_alerts, not a fingerprint and not a brand id; brandId is the brand identity id from list_brands naming the brand that owns the alert, required when the organisation holds several brands and no active brand is stored. What this writes is a TIMER: park the warning now, let the countdown run, and at the instant you named the row comes back to the working set exactly as it was, with nothing about the underlying matter judged either way. Deferral is not closure, so reach for it whenever the honest answer is later rather than never — parked until tomorrow, until the campaign ends, until whenever you name.
resolve_brand_pulse_alertRequired arguments: alertIdbrand_pulse_alertsDestructiveResolve a Brand Pulse alert: mark the thing it warned about as dealt with. This is terminal — a resolved alert never wakes, and reversing it is a human job on the dashboard, so only resolve when the underlying situation is genuinely over. Writes the same row the dashboard resolve control writes, with the actor and an audit event in one transaction, and returns alertId, the new status resolved, and the auditEventId. An already-terminal alert conflicts rather than being resolved twice. Use acknowledge_brand_pulse_alert while work is still in progress and snooze_brand_pulse_alert to defer it instead. alertId is the alert id from list_brand_pulse_alerts, not a fingerprint and not a brand id; brandId is the brand identity id from list_brands naming the brand that owns the alert, required when the organisation holds several brands and no active brand is stored. What this writes is an ENDING, and it is the one transition on this family that cannot be taken back from this server: the row leaves the working set for good, a later match on the same subject raises a FRESH alert rather than reviving this one, and undoing the call means a person clicking on the dashboard. Choose it only when the matter behind it is genuinely finished, never merely paused for now.

Roster contracts and deals

Read the representation contracts your organisation holds with the creators on its roster: the whole book in one call, or one contract in full to see whether it still needs the creator signature or your countersignature. A contract belonging to another organisation is reported as not found, never as forbidden. You can also send a draft contract out for signature, which is the one move on this family that changes anything. Countersigning in your own name, signing on a creator behalf, and voiding a live instrument all stay in the web app: an agent asks for a signature here, it never applies one. Beside the paperwork sits the deal book: the brand deals you are tracking for those creators, page by page, one deal in full with the timeline of every stage it has moved through, and the count in each stage with the gross and commission totals behind it. The deal reads are read-only: logging a deal, moving it to the next stage and raising a commission invoice all stay with people. The person who introduced a deal is never published: a row names the brand and carries the contact identifier, never a name or an address.

Entitlements used by this family: deals, talent_roster

Try asking

  • Which of our roster contracts are still waiting for a signature?
  • Open that contract in full: has the creator signed and have we countersigned?
  • Send that draft representation contract to the creator so they can sign it.
  • Which brand deals are still at negotiating, and what are they worth to us?
  • Open that deal: how long has it been sitting at this stage, and who moved it?
  • How does our pipeline look right now, and how much commission is in it?
ToolEntitlement neededEffectWhat it does
list_roster_contractstalent_rosterReadsList the talent-roster contracts this organisation has issued to the creators it represents. Returns: contract id, kind (REPRESENTATION or DEAL), status, the representation or deal it hangs off, creator id, creator handle, creator name, contract template name, template version id, sent date, signed date, countersigned date, whether a signed PDF exists, and created date. A roster contract is the signable instrument a talent manager uses to represent a creator or to paper an individual brand deal; it is NOT a job posting, a job offer or a content-rights agreement. Filter by status to find contracts awaiting a signature, or by kind to separate representation papers from deal papers. The status lifecycle runs DRAFT (not yet sent), SENT (awaiting signature), SIGNED, COUNTERSIGNED, IN_FORCE (fully executed and binding) and VOIDED (cancelled before force), so a status filter can pull drafts waiting to go out or void records alike. Use get_roster_contract for one contract by id. Takes no cursor: it returns the organisation contract book in one call, and total counts the rows it returned. Use it to answer which creators are under contract with us and which paperwork is still unsigned: the engagement agreements, not the usage-rights ones.
get_roster_contractRequired arguments: contractIdtalent_rosterReadsGet one talent-roster contract by its id, scoped to this organisation. Returns: contract id, kind (REPRESENTATION or DEAL), status, the representation or deal it hangs off, creator id, creator handle, creator name, contract template name, template version id, sent date, signed date, countersigned date, whether a signed PDF exists, and created date. Read this before deciding whether a contract still needs the creator signature or the organisation countersignature. Use list_roster_contracts to discover contract ids. A contract belonging to another organisation is reported as not found, never as forbidden. contractId is the roster contract id exactly as returned by list_roster_contracts; the contract returned here is that one row in full, not a list. The expanded row carries representationId when the paper covers a creator representation and dealId when it covers a single brand deal, exactly one of which is populated, plus signedPdfAvailable, which says whether a downloadable executed copy has been produced yet.
send_roster_contractRequired arguments: contractIdtalent_rosterWritesSend a draft talent-roster contract to the creator it names, moving it from draft to awaiting-signature and notifying the creator portal that a paper is waiting to be signed. A roster contract is the signable instrument a talent manager uses to represent a creator or to paper one brand deal; sending is the moment it stops being a draft and reaches the creator. Returns the contract after the send: contract id, kind (REPRESENTATION or DEAL), status, the representation or deal it hangs off, creator id, creator handle, creator name, contract template name, template version id, sent date, signed date, countersigned date, whether a signed PDF exists, and created date. Only a draft can be sent. A contract already awaiting signature, already signed, already in force or voided is refused with CONFLICT and nothing is written, so a repeat of a send that already succeeded never posts a second signature request. This tool asks for a signature; it never applies one. Countersigning in the organisation own name, signing on the creator behalf, and voiding a live instrument are all deliberately absent from the agent surface and stay in the web app. Use list_roster_contracts with status DRAFT to find contracts waiting to go out, and get_roster_contract afterwards to confirm the sent date landed. The contractId is the id exactly as returned by list_roster_contracts, and the contract must still be in DRAFT status when the send happens.
list_dealsdeals, talent_rosterReadsList the brand deals a talent manager is tracking for the creators it represents, most recently changed first. Returns data (dealId, stage, source, brandName, brandContactId, contactWithheld, jobOfferId, creatorId, handle, platform, creatorName, grossAmount, currency, commissionPct, commissionAmount, deliverables, exclusivity, exclusiveCategories, exclusivityExpiresAt, expectedPayoutAt, notes, createdAt, updatedAt), total, totalIsExact, hasMore, nextCursor and scopeReason. One deal is one piece of paid work between a represented creator and a brand, logged whether it was booked through Justify or arranged elsewhere; it is not a job, an offer or a signed contract. The stage sequence runs PITCHED, NEGOTIATING, CONTRACTED, DELIVERING, INVOICED, PAID and LOST, so a stage filter separates the pitches still open from the money already banked. commissionAmount is derived from grossAmount and commissionPct rather than stored, and exclusiveCategories with exclusivityExpiresAt say which categories the creator is barred from until when. The individual who introduced the work is withheld, which contactWithheld states on every row: brandName is published, brandContactId points at that separate record, and no invoice handle reaches this wire either. Pages by cursor and limit (1 to 50, default 20): while hasMore is true, call again with cursor set to the returned nextCursor verbatim. A cursor carries the filters it was minted under and is refused when replayed under others, rather than answering with rows from another question. Read-only: logging work, moving a stage and raising a commission invoice all stay with people. Pair with get_deal for one record plus its stage moves, and get_deal_pipeline for the tally in each stage.
get_dealRequired arguments: dealIddeals, talent_rosterReadsGet one brand deal by id, scoped to this organisation, together with the chronology of every stage move recorded against it. Returns data (every field list_deals returns for a row), stageHistory (fromStage, toStage, actorType, reason, createdAt for each move, oldest first) and scopeReason. actorType names which side moved the deal, so a reader can tell a manager advancing their own pipeline from a brand or an automated step doing it; the named colleague behind a move is deliberately absent. Read this before deciding whether a deal has stalled: the distance between the newest move and today is how long the deal has sat where it is, and the reason field carries whatever was recorded when it moved. A deal belonging to another organisation is reported as not found, never as forbidden, so probing this tool teaches nothing about what exists elsewhere. dealId is the deal id exactly as list_deals returned it; what comes back is that single record in full rather than a page of them. Read-only: it neither moves the deal nor renders the brand-facing proof document, both of which stay with people. Use list_deals to discover deal ids, and get_deal_pipeline for the shape of the whole book.
get_deal_pipelinedeals, talent_rosterReadsCount the deals of this organisation at every stage at once, the way the deals board is drawn, and total the money standing behind them. Returns stages (one entry per stage with its name and count), totalDeals, moneyByCurrency (one entry per currency with grossTotal and commissionTotal), currenciesTracked and scopeReason. Every stage is present even when nothing sits there, so a zero is a stated fact rather than a missing key. Deals marked LOST are excluded from both money totals, because business that went away is not revenue, while their stage entry still counts them. grossTotal is what the brands owe in that currency and commissionTotal is the share the manager keeps, each summed in exact decimal arithmetic rather than floating point, and each in major units beside its code. Amounts are never converted between currencies: an organisation invoicing in two gets two entries and no exchange rate is applied to either. Takes no argument and no cursor: the answer is the whole book, so there is nothing to page and no filter to send. Read-only. Ask it first for the headline shape of the book, then list_deals to walk one stage and get_deal to open a single record.

Payments

Read where a job payment stands, funded, held or released, by the job offer or the payment transaction it belongs to. An unfunded offer is an answer, not an error. An agent can also ASK for a release, and that is all it can do: the request is held, and the money moves only when a named person opens a single-use approval link on app.justify.app while signed in and approves it there. No agent can approve its own request. Reversals and refunds stay in the web app.

Entitlements used by this family: connect_payments

Try asking

  • Has the payment for that creator's job offer been funded yet, and is any of it released?
  • The deliverables are approved, so start the release of that escrow payment and send me the approval link.
ToolEntitlement neededEffectWhat it does
get_payment_statusconnect_paymentsReadsRead the escrow payment state for one job offer, or for one payment transaction by its own id. Returns: found, jobOfferId, canManage, and when a payment exists its id, status, amount, currency, platform fee, net payout amount, deposited date, released date, completed date, failed date, failure code, public failure message, deliverables-submitted date, created date and last-updated date. This is the brand-funded escrow that holds a creator fee between deposit and release; it is not a subscription invoice and not a credit balance. An offer that has never been funded returns found false with a null payment, which is an answer and not an error. canManage says whether the calling person could drive a deposit or release in the web app; this tool moves no money under any circumstances. Supply exactly one of jobOfferId or paymentTransactionId. Use list_job_applications to find offer ids.
release_job_paymentRequired arguments: idempotencyKey, paymentTransactionIdTakes an idempotencyKeyconnect_paymentsDestructiveAsk for the escrowed fee held against one accepted job offer to be paid out to the creator — the same act as the Release button on the job payment screen, through the same Stripe Connect helper. THIS CALL NEVER PAYS ANYBODY. It always refuses. What it does is reserve a job_payment operation and mint a single-use approval link on app.justify.app, bound to your organisation, to the named approver, to a hash of this exact payment and amount, and to a short expiry. The money moves only when that person opens the link while signed in to Justify and approves it there; an agent holds no authority to approve its own request, because the approval page needs a browser session an OAuth credential cannot mint. Refused before anybody is interrupted when the payment is not in a releasable state, when the offer behind it was never accepted, when the campaign that funded the escrow belongs to another organisation, or when the caller could not drive the same release in the web app. Hosts on protocol revision 2025-11-25 or later receive the link as a URL-mode elicitation; earlier hosts receive the identical link inside the error text. Watch the parked request with get_operation_status and list_operations: it reads status awaiting_approval until somebody acts, then succeeds carrying paymentTransactionId, jobOfferId, operationId, status and released. Reversal and refund are not offered here at all. Read the escrow first with get_payment_status. Use it when deliverables are approved and someone says to pay the creator.

Billing

What your organisation pays Justify, and what Justify has charged it. Read the subscription, its status, its plan, whether it bills monthly or yearly and the renewal date, beside the credit pools those tools spend from, then walk the invoice book behind it. This is the plan and the paper, not the escrow that funds a creator fee; that sits under Payments. Every tool here reads: no plan is changed, no card is charged, and no invoice is paid, voided or re-sent.

Entitlements used by this family: billing

Try asking

  • What plan are we on, when does it renew, and how many search credits have we got left this cycle?
  • List our invoices from Justify and tell me which ones are still unpaid.
ToolEntitlement neededEffectWhat it does
get_billing_summarybillingReadsRead where this organisation stands with Justify commercially: its subscription, its plan and its credit balance. Returns: billingRail, billingCustomerConnected, hasActiveSubscription, subscription (status, planKey, planName, isAnnual, currentPeriodEnd), planPageUrl, billingCycle (end, daysRemaining), credits (one row per credit type with allocation, remaining, used, pending, percentUsed) and spendThisPeriod (allocated, consumed, reserved, remaining across every type). billingRail says who bills: stripe means Justify charges a card; shopify means the plan is a Shopify App Pricing contract on the Shopify bill of the store, the subscription status is ACTIVE, CANCELLATION_SCHEDULED or FROZEN in Shopify terms, and planPageUrl is the only place the plan can be changed. currentPeriodEnd is the RENEWAL date — when the paid subscription period ends — and billingCycle.end is when the credit allocation refills; on a monthly plan they usually coincide and on an annual plan they do not, so quote the one the question asked for. Read this BEFORE any tool that spends: prepare_influencer_search and start_creative_test debit these very pools, and pending credits are already unspendable, so remaining minus nothing is the true headroom. A subscription status of past_due or unpaid means the card behind the plan failed and the account is heading for suspension; canceled means it has lapsed. billingCustomerConnected false means this organisation has never been attached to a billing customer at all, which is a different fact from a lapsed plan. This is subscription billing, not the escrow that funds a creator fee — for that use get_payment_status. It reads only: no plan is changed, no card is charged and no credit is granted or revoked by calling it. Use list_invoices for the paper trail behind the plan. Use it to answer what are we paying for and how much have we got left this month.
list_invoicesbillingReadsList the invoices Justify has raised against this organisation, newest first. Returns: data (id, number, status, currency, total, amountDue, amountPaid, amountRemaining, issuedAt, dueAt, paidAt, periodStart, periodEnd, hostedInvoiceUrl, invoicePdfUrl), total, totalIsExact, hasMore, nextCursor, billingCustomerConnected, billingRail and note. A workspace whose billingRail is shopify answers an empty page with a note, because Shopify issues its invoices on the Shopify bill of the store and Justify holds none to list. Every money field is in MINOR units — pence, cents — beside the currency code, so 4900 with GBP is £49.00; never render one as a major unit. Pages with an opaque cursor and limit (1 to 50, default 20): when hasMore is true, call again with cursor set to the returned nextCursor. total counts the whole filtered scope rather than this page, and Stripe publishes no such count, so it is walked and totalIsExact says whether the walk finished. status filters server-side: draft is an invoice never issued, open is issued and unpaid, paid is settled, uncollectible was written off, void was cancelled. hostedInvoiceUrl and invoicePdfUrl are the links to send a finance team; this tool cannot pay, void, refund or re-send an invoice, and nothing it does moves money. An organisation never attached to a billing customer answers with an empty page and billingCustomerConnected false, which is an answer rather than an error. Use get_billing_summary for the plan and the balance behind these charges. Use it to answer what have we been charged, which bills are outstanding and where is the receipt.

Content-rights agreements

Read the licences that decide whether content a creator delivered may actually be used: which channels and territories they cover, how long they run, whether they are exclusive, and whether the creator has signed and your side has countersigned. An agreement that is not ACTIVE grants nothing, and an expired or revoked one has stopped granting. Issuing an agreement is not an agent tool and stays in the web app.

Entitlements used by this family: content_rights_agreements

Try asking

  • Which of our content-rights agreements are still waiting on a signature or a countersignature?
  • Open that agreement in full: which channels and territories does it cover, when does it expire, and is it exclusive?
ToolEntitlement neededEffectWhat it does
list_content_rights_agreementscontent_rights_agreementsReadsList the content-rights agreements this organisation holds — the signed licences that say what it may do with the content its creators delivered. Returns: agreement id, lifecycle status, licence grant type, agreement version, creator id, creator handle, creator name, creator platform, the job posting and campaign the licence hangs off, the job application it papers, the contract template title, whether the licence is exclusive, the licence duration in months, the licence start and expiry dates, and the sent-for-signature, signed, countersigned, revoked, created and updated dates. A content-rights agreement is the licence covering usage of one creator deliverable; it is NOT a job offer, a roster contract or a payment. Filter by status to find licences still awaiting a creator signature or the brand countersignature, by creatorId for one creator, or by jobPostingId for one brief. The status lifecycle runs DRAFT, PENDING_SIGNATURE, CHANGES_REQUESTED, SIGNED (the creator has signed and the countersignature is owed), ACTIVE (fully executed and granting), DECLINED, EXPIRED, REVOKED, SUPERSEDED and CANCELLED, so a status filter can pull unsigned paper and lapsed licences alike. Pages with an opaque cursor and limit (1 to 50, default 20): when hasMore is true, call again with cursor set to the returned nextCursor; total counts every agreement in scope, not just the page returned. Use get_content_rights_agreement for one licence with its territories, channels, exclusivity categories and signature evidence. Issuing a new agreement is not an agent tool and stays in the web app.
get_content_rights_agreementRequired arguments: agreementIdcontent_rights_agreementsReadsGet one content-rights agreement in full by its id, scoped to this organisation. Returns everything the list returns plus the licence terms in full — grant type, the channels and territories the licence covers, whether it is exclusive and in which product categories, its duration in months, its start and expiry dates, and any separate rights fee — together with the contract template it was cut from (template id, template version id, title, version number, locale and signing jurisdiction), the signature facts (who signed, in what role, by what method, and when the creator signature, the brand countersignature, the first view and the activation landed), the terms, document, evidence and certificate hashes that prove which document was signed, whether a signed PDF and a completion certificate exist, any decline or revocation reason, and the ten most recent lifecycle events with their from and to states. Read this before assuming delivered content may be reused: an agreement that is not ACTIVE grants nothing, and an expired or revoked licence has stopped granting. The agreement body itself is deliberately not returned — it is the same legal text for every agreement cut from that template version, and documentHash identifies it exactly. Signer and countersigner e-mail addresses, IP addresses and browser user agents are never returned. Use list_content_rights_agreements to discover agreement ids. agreementId is the agreement id exactly as returned by list_content_rights_agreements; an agreement belonging to another organisation is reported as not found, never as forbidden.

Creator lists

The lists your organisation keeps its saved creators in, a shortlist, a client roster, a campaign longlist, with their sections and how many creators sit in each. Open one and you get its members in the same order and the same detail the Roster Workspace shows on screen, private notes included. Every answer says which scope it read: one brand when your organisation has brands, the whole organisation when it has none, which is the permanent shape of a talent-management account. Creating and rearranging lists stays in the web app.

Entitlements used by this family: creator_lists

Try asking

  • Which creator lists do we keep, and how many creators are in each?
  • Open our Autumn shortlist: who is on it, and what did we note about them?
ToolEntitlement neededEffectWhat it does
list_creator_listscreator_listsReadsList the saved creator lists this organisation curates in its Roster Workspace sidebar. Returns: list id, list name, its free-text description, kind (GENERAL for an ordinary working list, ROSTER for the managed-talent roster itself), its sidebar position, how many saved creators it holds, and its own sections with their headings and order. A creator list is a NAMED, ordered grouping of creators the organisation already saved — a shortlist, a client roster, a campaign longlist — and it is not a campaign, not a job posting and not an outreach sequence. Use get_creator_list for one list membership, creator by creator. Every answer carries scopeKind and scopeReason saying which scope produced it: a brand-partitioned read when the organisation holds brands, or the organisation-wide scope when it holds none and its account type never creates one, which is the permanent shape of a talent-management organisation. An empty answer is therefore always explained rather than silent, and a scope that cannot be settled is refused by name instead of being answered with an empty array. Takes no cursor: it returns the whole sidebar in one call, and total counts the lists it returned.
get_creator_listRequired arguments: listIdcreator_listsReadsGet one saved creator list and the creators inside it, in the same order and the same projection the Roster Workspace itself renders. Returns the list header — name, description, kind, sections — and one row per member carrying: the membership row id, the section it sits under, its position, the saved-creator record id, the platform creator id, handle, platform, display name, avatar, verified flag, followers, engagement rate, average likes, average views, the date it was saved, and the private note written against it. Rows arrive grouped by section and then by position, which is the reading order a person sees on screen. Use list_creator_lists to discover list ids. A list belonging to another organisation is reported as not found, never as forbidden, so a probe learns nothing about what exists elsewhere. This read needs no brand argument at all: a list id already names one row and that row already carries its own brand. Every roster entry sits under one section or directly on the list, in the order the workspace shows.

Uncover

The people already buying from your connected stores who turn out to have a following worth talking to. Uncover scores each shopper on what they spend and on how much reach they have, bands them from Bronze to Diamond, and tells you which of them you can invite to a campaign. Read the whole feed or open one match in full. The keyed hash of a shopper e-mail address and the internal customer records behind a match never leave the server. Uncover is sold on the Advanced plan, so an organisation on a lower plan sees these tools on no list at all.

Entitlements used by this family: manage, manage_uncover, uncover

Try asking

  • Which of our customers are hidden creators? Show me the Diamond and Gold bands first.
  • Open that match in full: how big is their following, what have they spent with us, and can we invite them?
ToolEntitlement neededEffectWhat it does
list_uncover_resultsmanage, manage_uncover, uncoverReadsList the Uncover discovery feed: the people already buying from this organisation connected stores who turn out to have a social following worth talking to, ranked by combined score, newest scoring first. Returns per match: match id, the store connection and its platform, the matched social handle and platform, avatar, followers, engagement rate, verified flag, band (DIAMOND, GOLD, SILVER or BRONZE), the customer score, the creator score, the two combined, lifetime spend with the store, the currency that spend is denominated in, order count, the shopper minimised display name, and the recommended action. This is the shop-customer discovery surface — it finds creators among people who already bought something — and it is not the influencer marketplace search, which looks outward at creators who have never bought from you. Filter by band, by social platform, by verified only, by one store connection, or by whether a match is inviteable; sort by combined score, followers or lifetime value. By default it returns hidden creators only, which is what the web feed shows; set hiddenOnly false to see every scored shopper. Pages with cursor and limit: when hasMore is true, call again with cursor set to the returned nextCursor. Never answer how many matches we have from one page. Read totalIsExact before quoting total: the feed read is bounded at 250 ranked matches, so a band or platform filter makes total a floor rather than a count. Use get_uncover_profile for one match in full.
get_uncover_profileRequired arguments: matchIdmanage, manage_uncover, uncoverReadsGet one Uncover match in full: the shopper the discovery pipeline matched to a social profile, read by its match id. Returns everything the feed row carries — store connection, matched handle and platform, avatar, followers, engagement rate, verified flag, band, customer score, creator score, combined score, lifetime spend and the currency it is denominated in, order count, the shopper minimised display name, recommended action — plus the profile URL, the biography, whether the reach threshold makes them a hidden creator, whether they may be invited, where social enrichment has got to, the platform creator id and Justify influencer id once resolved, and when the match was discovered and last re-scored. Deliberately narrower than the stored record: the keyed hash of the shopper e-mail address and the internal customer and participant join keys never reach an agent, because this surface is built on a shop customer list. Use list_uncover_results to discover match ids. A match belonging to another organisation is reported as not found, never as forbidden, so a probe learns nothing about what exists elsewhere.

Dashboard

The headline numbers your team sees when it signs in, in one call: how many creators you have saved, which campaigns are in flight, which of them still have no creative concept, which brand-lift studies are collecting answers and how many have come back, whether your outreach sending domain is verified, how far through the five-step setup guide you are, and how much of each product area you have ever used. It is the summary, not the feed: the suggestion cards stay in the web app, and reading this never changes what anybody is shown there.

Entitlements used by this family: dashboard

Try asking

  • Catch me up on the Aurora brand: where does it stand right now?
ToolEntitlement neededEffectWhat it does
get_dashboard_summarydashboardReadsWhich campaigns have no creative concept yet, which campaigns are in flight, how many creators are saved, is the outreach sending domain verified: the dashboard answers all of them in one read. Get the headline numbers the Justify dashboard puts in front of a person at sign-in, composed by the same read model the web dashboard renders from. Returns: savedInfluencerCount, inFlightCampaigns (id and name for every campaign still in flight: SUBMITTED, APPROVED, ACTIVE or PAUSED, and aged against its own end date so a finished campaign is not listed as running — a WIDER set than list_campaigns status ACTIVE, which excludes APPROVED and PAUSED), campaignsWithoutConcepts, activeBrandLiftStudies with their survey-response tallies, hasDnsSetup (whether an outreach sending domain is verified), daysActive since the organisation record was created, aiSuggestedSearch (the most-run saved marketplace search), featureUsage lifetime adoption tallies across campaigns, concepts, brand lift, outreach and creative testing, and setupProgress against the five-step onboarding chain. One call replaces five list calls: an agent that has just connected orients here before it opens any campaign, study or roster, which is the cold-start cost this surface was measured on. It summarises; it is not a feed. No card copy, no nudge and no impression is recorded by calling it, so reading it never changes what a person is shown on their own dashboard. Every answer names scopeKind and scopeReason, so a small figure can never be confused with a narrow scope: a brand-partitioned composition when the organisation holds brands, or an organisation-wide one when it holds none and its account type never creates one. Takes no cursor and no window: the figures are as of now, and totals are lifetime unless the field says otherwise.

Demo workspace

New brand accounts are seeded with a worked example, a campaign, a job posting, a creative-testing run and a brand-lift study, all belonging to a fictional brand, so that day one is not an empty screen. This tells you whether your organisation carries that example and lists exactly which record ids are it, so an agent never quotes a fictional number back to you as though it were yours. Once you own a real record of a kind, the example of that kind quietly stops appearing in that list; it is never deleted, and its own page keeps working.

Entitlements used by this family: demo_workspace

Try asking

  • Is anything in this workspace sample data, or is everything I am looking at ours?
ToolEntitlement neededEffectWhat it does
get_demo_workspace_statusdemo_workspaceReadsSay whether this organisation is a demo workspace — whether it was seeded at signup with the Fernwick example thread, and which record ids are that example rather than something a customer did. Returns: accountType, granted (the static account-type grant the seeding task consults, which never reads the override table), seeded, idMode (legacy for the reference organisation, namespaced for every other), reason, exampleIds for the seeded campaign, job posting, creative-testing run and brand-lift study, and exampleCreatorIds, which are identical across every seeded organisation because example creators are shared rather than copied. Call it BEFORE quoting any figure back to a customer: an id listed here is fixture content, so a campaign, a run or a study bearing one is an illustration and its numbers describe nobody. It also states what the seeding gates. Once an organisation owns one genuine record of a kind, the example record of that kind stops appearing in that list — graduation, never deletion, so opening it directly by id keeps working forever. Flag reads only: it never seeds, re-seeds, repairs or clears example content, and it writes nothing. Only a brand-shaped organisation is served this tool at all, because the seeding grant names brand and multi-brand alone; an agency or talent-management session sees it on no list, which is itself the answer that no example content exists there.

Help centre

The same help centre your team reads in the app, searchable by your agent. Ask a question the way you would say it out loud and you get back the articles a person would be shown, each with its title, its short answer and the link to open it in the app. The knowledge base travels inside the release, so nothing is fetched from the internet while answering, and articles written for other kinds of account or for plans you are not on are left out, which is the filtering the help page already does for whoever is signed in.

Entitlements used by this family: help

Try asking

  • How do I verify a sending domain so outreach emails can actually go out?
ToolEntitlement neededEffectWhat it does
search_helpRequired arguments: queryhelpReadsSearch the Justify help centre and get back the articles a person would be shown on the in-app help page, ranked by the same scorer. Returns per hit: title (the question that article answers), path (the link that reopens it in the help centre) and snippet (its short answer). The envelope adds resultCount and suggestedQuestions, which are the follow-ups the top article offers and make good next queries. Ask it the way a customer would speak — how do I verify a sending domain, what is a Brand Lift study, why did my creative test stop — rather than in keywords; the scorer rewards a whole question and matches aliases the article declares. The corpus is the knowledge base shipped inside this release and read from local files: nothing is retrieved over the network while answering, so an article can never be a page that changed underneath it, and the answer never depends on an outside site staying up. Hits are filtered by what this organisation may see: an article tagged to other account types, or to entitlements the caller does not hold, is left out, which is what the help page does for a signed-in person. One narrowing is specific to this transport and worth knowing: an article tagged to a named subscription plan is left out here, because resolving the live plan would mean calling the payment provider mid-search. Four articles carry such a tag today, and questions strictly about plan pricing are the ones to expect a thin answer on. It reads documentation, never records: no campaign, creator, study or invoice is touched, and a question asked here is not stored as a support ticket. There is no cursor and no second page — ranking puts the answer at the top or nowhere, so when resultCount equals limit the remedy is to raise limit, up to 50, rather than to ask again. Use it to answer how-to and what-does-this-mean questions before reaching for a product tool.

Settings: workspace, self and team

How your workspace is set up and who is inside it. Read the organisation configuration: display name, account type, the currency your figures are quoted in, the timezone your reporting days follow and the colours and fonts applied to what you publish. Read your own profile as the profile screen serves it, including which privacy permissions you have granted. Walk the seats in your organisation: the colleagues who hold one, the invitations still waiting, and how many seats your plan includes. Every tool here reads. Changing a setting, inviting somebody, removing somebody or altering a role all stay in the web app, because a settings change is a consequential act a person should make. The organisation and team tools need the same admin permissions those two settings pages ask for.

Entitlements used by this family: settings_organisation, settings_profile, settings_team

Try asking

  • How is our Justify workspace set up: which currency do our figures come in and which timezone do our reporting days follow?
  • Who am I signed in as here, and have I agreed to marketing mail?
  • List everybody on my team with their role, show me any invitations still outstanding, and tell me whether we have a spare seat.
ToolEntitlement neededEffectWhat it does
get_organisation_settingssettings_organisationReadsRead how this organisation is configured on Justify: the display name it goes by, the account type it operates as, the money and calendar defaults its figures are expressed in, the visual identity applied to what it publishes, and the spend threshold above which a gift parcel needs a person to sign it off. Returns organizationId, name, accountType, createdAt, currency, reportingTimezone, branding (primaryColor, secondaryColor, accentColor, headerFont, bodyFont, logoUrl, customised), giftApprovalEnabled, giftApprovalThreshold and giftApprovalThresholdCurrency. giftApprovalEnabled, giftApprovalThreshold and giftApprovalThresholdCurrency are the gate in front of create_gift_order, and this is the only place to read them before you send a parcel. With giftApprovalEnabled false NOTHING parks and every gift dispatches on the create_gift_order call itself. With it true, a parcel worth more than giftApprovalThreshold parks as PENDING_APPROVAL for approve_gift_order instead. Three ways a parcel parks that the number alone does not show, all of them deliberate: a null giftApprovalThreshold while enabled parks EVERY gift, a line whose catalogue value was never recorded parks, and a line priced in any currency other than giftApprovalThresholdCurrency parks. Unknown value never ships quietly. The moment the gate runs depends on how the gift was addressed: a parcel created with an explicit shippingAddress is weighed on the create_gift_order call, and one created with requestAddressFromCreator true is weighed later, when the creator confirms their address. currency is the ISO-4217 code every money figure is quoted in, and null means nobody has chosen one, in which case USD is assumed. reportingTimezone is an IANA zone name such as Europe/London: it decides where a reporting day starts and ends, and null means those boundaries fall back to UTC. Justify keeps no separate language column, so those two fields ARE the locale this settings screen offers. branding.customised is false when no palette was ever saved, in which case the five style values are estate defaults rather than anything anyone chose; each colour is a six-digit hex triplet and each font names a Google typeface. It reads only: nothing is renamed, no default is rewritten and no palette is saved by calling it. Changing any of them is a settings write, still owned by the web app, and an agent path to one would need its own approval ruling first. Deliberately absent, and not by oversight: the billing customer identifier, the mail provider key, the webhook signing secret and the telephony credential are never loaded onto this wire at all. For the plan and the credit balance behind it call get_billing_summary; for the entitlements this connection holds call get_mcp_access_status; for outbound sender configuration call get_sender_readiness. Use it to answer how are we set up, which currency do our numbers come in, and which clock does a reporting day follow.
get_my_profilesettings_profileReadsRead the profile of the person this connection is acting as, exactly as the profile settings screen serves it back to them. Returns userId, identityProviderUserId, email, name, role, accountType, joinedAt and consents (consentType, granted, grantedAt, revokedAt). The email is the sign-in address of the caller and of nobody else: this tool accepts no argument, resolves the identity from the authenticated credential, and therefore cannot be aimed at a colleague. role is the application role deciding which permissions the caller holds and accountType is the persona this surface is shaped by; both are facts about the caller rather than fields anyone edits on that screen. consents carries the newest privacy record per key — gdpr_data_processing and marketing_emails — where granted is true only when that record says granted AND carries no revokedAt, so a withdrawn permission can never read as live. It reads only: no name is edited, no password is reset, no photograph is uploaded and no privacy permission is granted or withdrawn by calling it. Each of those belongs to the person themselves, and an agent path to one would need its own approval ruling first. For the colleagues beside the caller call list_team_members; for what this connection may do call get_mcp_access_status. Use it to answer who am I signed in as, which address did I sign up with, and did I ever agree to marketing mail.
list_team_memberssettings_teamReadsList the seats inside this organisation: the colleagues who hold one and the invitations still waiting to be accepted, newest first. Returns data (seatId, userId, name, email, role, status, since, invitationExpiresAt), total, hasMore, nextCursor, seatCap and emptyReason. status is active for somebody already inside, and invited for a reservation nobody has accepted yet; an invited entry carries no userId and no name because neither exists until somebody takes the invitation up, and invitationExpiresAt says when that reservation lapses. total counts every seat in the filtered scope rather than the number on this page, and it is the same arithmetic the invitation door enforces — people inside plus live reservations — so total measured against seatCap is the headroom left before an invitation is refused. Pages with an opaque cursor and limit (1 to 50, default 20): when hasMore is true, call again with cursor set to the returned nextCursor. The roster is read from the identity provider, which is the same store the team settings page renders, so this lists exactly the colleagues that page lists; the invitations beside them are our own reservation rows. An empty data array always arrives with emptyReason set, so an empty roster is a measured zero rather than an unread store, and a cursor past the end says that instead of looking like an empty organisation: read that sentence before concluding there is nobody here or that a seat is free. Addresses appear here because the tool is reachable only by a caller holding manage_team, which is exactly who the team settings table shows them to. It reads only: nobody is invited, nobody is removed, no seat is promoted or demoted and no reservation is revoked by calling it. Each of those is a team write, still owned by the web app, and an agent path to one would need its own approval ruling first. Use it to answer who is beside me here, what may each of them do, and have we a spare seat before inviting somebody.

Signed influencers and approved content

The two shelves the Manage hub keeps across every job posting at once, rather than one posting at a time. The first is who you have actually signed: everyone whose application you accepted and who is now working with you, with the network they post on, the size of their following and the posting and campaign each signing sits under. The second is what those signings have produced and you have approved: the verified library of work you may use, each file described by what it is, which job it came from, who uploaded it, when you approved it and how the usage licence stands. The files themselves stay on the server: an agent gets the description, never the video, the picture or a link to either. Narrow the roster by network or by campaign, and the library to films or to stills. Signing somebody, approving a file and paying for it all stay with people.

Entitlements used by this family: job_board, manage_jobs

Try asking

  • Who have we actually signed across all our jobs, and which campaign is each on?
  • Show me only the creators we have signed on TikTok.
  • What content have we approved that we are allowed to keep using?
  • List the approved films from our jobs and tell me whose licence is about to run out.
ToolEntitlement neededEffectWhat it does
list_signed_influencersjob_board, manage_jobsReadsList everyone an organisation actually hired through its job board: one row per accepted or active application, gathered across every posting at once, exactly as the Manage hub Signed Influencers tab gathers them. A signing is a working relationship rather than an applicant or a saved shortlist entry — somebody whose application a brand accepted and who is now under way. Anyone hired onto two postings legitimately occupies two rows, each anchored to the posting that took them on. Returns data (applicationId, creatorId, handle, name, platform, followersCount, engagementRate, hasPortalAccount, avatarWithheld, jobId, jobBrandIdentityId, jobTitle, campaignName, lastChangedAt), total, totalIsExact, hasMore, nextCursor and scopeReason. Narrow it the two ways that tab does: platform for one network, campaignName for a single campaign matched exactly. Both narrowings are answered by the database, never by trimming afterwards. The portrait is withheld, which avatarWithheld states on every row; no e-mail address, telephone number, postal address or payout handle exists here at all, and hasPortalAccount replaces the withheld account identifier with the single fact an agent can act on — whether that person is reachable in the creator portal. Pages by cursor and limit (1 to 50, default 20): while hasMore is true, call again with cursor set to the returned nextCursor verbatim. total counts what was served, which totalIsExact qualifies, because the walk is a keyset and takes no count. Read-only. Accepting an application, sending an offer and releasing money all stay with people. Use it to answer who a brand is working with right now. get_job opens the posting a row names; list_job_applications answers the different question of who applied to one posting, whatever became of them.
list_approved_contentjob_board, manage_jobsReadsList the approved work an organisation banked across every job posting at once: the verified content library the Manage hub Content tab shows, organisation-wide and cleared for use. That approval gate is the whole difference from list_job_deliverables, which reads one posting at a time and shows every upload whatever state it stands in, drafts and rejections included. Nothing arrives on this shelf until a brand approves it, the same moment that unlocks payment release. It is equally not a campaign-library asset: each file was made for a job by a creator, not uploaded by the brand. Returns data (deliverableId, title, description, mediaKind, mediaWithheld, jobId, jobBrandIdentityId, jobTitle, campaignName, creatorName, approvedAt, uploadedAt, qualityScore, productDetected, presenceMeasured, presence, rightsStatus), total, totalIsExact, hasMore, nextCursor and scopeReason. mediaKind narrows to films or to stills, and it narrows in the database rather than by trimming afterwards, so a short page always means a short shelf. qualityScore is the mark the automated check gave the file out of ten, or null where nothing ever scored the file, so zero never stands in for silence. rightsStatus badges the usage licence as active, expiring, expired or pending; the licence document and its identifier belong to the content-rights reads under their own entitlement and are absent here. This is the organisation-wide answer to whether a brand actually appears in the work its creators delivered, so it is read across every posting at once rather than one job at a time. presenceMeasured is what keeps that answer honest: the stored productDetected column defaults to false, so read productDetected only when presenceMeasured is true, and otherwise say that nothing watched the file. presence carries the screen-time facts that were measured: onScreenSeconds, firstAppearanceSecond, assetDurationSeconds and framesAnalysed. framesAnalysed below the file whole-second count means only the opening window was watched, so never state a share of the whole file from it. Because approval is the gate, an answer with no presence anywhere covers the work a brand banked and says nothing at all about uploads nobody approved, which scopeReason states on every answer. get_job_content_verification reads the same facts one posting at a time, with the verdict, brand-safety and feedback beside them. Every file stays behind the projection, which mediaWithheld states on each row: no playback address, download link, storage key or poster leaves the server, because an agent reasoning about what a brand owns needs the description rather than the bytes. Pages by cursor and limit (1 to 50, default 20): while hasMore is true, call again with cursor set to the returned nextCursor verbatim. total counts what was served, which totalIsExact qualifies. Read-only. Approving, rejecting and asking for a revision all stay with people. Use it to answer what a brand banked and may reuse. get_job opens the posting a row names.

Creator portal: a creator’s own storefront, rates, site and listings

The only family on this page that is not served to an organisation connection. These tools answer for one creator, about that creator, on a connection the creator opened themselves from the creator portal. Two read: how their recommendation storefront is performing and which AI assistants have found their public profile, and what creators of their measured size report being paid, with a named brand’s payment reliability beside it when they ask for one. The rest do everything the portal does to their public site and its listings, through the same writers: add a property they recommended in one of their own posts (reading a page they have open when a portal refuses our reader, or finding the listings on their own website), list, read, edit, reconfirm, pin, order and remove them, read where each listing’s photos stand on rights, add and remove a home’s room photos (placed through a signed upload, because a photo is larger than a request can carry) and restyle its rooms within their monthly Creator Pro restyle credits, and match their own posts to their listings, where only their confirmation puts a post on a page. Their site’s plan is read, changed as a draft, drafted from one sentence about what they do, published and reverted; its words, featured listings and contact lines are read and changed; and AI Pulse reads which AI assistants have read their pages and how each page compares with the benchmark. Adding a property listing collects its photos and restyles its rooms automatically, within the creator’s Real Estate credits, and drafting from a sentence asks a paid model once; nothing goes live until the creator publishes it. No tool takes a creator, handle or user argument, because the server resolves the creator from their own sign-in and can only ever act for the caller. Opening the connection at all needs creator agent access, which every creator account holds, free or paying; what each tool needs beyond that is in the table beside it rather than repeated here. Where nothing has been measured the figure is null rather than zero, and a sentence says why.

Entitlements used by this family: creator_market_intelligence, recommendations

Try asking

  • How is my recommendation storefront doing over the last 30 days, and which AI assistants have been citing me?
  • What do creators my size say they were paid, and is the brand that just offered me a deal reliable about paying?
  • Add this villa to my page: here is the listing and here is my TikTok about it. I work for the agency.
  • Is my new listing live yet, and have the rooms been restyled?
  • Make my site feel more premium and lead with my reviews, but let me see it before it goes live.
  • Which of my recent posts are about which of my listings?
  • Restyle the living room and bedroom photos on my Marbella villa in three styles, and tell me how many restyles I have left.
ToolEntitlement neededEffectWhat it does
get_creator_storefront_performancerecommendationsReadsReport how this creator's own recommendation storefront is performing, and which AI assistants have found their public profile. Returns profileState; range and windowDays; totals (totalClicks, totalCodeCopies, totalAiReferrals, totalConfirmedCitations, aiReferralPct); aiDiscovery (verifiedVisits, distinctAssistants, priorWindowVisits, currentWindowVisits, trendWindowDays, changePct, byAssistant); and recommendations, one row each with clicks, code copies, AI referrals, confirmed citations, a daily sparkline and a referrer breakdown. Call it to answer whether the storefront is working, which recommendations are earning the clicks, whether AI crawler discovery is rising or falling, and which assistants are citing this creator. The longest window on offer is 90 days, and there is no year: the clicks these figures are counted from are kept for 90 days, the daily aggregate they roll into holds only days past that age, and a year asked of it would answer from that archive alone. Not every figure follows range either, and the ones that do not say so: confirmed citations are lifetime counters kept on each recommendation, and the crawler trend always compares two fixed seven-day windows under trendWindowDays. Everything else, the click totals, the recommendation rows, the sparkline, the referrer breakdown and the crawler counts, follows the window you asked for. It takes no creator, handle or user argument: the server resolves the creator from their own sign-in, so it can only ever report on the caller. Where nothing was measured the field is null rather than zero, and a sentence says why.
get_creator_rate_benchmarkcreator_market_intelligenceReadsReport what creators of this creator's measured size say they were paid, and, when a brand is named, that brand's payment reliability and how creators described working with them. Returns measuredFollowers and followerBand; rates (median, p25, p75, min, max, currency, sampleSize, originalSampleSize, excludedSampleSize, evidenceCoverage); hasData with an unavailableReason when false; and, for a named brand, reviewCount, trustScoreOutOfFive, paymentReliabilityPct, paymentEvidenceCoverage, a payment breakdown, sentiment counts and quoted highlights. Call it before quoting a fee, before agreeing terms with a company this creator has not worked with, or to answer whether a rate on the table is in line with what peers report. Name a company to look it up beside the rates, or omit it to ask only what creators of this size are being paid. It takes no follower count: an invented number would move the whole benchmark, so the server uses the size it has measured and says plainly when it has measured none. Every figure is an aggregate, no individual creator is named, and nothing is published below the minimum sample the arithmetic enforces.
add_listingRequired arguments: listingUrl, materialConnection, photoRights, postUrl, visitedOrPermittedrecommendationsWritesAdd a property listing to this creator's public profile, proven by one of their own social posts. Give the listing link (the property page on the agency or portal site) and the link to the post where the creator recommended it; the post must be on the creator’s own account. The creator must confirm they have visited the property or have permission to recommend it publicly, must say on what basis their page shows the listing’s photos, and must name their material connection to it (ORGANIC if none; CLIENT, PARTNER, PAID and the rest require the brand, agency or partner behind it). Returns the listing with its listingId, its public page, and processing PENDING. The server then reads the listing page, collects all of its photos for the public page, and restyles its rooms automatically within the creator’s Real Estate credits; call get_listing to follow that. When the listing’s portal refuses our reader, read the page the creator has open with preview_listing first and pass its captureId. Adding the same listing link again returns the listing already there (alreadyListed true) and starts nothing, so a retry never duplicates it or spends twice.
list_my_listingsrecommendationsReadsList every listing and recommendation on this creator's profile, pinned first, in the creator's own order. Returns, for each, its listingId, title, listingUrl, status, processing (PENDING, SUCCESS or FAILED), isPinned, position, publicUrl and publicBlockers, the reasons its public page does not answer yet (empty once it does). Call it for the ids get_listing, pin_listing, reorder_listings and remove_listing take. A profile holds at most 50 listings, so this is the whole list in one call: there is no second page.
get_listingRequired arguments: listingIdrecommendationsReadsRead one of this creator's listings and what has happened to it since it was added. Returns the listing (listingId, title, listingUrl, status, processing, isPinned, position, publicUrl, publicBlockers: why the public page does not answer yet, empty once it does); galleryPhotoCount, how many of the listing’s own photos its public page shows; and restyles: how many room restyles are queued, running, completed, failed or stopped, and how many are published on the public page. Call it after add_listing to see when the photos and the restyled rooms are live.
pin_listingRequired arguments: listingId, pinnedrecommendationsWritesPin one of this creator's listings to the top of their public profile, or unpin it. Returns the listingId and the pinned state it now has. Pinning a listing that is already pinned leaves it as it is.
reorder_listingsRequired arguments: listingIdsrecommendationsWritesRearrange this creator's listings: put them in a new order on their public profile. Give the listingIds in the order they should appear; pinned listings still show first. Returns how many listings were placed. Sending the same order again leaves the profile as it is.
remove_listingRequired arguments: listingIdrecommendationsDestructiveRemove one of this creator's listings from their public profile, for instance once the property has sold or let. Its public page stops showing it at once. Returns the listingId and removed: removed when this call removed it, already_removed when an earlier call had.
get_site_planrecommendationsReadsRead the plan of this creator's public site: the order its sections come in and how each is headed, the optional sections hidden, the accent colour and the order a home's ways to act are offered in. Returns livePlan, the plan every public page draws now; publishedVersion; the newest versions (at most 20, newest first, each with whether it is live); totalVersions; creatorPro, whether this creator may customise the plan at all; and catalogue, every component a plan may name with the headings it approves and whether it is mandatory (a mandatory section may be moved, never hidden). Call it before propose_site_change, which takes changes in exactly these terms, and for the version numbers publish_site_plan and revert_site_plan take.
propose_site_changerecommendationsWritesPropose a change to this creator's public site, such as making it look more premium or leading with their reviews, as a draft the creator previews and approves. Give only what changes, in the terms get_site_plan lists: blocks (the sections in the order they should lead, each with one of its approved headings), hide (optional sections to leave out), accent (a six-digit hex colour) and actions (the order a home’s ways to act are offered in). Everything not given stays as the live plan has it. The plan is checked against the catalogue before it is stored, and refused with every problem named. Returns the draft (its version, the plan and published false) and editorUrl, where the creator reviews it beside the live version. Nothing is published: call publish_site_plan with the draft’s version once the creator approves it. Proposing exactly the newest draft again returns that draft (alreadyProposed true) and stores nothing new. Customising the plan needs Creator Pro.
publish_site_planRequired arguments: versionrecommendationsWritesMake one of this creator's site-plan versions the one every page of their public site draws, once the creator has approved it (to unveil a redesign): usually the draft propose_site_change or save_creator_sentence stored. Their pages are refreshed and search engines are asked to read them again. Returns the version now live and outcome: published when this call published it, already_published when it was already live, in which case nothing moved and nothing was announced again. Every version is kept; revert_site_plan brings back an earlier one. Publishing a customised plan needs Creator Pro.
revert_site_planRequired arguments: toVersionrecommendationsWritesBring back an earlier version of this creator's site plan, for instance when they do not like the change they just published. Give toVersion, a version earlier than the live one (get_site_plan lists them); every page of the public site draws it again and is refreshed. Later versions are kept as drafts, so the change can be published again. Returns the version now live and outcome: reverted when this call restored it, already_live when that version was already live, in which case nothing moved. A version newer than the live one is a draft: publish it with publish_site_plan instead.
save_creator_sentenceRequired arguments: sentencerecommendationsSpends creditsSave what this creator does, in one sentence of their own words such as "I help advertise restaurants", and draft their site plan from it. The sentence is checked against the content policy first: a refused one is stored and drafts nothing (outcome REFUSED), and one held for review drafts nothing either (WITHHELD). With Creator Pro the site plan is then drafted from it (DRAFTED, or FAMILY_DEFAULT when the default plan for that kind of work was drafted instead); without it the sentence is kept and nothing is drafted (CREATOR_PRO_REQUIRED). Returns the sentence with its verdict and the family of work it was read as, outcome, the draft (or null) and editorUrl. The draft is never published here: show it to the creator, then call publish_site_plan with its version. Drafting asks a paid model once; saving the same sentence again asks nothing and returns UNCHANGED with the newest draft on file, so to redraft change the words.
get_site_detailsrecommendationsReadsRead the words and contact lines on this creator's public site, beside its plan: the headline (the site's tagline) and description search engines and AI assistants read, the listings it features first (at most three), and the public contact lines, the email and phone a visitor may use and the label of the enquiry button. Returns headline, description, featuredListingIds, contact and creatorPro (the headline, description and featured listings are drawn only with Creator Pro; they are kept without it). Change them with update_site_details and update_public_contact; get_site_plan reads the plan itself.
update_site_detailsrecommendationsWritesChange the words on this creator's public site and which listings it features first: the headline and description search engines and AI assistants read, and up to three featured listings (the site editor's featured pins; pin_listing sets a listing's own pin). Give only what changes: headline (at most 120 characters), description (at most 240), each plain text, null to return to the default; featuredListingIds, the whole featured list in order (each a live listing of this creator’s from list_my_listings, empty to feature none). Everything not given stays as it is. Returns headline, description and featuredListingIds as now stored. The public pages are refreshed at once. Needs Creator Pro; sending what is already stored changes nothing.
update_public_contactrecommendationsWritesChange how visitors to this creator's public site can reach them: whether their email and phone are shown, which ones, and the words on the enquiry button (such as Arrange a viewing). A home's Call and enquiry buttons use these when the listing names no number or address of its own. Give only what changes; everything not given stays as it is. Showing the email needs publicEmail, showing the phone needs publicPhoneE164 in international form (+447700900123). Returns the contact lines as now stored. The public pages are refreshed at once; sending what is already stored changes nothing.
reset_siterecommendationsDestructivePut this creator's public site back to its defaults, as the portal's reset does (a factory reset of the site, not of the listings): the headline, the description, the pins and the sentence about what they do are cleared, and the site draws its default plan again. Every site-plan version is kept, so a plan can be published again with publish_site_plan; the listings themselves are not touched. Confirm with the creator before calling it. Returns outcome: reset when this call cleared the settings, already_reset when there was nothing to clear. The public pages are refreshed at once.
list_my_postsrecommendationsReadsList this creator's own recent posts on their platform, newest first (chronological), as the portal's post picker shows them: for the postUrl add_listing and confirm_listing_match take. Returns, for each, postUrl, postId, the start of its caption, when it was posted, its format, its view, like and comment counts where the platform reports them, and links, the links found in the caption or the post (the first is usually the thing it recommends). At most 50 posts, in one call: there is no second page.
preview_listingRequired arguments: listingUrlrecommendationsWritesRead a listing page the way adding it would, without adding it: what the page is, the facts it states, whether it says enough to be published and whether the content policy would let it be. Give listingUrl, and kind when the page is not a home (PROPERTY by default, the kind add_listing adds). Some portals refuse our reader; when outcome is unobserved for that reason, ask the creator for the page they have open and give its HTML as page.html (at most 500,000 bytes once JSON-encoded as UTF-8, quotes and escapes counted; cut it and set page.truncated true when it is longer), with the address it was open at as listingUrl. Returns outcome (observed, unobserved or rejected) and reason; alreadyRecommended, the creator’s own listing of the same home or product when they already have one (as the portal’s duplicate check finds it); the listing (title, kind, price, currency, completeness: COMPLETE, PARTIAL, WRONG_SUBJECT or EMPTY, and the facts it is missing); facts, each labelled; contentPolicy (verdict, category and the label it would carry); kindMismatch; agency, who lists the home and their own page when it names one; and captureId when a page you gave was read, to pass to add_listing so the listing is read from it again. Nothing is published.
import_listings_from_siteRequired arguments: siteUrlrecommendationsComputesFind the listings on this creator's own website, such as their agency's site, so they can add the ones they recommend. Give siteUrl, the site’s home page. The site’s robots.txt is honoured, its sitemaps are followed on its own host only, and each page is read for a home’s facts: a COMPLETE page states every fact a home needs; a PARTIAL one states a price or bedrooms but not everything. It reads at most ten sitemaps and a hundred pages in under a minute, and says when a bound stopped it (incomplete). Returns candidates (url, title, imageUrl, price, currency, verdict and missing facts) and the count of pages listed, disallowed, read, not listings and unread. Nothing is added: add each listing the creator picks with add_listing.
update_listingRequired arguments: listingIdrecommendationsWritesEdit one of this creator's listings, as the portal's edit does: their own words about it, its category, a promo code, their material connection, the facts they confirm or correct, and the regulated categories they declare. Give the listingId and only what changes. confirmedFacts sets facts the page left out or got wrong, each a property the listing’s kind states, named as search engines name it (such as numberOfBedrooms), with its value; a confirmed fact outranks the page’s. declaredCategories is the whole list of regulated categories the creator declares for it (today only ALCOHOL can be declared, with an 18+ label), an empty list withdrawing them. A new recommendation text or new declarations send the listing back through the content policy, so it leaves the public page until that answers. Returns changed, the fields written; sending what is already stored changes nothing and writes none.
reconfirm_listingRequired arguments: listingIdrecommendationsWritesRecord that this creator still recommends one of their listings, as the portal's Still recommend? button does. Its public page then shows it as freshly confirmed, and search engines and AI assistants read the new date. Give the listingId. Returns the listingId and verifiedAt, the date now on record; confirming again simply moves the date to now and changes nothing else.
get_listing_photo_rightsRequired arguments: listingIdrecommendationsReadsRead where one of this creator's listings stands on photo rights: the basis they attested when they added it (LISTING_PARTY, PERMISSION_GRANTED or LINKED_PUBLIC_LISTING), the site the photos came from, how many of the listing’s photos its page shows, how many carry a photographer’s credit and how many are withheld, and every request anyone has made to take a photo or a restyled room down, with its status. A photo someone asks to take down is withheld at once, with every restyle made from it, until the request is reviewed. Give the listingId. Returns basis, sourceHost, attestedAt, photos (total, credited, withheld) and takedowns (each with the photo, whether it is a listing photo or a restyle, the basis given, its status, when it was received and when it was resolved).
propose_listing_matchesrecommendationsReadsSuggest which of this creator's own recent posts are about which of their listings, so the right posts can be shown on each listing's page. Each proposal names its tier and the signals it rests on: tier A is an explicit link in the post to the listing; tier B is two of: the caption pointing at the link in bio with the listing’s title, its street address, its price or its venue. Weaker pairings are not proposed, and nor is a post already on the listing or one the creator dismissed for it. Returns proposals (listingId, tier, signals, postUrl, postId, caption, postedAt). Nothing is attached: confirm_listing_match puts a post on its listing, which is the only way one becomes public, and dismiss_listing_match stops a wrong one being proposed again.
confirm_listing_matchRequired arguments: listingId, postUrlrecommendationsWritesConfirm, and so endorse, that one of this creator's posts is about one of their listings, which puts the post on the listing's public page: the only way a proposal from propose_listing_matches becomes public. Give the listingId and the postUrl the proposal named (any post of the creator’s own may be given). The post is checked as the creator’s own, and the listing is checked again by the content policy before it shows the post. Returns outcome: confirmed when this call attached it, already_confirmed when the listing already shows that post, in which case nothing changes.
dismiss_listing_matchRequired arguments: listingId, postUrlrecommendationsWritesTell Justify that one of this creator's posts is unrelated to one of their listings, as when a proposal paired them wrongly, so propose_listing_matches never proposes that pairing again. Nothing on the public site changes. Give the listingId and the postUrl the proposal named. Returns outcome: dismissed when this call recorded it, already_dismissed when it was already dismissed, in which case nothing changes.
list_listing_photosRequired arguments: listingIdrecommendationsReadsRead the room photos of one of this creator's home listings and what restyling has left: each photo's photoId, room, status and whether it passed review, the restyles made from each, how many more photos the listing takes, and how many restyles remain this month. A listing takes up to four room photos; a restyle names photos that passed review and exactly three styles. Room photos and their restyles are part of Creator Pro. Give the listingId. Use the photoIds with remove_listing_photo and restyle_listing_rooms.
request_listing_photo_uploadRequired arguments: byteLength, contentType, listingId, sha256recommendationsWritesGet a place to put a room photo for one of this creator's home listings, because a photo is larger than a request can carry. Give the listingId, the photo’s contentType (image/jpeg, image/png or image/webp), its byteLength and the sha256 of its bytes. Returns uploadUrl, a fifteen-minute address to PUT the photo’s bytes to with that Content-Type header, the storageKey to pass to add_listing_photo, expiresAt and maxBytes. Nothing is stored on the listing until add_listing_photo adds it. Room photos are part of Creator Pro.
add_listing_photoRequired arguments: listingId, rightsAttested, room, storageKeyrecommendationsWritesAdd a room photo to one of this creator's home listings, once its bytes are at the storageKey request_listing_photo_upload gave. The photo is checked against the key’s sha256, scanned and reviewed, and stored without its camera metadata; the creator must confirm they have the rights to use it (rightsAttested: true). Give the listingId, the storageKey, the room it shows and rightsAttested. Returns the photoId, its room and its status. The placed upload is used once: adding the same storageKey again finds nothing to add. Room photos are part of Creator Pro.
remove_listing_photoRequired arguments: listingId, photoIdrecommendationsDestructiveRemove a room photo from one of this creator's home listings, with every restyle made from it. The photo and its restyles are deleted from storage and cannot be brought back; add the photo again to restyle it again. Give the listingId and the photoId from list_listing_photos. Returns the photoId removed. A photo already removed answers NOT_FOUND. Room photos are part of Creator Pro.
restyle_listing_roomsRequired arguments: listingId, photoIds, stylesrecommendationsSpends creditsRestyle room photos of one of this creator's home listings: each photo is redrawn in each style, and the restyles appear on the listing’s public page beside the photo once they are made. Each restyle uses one of the creator’s monthly Creator Pro restyle credits; list_listing_photos says how many remain. Name one to four photoIds that passed review and exactly three different styles. Returns queued, how many restyles are now under way from this call. Asking again for a photo and style already restyled or under way uses no further credit.
get_ai_pulserecommendationsReadsRead this creator's AI Pulse, as the portal shows it: which AI assistants' crawlers have verifiably read their public profile, week by week over eight weeks (a visit counts only when the crawler was confirmed at the network or by its signature, never by its claimed name), and the golden bar, each of their own pages held to the benchmark page of its kind over 28 days. For each page the golden bar gives its verdict (MEETS, BELOW, NO_DATA, NOT_COMPARABLE or NO_FLOOR) and each figure beside its floor: AI crawler reads, visits arriving from an AI answer, citations in Bing’s AI answers, and the page’s Core Web Vitals. A figure nothing measures is null with the verdict NO_DATA, never zero. Returns totalThisWeek, hasAnyVerified, byVendor (vendor, label, countThisWeek, weekly counts) and goldenBar (windowDays and pages).

Advertising accounts, spend policy and boost eligibility

The advertising accounts your organisation buys media through, one per brand: whether each is still connected, whose login is behind it, what currency its budgets are set in, which timezone its days are counted in, and the spend cap and amount spent as the advertising network itself last reported them. Every money figure here is the network's own reading, never the figure Justify asked for, and each carries how many seconds old it is so you can tell a live number from a stale one. Two acts sit beside the reads: take a fresh reading from the network now, and stop an account for good. Stopping one asks every advert still running on it to stop on the network first and refuses outright if any of them cannot be stopped, because a disconnected account is one nothing can pause afterwards. Connecting an account is a person job: the link tool hands you the page to open, and the account picker on it never chooses for you, because one login can reach personal accounts and other companies' accounts as well as the brand's own. Beside the accounts sits the question money actually turns on: which of a campaign's tracked creator posts may be advertised at all. That answer composes three separate checks — what the network will let this account advertise, what the creator has agreed to, and whether the signed rights cover paid media to the campaign's last day — into one verdict per post with one reason for a refusal, taken freshly every fifteen minutes. Above all of it sits the spend policy: one ceiling per client brand, or the organisation's default where a client has none, saying what may be spent in a month, what one advert may spend in a day and in total, and the amount above which a second person has to agree. It is append-only, so the ceiling a live advert was approved under stays readable after somebody moves it. Lowering a ceiling is written straight away. Raising one is held on the ledger for a colleague you name and takes effect only when that person agrees in a browser, because raising the ceiling is what lets a later spend go through with nobody else looking.

Entitlements used by this family: media_buying

Try asking

  • Which advertising accounts have we connected, which brand does each belong to, and are any of them broken?
  • Take a fresh reading of our Meta ad account and tell me its spend cap and how much it has spent.
  • We have no advertising account connected for the Aurora brand, so give me the link to set one up.
  • We are stopping paid media on that account: disconnect it, and tell me how many live adverts had to be stopped first.
  • Which posts in the autumn campaign can we put money behind, and for the ones we cannot, what has to happen first?
  • Which creators have agreed to us running their posts as adverts, and whose permission is about to run out?
  • Ask Maya if we can run her autumn post as a paid advert from her own handle for the Aurora brand.
  • We have finished with that creator: take their permission back, and tell me which adverts had to be stopped first.
  • The creator sent over the Spark code for their TikTok video and said it is good for thirty days — save it against their pending request.
  • What would it cost to put five hundred pounds behind that post over the next fortnight, and are we allowed to?
  • Build that boost, paused, under the creator’s handle, and tell me the Meta ids so I can find it in Ads Manager.
  • Find me interests around sustainable skincare we could target, and check the ones we used last quarter are still offered.
  • Is this audience still valid — women 25 to 44 in the UK and Ireland, on those three interests — and give me the stamp so I can build with it.
  • Roughly how many people would that audience reach at forty pounds a day, and is it wider than the preset we used last time?
  • Show me what the advert will look like in Instagram stories before we build anything.
  • Which audiences have we saved for Aurora, and which one is the default?
  • Save that audience for Aurora as "UK skincare 25-44" so we can use it on the rest of the autumn posts.
  • That boost is ready — send it to Priya to sign off, and tell me what it will cost if she agrees.
  • Boost the autumn post for all thirty of our clients on the UK skincare audience — two hundred pounds each over the fortnight.
  • That whole wave is built and paused — send it to Priya as one approval and tell me the total she will be agreeing to.
  • Stop the advert on that boost right now — and tell me whether Meta has actually confirmed it, not just that we asked.
  • That boost stopped itself overnight — I have read what happened, so put my name against it.
  • We paused that boost while legal checked the claim; it is cleared now, so send it to Priya to put back on.
  • That boost is performing — ask Priya to lift it to eighty pounds a day and two thousand in total, running to the end of the month.
  • Bring that boost back to twenty pounds a day and finish it on Friday instead — do it now, do not wait on anybody.
  • We are done with that advert: close it off for good and tell me what it ended up costing.
  • What is each client allowed to spend this month, how much of it is left, and is any budget change waiting on somebody?
  • Aurora is overspending, so bring their monthly ceiling down to two thousand pounds today.
  • We need more headroom on the autumn push: ask for Aurora’s monthly ceiling to go up to nine thousand and send it to Priya to approve.
ToolEntitlement neededEffectWhat it does
list_ad_accountsmedia_buyingReadsList the advertising accounts this organisation has connected for paid amplification of creator posts. Returns one row per connection with connectionId (the id every other ad-account tool takes), provider, brandId, providerAccountId, providerAccountName, status, disconnectReason, accessRole, currencyCode, accountTimezone, tokenExpiresAt, spendCapReadBack, amountSpentReadBack, spendReadBackAt, freshnessSeconds, resultsFreshnessSeconds and usageRefusalsToday, plus a total, which is how many rows this call served: the read is bounded at 200 connections and takes no cursor, because no organisation connects that many. status ACTIVE means the account can be spent on; EXPIRING means the login is being renewed and is still live; DISCONNECTED and ERROR both refuse every write until somebody reconnects the account in Justify. spendCapReadBack and amountSpentReadBack are the figures the network itself last reported, as decimal strings in currencyCode, never the figures Justify asked for, and freshnessSeconds says how old that reading is in whole seconds. resultsFreshnessSeconds is the separate and more important age: whole seconds since the newest daily spend-and-results row for the account was synced, which is what the figures a buyer reads are built from, held under thirty minutes as a service level. usageRefusalsToday counts the readings taken during the account's own day that sat at or above the line where the rate-limit budget refuses an ordinary write. Use it to answer which ad accounts we can spend through, whose money is behind a boost, and whether any of them has stopped working. Filter by brandId for one client brand and by status to see only the live ones; then get_ad_account for the detail, refresh_ad_account_state to take a fresh reading, or get_ad_account_connect_link when there is nothing connected yet.
get_ad_accountRequired arguments: connectionIdmedia_buyingReadsRead one connected advertising account in full: connectionId, provider, brandId, providerAccountId, providerAccountName, providerBusinessId, status, disconnectReason, accessRole and accessVerifiedAt, currencyCode, accountTimezone, pageId, instagramAccountId, tokenExpiresAt, spendCapReadBack, amountSpentReadBack, spendReadBackAt, freshnessSeconds, resultsFreshnessSeconds, usageRefusalsToday, lastError, lastErrorAt, connectedAt and disconnectedAt. Every figure is the stored reading rather than a fresh call to the network, so freshnessSeconds is what tells you whether it is worth trusting; call refresh_ad_account_state to take a new one. resultsFreshnessSeconds is the age of the spend and results themselves, in whole seconds since the newest daily row was synced, and an account whose figures have stopped arriving reads its age here rather than a blank; usageRefusalsToday counts the readings during the account's own day that sat at or above the line where the rate-limit budget refuses an ordinary write. accessRole is the strongest task the stored login actually holds on the account, read back from the network rather than assumed from the login. accessVerifiedAt is when that role was last proved, and providerBusinessId names the business portfolio the account sits under; pageId and instagramAccountId are the page and professional profile a branded advert would run beneath. Use it to answer what state one ad account is in, which brand it belongs to, what currency its caps are in, and why it stopped working. Find the connectionId with list_ad_accounts first.
get_ad_account_connect_linkmedia_buyingComputesMint the Justify address a person opens to connect an advertising account to a brand. Returns connectUrl, the brandId the connection will belong to, provider and expiresInSeconds. The link carries a one-time code tied to this organisation, that brand and the person who asked for it, so it works once, for them, and lapses after expiresInSeconds. Nothing is connected by calling this: the login needs a browser and a signed-in person, and the account picker on that page never chooses for them, because one Facebook login can reach personal accounts and other companies' accounts as well as the brand's own. provider names which advertising network the person will connect; omit it for the default network, which is what every call made before that argument existed asked for. Each network has its own settings page and its own one-time code, so a link minted for one cannot be redeemed by the other. Use it when list_ad_accounts shows no connected account for a brand, or shows one that has been disconnected, and hand the URL to the person who administers the advertising account. brandId is the brand identity id from list_brands; an organisation holding several brands must name one, because the connection belongs to exactly one brand and a buyer on one client must never reach another client spend.
refresh_ad_account_stateRequired arguments: connectionIdmedia_buyingWritesTake a fresh reading of one advertising account from the network right now: exchange the stored login, then read the account behind it and store what the network said. Returns the account in the same shape get_ad_account serves, plus tokenOutcome and accountReadBack. tokenOutcome REFRESHED means the login was exchanged and the account was read; AUTH_FAILED means the network refused the login and the failure was counted against the connection, which stops the account once it happens enough times; ERRORED means the network could not be reached and nothing was counted. accountReadBack says whether the figures below the token came from the network on this call. What it reads back is the ACCOUNT: its name, its timezone, its spend cap and what it has spent. Creator advertising permissions and whether a particular post can be boosted are separate read-backs with their own tools. An account that is already disconnected or in error is refused rather than refreshed, because there is no login left the network would honour. Use it before quoting a cap or a spend figure that matters, or when get_ad_account shows a large freshnessSeconds. Repeating it is safe: it reads the same account again and stores the same columns.
disconnect_ad_accountRequired arguments: connectionId, idempotencyKeyTakes an idempotencyKeymedia_buyingDestructiveStop an advertising account: ask every ad still delivering on it to stop ON THE NETWORK first, then mark the connection disconnected, then hand the login back to the network. Returns the account, boostsPauseRequested, tokenRevoked and alreadyDisconnected. If any live ad cannot be asked to stop, NOTHING is changed and the call refuses with the count still delivering, because a disconnected account is one Justify can no longer pause anything on. The reason recorded is that the advertiser asked, which is durable: no later login revives the connection by itself, and only a person deliberately reconnecting the account brings it back. Calling it on an account that is already disconnected changes nothing, answers alreadyDisconnected true and leaves the original reason it stopped for intact. An account in ERROR, whose login the network keeps refusing, IS stopped in the ordinary way: stopping needs no working login, and it is the account an advertiser most wants rid of. A refused hand-back of the login is recorded in tokenRevoked and never undoes the disconnect. Use it only when somebody has asked to stop buying media through this account. Read it with get_ad_account first, and use get_ad_account_connect_link to connect it again afterwards. Requires an idempotencyKey so a retried call cannot stop the account twice.
list_boostable_postsRequired arguments: campaignIdmedia_buyingReadsRead which of a campaign's tracked creator posts can be turned into a paid advert under the creator's own handle, and why the rest cannot. Returns one row per tracked post with postId, creatorId, creatorHandle, platform, postUrl, publishedAt, accountTimezone, spendEndsOn, verdict, reasonCode, providerErrors, permissionId, licenseId, observedAt, ageSeconds and boostReceipt, beside the campaign and its brandId. accountTimezone is the zone the advertising account behind THAT row’s own network counts its days in, and spendEndsOn is the instant that row's licence was judged against: the end of the campaign's last day in that zone. They are stated per row because a campaign may hold a Meta account and a TikTok advertiser in two different zones, so the same last day ends at two different instants. verdict ELIGIBLE means three separate checks agreed at observedAt — the advertising network offers the media for a partnership advert, the creator's permission is granted and confirmed by the network, and a paid-media licence covers the asset to the last spending day. Anything else carries one reasonCode naming the nearest cause: PLATFORM_UNSUPPORTED, NO_CONNECTION, NO_PERMISSION, PERMISSION_PENDING, PERMISSION_EXPIRED, PERMISSION_REVOKED, PERMISSION_UNVERIFIED, PROVIDER_UNREADABLE, NOT_FOUND, PROVIDER_INELIGIBLE, or one of the licence refusals LICENCE_MISSING, LICENCE_NOT_ACTIVE, LICENCE_ORGANIC_ONLY, LICENCE_REVOKED, LICENCE_EXPIRES_BEFORE_END_DATE and LICENCE_AMBIGUOUS, which means the creator holds several rights agreements in this campaign and they disagree about paid media, so a person has to say which job the post was delivered under. providerErrors carries the network's own error codes, verbatim, for a post it refuses. Every eligible row carries boostReceipt: an opaque single-use token tying this exact organisation, brand, campaign, post, permission and licence to the reading above, and it is the only way to build a boost. Hand it back unchanged and never build one without it. A reading taken more than fifteen minutes ago is refreshed before it is served; pass forceRefresh to take a new one now. Use it to answer which posts we can put money behind, why a post cannot be boosted, and what a creator or a rights manager has to do before it can be.
list_creator_ad_permissionsmedia_buyingReadsList the standing permissions creators have given this organisation to run their own posts as adverts from their own handles. Returns one row per permission with permissionId (the id the other permission tools take), brandId and brandName, creatorId and creatorHandle, provider, permissionType, identityMode, status, providerPermissionId, providerItemId, hasSparkCode, expiresAt, requestedAt, grantedAt, revokedAt, lastReadBackStatus, lastReadBackAt and contentRightsAgreementId, plus a total, which is how many rows this call served: the read is bounded at 200 permissions and takes no cursor. permissionType PARTNERSHIP is the creator authorising the brand on their Instagram account; SPARK_CODE is the per-post token a creator mints in their TikTok app. identityMode says whose handle the advert runs under: CREATOR, the creator’s own, on every grant Justify opens, because running the creator’s own post from their own handle is what both kinds of grant are for. status GRANTED is the only state a boost may run under; PENDING means asked and not answered; REVOKED means the consent has ended; EXPIRED means the grant ran out. On Instagram, GRANTED is only ever written from a reading of the network, never from somebody pressing a button, and lastReadBackStatus carries the network’s own word verbatim, or NOT_FOUND when the whole list was read and the creator was not on it, or VALIDATED when TikTok recognised a Spark code. A null lastReadBackStatus means no network has confirmed anything. expiresAt is null on a partnership grant and that means it has no end: Meta publishes none and Justify sets none, so the grant stands until the creator withdraws it, and what bounds paid usage is the licence. On a Spark code it is the validity the creator chose when they minted it, which does run out. Use it to answer which creators have agreed, which are still waiting, and which grants are about to run out; then request_creator_ad_permission to ask, record_spark_ads_code to record a TikTok code, and revoke_creator_ad_permission to give one back. The Spark code itself is a credential and is never on this payload: hasSparkCode says one is held and nothing more.
request_creator_ad_permissionRequired arguments: creatorId, idempotencyKeyTakes an idempotencyKeymedia_buyingWritesAsk one creator for permission to run their posts as adverts from their own handle, on behalf of one brand. Returns the permission row and readBack, which says whether the network was asked on this call and what it answered. THE ANSWER IS ALMOST ALWAYS PENDING, and that is the point. On Instagram the request is posted to the network from the brand’s own Instagram professional account, and then the permission state is read straight back: a creator who had ALREADY approved this brand as a business partner in their Instagram app is granted on this very call, and a creator who has not stays PENDING until they do it there. GRANTED is written from that reading and never from the request succeeding, so nothing in this call can produce consent the creator has not given. On TikTok nothing is sent, because there is no creator-side Spark interface to send to: the row opens PENDING, the creator is told, and the grant arrives when they generate a code in the TikTok app and it reaches record_spark_ads_code. The NETWORK is decided by the creator’s own platform and never by the caller, so a creator on TikTok gets a Spark request whatever the brand advertises on. Asking a creator twice is one permission, not two; asking again after a withdrawal or an expiry starts a fresh request the creator answers again. contentRightsAgreementId names the paid-media licence the grant will rest on where one is already agreed; a grant may exist without one, and it is the boost that a missing licence refuses, never the grant. Use it when list_creator_ad_permissions shows no permission for a creator you want to boost, or shows one that has ended. Requires an idempotencyKey so a retried call cannot reach the creator as a second request.
revoke_creator_ad_permissionRequired arguments: idempotencyKey, permissionIdTakes an idempotencyKeymedia_buyingWritesGive one creator permission back on behalf of the organisation that holds it. Returns the withdrawn permission and every advert the withdrawal asked to stop. THE ADVERTS COME OFF FIRST. Each one still delivering under the permission is asked to stop ON THE NETWORK before the permission itself changes, and if any one of them cannot be asked, NOTHING is changed and the call refuses with the count still delivering — recording rights as ended while money kept going out under them is the one outcome this tool exists to prevent. The boosts come back as PAUSE_REQUESTED rather than PAUSED: a stop that has been asked for and not yet confirmed by the network is still delivery, and this answer says so rather than a friendlier word. On Instagram the withdrawal is then sent to the network on the same list the request went out on, so the brand stops being an approved partner there too; on TikTok there is no such interface, and the withdrawal is the Spark code being destroyed, which this call does. The network is read back afterwards and its word is kept BESIDE the withdrawal rather than deciding it: Instagram may still report the brand as approved for as long as it takes to catch up, and the disagreement is worth keeping. A permission that is not granted is refused rather than withdrawn again, because how a permission ended is part of the record. Asking again afterwards with request_creator_ad_permission is the one route back to a grant, and the creator answers it again. Use it when a brand no longer wants to run a creator’s posts, or when a contract ends. A creator withdrawing their own consent does it in their own portal, which stops the same adverts. Requires an idempotencyKey.
record_spark_ads_codeRequired arguments: expiryDays, idempotencyKey, permissionId, sparkCodeTakes an idempotencyKeymedia_buyingWritesRecord the TikTok Spark Ads authorisation code a creator generated for one of their posts, against the PENDING or GRANTED TikTok permission it answers. Returns the permission, now granted for the number of days the creator chose. THE CODE IS A CREDENTIAL and is treated as one: it is checked with TikTok as it arrives, encrypted before it reaches any record, and never returned, shown again or written to a log. hasSparkCode on the answer says a code is held and nothing more, and no tool reads one back. WHAT THE CHECK MEANS. TikTok is asked whether it recognises the code; when it does, lastReadBackStatus reads VALIDATED, which is the whole of what TikTok’s answer says. When the brand has no live TikTok advertising account to ask with, the code is still recorded and lastReadBackStatus stays null, which every later reader takes as unverified rather than as refused. A code TikTok actively refuses is refused here, because a code the network will not honour is not permission. expiryDays is the creator’s own choice from the four TikTok offers — 7, 30, 60 or 365 — and Justify never picks for them: a code Justify believed lasted longer than TikTok does is an advert that stops delivering with nobody told why. Justify RECORDS the number of days you say the creator chose and checks it against nothing, because TikTok’s answer about a code carries no expiry to check it against; what does bound it is the re-record rule above, which can only ever shorten a grant already standing. The permission is named and the creator whose consent it is comes off that row, never out of this call, so a code can never be put against somebody else’s grant. An Instagram permission is refused: a partnership grant is given by the creator approving the brand in their Instagram app, not by a code. A PERMISSION THAT HAS ENDED IS REFUSED with CONFLICT, whether the creator withdrew it (REVOKED) or it ran out (EXPIRED): a grant comes back only through a fresh request the creator answers, never through a code the brand still holds. A permission that is already GRANTED accepts a code and replaces the credential with it, because a creator who re-mints their code has changed the code and not their mind — but a re-record NEVER LENGTHENS THEIR CONSENT: the permission keeps the moment it was granted, and its expiry becomes the earlier of the one it already holds and the one expiryDays asks for. A shorter expiryDays does shorten it, because ending sooner is the brand handing rights back. Use it when a creator has handed the brand a code out of band; the creator can also paste it into their own portal, which does the same thing. Requires an idempotencyKey so a retried call cannot re-record a code and push its end date out.
search_ad_targetingRequired arguments: kindmedia_buyingComputesAsk the advertising network what may go into a boost audience, from one of four catalogues. kind INTEREST and GEO take a keyword query; kind BEHAVIOUR and DEMOGRAPHIC are catalogues the network lists. GEO answers with countries and DEMOGRAPHIC with family statuses, which are the granularity and the catalogue a boost audience can carry; the network’s other demographic catalogues and its region and city options are not offered here, because an id nothing downstream accepts is a dead end. Returns one row per option with id, name, kind, path, locationType for geography, and the network’s own estimatedAudienceLowerBound and estimatedAudienceUpperBound where it states them, beside the brand the account belongs to and observedAt. HAND THE IDS BACK UNCHANGED to validate_ad_targeting: an id typed by hand or remembered from another account is the thing validation exists to catch. Custom, lookalike and customer-list audiences are not offered by this tool or by any other in Justify. Use it to answer what we can target, whether the network still lists an interest by name, and which countries, ages, interests, behaviours and family statuses an audience can be narrowed to.
validate_ad_targetingRequired arguments: specmedia_buyingComputesPut a boost audience to the advertising network and get the validationStamp that lets it be spent against. This is the deprecation check: an interest the network has retired still looks like a valid string, and a targeting spec that has never been countersigned is a guess. Answers verdict VALID or REFUSED. VALID carries validationStamp and validationStampExpiresAt; hand the stamp back to get_boost_estimate, save_ad_targeting_preset or create_boost with the SAME spec, unchanged — the stamp is computed over the audience itself, so editing one interest stops it verifying. REFUSED carries exactly one reasonCode naming the nearest cause: NO_CONNECTION, NO_COUNTRIES, AGE_BAND_INVALID, AGE_BAND_UNSUPPORTED, OPTION_UNKNOWN, OPTION_DEPRECATED, OPTION_CLASS_UNSUPPORTED, AUDIENCE_TOO_NARROW or PROVIDER_UNREADABLE, and deprecatedOptionIds lists the options the network has retired, so replacements can be looked up. AGE_BAND_UNSUPPORTED and OPTION_CLASS_UNSUPPORTED are a network saying it does not sell what was asked for: one sells whole age bands rather than a range, and one has no field for behaviours or demographics at all. The refusal names what to change. A refusal carries NO stamp at all, so a spec the network rejected cannot be carried to the build. The stamp lives ten minutes and is bound to this organisation. NOTHING IS SPENT, SAVED OR BUILT by this call. A client can hold accounts on more than one advertising network: name the network of the post the audience is for as provider, and the audience is put to that account; a network the client holds no live account on is refused with NO_CONNECTION rather than answered by another. Use it before every boost, and again whenever an audience has been edited.
get_boost_estimateRequired arguments: dailyCap, targetingmedia_buyingComputesAsk the advertising network roughly how many people a boost audience could reach at a given daily budget. Takes either a saved preset by id or a spec with its validation stamp, plus the dailyCap the estimate is taken at. Returns estimatedAudienceLowerBound and estimatedAudienceUpperBound — the size of the audience — and estimatedDailyReachLowerBound and estimatedDailyReachUpperBound — how many of them a day at that budget — beside the currency, the daily cap and estimateNotice. EVERY FIGURE IS AN ESTIMATE AND NONE IS A COMMITMENT. They are the network’s own bounds, they move with the auction, the creative and what is actually spent, and a null means the network declined to give a figure rather than that the answer is zero. Show them with the estimate wording beside them, never as a promised reach. NOTHING IS SPENT OR BUILT by this call. Use it to answer how big an audience is, whether a budget is worth the reach it buys, and which of two audiences is wider.
preview_boostRequired arguments: boostReceiptmedia_buyingComputesRender what the advert built from a creator post will look like, before anything is built. Takes the boostReceipt from list_boostable_posts, unchanged, and an optional placement of INSTAGRAM_STANDARD, INSTAGRAM_STORY, MOBILE_FEED_STANDARD or DESKTOP_FEED_STANDARD. Returns previewHtml, the network’s own rendered frame, or null with unavailableReason saying why there is none — which is the honest answer on a network that renders no preview for an advert built from an existing post, and on a placement the network declines. What runs in that case is the creator’s own post, unchanged. THE RECEIPT IS ONLY READ, NEVER SPENT: previewing costs you nothing and the same receipt still builds the boost. NOTHING IS SPENT OR BUILT by this call. Use it to answer what the advert will look like and how it differs by placement.
list_ad_targeting_presetsmedia_buyingReadsRead the reusable boost audiences a client already has: Justify’s own presets, and optionally the saved audiences the advertising network holds for the same ad account. A preset is a named, reusable audience — not a segment, not a list of people — and the two sources are kept apart because only one of them can be written to. Returns one row per preset with id, source, brandId, name, description, isDefault, targeting, platformSummary, editable, createdAt and updatedAt. source justify is a preset this organisation saved: its targeting is published in full and it can be saved over. source platform is a saved audience read off the network: its targeting is null, the network’s own summary is in platformSummary, and editable is false because the network’s API allows nobody to create, edit or delete one. Pass includePlatformSaved to read the network’s half; without it the answer is Justify’s own rows and reaches no network at all. A preset id from here can be handed straight to get_boost_estimate or create_boost. Use it to answer which audiences we have used before for this client and which one is the default.
save_ad_targeting_presetRequired arguments: name, provider, spec, validationStampmedia_buyingWritesSave a validated boost audience under a name, so it can be reused on later boosts. Takes the spec and the validationStamp validate_ad_targeting minted for that exact audience, a name, and optionally a description and isDefault. Returns the saved preset with its id, which can be handed straight to get_boost_estimate or create_boost. THE STAMP IS REQUIRED: an audience nobody put to the advertising network cannot be saved, and a spec edited after it was validated stops verifying and is refused. SAVING IS CONVERGENT: the name identifies the preset within the client, so saving the same name again edits that preset rather than making a second one. This call needs no idempotency key for that reason. THE NETWORK TRAVELS WITH IT: pass back the provider validate_ad_targeting answered on, because an audience validated on one network means nothing on another and a preset is only ever offered to a boost on the network it was validated for. NOTHING IS SPENT and no advert is built or changed; a preset only names an audience. Saved audiences held on the advertising network itself are read-only and cannot be saved over from here — the network’s own API allows nobody to. Use it after validating an audience the client will want again.
prepare_boostRequired arguments: boostReceipt, currencyCode, dailyCap, endsAt, lifetimeCap, startsAt, targetingmedia_buyingComputesPrice and judge one boost before anything is built, and get the confirmation that lets you build it. Takes the boostReceipt from list_boostable_posts, both caps, the currency and the flight, and answers verdict READY or REFUSED. READY carries routing (the ad account, its timezone and currency, the campaign, the post and the creator's handle), an approval block (both caps, the schedule, the client's monthly ceiling, what is left of it, the per-boost maxima, the second-approver threshold and whether this boost is at or above it, the network's minimum daily budget, and when the permission and licence run out), the creator's post address, and confirmationToken. REFUSED carries exactly one reasonCode naming the nearest cause: NO_CONNECTION, NO_AUDIENCE_GRAMMAR, NO_SPEND_POLICY, PERMISSION_NOT_GRANTED, SCHEDULE_INVALID, RIGHTS_EXPIRE_BEFORE_END, CURRENCY_MISMATCH, BELOW_MINIMUM_BUDGET, ABOVE_DAILY_MAXIMUM, ABOVE_LIFETIME_MAXIMUM, ABOVE_MONTHLY_HEADROOM or BOOST_LIMIT_REACHED. NOTHING IS SPENT, RESERVED OR BUILT by this call, and no credit moves: it reads, judges and mints a token. The token is single use, lives ten minutes, and is bound to this organisation, this brand, this campaign, this post, this spend policy revision and these exact figures — change any of them and create_boost refuses it. Show the approval block to the person before you build anything. Use it to answer what a boost would cost, whether it is allowed, and what has to change before it is.
create_boostRequired arguments: boostReceipt, confirmationToken, currencyCode, dailyCap, endsAt, lifetimeCap, startsAt, targetingmedia_buyingWritesBuild the boost prepare_boost quoted: four objects on the advertising network, all PAUSED, under the creator's own handle with the brand named as the sponsor. Takes the same boostReceipt, caps, currency and flight you were quoted for, plus the confirmationToken that quote handed back. THIS CALL SPENDS NOTHING AND STARTS NOTHING. Everything it creates is paused, and no path through it can make an advert live: going live is a separate act that a named person has to agree to. What it returns is the boost row — its status, the caps the advertising network reports back with the moment they were read, the schedule, the provider campaign, ad set, creative and ad ids — and stepsCompleted, the parts of the build that landed. The caps you asked for are never echoed back to you: what the network holds is the only figure the money will be spent against, and a null pair means it has not been read back yet. REFUSED if anything has changed since the quote: a cap, a day, the post, the client spend policy, the creator's permission or the licence. Refused if the receipt or the confirmation has already been used, has expired, or was issued for another organisation, brand, campaign or post. Refused when the advertising account already holds twenty five boosts that have never gone live: the unlaunched-boost ceiling, which this call is the only one that can raise. Read prepare_boost again and build from the fresh answer. HELD BACK rather than refused when the advertising network has nearly no request allowance left for this account: the answer is SERVICE_UNAVAILABLE, it is marked retryable, and it carries the wait the network itself asked for. Justify stops writing at ninety percent of that allowance so that stopping a live boost is never the call that cannot be made. Wait and call again with the SAME confirmationToken; do not re-quote, because nothing about the boost has changed. The confirmationToken IS the retry key: calling again with the same token answers with the boost the first call built instead of building a second one.
activate_boostRequired arguments: boostId, idempotencyKeyTakes an idempotencyKeymedia_buyingDestructiveAsk for one built boost to start spending. THIS CALL NEVER STARTS ANYTHING, at any amount: it holds the go-live on the Justify ledger for a named person and hands back an approvalUrl, an operationId and the price. The advert becomes live only when that person opens the link while signed in to Justify and agrees; an agent holds no authority to agree, because the approval page needs a browser session an OAuth credential cannot mint. What is held is the whole price: the account currency, both ceilings as the advertising network itself reports them, what the boost has spent so far, the flight with the account timezone, the client's monthly ceiling and what is left of it, the creator whose handle the advert runs under, and the spend policy revision all of it was judged against. If a ceiling changes between the ask and the click, the approval is refused rather than starting a spend nobody was shown. REFUSED before anybody is interrupted when the boost is not READY, when the advertising network has not confirmed its ceilings, when the creator's permission or the paid-media licence no longer covers the flight, when a ceiling is above the client's per-boost maximum, or when the lifetime ceiling is more than the client has left to spend this month. Every one of those is the world having moved, so re-read prepare_boost and ask again from the fresh answer. Three refusals are NOT that, and asking again is wrong for all three: NO_PROVIDER_AD means the build never produced an advert, so build one rather than retry this call; REVIEW_REJECTED means the advertising network refused the advert on policy and no path in Justify sends it again, so the creative has to change; REVIEW_BLOCKED means the network is holding the advert over money — the card, the settlement, or the account being disabled — so the creative is not the problem and the fix is billing, with the network. Below the client’s approvalThreshold the person who asked may agree themselves; at or above it they may not, so name approverUserId — a colleague from the approvers list on get_spend_policy. Hosts on protocol revision 2025-11-25 or later receive the link as a URL-mode elicitation; earlier hosts receive the identical link inside the error text. Spend that has been delivered is irrecoverable: pausing a live boost stops the next pound, never the last one, which is why this is held for a person rather than run on your word. Watch the held request with get_operation_status and list_operations: it reads awaiting_approval until somebody acts. Requires an idempotencyKey: an exact retry while the request is still waiting returns the SAME link rather than asking a second colleague about one spend.
create_bulk_boostRequired arguments: currencyCode, endsAt, idempotencyKey, lines, startsAt, targetingPresetIdTakes an idempotencyKeymedia_buyingWritesBuild up to fifty boosts at once, all PAUSED, under one envelope and one saved audience. Takes one AdTargetingPreset id, one currency, one flight and one line per post — its boostReceipt from list_boostable_posts and both its ceilings. THIS CALL SPENDS NOTHING AND STARTS NOTHING. Every advert it makes is paused, exactly as create_boost leaves one, and no path through it can make anything live: starting a wave is a separate act a named person has to agree to, on activate_boost_batch. EVERY LINE IS PRICED AND JUDGED ON ITS OWN, against the creator’s permission, the paid-media licence, the advertising network’s minimum daily budget and the client’s ceilings. A line that fails does NOT stop the wave: it comes back with outcome FAILED and the reason, its position kept, and the lines around it are still built. Read the lines array before telling anyone the wave is running. REFUSED WHOLE, before anything is built, when the receipts name more than one client (MIXED_BRANDS — send one wave per client), when the client has no live advertising connection (NO_CONNECTION), when a receipt will not open (RECEIPT_INVALID), or when the wave is wider than the advertising account's remaining unactivated boosts (UNACTIVATED_CEILING — an account holds only so many that have never gone live, create_boost names the figure, and this refusal says how many of those places are free, so a wider wave goes as more than one envelope). Requires an idempotencyKey, and it is the ENVELOPE’s own identity: retrying the whole call with the same key finds the same envelope and re-uses every advert the first attempt already made, rather than building the wave twice on a client’s account. Use it for an agency wave — one brief across many clients — and use create_boost for one post.
activate_boost_batchRequired arguments: batchId, idempotencyKeyTakes an idempotencyKeymedia_buyingDestructiveAsk for a whole wave of built boosts to start spending, on ONE approval. THIS CALL NEVER STARTS ANYTHING, at any amount: it holds the go-live on the Justify ledger for a named person and hands back an approvalUrl, an operationId and the summed price. The adverts become live only when that person opens the link while signed in to Justify and agrees; an agent holds no authority to agree, because the approval page needs a browser session an OAuth credential cannot mint. What is held is the whole price, line by line: the account currency, every line’s advert and both its ceilings as the advertising network itself reports them, the flight with the account timezone, the summed lifetime ceiling this one agreement commits, the client's monthly ceiling and what is left of it, and the spend policy revision all of it was judged against. If a ceiling changes on any line between the ask and the click, the approval is refused rather than starting a spend nobody was shown. REFUSED before anybody is interrupted when no line of the wave is a boost the advertising network has confirmed the ceilings of (NO_LIVE_LINES), when a line can no longer be priced at all — its rights, its currency or its per-boost ceilings (LINE_REFUSED) — and, the refusal a wave exists to make possible, when the SUMMED price is more than the client has left to spend this month (CLIENT_CAP_EXCEEDED). Fifty lines that each fit the ceiling can plainly exceed it together, and no per-boost approval could ever catch that. Below the client’s approvalThreshold the person who asked may agree themselves; at or above it they may not, so name approverUserId — a colleague from the approvers list on get_spend_policy. The threshold is compared against the SUM, so a wave will often need a second person where each line would not. Hosts on protocol revision 2025-11-25 or later receive the link as a URL-mode elicitation; earlier hosts receive the identical link inside the error text. A line that will not start does not stop the wave: the rest go live and that line stays READY for activate_boost on its own. What a wave has already delivered can never be taken back, which is why one person agrees to the whole of it before any of it begins. Watch the held request with get_operation_status and list_operations: it reads awaiting_approval until somebody acts. Requires an idempotencyKey: an exact retry while the request is still waiting returns the SAME link rather than asking a second colleague about one wave.
pause_boostRequired arguments: boostIdmedia_buyingWritesStop one running boost. The advertising network is told first, its own delivery verdict is read straight back, and only then does the record here move — so what comes back is what the network says rather than what Justify asked for. THE ANSWER HAS TWO SHAPES AND THEY ARE NOT THE SAME. platformPauseApplied true with the boost PAUSED means the advert has genuinely stopped. platformPauseApplied false with the boost PAUSE_REQUESTED means Justify asked and the network has not confirmed it: the advert may still be delivering, the money may still be going out, somebody has been alerted, and calling this again is the right thing to do. Never report a stop to a person on the second shape. This call is admitted whatever else is throttled. Every other write in this family can be refused for calling too often; stopping a spend cannot, because a limit that stopped somebody stopping their own money would cost them money. It spends nothing, ends nothing and deletes nothing: the advert, the ad set and the campaign all stay exactly where they are and the boost can be started again through resume_boost, which a named person has to agree to. REFUSED when the boost is not running or is already stopping — read its status with get_boost first — and when the advertising account is no longer connected.
resume_boostRequired arguments: boostId, idempotencyKeyTakes an idempotencyKeymedia_buyingDestructiveAsk for one stopped boost to start spending again. THIS CALL NEVER STARTS ANYTHING, at any amount: it prices the restart in full, holds it on the Justify ledger for a named person and hands back an approvalUrl, an operationId and the price. The advert runs again only when that person opens the link while signed in to Justify and agrees; an agent holds no authority to agree. What is held is the whole price, exactly as a first go-live holds it: the account currency, both ceilings as the advertising network itself reports them, what the boost has already spent, the flight with the account timezone, the client’s monthly ceiling and what is left of it, the creator whose handle the advert runs under, and the spend policy revision it was all judged against. REFUSED, before anybody is interrupted, when the boost is not stopped, when the advertising network has not confirmed its ceilings, when the creator’s permission or the paid-media licence no longer covers the flight, when a ceiling is now above what the client’s policy allows, or when the boost no longer fits what the client has left to spend this month. REFUSED while anything that stopped it is still standing. A boost this product stopped on its own — pacing too fast, the client’s ceiling reached, drift against the network — is refused with ALERT_UNACKNOWLEDGED until somebody records that they have seen the episode it raised: read them with list_boost_alerts and acknowledge each open one with acknowledge_boost_alert, and the restart can then be priced and held for a named person. Acknowledging proves nothing about the rights or the login, so a boost stopped for those is admitted only once the rights cover the flight again and the account is connected again, both re-read here. An advert the ADVERTISING NETWORK stopped itself is never restarted by asking: that is resolved with the network. Below the client’s approvalThreshold the person who asked may agree themselves; at or above it they may not, so name approverUserId. Requires an idempotencyKey: an exact retry while the request is still waiting returns the SAME link rather than asking a second colleague about one spend.
raise_boost_capsRequired arguments: boostId, dailyCap, endsAt, idempotencyKey, lifetimeCapTakes an idempotencyKeymedia_buyingDestructiveAsk for one boost to be allowed to spend more, or to keep running for longer. THIS CALL RAISES NOTHING, at any amount: it prices the change in full, holds it on the Justify ledger for a named person and hands back an approvalUrl, an operationId and the price. The ceilings move only when that person opens the link while signed in to Justify and agrees, and the advertising network is changed inside their own request rather than this one. Send all three figures every time, even the ones that are not moving: the daily ceiling, the total ceiling and the last spending day. Every one of them must be the same as, or higher than, what the advertising network holds today — a figure that goes DOWN is refused here and belongs to lower_boost_caps, which happens at once and needs nobody’s agreement. What is held is the whole price. Beside the two ceilings being asked for it names previousDailyCap and previousLifetimeCap — what the advertising network holds today — and increase, how much more in total this boost could spend once the raise lands. With them: the account currency, the flight with the account timezone, the client’s monthly ceiling and what is left of it after its other boosts, the creator whose handle the advert runs under, and the spend policy revision it was judged against. A previousLifetimeCap that moves between the ask and the click voids the approval rather than raising from a figure nobody was shown. REFUSED, before anybody is interrupted, when a ceiling is above what the client’s spend policy allows one boost to spend, when the total is more than the client has left this month, when the advertising network has not confirmed the ceilings the boost holds now, when the rights behind the post no longer cover the flight, or when a guardrail rather than a person is what stopped the boost. RATE_LIMITED when this boost has already had four ad set budget changes in the past hour, which is the advertising network’s own limit. The answer carries retryAfterSeconds; wait that long and ask again. Below the client’s approvalThreshold the person who asked may agree themselves; at or above it they may not, so name approverUserId. Requires an idempotencyKey: an exact retry while the request is still waiting returns the SAME link.
lower_boost_capsRequired arguments: boostId, dailyCap, endsAt, idempotencyKey, lifetimeCapTakes an idempotencyKeymedia_buyingWritesReduce how much one boost may spend, or bring forward the day it stops, now. This one does NOT wait on anybody: lowering a ceiling reduces what a client can spend, so there is nobody to protect by holding it. The advertising network is changed first, both ceilings are read straight back off it, and only then does the record here move. Send all three figures every time, even the ones that are not moving: the daily ceiling, the total ceiling and the last spending day. Every one of them must be the same as, or lower than, what the advertising network holds today — a figure that goes UP is refused here and belongs to raise_boost_caps, which a named person has to agree to. capsVerified false is the answer that matters: the advertising network reported back something other than what was asked for, so the boost has been put where nothing can start it and somebody has been paged. The caps you sent are never echoed back to you; what comes back on the boost is what the network itself reports. RATE_LIMITED when this boost has already had four ad set budget changes in the past hour, which is the advertising network’s own limit. The answer carries retryAfterSeconds, and adSetBudgetChangesRemainingThisHour on a success tells you how many are left before it bites. REFUSED when the boost has finished, failed or been refused by the network, when the advertising network has not confirmed the ceilings it holds now, and when the figures sent are the ones it already holds.
end_boostRequired arguments: boostId, idempotencyKeyTakes an idempotencyKeymedia_buyingDestructiveEnd one boost for good. THIS CANNOT BE UNDONE: the advert, the ad set and the campaign are archived on the advertising network, and a client who wants that advert again buys a new one. Nothing is ever deleted — everything stays readable in Ads Manager with its history and its figures intact — but nothing archived here is ever turned back on. The order is what makes it safe. A boost that is still running is stopped first, and the advertising network has to CONFIRM the stop; then the three objects are archived, innermost first; then what the boost actually cost is read off the network; and only then is it recorded as finished, with that figure on it. REFUSED, with nothing archived, when the advertising network will not confirm the stop. The boost is left marked as stopping, somebody is alerted, and it may still be delivering — read its status and try again rather than reporting it as ended. Refused too when the network does not report what it spent: everything is stopped and archived by then, and asking again finishes it. It holds nothing for anybody and asks nobody to agree, because no money leaves when a boost ends. Stopping a boost you may want back is pause_boost; this is the one that closes it. REFUSED when the boost has already finished, failed or been refused by the advertising network, and when the advertising account is no longer connected. Requires an idempotencyKey: an exact retry returns the answer the first call got rather than asking the network to archive an advert twice.
list_boostsmedia_buyingReadsList the boosts in this organisation, newest first: the licensed creator posts that are running, or have run, as paid adverts under the creator’s own handle. Returns one row per boost with id (the id every other boost tool takes), brandId, campaignId, postId, creatorId, connectionId, provider, status, pauseReason, currencyCode, providerDailyCapReadBack, providerLifetimeCapReadBack, capsVerifiedAt, startsAt, endsAt, the four provider object ids, sagaStep, policyRevisionId, contentLicenseId, permissionId, createdVia, activatedAt, spendToDate and version. THE ONLY CAPS ON THIS SHAPE ARE THE ONES THE NETWORK REPORTED BACK, and they are null until it has confirmed them. The figures a person entered are a request Justify made; get_boost publishes both pairs side by side under names that cannot be confused. status ACTIVE is the only state that spends; READY means built and paused, waiting for somebody to agree; AWAITING_APPROVAL means somebody has been asked; PAUSE_REQUESTED means Justify asked the network to stop and is waiting for it to say that it did; PAUSED_UNVERIFIED means the caps the network reports do not match the caps that were approved. pauseReason says who or what stopped it. Filter by campaignId, creatorId, provider, status and pauseReason. Pages with cursor and limit: when hasMore is true, call again with cursor set to the returned nextCursor. Never answer "how many boosts" from one page: total is every boost matching the filter, the rows are one page of it. Use it to answer what is running behind a campaign, what a creator’s handle is carrying, and what stopped and why; then get_boost for one in full, list_boost_alerts for what has gone wrong, and list_boost_receipts for what was actually done on the network.
get_boostRequired arguments: boostIdmedia_buyingReadsRead one boost in full: the row as list_boosts publishes it, both pairs of caps, the advertising network’s own status and review feedback, and the approval chain behind it. THE TWO PAIRS OF CAPS ARE NAMED APART ON PURPOSE. caps.enteredDailyCap and caps.enteredLifetimeCap are what Justify was asked to ask the network for; caps.readBackDailyCap and caps.readBackLifetimeCap are what the network says it holds, and caps.capsVerifiedAt is when that reading was taken. They are only ever equal because something checked, so never quote an entered figure as a ceiling: a boost whose read-back pair is null has no ceiling anybody has confirmed, and one whose pairs disagree is the exact failure the read-back exists to catch. Every figure is a decimal string in caps.currencyCode. effectiveStatus is the network’s own word for the advert, kept verbatim; reviewFeedback is why it was rejected or flagged, and a rejection is never resubmitted. approvals is the chain, newest ask first, read from the operation ledger and the approval records and never recomputed: operationId names the ledger row to watch with get_operation_status, operationRawStatus reads AWAITING_APPROVAL while a named person still has to decide, and decision, approvedByUserId, amountApproved and decidedAt are null until somebody does. There is no approval link on this answer and there will not be one: it is single-use, it belongs to the colleague it was minted for, and an agent cannot open it. APPROVALS IS BOUNDED, so never quote it as the whole history without reading the two fields beside it: approvalsTotal is every ask ever made for this boost and approvalsHasMore is true when this answer carries only the newest of them. A boost that has been raised more times than the bound needs list_boost_approvals, which pages the whole chain. Use it to answer what one boost is actually capped at, whether the network agreed, who agreed to the spend and when. Find the id with list_boosts.
list_boost_approvalsRequired arguments: boostIdmedia_buyingReadsRead the approval chain for one boost, newest ask first: every time somebody asked for it to start spending, and what was decided. Returns one row per ask with operationId, operationStatus, operationRawStatus, requestedAt, approvalId, decision, requestedByUserId, approvedByUserId, amountApproved, currencyCode, payloadHash, policyRevisionId, decidedAt and expiresAt, plus the page keys total, hasMore and nextCursor. IT IS READ, NEVER REBUILT. An ask that nobody has settled has a ledger row and no approval record, because the record is written by the settlement and by nothing else: that link reads pending, with operationRawStatus AWAITING_APPROVAL and a null decision, approver and amount. There is no approval link on this answer, deliberately: it is single-use and belongs to the colleague it was minted for. amountApproved is the money the approver was actually shown, as a decimal string in currencyCode, and payloadHash is the fingerprint of what they read, so a receipt, an operation record and an approval page can be compared as three copies of one figure. Use it to answer who agreed to this spend, when, under which spend policy, and whether anybody is still waiting to be asked. Find the boost id with list_boosts.
list_boost_alertsmedia_buyingReadsRead the guardrail episodes raised against this organisation’s paid media, newest first: pacing, spend spikes, zero delivery, a disapproval, lapsed rights, a dead login, a client ceiling, cap drift and stale figures. Returns one row per episode with id, boostId, connectionId, brandId, kind, severity, dedupeKey, message, context, raisedAt, acknowledgedAt, acknowledgedByUserId and resolvedAt, plus the page keys total, hasMore and nextCursor. ONE ROW PER EPISODE, NOT PER READING: dedupeKey is the episode, so a breach that keeps breaching pages once rather than every fifteen minutes. acknowledgedAt records that somebody saw it and resumes nothing; resolvedAt is when the condition itself cleared, and an episode can be acknowledged and still unresolved. context carries the figures the raiser recorded, verbatim. Naming a boostId narrows the list to that boost. Omit it to see the whole organisation, which is the only way to see the episodes raised about an AD ACCOUNT rather than a boost, such as a login that has stopped working or a client that has reached its monthly ceiling: those carry a null boostId. Use it to answer what has gone wrong, what stopped on its own and what somebody still has to look at.
list_boost_receiptsRequired arguments: boostIdmedia_buyingReadsRead the money ledger for one boost, OLDEST FIRST: every call Justify made on the advertising network for it, and every decision recorded about it. Returns one row per event with id, eventType, subjectType, subjectId, boostId, connectionId, actorKind, actorUserId, principal, mcpOperationId, approvalId, policyRevisionId, before, after, providerObjectId, providerRequestId, usageHeaders and createdAt, plus the page keys total, hasMore and nextCursor. IT IS NEVER SUMMARISED. The rows are served in the order they happened, because that is the order an auditor rebilling a client reads them in, and a summary is exactly what they cannot check. The ledger is append-only: a row is what was true when it was written, and a correction is another row. providerRequestId is the network’s own request id, fbtrace_id on Meta and request_id on TikTok, which is what ties a Justify row to the platform’s own log; usageHeaders is the rate-limit snapshot the network answered with, where the writer stored one; before and after are the object as it stood on either side of the call. Pages with cursor and limit, oldest first, so a cursor into it stays valid however many rows are appended afterwards. Use it to answer what was actually done on the network for this boost, under whose approval and against which spend policy. Find the boost id with list_boosts.
acknowledge_boost_alertRequired arguments: alertIdmedia_buyingWritesRecord that somebody has seen one guardrail episode. Returns acknowledged — true when THIS call wrote the stamp, false when it was already recorded — beside the episode itself, with acknowledgedAt and acknowledgedByUserId as they now stand. IT RESOLVES NOTHING AND STARTS NOTHING. resolvedAt belongs to the guardrail engine, which clears it when the condition itself goes away, so an acknowledged episode is normally still unresolved. No advert is touched, no ceiling moves and no money is committed. WHAT IT UNLOCKS. A boost this product stopped on its own cannot be priced for a restart while any episode about it is unread: resume_boost refuses with ALERT_UNACKNOWLEDGED. Acknowledging every open episode lets that restart be priced and held for a named person to agree to, and every other fact — the rights, the ceilings the advertising network reports back, the client's remaining month and the live connection — is judged again on that path. An advert the NETWORK stopped is not unlocked by this or by anything else here: that is resolved with the network. Calling it twice is safe and writes nothing the second time. REFUSED when no episode in this organisation has that id, and when the condition behind it has already cleared — a resolved episode has nothing left for anybody to say they have seen. Find the id with list_boost_alerts, which also shows which episodes are still open.
get_ad_account_insightsmedia_buyingReadsRead what a client's whole advertising account did: impressions, reach, clicks and spend, at account, campaign, ad set or advert level, over a window you name. THIS IS THE ONLY TOOL THAT ANSWERS ABOUT ADVERTS JUSTIFY DID NOT BUILD — get_boost_results and the boost reads answer only about the creator posts this organisation boosted, and a client is billed for the whole account. Meta only in this version: a brand connected to TIKTOK_ADS is refused by name, because that network reports on a different window grammar. Name the window as datePreset (TODAY, YESTERDAY, LAST_3_DAYS, LAST_7_DAYS, LAST_14_DAYS, LAST_28_DAYS, LAST_30_DAYS, LAST_90_DAYS, THIS_MONTH, LAST_MONTH, THIS_QUARTER, LAST_QUARTER, THIS_YEAR, LAST_YEAR) OR as since and until days, never both. A WINDOW WIDER THAN NINETY DAYS ANSWERS WITH A HANDLE AND NO FIGURES. mode reads REPORT_REQUESTED, reportRunId carries the advertising network’s own report id, rows is empty and total is zero: that is not a quiet quarter, it is a report being built. Call again with reportRunId and nothing else; while the job runs the answer stays REPORT_REQUESTED with reportStatus and reportPercentComplete from the network verbatim, and when it finishes mode reads ROWS and the figures arrive. THIS_QUARTER, LAST_QUARTER, THIS_YEAR and LAST_YEAR always take that path, because a quarter can be ninety-two days. A report lives thirty days from the moment it was asked for; after that the call is refused with conflictReason REPORT_EXPIRED naming the day it died, and the fix is to ask for the window again. Nothing here is held for anybody’s approval: it is a read, and the handle is the network’s own. timeIncrement cuts the window into rows: WHOLE_WINDOW (the default) is one row for the lot, DAILY one per day, WEEKLY per seven days, FOUR_WEEKLY per 28 days, MONTHLY per calendar month. breakdown splits every row by ONE curated dimension: AGE, GENDER, AGE_AND_GENDER, COUNTRY, REGION, PUBLISHER_PLATFORM, PLATFORM_POSITION, IMPRESSION_DEVICE, PLACE_PAGE_ID, IMAGE_ASSET or VIDEO_ASSET. The columns it added are on every row under breakdowns, in the network’s own names. PLACE_PAGE_ID, IMAGE_ASSET and VIDEO_ASSET are not available at ACCOUNT level and are refused with INVALID_REQUEST and retryable false before the call is made, so ask for them at CAMPAIGN, ADSET or AD level. EVERY FIGURE IS THE NETWORK’S OWN. spend is a decimal string in currencyCode, the ad account’s currency rather than the organisation’s, so an agency reading six clients never adds two currencies together. A count the network did not report is null and never a zero. dateStart and dateStop on each row are the days the network says that row is for. AT MOST 500 ROWS PER CALL, and rowCeiling says so on every answer. When hasMore is true the answer is one page: call again with cursor set to nextCursor. total is how many rows this call served and totalIsExact is false whenever there is another page, so a page is never quoted as a quarter. Use it to answer what a client spent, which campaign carried it, who saw it and on which platform. Find the brandId with list_brands, and use get_boost_results for the creator posts this organisation boosted rather than for the account as a whole.
get_spend_reconciliationmedia_buyingReadsRead what the advertising network says it billed on a connected ad account against what Justify recorded, one row per account per period, newest first. Returns id, brandId, connectionId, boostId, periodStart, periodEnd, timezone, platformSpend, recordedSpend, variance, currencyCode, status, providerReportRunId, providerReportRunRequestedAt, reconciledAt, createdAt and updatedAt, plus the page keys total, hasMore and nextCursor. THE NETWORK’S FIGURE IS THE AUTHORITATIVE ONE. platformSpend is what the account was billed; recordedSpend is what Justify held before the comparison; variance is the first less the second, SIGNED, so a positive figure means Justify was under-recording. Every figure is a decimal string in currencyCode, which is the AD ACCOUNT’s currency whatever a boost’s own ceiling was agreed in. THE DAYS ARE THE ACCOUNT’S OWN. periodStart and periodEnd are calendar days in timezone, never instants, so a period never has to be re-derived through a zone. status MATCHED means the two figures agreed inside the tolerance, which is one percent of the network’s figure or one unit of the account currency, whichever is smaller. VARIANCE means they did not, and a SPEND_VARIANCE episode was raised once for that account and period; find it with list_boost_alerts. PENDING_REPORT is a month whose evidence has been asked for and not yet pulled, so its figures are not yet a measurement. REPORT_EXPIRED is a monthly report run whose thirty-day life ran out before anybody pulled it. UNMEASURABLE is a month the network never answered whole, given up on after repeated attempts: it holds no figures, a RECONCILIATION_UNMEASURABLE episode says why, and nothing will be asked of the network for that month again. providerReportRunId is the network’s own asynchronous report run for a MONTHLY row, and providerReportRunRequestedAt is when it was asked for: the run is kept for thirty days from that moment and nothing can be pulled from it afterwards. Both are null on a nightly row, whose figures came from a synchronous read. Filter by brandId, connectionId, status, periodFrom and periodTo. Pages with cursor and limit: when hasMore is true, call again with cursor set to the returned nextCursor. Never answer "how much did we spend" from one page: total is every period matching the filter, the rows are one page of it. Use it to answer whether Justify’s numbers can be rebilled to a client, where they drifted and by how much; then list_boost_receipts for what was actually done on the network for one boost.
get_spend_policymedia_buyingReadsRead the live spend policy for every client brand this organisation buys media for, or for one of them. Returns one row per brand under policies with a total, plus approvers, and each row carries brandId, brandName, hasOwnPolicy, revision, policyRevisionId, currencyCode, monthlyCap, perBoostDailyMax, perBoostLifetimeMax, approvalThreshold, accounts, connectionId, providerAccountId, providerAccountName, accountCurrencyCode, currencyMismatch, amountSpentReadBack, spendReadBackAt, headroom and pendingApproval. accounts names EVERY connected ad account this brand buys with, each with its network, so a brand live on both networks is never described as live on one; connectionId and the providerAccount fields name the first of them, which is the account amountSpentReadBack came from. Every amount is a decimal string with two places, stated in the currencyCode on that same row; headroom is monthlyCap less amountSpentReadBack and is SIGNED, so a brand that has spent past its ceiling reads as a negative figure rather than as an empty tank. amountSpentReadBack is what the advertising network itself last reported the account had spent, with spendReadBackAt saying when; it is never a figure Justify computed, and this call takes no new reading — refresh_ad_account_state does that. hasOwnPolicy false means the brand has no policy of its own and is reading the organisation default, so a ceiling shown against it was set for everybody. currencyMismatch true means the ceiling and the ad account are stated in different currencies, and amountSpentReadBack, spendReadBackAt and headroom all come back null rather than subtracting one currency from another. revision and policyRevisionId name the revision the figures come from: policy is append-only, so the ceiling a boost was approved under stays readable after somebody moves it. approvalThreshold is the amount at or above which a boost needs a second person. pendingApproval is a raise this brand is already waiting on, naming the colleague it was sent to, when it was asked for and when the approval link expires — nobody can settle it after that and it has to be asked for again; a second raise for the same brand is refused while one waits. approvers are the colleagues who could settle such a raise, and never the person asking, because the approver must differ from the requester. Use it to answer what a client may spend this month, how much headroom is left, who has to agree before a ceiling moves and whether a change is already waiting. Lower a ceiling with tighten_spend_policy and raise one with loosen_spend_policy.
tighten_spend_policyRequired arguments: approvalThreshold, idempotencyKey, monthlyCap, perBoostDailyMax, perBoostLifetimeMaxTakes an idempotencyKeymedia_buyingWritesLower a client brand's spend ceilings, as a new revision written straight away. Takes all four figures in full — monthlyCap, perBoostDailyMax, perBoostLifetimeMax and approvalThreshold — because a revision is a complete policy rather than a delta, and returns outcome saved, the brand row the save produced, raised as an empty list, and approvalUrl and operationId both null. Every amount is a decimal string with two places. The currency is never yours to choose: a brand that already has a policy keeps the currency that policy is stated in, and a brand writing its first revision states it in the currency its connected ad account bills in, because a cap the network will refuse is not a cap. A brand with neither is refused and told to connect the account first. REFUSED IF ANY FIGURE GOES UP, naming the ones that do and sending you to loosen_spend_policy: raising a ceiling needs a second person and this tool never asks for one. A save that lowers one figure and raises another is a raise, whole, and belongs to that tool. Nothing is edited: policy is append-only, so the ceiling a live boost was approved under stays readable underneath the new one. Read the current figures with get_spend_policy first. Requires an idempotencyKey so a retried call cannot write a second revision.
loosen_spend_policyRequired arguments: approvalThreshold, approverUserId, idempotencyKey, monthlyCap, perBoostDailyMax, perBoostLifetimeMaxTakes an idempotencyKeymedia_buyingDestructiveAsk for a client brand's spend ceilings to be raised. THIS CALL NEVER RAISES ANYTHING. It writes no revision at all: it holds the change on the Justify ledger for the colleague you name and hands back outcome parked, the still-live brand row, the ceilings it would raise, an operationId and an approvalUrl. The higher ceiling exists only after that person opens the link while signed in to Justify and agrees; an agent holds no authority to agree, because the approval page needs a browser session an OAuth credential cannot mint, and the person who asks can never be the person who agrees. Takes all four figures in full — monthlyCap, perBoostDailyMax, perBoostLifetimeMax and approvalThreshold — as decimal strings with two places, plus approverUserId, the colleague who will settle it. Read the candidates from the approvers list on get_spend_policy; it never contains the person asking. REFUSED IF NOTHING GOES UP, and sent to tighten_spend_policy instead: a save that only lowers costs nobody anything and waiting on a colleague to allow it would be perverse. Refused while a raise for the same brand is already waiting, so a second one cannot be approved weeks later over a ceiling that has moved. Refused when the organisation has no other member who could agree. Watch the held request with get_operation_status and list_operations: it reads awaiting_approval until somebody acts. If the ceiling is tightened while it waits, the approval is refused rather than writing the figures you sent over a lower ceiling somebody set deliberately. Use it when a client needs more headroom than the live policy allows. Requires an idempotencyKey: while the raise is still waiting a retry with the same key is told the request is in progress rather than asking a colleague twice, and once somebody has settled it the same key answers with what they decided.

Marketing Mix Model

Where your sales come from. The Marketing Mix Model fits a weekly spend sheet against what you sold and returns how much of the outcome each channel drove over the window, with the range around each figure, the spend behind it, and the response curves saying whether a channel is saturated and how long a week of spend keeps working. Read the runs your organisation has fitted, open one for the headline answer, ask for the curves separately when you want them, ask what one channel would bring at a weekly spend you choose, read the ladder of next tests the run ranked, start a new fit from a sheet you hold, and open a campaign draft from the influencers a run credited. Read the verdict before you quote a number: a run marked exploratory did not converge, did not pass calibration, or needs review, and its figures are indicative. Checks the product has not built yet are returned as not_checked, naming the card that will bring them, never as a pass.

Entitlements used by this family: campaign_wizard, mmm

Try asking

  • Which Marketing Mix Model runs have we fitted, and did the last one converge?
  • Open that run: where did our sales come from over the window it modelled?
  • Is paid social saturated on that run, and what would another pound buy?
  • Fit a Marketing Mix Model on this weekly spend sheet against our revenue column.
  • What would paid social bring if we spent 4,000 a week on it, on that run?
  • What is the cheapest test we could run next to sharpen that run?
  • Open a campaign draft with the influencers that run credited.
ToolEntitlement neededEffectWhat it does
list_mmm_runsmmmReadsList the Marketing Mix Model runs this organisation has fitted, newest first. The Marketing Mix Model is the measurement that says where your sales came from: how much of the outcome each channel drove over a modelled window. Each row carries analysisRunId, orgId, status, modelVersion, createdAt, completedAt, converged, calibrated, calibrationBridgeId, the brandIdentityId and brandName the run modelled, the window it covered, channelCount, outcomeKind and holdoutMape, the mean error on the weeks the model never saw. Every one of those can be null and a null is an honest gap: a run still sampling has no envelope, and a run whose stored input set no longer parses has no window or channel count rather than an invented one. Start here, then take an analysisRunId into get_mmm_report for the headline answer and get_mmm_response_curves for the spend curves. There is no cursor: hasMore true means raise limit, up to 50, and ask again. An organisation that has never fitted a model answers an empty list, never an error.
get_mmm_reportRequired arguments: analysisRunIdmmmReadsOpen one Marketing Mix Model run and read what it concluded: where the organisation’s sales came from over the modelled window. Returns run (analysisRunId, status, modelVersion, createdAt, completedAt), window, outcome, contributions (per channel: contributionValue in outcome units with its interval, contributionShare, spend and currencyCode), baseline, claimPolicy (verdict model_consistent or exploratory, the reasons behind it, and the pooledIntervalCaveat to quote whenever you quote an interval), diagnostics, holdout, calibration and flags. Read claimPolicy.verdict before quoting a number: an exploratory run is a model that did not converge, did not pass calibration, or needs review, and its figures are indicative rather than decided. The response curves and the per-period rows are deliberately not here, because the envelope carries hundreds of kilobytes of them: ask get_mmm_response_curves when you want the spend curves. A run whose model has not returned yet answers ready false with a reason and a detail to relay, which is an honest state and never an error.
get_mmm_response_curvesRequired arguments: analysisRunIdmmmReadsRead the response curves one Marketing Mix Model run fitted: how the modelled outcome changes as spend on a channel changes, and how long a week of spend keeps working. Returns curves, each with curveId, metricId, curveKind (saturation_response or adstock_decay), the channelId it belongs to, functionalForm, xUnit, yUnit, ciLevel and a grid of up to 64 points carrying x, yMean, yHdiLow, yHdiHigh and marginalReturn, plus currentSpendPerPeriodMicros saying where each channel sits on its own curve today. Use it after get_mmm_report when somebody asks whether a channel is saturated, what another pound would buy, or how far ahead this week’s spend still works. Name a channelId to get one channel rather than every curve on the run. These are the model’s own grids, not a forecast: read the report’s claimPolicy.verdict first, because an exploratory run’s curves are indicative. A run with nothing publishable answers ready false with a reason to relay.
mmm_what_ifRequired arguments: analysisRunId, channelId, spendMicrosmmmReadsAsk what one channel would bring at a weekly spend you choose, answered from one Marketing Mix Model run’s own fitted curve. This is the same question the report’s Ask panel asks and the same answer it is given. Send analysisRunId from list_mmm_runs, channelId from get_mmm_report’s contributions, and spendMicros as whole micros written as digits (a string, never a JSON number: a big weekly budget overruns the exactly-representable integer range, and a silently rounded budget is the worst possible input to a question about budgets). Returns readings with responseMean, the best estimate at that spend in the outcome’s own unit, and the receipt naming the curve artefact it was read off, plus verdict and caveat. There is no range on this figure and one must never be invented for it: the reading plugs the curve’s own best-estimate coefficients into its saturation form at that one spend, and the run’s own ranges live on the stored grid that get_mmm_response_curves serves. A status of absent means the run carries no curve for that channel, which is a gap and never a zero. Read verdict before quoting: on an early read the caveat is the withheld-guidance sentence, and this figure is arithmetic rather than advice. A run with nothing publishable answers ready false with a reason to relay.
propose_next_testRequired arguments: analysisRunIdmmmReadsRead the ladder of next tests one Marketing Mix Model run proposed: what to change on one channel for a few weeks so the model can tell that channel apart from the rest, in the run’s own ranked order, cheapest narrowing first. Send analysisRunId from list_mmm_runs. Returns designPeriods, the weeks each design runs for; rungZero, the free rung, a stretch already in your own weeks where one channel changed on its own, absent with its reason until the miner that finds one is built; refusal, why this run could not rank a next test at all, carrying the scorer’s own reason and null when it ranked one; rungs, each carrying kind (pause, flight_above, flight_below or geo_split), channelId, displayName, periods, cost in whole currency units, currencyCode, narrowing as the share of the range the design removes, widthRatio and the darkGeoIds a geo split goes dark in; and study, the Brand Lift rung, which is the top of the ladder and never a requirement, carrying price or the priceReason saying why no per-study figure exists to show. A cost is media you would be buying differently for those weeks rather than a fee, and a null currencyCode means the run mixes currencies so nothing was totalled. Read verdict before quoting a rung: on an early read the caveat is the withheld-guidance sentence, and the ladder is arithmetic rather than advice. An empty rungs list with refusal null means every channel is already dark in the latest weeks, while an empty one beside a refusal means the scorer could not rank anything and the reason says why; a run whose envelope carries no ladder at all answers ready false with reason ladder_absent, which is an honest gap and never an empty ladder.
start_mmm_runRequired arguments: csv, currencyCode, idempotencyKey, mappings, outcomeColumn, outcomeKind, reason, weekColumnTakes an idempotencyKeymmmWritesFit a new Marketing Mix Model on a weekly spend sheet, which is the measurement that says where your sales came from. Send the sheet as csv text with the header of the week column, the header of the outcome column, what that outcome counts, the currency, and one mapping per channel column. brandIdentityId names the brand whose sales the model must account for: required when your organisation holds several brands and no active brand is stored, and always one of your own. Requires idempotencyKey (an exact retry replays the original answer rather than queuing a second run) and reason, your own reason of at least 10 characters, which is audited with the write. Returns data with triggerId, brandIdentityId and preflight, the checks the front door ran on your sheet: which channels it read spend for, whether that spend varies enough to be told apart from the baseline, whether the weeks have holes, and whether the outcome column is complete. A check reading not_checked names the card that will bring it, a check reading engine_checked is one the model itself runs on the fitted run rather than on your sheet, and neither is ever a pass. ledgerChannels is optional: send ["creator"] and the influencer channel is read out of your own campaign ledger rather than from a column you typed back into the sheet. driftChannels is optional: name the channels whose effect you believe changed over the window and each one is then fitted with an effect that moves rather than one number for the whole period, at the cost of a wider range on it. geoLevel is optional: name country or region and the model explains your connected store’s own sales for the one place your orders in that window came from, read off the orders, instead of the outcome column you sent. The model itself takes minutes: poll list_mmm_runs for the run, then get_mmm_report once it lands. A sheet with a data column you did not map is refused by name rather than modelled on half a media plan, and unknown input keys are refused rather than ignored.
book_creators_from_mmmRequired arguments: analysisRunId, idempotencyKey, reasonTakes an idempotencyKeycampaign_wizard, mmmWritesOpen one campaign wizard draft pre-filled from the influencer posts a Marketing Mix Model run credited, so the next campaign starts from the media the model actually accounted for. Send analysisRunId from list_mmm_runs. Returns data with campaignId, status DRAFT, currentStep, name, brandIdentityId and influencers, each carrying the run’s channelId, its displayName and postRefId, the partner’s own identifier for the post from the run’s mapping receipt. It creates a DRAFT and nothing else: no campaign is submitted, nobody is invited and no payment is touched, so carry the campaignId into save_campaign_wizard_step, set_campaign_wizard_creators and submit_campaign_wizard to take it further. postRefId is not a saved influencer id, so resolve each one with list_influencers or a marketplace search before attaching anybody. A run whose media plan named no influencer posts is refused by name rather than opening an empty draft, and so is a run with nothing publishable yet. Requires idempotencyKey (an exact retry replays the original draft rather than opening a second one) and reason, your own reason of at least 10 characters, which is audited with the write.

The cookbook

Cookbook: from a blank campaign to a creative prediction

This is one journey run end to end. Every call is a real tool with its real arguments, in the order the server expects them. Identifiers shown as UUIDs are placeholders — use the ones the previous call returned.

Two rules run through the whole thing. Every write takes an idempotencyKey you choose and keep stable, so a retry after a timeout returns the original result instead of doing the work twice. And anything that spends money — the marketplace search, a Creative Testing run — is never a single call: you see the cost first and approve it.

1. Find out which brand you are working on

Nearly every tool answers for one brand, and it reads the brand you already have selected rather than one you name: your stored active brand is the rule everywhere. set_active_brand is what changes it, and it is the one tool that takes a brandId of its own. When a tool resolves your active brand it says so in an ACTIVE_BRAND_APPLIED notice beside the answer; when your organisation holds several brands and none is selected, the call refuses with ACTIVE_BRAND_REQUIRED and names them rather than picking one.

```json
{
  "tool": "list_brands",
  "arguments": {}
}
```

2. Create the draft campaign

This creates a draft Campaign Wizard through the same path the web app uses. Give it the basics you already know; anything missing can be filled in on the next step.

The response carries the new campaignId and a version number. Keep the version — the step saves use it to detect that somebody else edited the draft in the meantime.

```json
{
  "tool": "create_campaign_wizard",
  "arguments": {
    "idempotencyKey": "autumn-skincare-create-2027-09-01",
    "name": "Autumn skincare launch",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "brand": "Fernwick",
    "businessGoal": "Grow awareness of the autumn serum range with UK skincare audiences.",
    "startDate": "2027-09-15T00:00:00Z",
    "endDate": "2027-10-31T00:00:00Z",
    "timeZone": "Europe/London",
    "budget": {
      "currency": "GBP",
      "total": 40000,
      "socialMedia": 25000
    },
    "targetPlatforms": [
      "INSTAGRAM",
      "TIKTOK"
    ]
  }
}
```

3. Save the objectives, then the audience

Steps 2 and 3 of the wizard are the objective and the audience. Send them one at a time, each with the version the previous call returned as expectedVersion. Ask the person for anything you do not know rather than inventing it — the wizard is the record the rest of the platform measures against.

Step 4 is the asset step and is not saved this way: assets are attached with link_campaign_wizard_asset, further down.

```json
{
  "tool": "save_campaign_wizard_step",
  "arguments": {
    "idempotencyKey": "autumn-skincare-step2-2027-09-01",
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "step": 2,
    "expectedVersion": 1,
    "data": {
      "primaryKPI": "BRAND_AWARENESS",
      "messaging": {
        "mainMessage": "A gentler autumn routine, backed by dermatologists.",
        "hashtags": [
          "#autumnskin"
        ]
      },
      "expectedOutcomes": {
        "BRAND_AWARENESS": "Lift prompted awareness among UK women 25-44 by five points."
      },
      "step2Complete": true
    }
  }
}
```
```json
{
  "tool": "save_campaign_wizard_step",
  "arguments": {
    "idempotencyKey": "autumn-skincare-step3-2027-09-01",
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "step": 3,
    "expectedVersion": 2,
    "data": {
      "demographics": {
        "genders": [
          "Female"
        ],
        "age25_34": 60,
        "age35_44": 40
      },
      "locations": [
        {
          "location_id": "gb",
          "display_name": "United Kingdom"
        }
      ],
      "step3Complete": true
    }
  }
}
```

4. Turn the brief into a costed search plan — this spends nothing

prepare_influencer_search resolves your sentence into canonical Justify filters. It calls no provider and spends no credit. It answers with the exact filters it settled on, the estimated credit cost, a confirmation text written for the person, and a short-lived single-use confirmation token.

If a filter you asked for is not supported, this is where you find out — the tool refuses with the canonical suggestion rather than quietly dropping it.

```json
{
  "tool": "prepare_influencer_search",
  "arguments": {
    "prompt": "UK fitness and wellbeing creators with 10k to 100k followers and strong engagement",
    "platform": "instagram",
    "parsePrompt": true,
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "limit": 16,
    "offset": 0
  }
}
```

5. Show the person the confirmation text and the cost, word for word

Do not paraphrase this step and do not skip it. Print the confirmationText the tool returned exactly as returned, alongside estimatedCreditCost, and wait for an explicit yes. A paid search nobody asked for is the failure the two-step flow exists to prevent.

Report the number the tool gave you; never estimate one. Offset 0 is the only page that can spend a credit, and a result already cached for your organisation costs nothing. The token expires — if it has, prepare again and ask again rather than reusing old approval.

6. Run the search you were given permission for

Send back the confirmationToken and the exact confirmationText you showed, with a stable idempotencyKey of your own. The token is single-use. Repeating the same idempotencyKey after a timeout returns the original response and its original receipts instead of charging twice.

Each creator in the response carries a single-use searchReceipt. That receipt is what lets you save the creator or put them on a campaign without a second paid lookup.

```json
{
  "tool": "confirm_influencer_search",
  "arguments": {
    "idempotencyKey": "autumn-skincare-search-2027-09-01",
    "confirmationToken": "<confirmation-token-from-step-4>",
    "confirmationText": "Search Instagram for UK fitness and wellbeing creators with 10,000-100,000 followers. This will use 1 influencerSearch credit."
  }
}
```

7. Save the creators you want to keep

Saving puts a creator on your organisation roster, where list_influencers, get_influencer and the collaboration workspace can reach them. Save by handle and platform with the fresh searchReceipt, or by influencerId if the creator is already saved.

One thing to expect rather than treat as an error: saving refreshes the creator record but not the profile analytics cache, which is warmed when a person opens the profile in the web app. get_influencer_profile_analytics will miss for a creator you just saved.

```json
{
  "tool": "save_influencer",
  "arguments": {
    "idempotencyKey": "autumn-skincare-save-lauren-2027-09-01",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "handle": "lauren.runs",
    "platform": "instagram",
    "searchReceipt": "rcpt_9a1b2c3d4e5f6071"
  }
}
```

8. Put the creators on the campaign

The draft you built in step 2 takes its roster through set_campaign_wizard_creators. Mode append adds to whoever is already there; mode replace swaps the roster wholesale. Each creator is identified either by a saved influencerId or by a fresh searchReceipt — handle and platform are match fields only.

If you are working on an owned draft campaign you did not build in this session, add_influencers_to_campaign is the same act through the canonical campaign-creator path. Use one or the other, not both.

```json
{
  "tool": "set_campaign_wizard_creators",
  "arguments": {
    "idempotencyKey": "autumn-skincare-creators-2027-09-01",
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "mode": "append",
    "creators": [
      {
        "influencerId": "7d6e5f4a-3b2c-4109-8f7e-6d5c4b3a2918"
      }
    ]
  }
}
```
```json
{
  "tool": "add_influencers_to_campaign",
  "arguments": {
    "idempotencyKey": "autumn-skincare-add-creators-2027-09-01",
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "influencers": [
      {
        "influencerId": "7d6e5f4a-3b2c-4109-8f7e-6d5c4b3a2918"
      }
    ]
  }
}
```

9. Attach the creative you will test

Creative Testing needs one linked, runnable asset. Browse the library for a ready, brand-owned asset, then link it to the draft. Only existing brand-owned assets whose durable-storage and link-readiness checks pass can be attached — upload identifiers and raw submission ids are refused.

If the file is not in the library yet, request_library_asset_upload gives you an upload URL and finalize_library_asset closes it out once the bytes are stored.

```json
{
  "tool": "list_library_assets",
  "arguments": {
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "limit": 20
  }
}
```
```json
{
  "tool": "link_campaign_wizard_asset",
  "arguments": {
    "idempotencyKey": "autumn-skincare-link-asset-2027-09-01",
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "assetId": "9c8b7a65-4d3e-42f1-8a09-b1c2d3e4f5a6",
    "role": "hero",
    "rationale": "Fifteen-second vertical cut, strongest opening three seconds."
  }
}
```

10. Validate, then submit

validate_campaign_wizard runs the same server-derived completion and blocking rules as the web app. If canSubmit is false, report the blockingIssues exactly as returned rather than guessing at the cause.

Submit only after canSubmit is true. Submission is the trust gate: Creative Testing and Brand Lift both refuse to work from a draft.

```json
{
  "tool": "validate_campaign_wizard",
  "arguments": {
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d"
  }
}
```
```json
{
  "tool": "submit_campaign_wizard",
  "arguments": {
    "idempotencyKey": "autumn-skincare-submit-2027-09-01",
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "expectedVersion": 4
  }
}
```

11. Check the campaign is ready to measure

get_campaign_workflow_status reports readiness. If readiness.canStartCreativeTesting is false, stop and report the blockers — do not try to start a run from a draft campaign.

Then list the linked assets and choose exactly one where canStartCreativeTesting is true.

```json
{
  "tool": "get_campaign_workflow_status",
  "arguments": {
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d"
  }
}
```
```json
{
  "tool": "list_campaign_creative_assets",
  "arguments": {
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d"
  }
}
```

12. Start the Creative Testing run

A run needs the submitted campaign, one runnable asset, and explicit audience evidence: locale, platform, audience size and the campaign primary KPI. audienceSize must match the platform-configured size for this test type. Never present a nominal cohort size as an effective sample size, and never invent campaign evidence that is not there.

The response carries a runId and an operationId. The run is asynchronous — the operationId is how list_operations and get_operation_status find it again if the conversation is interrupted, and cancel_creative_test stops it.

```json
{
  "tool": "start_creative_test",
  "arguments": {
    "idempotencyKey": "autumn-skincare-test-2027-09-01",
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "assetId": "9c8b7a65-4d3e-42f1-8a09-b1c2d3e4f5a6",
    "name": "Autumn serum hero cut",
    "simulationMode": "ORGANIC",
    "targetAudience": {
      "locale": "en-GB",
      "platform": "instagram",
      "audienceSize": 200,
      "primaryKPI": "BRAND_AWARENESS"
    }
  }
}
```

13. Watch it, then read the prediction

Poll with list_creative_tests, or re-read get_campaign_workflow_status. Once a run is report-ready, get_creative_test_results returns the full prediction and get_creative_test_agent_data returns the immutable per-agent decision evidence behind it.

That evidence is the point. A prediction with no evidence attached is a number, not a finding.

```json
{
  "tool": "list_creative_tests",
  "arguments": {
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "limit": 10
  }
}
```
```json
{
  "tool": "get_creative_test_results",
  "arguments": {
    "runId": "2c6a7f11-5b2c-4d3e-8f90-1a2b3c4d5e6f",
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d"
  }
}
```

Cookbook: from a tracked creator post to a paid boost

The second journey buys media. A post an influencer has already published, and a brand has the rights to, becomes a paid advert that runs under the influencer’s own handle, on the brand’s own advertising account.

Read the money rule before the first call. No call you make here starts a spend: every object this journey creates is created paused on the advertising network, the price is quoted before it is built, and every step that would put money behind the advert, taking it live, raising a ceiling, starting it again, is parked for a named person who settles it in their own browser. A tool that would spend money is never a single call.

The sealed strings, the receipt, the validation stamp and the confirmation token, are passed on exactly as they came back. They are what bind a boost to the post, the audience and the price that were checked.

1. Check which advertising account the brand buys with

Boosting needs an advertising account the organisation has connected for that brand. status ACTIVE is the only state that can be spent on: DISCONNECTED and ERROR refuse every write until somebody reconnects the account, and EXPIRING is a login being renewed that still works.

Two fields decide the rest of the journey. currencyCode is the currency every cap must be stated in, because a cap in another currency is refused rather than converted. freshnessSeconds says how old the spend figures beside it are: they are the network’s own last reported figures, not a live reading.

```json
{
  "tool": "list_ad_accounts",
  "arguments": {
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "status": "ACTIVE"
  }
}
```

2. Ask which of the campaign’s posts may be boosted, and why the rest may not

This is one verdict per tracked creator post in the campaign. ELIGIBLE means three separate checks agreed at the moment of the reading: the network offers the post for a partnership advert, the creator’s permission is granted and confirmed by the network, and a paid-media licence covers the post to the last day you could spend on it.

Anything else carries one reasonCode naming the nearest cause, and that reason is the answer to give the person who asked. Do not retry a refusal in the hope of a different verdict.

Every eligible row carries a boostReceipt. It is sealed, single use, and it is what names the post, the permission and the licence the boost may use. Pass it on unchanged; the network’s own media id is deliberately nowhere else.

Each row also states the zone its own network counts days in, under accountTimezone, and the instant its licence was judged against, under spendEndsOn. Read them off the row rather than assuming one for the campaign: a campaign may hold a Meta account and a TikTok advertiser in two different zones, and then the same last day ends at two different instants.

The reading is at most fifteen minutes old. Set forceRefresh true when a permission or a licence has just changed and you need the network asked again now.

```json
{
  "tool": "list_boostable_posts",
  "arguments": {
    "campaignId": "6f9d0f0e-1f3a-4b0f-9a2f-6a1a2b3c4d5e"
  }
}
```

3. Build the audience out of the network’s own options

Interests, behaviours, demographics and places are the network’s catalogues, and the ids in them are the only ids the rest of this journey accepts. Search them; never invent an id, and never carry one over from another account.

Then put the whole audience to the network. validate_ad_targeting is the deprecation check: an interest the network retired last month still looks like a perfectly good string. When every option still stands, it returns a validationStamp, good for ten minutes and bound to your organisation. A refusal returns none at all, which is why there is no way to spend against an audience nobody checked.

```json
{
  "tool": "search_ad_targeting",
  "arguments": {
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "kind": "INTEREST",
    "query": "skincare",
    "limit": 25
  }
}
```
```json
{
  "tool": "validate_ad_targeting",
  "arguments": {
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "spec": {
      "ageMin": 25,
      "ageMax": 44,
      "countries": [
        "GB"
      ],
      "genders": "ALL",
      "interestIds": [
        "6003371567474"
      ]
    }
  }
}
```

4. See who it could reach, and what the advert would look like

An estimate is meaningless without the budget it was taken at, so the daily cap goes in with the audience. What comes back are bounds, named as bounds, with a notice in words saying so. A bound the network declines to give stays null rather than becoming a zero: the network having no figure is a different fact from an audience nobody can reach.

preview_boost renders the advert itself from the receipt, so what you show the person is the network’s own rendering rather than a description of one.

```json
{
  "tool": "get_boost_estimate",
  "arguments": {
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "dailyCap": "50.00",
    "targeting": {
      "kind": "validated",
      "spec": {
        "ageMin": 25,
        "ageMax": 44,
        "countries": [
          "GB"
        ],
        "genders": "ALL",
        "interestIds": [
          "6003371567474"
        ]
      },
      "validationStamp": "mbt1.key-v1.1789200000.9f2c7d41ba08…"
    }
  }
}
```
```json
{
  "tool": "preview_boost",
  "arguments": {
    "boostReceipt": "mbr1.key-v1.1789200000.4c1f9ab2e7d0…",
    "placement": "INSTAGRAM_STANDARD"
  }
}
```

5. Price it. Nothing is built yet

prepare_boost is the quote, and it judges everything at once: the paid-media licence, the creator’s permission, the network’s own minimum daily budget, the ad account currency and the client’s spend ceilings. It answers READY or REFUSED with one reason code, and it writes nothing either way.

On READY it mints a confirmationToken: sealed, single use, ten minutes, and bound to the organisation, the brand, the campaign, the post, the live spend-policy revision and those exact figures. Show the person the quote. Changing a cap, a date or the audience after this point does not adjust the boost, it invalidates the quote.

Caps are decimal strings with two places, in the account currency, with the flight stated as instants. Days are the ad account’s own timezone, which is why the account read in step one matters.

```json
{
  "tool": "prepare_boost",
  "arguments": {
    "boostReceipt": "mbr1.key-v1.1789200000.4c1f9ab2e7d0…",
    "currencyCode": "GBP",
    "dailyCap": "50.00",
    "lifetimeCap": "500.00",
    "startsAt": "2027-09-15T00:00:00Z",
    "endsAt": "2027-09-25T00:00:00Z",
    "callToAction": "SHOP_NOW",
    "targeting": {
      "kind": "validated",
      "spec": {
        "ageMin": 25,
        "ageMax": 44,
        "countries": [
          "GB"
        ],
        "genders": "ALL",
        "interestIds": [
          "6003371567474"
        ]
      },
      "validationStamp": "mbt1.key-v1.1789200000.9f2c7d41ba08…"
    }
  }
}
```

6. Build it, paused

create_boost re-judges all of it, burns the confirmation and the eligibility receipt, and only then builds the campaign, ad set, creative and advert on the network. Every figure must be the figure that was quoted: a different cap is refused, not honoured.

The confirmationToken is also this call’s retry key, so a repeat after a timeout returns the original result instead of building a second advert.

Everything is created paused, and no path through this tool writes an active advert. The boost lands PAUSED_UNVERIFIED, which means built and not yet confirmed as paused by the network.

```json
{
  "tool": "create_boost",
  "arguments": {
    "boostReceipt": "mbr1.key-v1.1789200000.4c1f9ab2e7d0…",
    "confirmationToken": "mbc1.key-v1.1789200600.6ae30f5c92b1…",
    "currencyCode": "GBP",
    "dailyCap": "50.00",
    "lifetimeCap": "500.00",
    "startsAt": "2027-09-15T00:00:00Z",
    "endsAt": "2027-09-25T00:00:00Z",
    "callToAction": "SHOP_NOW",
    "targeting": {
      "kind": "validated",
      "spec": {
        "ageMin": 25,
        "ageMax": 44,
        "countries": [
          "GB"
        ],
        "genders": "ALL",
        "interestIds": [
          "6003371567474"
        ]
      },
      "validationStamp": "mbt1.key-v1.1789200000.9f2c7d41ba08…"
    }
  }
}
```

7. Going live is a person’s decision, not a call you make

A paused boost spends nothing. Taking it live is activate_boost, and it is the one place in this product where money starts leaving an account, so it is not a call that simply succeeds. It starts nothing at any amount: it prices the go-live in full, parks it on the Justify ledger and hands back an operationId, an approvalUrl and the price. The advert becomes live only when the named person opens that link in their own browser session and agrees.

You hold no authority to agree. The link needs a signed-in Justify session an OAuth credential cannot mint, and below the client’s approvalThreshold the person who asked may settle it themselves while at or above it they may not, which is why approverUserId names the colleague. Watch the parked ask with get_operation_status: it reads awaiting_approval until somebody acts.

Everything after activation is the same shape. Pausing, lowering a cap or ending a boost calls the network first and changes the Justify row only once the network has agreed, so a boost is never reported as stopped while it is still delivering.

```json
{
  "tool": "activate_boost",
  "arguments": {
    "boostId": "7f3c2a10-6b8d-4e52-a1c9-3d4e5f6a7b8c",
    "approverUserId": "user_2mB7Qk3rZpLxWv9AaCd1",
    "idempotencyKey": "autumn-skincare-golive-2027-09-15"
  }
}
```

8. Read the boost back after the approval is settled

Once the colleague has agreed, read the boost rather than assuming what the click did. status is Justify’s word for it and effectiveStatus is the network’s own, kept verbatim; caps carries both pairs, the figures Justify asked the network for and the figures the network says it holds, with the moment that reading was taken beside them.

approvals is the chain behind it, newest ask first, with the operationId of each parked request. There is no approval link on this answer and there will not be one: it is single use and it belongs to the colleague it was minted for.

```json
{
  "tool": "get_boost",
  "arguments": {
    "boostId": "7f3c2a10-6b8d-4e52-a1c9-3d4e5f6a7b8c"
  }
}
```

9. Change a ceiling, in the direction that decides who agrees

Lowering is immediate and raising is parked, and the reason is the money. Reducing what a client may spend protects nobody by waiting, so lower_boost_caps changes the network first, reads both ceilings straight back off it and only then moves the record here. Raising one spends more of somebody’s money, so raise_boost_caps parks the whole new price for a named person exactly as the go-live did, and answers with its own operationId and approvalUrl.

Send all three figures to either tool every time, including the ones that are not moving. The network allows four ad set budget changes an hour and says so through retryAfterSeconds when the fifth is refused.

```json
{
  "tool": "lower_boost_caps",
  "arguments": {
    "boostId": "7f3c2a10-6b8d-4e52-a1c9-3d4e5f6a7b8c",
    "dailyCap": "40.00",
    "lifetimeCap": "400.00",
    "endsAt": "2027-09-25T00:00:00Z",
    "idempotencyKey": "autumn-skincare-lower-2027-09-16"
  }
}
```
```json
{
  "tool": "raise_boost_caps",
  "arguments": {
    "boostId": "7f3c2a10-6b8d-4e52-a1c9-3d4e5f6a7b8c",
    "dailyCap": "60.00",
    "lifetimeCap": "600.00",
    "endsAt": "2027-09-25T00:00:00Z",
    "approverUserId": "user_2mB7Qk3rZpLxWv9AaCd1",
    "idempotencyKey": "autumn-skincare-raise-2027-09-17"
  }
}
```

10. Stopping is a call you may make; starting again is not

pause_boost is admitted whatever else is throttled, because a limit that stopped somebody stopping their own money would cost them money. Read the answer carefully: platformPauseApplied true with the boost PAUSED means the advert has genuinely stopped, while false with PAUSE_REQUESTED means Justify asked and the network has not confirmed it, the advert may still be delivering, and calling again is the right thing to do. Never report a stop to a person on the second shape.

resume_boost prices the restart in full and parks it, like every other spend. It is refused while anything that stopped the boost is still standing: a boost this product stopped on its own reads ALERT_UNACKNOWLEDGED until somebody records that they have seen the episode, through list_boost_alerts and acknowledge_boost_alert.

```json
{
  "tool": "pause_boost",
  "arguments": {
    "boostId": "7f3c2a10-6b8d-4e52-a1c9-3d4e5f6a7b8c"
  }
}
```
```json
{
  "tool": "resume_boost",
  "arguments": {
    "boostId": "7f3c2a10-6b8d-4e52-a1c9-3d4e5f6a7b8c",
    "approverUserId": "user_2mB7Qk3rZpLxWv9AaCd1",
    "idempotencyKey": "autumn-skincare-resume-2027-09-18"
  }
}
```

11. Watch the money

The spend policy is the honest place to look. Each brand row carries the amount the network itself last reported as spent, when that was read, and the headroom against the monthly ceiling. Headroom is signed, so a brand that has spent past its ceiling reads as a negative figure rather than an empty one.

The connected account carries the same read-back per account, with freshnessSeconds beside it. Quote the figure and its age together; a stored reading presented as a live one is the one mistake this surface must not make.

get_ad_account_insights is the account’s own delivery report, on the network’s figures and the account’s own days. Above ninety days the network answers with a handle rather than rows, and the same tool reads the rows back with reportRunId once the job has finished. Reading a boost’s daily results next to the organic figures of the same post is still not on this address.

```json
{
  "tool": "get_spend_policy",
  "arguments": {
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d"
  }
}
```
```json
{
  "tool": "get_ad_account",
  "arguments": {
    "connectionId": "d0a91b4e-7c33-4f61-9b0a-5e2d7c9f1a34"
  }
}
```
```json
{
  "tool": "get_ad_account_insights",
  "arguments": {
    "brandId": "b41d2f88-8f2e-4a5b-9c3d-0e1f2a3b4c5d",
    "level": "CAMPAIGN",
    "timeIncrement": "DAILY",
    "since": "2027-09-15",
    "until": "2027-09-25",
    "limit": 100
  }
}
```

12. Hand the client the files

The receipts file and the results file are what an agency rebills a client from, and neither has a tool on this address. They are served as files over HTTPS rather than as tool answers, because a ledger a buyer reconciles against is a download they keep, not a payload in a conversation: GET /api/media-buying/receipts/export and GET /api/media-buying/results/export, each taking brandId, from, to and format, and each answering as CSV or as newline-delimited JSON with one content hash repeated in the response header.

Every money change this journey made is in the first of those files, with the advertising network’s own request id on the row that made it, which is the difference between a pound somebody can trace to a platform call and a pound they cannot.

Registered prompts

The server also registers guided workflows. A client that supports MCP prompts can pull one in and follow it step by step, which is usually quicker than describing the sequence yourself. They are filtered by the same entitlements as the tools.

  • campaign_analysisStep-by-step guide for fully analysing a campaign — details, creators, engagement, canvas intelligence.
  • campaign_measurement_workflowCampaign-first workflow for Creative Testing and read-only Brand Lift study list, detail, and status context. Use this before measurement analysis.
  • campaign_wizard_creationStep-by-step guide for creating, filling, validating, and submitting a Campaign Wizard through MCP.
  • influencer_discoveryStep-by-step guide for finding, evaluating, and recommending marketplace influencers through the costed prepare/confirm search flow.

Deliberately not available

A few tool names are centrally refused. They are listed so you know the refusal is a decision rather than a gap.

  • search_influencersCentrally denied because a paid search must never run without an explicit confirmation step. Use prepare_influencer_search to build a costed plan, then confirm_influencer_search to execute it.

Generated from artifacts/mcp/capability-manifest.static.json, which is regenerated from the tool registry on every release.

Justify for agentsPrivacyjustify.app