Function reference
Three endpoints, one body shape. The unit on this page is the function name: what you choose
is the fn you put in the body, and the badge tells you which endpoint it goes to.
POST https://api.selda.ai/mcp/query read something needs the read scope
POST https://api.selda.ai/mcp/mutate change something needs the write scope
POST https://api.selda.ai/mcp/run start slow work needs the pipeline scope
Authorization: Bearer sk_live_...
{ "fn": "<name>", "args": { ... } }Most of these also have a REST path: GET /v1/leads/{leadId}, POST /v1/runs/{runId}/confirm-companies.
They are the same call at a different URL, answered by the same dispatcher, so a path can reach
nothing this form refuses. The list is at REST paths and in
openapi.json.
convex/lib/mcpRegistry.ts, the same table the endpoints dispatch from. A function
added to the registry appears here on the next build; one removed disappears. A test fails if the
two ever disagree, which is why the tables below no longer list functions that do not exist.Authentication
Authorization: Bearer sk_live_...Your organisation is resolved from the key. You never send an orgId, and a key cannot reach a
workspace outside the organisation it was made in. Keys are stored hashed, shown once, and revocable
from Settings.
sk_test_... keys reach every read and write that costs Selda nothing and cannot spend or send.
The table below marks the ones a test key is refused, and says why.
The response envelope
{ "value": ..., "request_id": "req_..." }On failure:
{ "error": { "type": "...", "code": "...", "message": "...", "request_id": "req_..." }, "request_id": "req_..." }request_id is also in the X-Request-Id header, so a caller can correlate a request without
parsing the body. It is safe to log and to quote in a support ticket.
| Status | Meaning |
|---|---|
401 | Missing, expired or revoked key |
403 | The key lacks the scope, the plan entitlement, or is a sandbox key on a live-only function |
400 | Unknown fn, or arguments the validator refused |
429 | Rate limited. Wait and retry |
500 | The function threw. The message says what, without leaking internals |
Every function
74 registry entries across 72 names. 74 have their arguments derived from the target function’s own validator; 0 are partly derived; 0 say so instead of guessing.
fn | Scope | What it does | Limits | |
|---|---|---|---|---|
mutate | brain.add | write | Add one thing Selda should know: a product, a partner, a reference, a company fact, a note, something it must never say, or a writing_rule — a standing instruction about HOW messages are written, which reaches the composer as a directive and is never quoted as material. | |
query | brain.list | read | The workspace’s structured knowledge: products, partners, references, company facts, and the things Selda must never say. Each item has a type, a title and a body. | |
mutate | brain.remove | write | Take one Brain item back out. The human owns what Selda knows. | |
mutate | brain.update | write | Rewrite the title and body of one Brain item. | |
mutate | campaigns.addLeads | write | Put specific leads into a campaign. | |
mutate | campaigns.addLeadsByTag | write | Put every lead carrying a tag into a campaign (legacy table). | |
mutate | campaigns.addRule | write | Add a campaign rule. | |
mutate | campaigns.create | write | Create a campaign (legacy table, not the one the app’s campaign-flow UI reads). | |
query | campaigns.get | read | One campaign: status, channels, settings, leads. | |
query | campaigns.list | read | Campaigns in a workspace. | |
mutate | campaigns.lockMessageStructure | write | Lock a campaign’s message structure so every locked block ships exactly as written and nothing rewrites it, or unlock it with locked: false. Sends nothing. | |
query | campaigns.messageStructure | read | Read what a campaign’s message is made of: every block, which ones ship verbatim, the instruction behind each generated one, the shape, and whether it is locked. | |
mutate | campaigns.setMessageStructure | write | State what a campaign’s message is made of: blocks that ship WORD FOR WORD, blocks Selda writes from an instruction you give it, the paragraph count, and what must never appear. Refuses to change a locked structure. Sends nothing. | |
query | campaigns.stats | read | Campaign counters: sent, delivered, opened, clicked, replied, bounced. | |
mutate | campaigns.update | write | Change a campaign. | |
run | company.lookup | pipeline | Resolve a company and return the right people to reach. Starts nothing. | live key only |
mutate | connectors.create | write | Register a data connector. | |
mutate | connectors.delete | write | Remove a data connector. | |
query | connectors.list | read | Data connectors registered for this workspace. | |
run | connectors.sync | pipeline | Pull from a connected data source. | live key only |
query | credits.info | read | Credit balance, daily free credits, usage, plan. | |
mutate | drafts.remove | write | Take one draft out of a run so it cannot be sent. The row stays visible with your reason and the app can put it back. Refuses a message that already went out. | |
mutate | drafts.update | write | Rewrite the draft on one run lead. Refuses a message that already went out; never sends. | |
run | engine.start | pipeline | The full pipeline from a brief: find companies → research → fit → hook → draft. It STOPS at the company list (run status awaiting_profile) and waits for a person to confirm the companies and the decision-maker roles in the Selda app. Poll runs.status and read awaitingHuman. Nothing is ever sent from here. | live key only |
mutate | events.ingest | write | Report that something happened outside Selda (a form, an analysis, an ad response). Creates the lead if new, recognises it if known, records it on the timeline, and can put it on a campaign’s review list. Pass autoAdvance to have Selda write the reply from the Brain straight away and leave it in the Sales Inbox, draft.ready is published when it is there. It never sends. | needs inboundIntake |
mutate | flows.create | write | Create a flow: a trigger plus the steps to run when something arrives. Off unless you say otherwise. No step can send. | |
mutate | flows.delete | write | Delete a flow and its run log. | |
query | flows.list | read | The flows in a workspace: what runs when something arrives from outside, the steps in order, and whether each is switched on. Includes the workspace’s flow instruction files. | |
query | flows.runs | read | What a flow actually did, run by run, step by step, including the steps that did nothing and why. | |
mutate | flows.saveSkill | write | Write or rewrite an instruction file a flow step reads: how this business decides what an enquiry is. | |
mutate | flows.setEnabled | write | Switch a flow on or off. | |
mutate | flows.update | write | Rewrite a flow’s name, trigger or steps. | |
mutate | inbox.addMessage | write | Put one message into a lead’s Sales Inbox thread, in either direction, even for somebody who was never in a campaign. Creates the lead if it is new. This can send nothing. | |
mutate | knowledge.append | write | Add to what Selda knows about your business. | |
query | knowledge.get | read | What Selda knows about your business: the prose that grounds every message. | |
mutate | knowledge.set | write | Replace what Selda knows about your business. | |
mutate | leads.add | write | Add one company/contact. Pass analysis with research you already did and the message is written from it instead of a fresh crawl. | |
mutate | leads.addAlias | write | Claim another email address for a lead, so a reply from it lands in the same conversation. Also adopts that address’s earlier unlinked inbound. | |
mutate | leads.addBatch | write | Add many companies/contacts in one call. | |
mutate | leads.addTag | write | Tag a lead. | |
mutate | leads.delete | write | Remove one lead. Deleting is the caller’s act, Selda never removes a lead on its own. | |
mutate | leads.deleteBatch | write | Remove many leads. | |
run | leads.enrich | pipeline | Enrich one lead from a natural-language instruction. | live key only |
run | leads.enrich | pipeline | Enrich a workspace’s leads from a natural-language instruction (legacy path). | live key only, not reachable |
run | leads.enrichBatch | pipeline | Enrich many leads from a natural-language instruction. | live key only |
query | leads.get | read | One lead in full: research, fit, outreach angle, notes. | |
query | leads.list | read | Leads in a workspace. | |
mutate | leads.merge | write | Merge duplicate leads. | |
mutate | leads.skip | write | DELETES a lead and every message on it (legacy path, Clerk-authenticated, an API key cannot reach this; use leads.delete instead). | |
mutate | leads.update | write | Edit a lead’s fields, including its status. Org-scoped, so an API key can reach it. | |
mutate | leads.update | write | Edit a lead (legacy path, shadowed by the org-scoped leads.update above). | not reachable |
mutate | leads.updateStatus | write | Set a lead’s status (legacy path, Clerk-authenticated, an API key cannot reach this; the MCP tool uses the org-scoped leads.update). | |
run | material.import | pipeline | Your prospect folder → a campaign + company list, then it stops. Uploading material is not permission to send. | live key for some arguments |
upload | POST /mcp/material/upload | pipeline | Raw file bytes in, storageId out. Send the file’s path in X-Selda-Path. That path is how Selda maps a file to a company. Then hand the ids to material.import. | |
mutate | messages.approve | write | Approve a drafted message. Approval only. It does not send. | |
query | messages.byLead | read | The whole thread with one lead, sent and received. | |
query | messages.byProject | read | Messages in a workspace. | |
run | messages.generate | pipeline | Draft a message for a lead. | |
query | projects.get | read | One workspace in full: business context, market analysis, ICP, settings. | |
query | projects.list | read | Your workspaces. Start here. Every other fn needs a projectId. | |
mutate | projects.updateContext | write | Rewrite a workspace’s business context. | |
run | replies.classify | pipeline | Classify inbound replies. | |
mutate | replies.draft | write | Write a reply draft into a lead’s Sales Inbox thread. A person reviews and sends it in the app, this can send nothing. | |
run | replies.draft | pipeline | Draft an answer to a reply. | |
run | replies.preview | pipeline | Ask how Selda would answer an enquiry, from this workspace’s Brain, without creating a lead or storing a draft. Same writer the real reply uses, so tuning against this tunes the real thing. Stores nothing and sends nothing. | |
mutate | runs.archive | write | Close a campaign run and take it off the active list. Keeps every contact and every message, deleting contacts stays a human act in the app. | |
mutate | runs.confirmCompanies | write | 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. | live key only |
query | runs.leads | read | The companies a run found, each with the message Selda drafted for it. Nothing is sent. | |
query | runs.list | read | Every campaign run in a project, newest first, with its status. Use it to find a runId you no longer have. Runs the human archived are left out; pass includeArchived: true to see them too. | |
mutate | runs.rename | write | Give a campaign run a name a person would recognise. An empty name restores the derived title. | |
run | runs.startFromLeads | pipeline | Start a campaign from leads already pushed in with selda_add_lead, selected by the source label you gave them. No discovery, Selda writes a message per lead from the analysis that came with it, and stops at the drafts. | live key only |
query | runs.status | read | Status of one campaign run: phase, companies found, contacts resolved, drafts written, errors. | |
mutate | webhooks.create | write | Register an endpoint for events like reply.received. | |
mutate | webhooks.delete | write | Remove a webhook endpoint. | |
query | webhooks.list | read | Outbound webhook endpoints registered for this workspace. |
What is not here, on purpose
There is no function that sends. launchRun is in no registry, not this one and not the MCP
one, and it never will be. A script can prepare a campaign completely: import material, confirm the
companies, write and rewrite every draft. A human presses send in the app. That is the product’s
central claim, not a gap waiting to be filled.
There is no function that creates a workspace. projects.create is absent rather than gated,
because a rule enforced by a plan tier is a rule with a price on it.
The live manifest, always current and needing no key: GET https://api.selda.ai/mcp/capabilities.