Connect your app (API)Function reference

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.

This page and every function page under Reference / API functions are generated from 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.

StatusMeaning
401Missing, expired or revoked key
403The key lacks the scope, the plan entitlement, or is a sandbox key on a live-only function
400Unknown fn, or arguments the validator refused
429Rate limited. Wait and retry
500The 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.

fnScopeWhat it doesLimits
mutatebrain.addwriteAdd 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.
querybrain.listreadThe 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.
mutatebrain.removewriteTake one Brain item back out. The human owns what Selda knows.
mutatebrain.updatewriteRewrite the title and body of one Brain item.
mutatecampaigns.addLeadswritePut specific leads into a campaign.
mutatecampaigns.addLeadsByTagwritePut every lead carrying a tag into a campaign (legacy table).
mutatecampaigns.addRulewriteAdd a campaign rule.
mutatecampaigns.createwriteCreate a campaign (legacy table, not the one the app’s campaign-flow UI reads).
querycampaigns.getreadOne campaign: status, channels, settings, leads.
querycampaigns.listreadCampaigns in a workspace.
mutatecampaigns.lockMessageStructurewriteLock 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.
querycampaigns.messageStructurereadRead 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.
mutatecampaigns.setMessageStructurewriteState 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.
querycampaigns.statsreadCampaign counters: sent, delivered, opened, clicked, replied, bounced.
mutatecampaigns.updatewriteChange a campaign.
runcompany.lookuppipelineResolve a company and return the right people to reach. Starts nothing.live key only
mutateconnectors.createwriteRegister a data connector.
mutateconnectors.deletewriteRemove a data connector.
queryconnectors.listreadData connectors registered for this workspace.
runconnectors.syncpipelinePull from a connected data source.live key only
querycredits.inforeadCredit balance, daily free credits, usage, plan.
mutatedrafts.removewriteTake 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.
mutatedrafts.updatewriteRewrite the draft on one run lead. Refuses a message that already went out; never sends.
runengine.startpipelineThe 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
mutateevents.ingestwriteReport 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
mutateflows.createwriteCreate a flow: a trigger plus the steps to run when something arrives. Off unless you say otherwise. No step can send.
mutateflows.deletewriteDelete a flow and its run log.
queryflows.listreadThe 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.
queryflows.runsreadWhat a flow actually did, run by run, step by step, including the steps that did nothing and why.
mutateflows.saveSkillwriteWrite or rewrite an instruction file a flow step reads: how this business decides what an enquiry is.
mutateflows.setEnabledwriteSwitch a flow on or off.
mutateflows.updatewriteRewrite a flow’s name, trigger or steps.
mutateinbox.addMessagewritePut 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.
mutateknowledge.appendwriteAdd to what Selda knows about your business.
queryknowledge.getreadWhat Selda knows about your business: the prose that grounds every message.
mutateknowledge.setwriteReplace what Selda knows about your business.
mutateleads.addwriteAdd one company/contact. Pass analysis with research you already did and the message is written from it instead of a fresh crawl.
mutateleads.addAliaswriteClaim another email address for a lead, so a reply from it lands in the same conversation. Also adopts that address’s earlier unlinked inbound.
mutateleads.addBatchwriteAdd many companies/contacts in one call.
mutateleads.addTagwriteTag a lead.
mutateleads.deletewriteRemove one lead. Deleting is the caller’s act, Selda never removes a lead on its own.
mutateleads.deleteBatchwriteRemove many leads.
runleads.enrichpipelineEnrich one lead from a natural-language instruction.live key only
runleads.enrichpipelineEnrich a workspace’s leads from a natural-language instruction (legacy path).live key only, not reachable
runleads.enrichBatchpipelineEnrich many leads from a natural-language instruction.live key only
queryleads.getreadOne lead in full: research, fit, outreach angle, notes.
queryleads.listreadLeads in a workspace.
mutateleads.mergewriteMerge duplicate leads.
mutateleads.skipwriteDELETES a lead and every message on it (legacy path, Clerk-authenticated, an API key cannot reach this; use leads.delete instead).
mutateleads.updatewriteEdit a lead’s fields, including its status. Org-scoped, so an API key can reach it.
mutateleads.updatewriteEdit a lead (legacy path, shadowed by the org-scoped leads.update above).not reachable
mutateleads.updateStatuswriteSet a lead’s status (legacy path, Clerk-authenticated, an API key cannot reach this; the MCP tool uses the org-scoped leads.update).
runmaterial.importpipelineYour prospect folder → a campaign + company list, then it stops. Uploading material is not permission to send.live key for some arguments
uploadPOST /mcp/material/uploadpipelineRaw 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.
mutatemessages.approvewriteApprove a drafted message. Approval only. It does not send.
querymessages.byLeadreadThe whole thread with one lead, sent and received.
querymessages.byProjectreadMessages in a workspace.
runmessages.generatepipelineDraft a message for a lead.
queryprojects.getreadOne workspace in full: business context, market analysis, ICP, settings.
queryprojects.listreadYour workspaces. Start here. Every other fn needs a projectId.
mutateprojects.updateContextwriteRewrite a workspace’s business context.
runreplies.classifypipelineClassify inbound replies.
mutatereplies.draftwriteWrite a reply draft into a lead’s Sales Inbox thread. A person reviews and sends it in the app, this can send nothing.
runreplies.draftpipelineDraft an answer to a reply.
runreplies.previewpipelineAsk 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.
mutateruns.archivewriteClose 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.
mutateruns.confirmCompanieswriteConfirm 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
queryruns.leadsreadThe companies a run found, each with the message Selda drafted for it. Nothing is sent.
queryruns.listreadEvery 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.
mutateruns.renamewriteGive a campaign run a name a person would recognise. An empty name restores the derived title.
runruns.startFromLeadspipelineStart 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
queryruns.statusreadStatus of one campaign run: phase, companies found, contacts resolved, drafts written, errors.
mutatewebhooks.createwriteRegister an endpoint for events like reply.received.
mutatewebhooks.deletewriteRemove a webhook endpoint.
querywebhooks.listreadOutbound 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.