runs.confirmCompanies
Confirm a run’s company list so Selda finds the decision-makers and drafts the messages. Spends credits. Sends nothing, the send is still a human press in the app.
| Endpoint | POST /mcp/mutate |
| Scope | write |
| Sandbox key | refused |
| Extra entitlement | none |
| MCP tools | selda_confirm_companies |
| Convex function | campaignRunner/mutations:confirmProfileFromApiKey |
A free sandbox key is refused: it runs the discovery and research engine: web search, crawls, model calls, per-lead credits.
Arguments
| Argument | Type | Required | Notes |
|---|---|---|---|
runId | id<"campaignRuns"> | required | |
language | string | required | |
languageChosen | boolean | optional | DID THE HUMAN NAME THAT LANGUAGE at this gate, or is it Selda’s proposal riding through? The card sends a language on every confirm, whether or not anyone touched the field, and the engine read the mere presence of confirmedProfile.language as “the operator answered the question”, then enforced it over every company’s own evidence and told them they had chosen it. Absent = an older client that does not send it; the presence of a language is then read as before, and simply never attributed to the human. |
channel | string | required | |
targetAudience | string | optional | |
targetRoles | string[] | optional | |
targetDecisionMakers | { title: string; why?: string }[] | optional | |
targetMarket | string | optional | |
greeting | string | optional | |
tone | string | optional | |
leadsPerCompany | string | optional | |
excludedDomains | string[] | optional | |
researchCount | number | optional | |
clarifyingAnswers | { question: string; answer: string }[] | optional | |
contactLookups | { phone?: boolean; linkedin?: boolean } | optional | WHAT THIS SEARCH IS ASKED TO LOOK UP BEYOND AN ADDRESS, the human’s answers, #127. Absent = an older client, and then normalizeContactLookups supplies the defaults, which are written to reproduce what those runs already did. Neither switch controls KEEPING anything. Every phone number and every profile URL a rung already running happens to publish is stored either way, dropping one to honour a switch would be Selda throwing away a fact nobody asked it to throw away. What they control is whether Selda spends anything going to look. |
contactResearchRequest | string | optional | What else the human wants found out about the decision-maker, in their own words. A real query, not a note: engineV3 runs one web search per named contact with exactly this question, stores the answer on the lead and hands it to the composer. Empty = nothing extra is searched for and nothing extra is spent. |
What Selda fills in
You never send these. The HTTP layer overwrites them from the API key, which is what keeps one organisation’s data out of another’s reach.
| Argument | Filled in from |
|---|---|
orgId | your organisation, resolved from the API key |
Example request
Required arguments only. Values in angle brackets are yours to fill in; the optional ones are in the table above.
curl -X POST https://api.selda.ai/mcp/mutate \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "fn": "runs.confirmCompanies", "args": { "runId": "<runId>", "language": "<language>", "channel": "<channel>" } }'{
"fn": "runs.confirmCompanies",
"args": {
"runId": "<runId>",
"language": "<language>",
"channel": "<channel>"
}
}Example response
Every endpoint answers in the same envelope.
{
"value": <the function's return value>,
"request_id": "req_..."
}The shape of value is not documented here. campaignRunner/mutations:confirmProfileFromApiKey declares no returns validator, so there is nothing to derive it from, and a shape written out by hand here is a shape that goes stale without anything noticing. Call it once against a test key and read what comes back.
On failure the body is { "error": { "type", "code", "message", "request_id" }, "request_id" } and request_id is echoed in the X-Request-Id header.
Generated from convex/lib/mcpRegistry.ts. Nothing in this reference sends: launchRun is in no
registry and never will be, so a script can prepare a campaign completely and a person still
presses send in the app.