events.ingest
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.
| Endpoint | POST /mcp/mutate |
| Scope | write |
| Sandbox key | allowed |
| Extra entitlement | inboundIntake |
| MCP tools | selda_ingest_event |
| Convex function | mcpQueries:ingestEvent |
A free sandbox key may call it.
inboundIntake entitlement on top of the scope. Without it the call comes back 403 and says so, rather than doing half the work.Arguments
| Argument | Type | Required | Notes |
|---|---|---|---|
projectId | string | required | |
type | string | required | Caller-defined: “form_submitted”, “analysis_completed”, “contract_signed”, anything. |
identity | object | required | |
payload | any | optional | Whatever the event carried. Stored whole; only summarized into the notes line. |
source | string | optional | Which system reported it, e.g. “evexcreative.com”. Stored as discoverySource. |
occurredAt | string | number | optional | ISO string or epoch ms. An unusable or out-of-range value falls back to now, and says so. |
idempotencyKey | string | optional | Replay protection, unique per org. A repeat returns the first result unchanged. |
attachToRunId | id<"campaignRuns"> | optional | Put the lead on this campaign run’s review list as well. The run’s own tone, signature, follow-up rhythm and safety rules already exist and are already approved; an inbound lead joining them beats it inventing a process of its own. It joins at the review stage with no message written, exactly like every other lead on that run. |
analysis | string | optional | Research the caller already did, used by engineV3 as authoritative grounding. |
tags | string[] | optional | |
autoAdvance | boolean | optional | Write the reply now, from the Brain, and leave it in the Sales Inbox for a person. Off by default because it spends. It never sends: the draft waits for the same approval as every other draft, and draft.ready is published so nobody has to poll to find it. When drafting cannot be done the lead is untouched and draft.failed says why. |
Shapes that did not fit the table:
identity: { email?: string; domain?: string; linkedinUrl?: string; name?: string; firstName?: string; lastName?: string; company?: string; jobTitle?: string; phone?: string }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": "events.ingest", "args": { "projectId": "<projectId>", "type": "<type>", "identity": {} } }'{
"fn": "events.ingest",
"args": {
"projectId": "<projectId>",
"type": "<type>",
"identity": {}
}
}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. mcpQueries:ingestEvent 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.