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 reads 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/mcpOr 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-onlyThe catalogue holds 170 tools, and 54 of them write, send or spend, which is what your host asks about. The read-only address leaves those out and carries the other 116, 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.
- 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.
- 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.
- 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 a fully entitled organisation connection is served 168, so the request is refused with array_above_max_length before the model sees it. A narrower role is served fewer, because a tool your account is not entitled to is never registered at all, so whether your own connection is refused depends on what you hold.
The cut that fits is the read-only one. Filter that same fully entitled connection to the tools whose annotations carry readOnlyHint true and you get 114 tools, which that API accepts; a narrower role gets a subset of those, so it fits as well. Everything that reads your campaigns, creators, jobs, library and Brand Lift is in it; what it leaves out are the tools that write, send or spend.
Both of those are numbers about one connection rather than about the catalogue. 116 catalogue entries carry readOnlyHint, and no single connection is served all of them: a creator connects to a separate and much smaller surface of their own, so what any one client is offered is smaller than what Justify holds.
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
annotations.readOnlyHint === true → 114 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(11)
- 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(7)
- Public recommendation corpus(1)
- Long-running operations(3)
- Growth experiments(7)
- Atlas operator library(14)
- 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 and rates(2)
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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
find_justify_tools | None beyond a signed-in account | Reads | Find 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_status | creator_agent_access or mcp_api | Reads | Report 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_brands | None beyond a signed-in organisation | Reads | List 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_estate | multi_brand_management | Reads | Read 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: brandId | brand_switcher | Writes | Choose 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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_campaigns | campaigns | Reads | List 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: campaignId | campaigns | Reads | Get 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: campaignId | campaign_wizard | Reads | Get 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 idempotencyKey | campaign_wizard | Writes | Create 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 idempotencyKey | campaign_wizard | Destructive | Save 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. |
set_campaign_wizard_creatorsRequired arguments: campaignId, creators, idempotencyKeyTakes an idempotencyKey | campaign_wizard | Destructive | Add 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 idempotencyKey | campaign_wizard | Writes | Link 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: campaignId | campaign_wizard | Computes | Validate 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. |
submit_campaign_wizardRequired arguments: campaignId, expectedVersion, idempotencyKeyTakes an idempotencyKey | campaign_wizard | Writes | Submit 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. Use it when someone says the campaign is finished and nothing is missing, and to put it in. |
get_campaign_analyticsRequired arguments: campaignId | campaigns | Reads | Get engagement analytics for a campaign. Returns: postCount, totals (views, likes, comments, shares), and org-level stats (totalCampaigns, publicCampaigns). 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. |
get_campaign_canvasRequired arguments: campaignId | campaign_canvas | Reads | Get 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. This is the richest single-call data source for campaign intelligence. Returns null if the canvas has not been generated yet for this campaign. |
generate_campaign_briefRequired arguments: campaignId | campaign_canvas, campaigns | Computes | Get 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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_influencers | influencer_list | Reads | List 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: influencerId | influencer_list | Reads | Get 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. |
save_influencerRequired arguments: idempotencyKeyTakes an idempotencyKey | influencer_list, influencer_marketplace | Writes | Save 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 idempotencyKey | campaigns, influencer_list, influencer_marketplace | Writes | Add 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: influencerId | influencer_list, influencer_marketplace | Reads | Get 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: campaignId | influencer_list | Computes | 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. |
prepare_influencer_searchRequired arguments: platform, prompt | influencer_list, influencer_marketplace | Spends credits | 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 idempotencyKey | influencer_list, influencer_marketplace | Spends credits | 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, platform | risk_reports | Reads | List 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, priceCents, 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, platform | risk_reports | Reads | Hand 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: reportId | risk_reports | Reads | Read 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, priceCents, 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. |
get_collaboration_workspace | influencer_marketplace | Reads | Get 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 idempotencyKey | influencer_marketplace | Writes | Add 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 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, text | influencer_marketplace | Destructive | Edit 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: noteId | influencer_marketplace | Destructive | Delete, that is retract, 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 deleted. 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, influencerId | influencer_marketplace | Writes | Assign 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, the assignee, and 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
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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
get_campaign_workflow_statusRequired arguments: campaignId | creative_testing | Reads | Inspect 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. |
list_campaign_creative_assetsRequired arguments: campaignId | creative_testing | Reads | List 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_tests | creative_testing | Reads | List creative testing runs (video predictions) for the resolved active brand. Returns: id, name, status, progress, verdict (GREEN/AMBER/RED with predicted reach), createdAt. Report-ready rows include reportReady=true and reportTrustEvidence; active rows return reportReady=false and reportTrustEvidence=null. 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: runId | creative_testing | Reads | Get 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: runId | creative_testing | Reads | Fetch 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: batchId | creative_testing_batch | Reads | Read 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 is org-scoped, never brand-scoped, exactly as the web leaderboard reads it. Refuses NOT_FOUND when no run in this organisation 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: runId | creative_testing_brand_track | Reads | Read 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: runId | creative_testing_heatmap | Reads | Read 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_images | creative_testing_image | Reads | List 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, predicted reach band) and the Justify Creative Score alignment for completed rows, plus total and hasMore. 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 idempotencyKey | creative_testing | Spends credits | Start 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: runId | creative_testing | Destructive | Cancel 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. |
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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_brand_lift_studies | brand_lift, brand_lift_reports | Reads | List 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. 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: studyId | brand_lift, brand_lift_reports | Reads | 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, 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 measured uplift and sampleSize the number of responses it was computed over. |
create_brand_lift_studyRequired arguments: campaignId, funnelStage, idempotencyKey, name, primaryKpiTakes an idempotencyKey | brand_lift | Writes | Create 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?
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_library_concepts | campaign_library_video | Reads | List AI-generated video concepts from the campaign library (latest versions, archived excluded — the same population the web library shows). 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: conceptId | campaign_library_video | Reads | Get 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 idempotencyKey | campaign_library_video | Spends credits | Write 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_assets | campaign_library_assets | Reads | List 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: assetId | campaign_library_assets | Reads | Get 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 idempotencyKey | campaign_library_assets | Writes | Create 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: assetId | campaign_library_assets | Writes | Confirm 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_tags | campaign_library | Reads | List 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_concepts | campaign_library_audio | Reads | List 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: audioConceptId | campaign_library_audio | Reads | Open 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_characters | campaign_library_characters | Reads | List 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: characterId | campaign_library_characters | Reads | Open 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_videos | campaign_library_characters | Reads | List 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_projects | campaign_library_clipping | Reads | List 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: clipStudioProjectId | campaign_library_clipping | Reads | Open 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_localisations | campaign_library_localisation | Reads | List 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: localisationId | campaign_library_localisation | Reads | Open 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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_commerce_stores | None beyond a signed-in organisation | Reads | List 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. |
list_commerce_productsRequired arguments: connectionId | None beyond a signed-in organisation | Reads | Browse 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_performance | None beyond a signed-in organisation | Reads | Get 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_integrations | integrations | Reads | List 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: provider | shopify_integration or tiktok_shop_integration or woocommerce_integration | Reads | Read 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_reports | manage_reports | Reads | List 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: reportType | manage_reports | Reads | Read 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_orders | manage_gifting | Reads | List 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. |
get_gift_orderRequired arguments: giftOrderId | manage_gifting | Reads | Fetch 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. |
create_gift_orderRequired arguments: connectionId, idempotencyKey, influencerId, linesTakes an idempotencyKey | manage_gifting | Destructive | Send 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. |
approve_gift_orderRequired arguments: giftOrderId | manage_gifting | Destructive | Approve 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: giftOrderId | manage_gifting | Destructive | Reject 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: giftOrderId | manage_gifting | Writes | Re-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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_outreach_campaigns | outreach | Reads | List 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. |
get_outreach_campaignRequired arguments: campaignId | outreach | Reads | Get 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: campaignId | outreach | Reads | Get 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_status | outreach | Reads | Get 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_readiness | outreach | Reads | Get 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. |
list_outreach_queue | outreach | Reads | List 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: campaignId | outreach | Reads | Get 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: campaignWizardId | outreach | Spends credits | 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 idempotencyKey | outreach | Destructive | Queue 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_status | outreach, outreach_sms | Reads | Get 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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
suggest_community_replyRequired arguments: messageId | community_automation, outreach | Writes | Triage 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: actionId | community_automation, outreach | Writes | Approve 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_queue | community_automation, outreach | Reads | List 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, name 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: conversationId | community_automation, outreach | Writes | Escalate 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. |
get_community_autopilot_config | community_automation, community_autopilot, outreach | Reads | Read 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_queue | community_moderation, social_inbox | Reads | List 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, assignedToUserId, 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_inbox | social_inbox | Reads | List 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, who it is assigned to, 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: conversationId | social_inbox | Reads | Get 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, assignee, 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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_jobs | job_board | Reads | List 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: jobId | job_board | Reads | Get 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: jobId | job_board | Reads | List 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. |
create_jobRequired arguments: campaignWizardId, description, idempotencyKey, titleTakes an idempotencyKey | job_board | Writes | 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 idempotencyKey | job_board | Writes | Send 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 idempotencyKey | job_board | Destructive | Soft-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). |
cancel_jobRequired arguments: idempotencyKey, jobIdTakes an idempotencyKey | job_board | Destructive | Hard-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, offerId | job_board | Destructive | Answer 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: jobId | job_board | Reads | List 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 idempotencyKey | job_board | Writes | Record 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: jobId | job_board, job_content_verification | Reads | 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: 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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
marketplace_plus_searchRequired arguments: query | marketplace_plus | Reads | Search 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, to | marketplace_plus | Reads | List 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. |
marketplace_plus_whos_in_town | marketplace_plus | Reads | The 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. |
marketplace_plus_get_talentRequired arguments: talentProfileId | marketplace_plus | Reads | Read 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: eventId | marketplace_plus | Reads | Read 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: agencyId | marketplace_plus | Reads | Read 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. |
marketplace_plus_trending_talent | marketplace_plus | Reads | The 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. |
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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
search_public_recommendations | None beyond a signed-in organisation | Reads | Search the Justify public corpus of creator recommendations. Returns per item: quote (structured body or post excerpt), stats (likes, comments, views), 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?
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_operations | None beyond a signed-in organisation | Reads | List 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. |
get_operation_statusRequired arguments: operationId | None beyond a signed-in organisation | Reads | Get 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: operationId | creative_testing | Destructive | Cancel 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. |
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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_growth_experiments | debug_tools | Reads | List 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, reason | debug_tools | Writes | Edit 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, reason | debug_tools | Writes | Promote 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. |
go_live_growth_experimentRequired arguments: code, reason | debug_tools | Writes | Take 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. |
complete_growth_experimentRequired arguments: code, reason, verdictNote | debug_tools | Destructive | Write 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. |
get_growth_wiring_options | debug_tools | Reads | List 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 idempotencyKey | debug_tools | Writes | Attach 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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
upsert_artifactRequired arguments: contentType, html, idempotencyKey, reason, slug, titleTakes an idempotencyKey | debug_tools | Writes | Store a Claude artefact snapshot in the Atlas artefact library as the next version of its slug, an upsert keyed on the slug. 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 are sent. The content hash is computed by the store, never supplied. tags file the artefact on the shelf: absent carries the previous version's tags forward, present replaces them; to change only the tags of an artefact already stored, use retag_artifact. Stored bytes are display and archive only; the messaging-system JSON remains the single machine truth. Unknown input keys are refused rather than ignored. Returns data (the stored version, with tags and created). |
retag_artifactRequired arguments: idempotencyKey, reason, slug, tagsTakes an idempotencyKey | debug_tools | Writes | File an artefact already in the Atlas artefact library under a new tag set. The library is append-only, so a retag appends a version carrying the same bytes (the sha256 is unchanged) and the new tags; identical tags return 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. tags replaces the whole set: send every tag the document should carry, and an empty array unfiles it. Unknown input keys are refused rather than ignored. Returns data (the resulting version with its tags and created). |
get_messaging_house | brand_playbook | Reads | Read 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: text | brand_playbook | Reads | Run 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. 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 idempotencyKey | brand_playbook | Writes | Propose 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 idempotencyKey | brand_playbook | Writes | Edit 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 idempotencyKey | brand_playbook | Destructive | Retire 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. |
supersede_messaging_entryRequired arguments: id, idempotencyKey, reason, successorTakes an idempotencyKey | brand_playbook | Destructive | Rework 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. |
publish_messaging_houseRequired arguments: idempotencyKey, reasonTakes an idempotencyKey | brand_playbook | Writes | Publish 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. |
list_artifacts | debug_tools | Reads | List 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) and versionCount. Optional q filters by title, slug or author (case-insensitive substring); sort orders by title (A to Z, the default), newest or versions. Paged by cursor: returns data, total (after the filter), libraryTotal (before it), hasMore and nextCursor; the next page is this same call with the cursor argument set to that nextCursor value and the same q and sort. Read-only. Use get_artifact with a slug to read an artefact's bytes or an earlier version. |
get_artifactRequired arguments: slug | debug_tools | Reads | Read one artefact from the library: its metadata (tags included, so a retag_artifact can be verified here), its sha256 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. 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. |
list_competitors | debug_tools | Reads | List 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. |
get_competitorRequired arguments: competitor | debug_tools | Reads | Read 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 idempotencyKey | debug_tools | Writes | Write 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. |
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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
get_brand_pulse | brand_pulse | Reads | Get 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_alerts | brand_pulse_alerts | Reads | List 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 who acknowledged or resolved it and when. 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. |
get_brand_pulse_sov | brand_pulse | Reads | Get 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: alertId | brand_pulse_alerts | Writes | Acknowledge 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. |
snooze_brand_pulse_alertRequired arguments: alertId, snoozeUntil | brand_pulse_alerts | Writes | Snooze 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: alertId | brand_pulse_alerts | Destructive | Resolve 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?
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_roster_contracts | talent_roster | Reads | List 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. |
get_roster_contractRequired arguments: contractId | talent_roster | Reads | Get 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: contractId | talent_roster | Writes | Send 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_deals | deals, talent_roster | Reads | List 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: dealId | deals, talent_roster | Reads | Get 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_pipeline | deals, talent_roster | Reads | Count 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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
get_payment_status | connect_payments | Reads | Read 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 idempotencyKey | connect_payments | Destructive | Ask 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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
get_billing_summary | billing | Reads | Read where this organisation stands with Justify commercially: its subscription, its plan and its credit balance. Returns: billingCustomerConnected, hasActiveSubscription, subscription (status, planKey, planName, isAnnual, currentPeriodEnd), billingCycle (end, daysRemaining), credits (one row per credit type with allocation, remaining, used, pending, percentUsed) and spendThisPeriod (allocated, consumed, reserved, remaining across every type). 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_invoices | billing | Reads | List 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 and billingCustomerConnected. 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?
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_content_rights_agreements | content_rights_agreements | Reads | List 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: agreementId | content_rights_agreements | Reads | Get 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?
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_creator_lists | creator_lists | Reads | List 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: listId | creator_lists | Reads | Get 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?
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_uncover_results | manage, manage_uncover, uncover | Reads | List 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: matchId | manage, manage_uncover, uncover | Reads | Get 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?
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
get_dashboard_summary | dashboard | Reads | 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?
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
get_demo_workspace_status | demo_workspace | Reads | Say 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?
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
search_helpRequired arguments: query | help | Reads | Search 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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
get_organisation_settings | settings_organisation | Reads | Read 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_profile | settings_profile | Reads | Read 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_members | settings_team | Reads | List 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.
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
list_signed_influencers | job_board, manage_jobs | Reads | List 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_content | job_board, manage_jobs | Reads | List 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 and rates
The only family on this page that is not served to an organisation connection. These two reads answer for one creator, about that creator, on a connection the creator opened themselves from the creator portal: 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. Neither tool takes a creator, handle or user argument, because the server resolves the creator from their own sign-in and can only ever report on 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?
| Tool | Entitlement needed | Effect | What it does |
|---|---|---|---|
get_creator_storefront_performance | recommendations | Reads | Report 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_benchmark | creator_market_intelligence | Reads | Report 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. |
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. Ask first, then pass the brandId you got back. If your organisation has exactly one active brand you can leave brandId out and the tools resolve it for you; if it has several and none is selected, the call refuses with ACTIVE_BRAND_REQUIRED 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-2026-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": "2026-09-15T00:00:00Z",
"endDate": "2026-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-2026-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-2026-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-2026-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-2026-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-2026-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-2026-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-2026-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-2026-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-2026-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"
}
}
```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.