Connect your app
A URL per operation, one auth header, no SDK. Works from any backend in any language.
1. Get a key
Settings → Apps → Selda MCP in the app. The sk_live_… value is shown once.
Start with an sk_test_… key. Test keys reach everything and cannot send anything, so you can
build the whole integration before any of it is real.
SELDA_API_KEY=sk_live_...
SELDA_PROJECT_ID=...2. Make one call
curl -X POST https://api.selda.ai/v1/leads \
-H "Authorization: Bearer $SELDA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "'"$SELDA_PROJECT_ID"'",
"company": "Example Oy",
"email": "owner@example.fi",
"companyDomain": "example.fi",
"source": "my-app"
}'{ "value": { "leadId": "...", "duplicate": false }, "request_id": "req_..." }Selda dedupes by email, so re-running is safe. That is the whole integration for most people.
3. That is the shape of everything else
A URL per operation, one auth header, one response envelope.
GET https://api.selda.ai/v1/leads?projectId=... read something
POST https://api.selda.ai/v1/leads change something
PATCH https://api.selda.ai/v1/leads/{leadId} edit one row
DELETE https://api.selda.ai/v1/webhooks/{webhookId} remove one
Authorization: Bearer sk_live_...GET carries its arguments in the query string; everything else takes a JSON body. Every answer is
{ "value": ..., "request_id": "req_..." }, and request_id is echoed in the X-Request-Id
header so you can quote it in a support ticket without parsing the body.
Every path · Every function and its arguments · OpenAPI 3.1 document
The same API, as an fn in a body
The paths above are a facade over an RPC surface that is still served and is not going away. If you are generating calls, or you want one URL and a name you look up, post the name instead:
POST https://api.selda.ai/mcp/query read something
POST https://api.selda.ai/mcp/mutate change something
POST https://api.selda.ai/mcp/run start work that takes a while
Authorization: Bearer sk_live_...
{ "fn": "<name>", "args": { ... } }It is not a second API, and neither form can reach what the other refuses: a path resolves to an
fn and both are answered by the same dispatcher, with the same key check, scope check, plan gate,
workspace binding and sandbox rule. Both lists are generated from the same registry, so they cannot
drift apart, and every function page shows the two side by side.
Which pattern do you need?
| You want to | Do this |
|---|---|
| Send leads from your backend, a cron job or a form | Push. leads.add, or events.ingest when something happened rather than somebody exists. Examples |
| Show your pipeline in your own dashboard | Read. GET /v1/campaigns/{campaignId}/stats, GET /v1/leads, GET /v1/messages/by-project. Reference |
| React when a reply arrives, without polling | Webhooks. Register a URL, Selda POSTs a signed payload. Webhooks |
| Let Selda fetch from you instead | Connectors. Selda polls your JSON endpoint on a schedule. Below |
| Drive Selda from Claude, ChatGPT or Cursor | MCP, not this. MCP server |
| Put Selda on a WordPress site | The plugin, not this. Selda for WordPress |
Using an AI tool to write the integration? Hit “Copy setup prompt” in the app and paste it into Claude Code, Cursor or Lovable with your product.
Or point it at these docs as plain text:
https://docs.selda.ai/llms-full.txtis every page in one file, andllms.txtis the index. Both are regenerated on every build, so they cannot drift from the pages.
Push your own research in
If you already researched the company, pass it in analysis. Selda uses it as the authoritative
research grounding: it writes the outreach from your analysis instead of crawling from
scratch, and labels the draft “Based on your analysis” in the app.
await fetch("https://api.selda.ai/v1/leads", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SELDA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
projectId: process.env.SELDA_PROJECT_ID,
company: "Example Oy",
companyDomain: "example.fi",
analysis:
"Example Oy builds industrial IoT sensors. Opened a Munich office in Q1 2026 and is hiring " +
"field engineers, a clear expansion signal to lead with.",
source: "claude",
}),
});Faithful by design. The
analysistext is treated as grounded source, so the message can reference its facts, but Selda never fabricates beyond it. Keep it factual and the outreach stays accurate.
Via MCP the same lands as the analysis parameter on selda_add_lead.
Let Selda poll you
Selda fetches your HTTP JSON endpoint on a schedule and syncs the records as leads. Useful when pushing from your side is the awkward direction.
Register it in Settings → Apps → Connect your API, or:
curl -X POST https://api.selda.ai/v1/connectors \
-H "Authorization: Bearer $SELDA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "...",
"name": "My App",
"url": "https://api.example.com/customers",
"authHeader": "Bearer sk_...",
"fieldMap": {
"company": "companyName",
"email": "ownerEmail",
"companyDomain": "website",
"notes": "city"
}
}'Your endpoint returns a JSON array. Selda maps fields via fieldMap, dedupes by email, and polls
on a schedule.
Nothing here sends
Not a lead you push, not an event you ingest, not a draft Selda writes in response. A script can build a campaign all the way up to drafted messages; a human presses send, in the app. There is no function that launches sends and no flag that changes it.
Next
- Examples, copy-paste in curl, Node, Python and PHP
- Webhooks, find out when something happens
- Function reference, every path, every
fnand its arguments - REST paths, the whole URL surface in one table
- Runnable code on GitHub
Source of truth: convex/mcpApi.ts. See Security for key and webhook secret handling.