REST APIStart here

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 toDo this
Send leads from your backend, a cron job or a formPush. leads.add, or events.ingest when something happened rather than somebody exists. Examples
Show your pipeline in your own dashboardRead. GET /v1/campaigns/{campaignId}/stats, GET /v1/leads, GET /v1/messages/by-project. Reference
React when a reply arrives, without pollingWebhooks. Register a URL, Selda POSTs a signed payload. Webhooks
Let Selda fetch from you insteadConnectors. Selda polls your JSON endpoint on a schedule. Below
Drive Selda from Claude, ChatGPT or CursorMCP, not this. MCP server
Put Selda on a WordPress siteThe 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.txt is every page in one file, and llms.txt is 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 analysis text 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

Source of truth: convex/mcpApi.ts. See Security for key and webhook secret handling.