---
title: "leads.add"
method: POST
path: "/v1/leads"
tags: ["leads"]
---

# leads.add

`POST /v1/leads`

## Request

- Base URL: `https://api.selda.ai`
- URL: `https://api.selda.ai/v1/leads`
- Auth: HTTP bearer

Add one company/contact. Pass `analysis` with research you already did and the message is written from it instead of a fresh crawl.

Dispatched to `leads.add` on `POST /mcp/mutate`. This route is a different URL for the same call: the request is answered by the same dispatcher, with the same authentication, the same scope check, the same organisation scoping and the same refusals.

- Scope: `write`
- A free sandbox key may call it
- MCP tools that wrap the same function: `selda_add_lead`
- Filled in from your API key, never sent by you: `orgId`, `userId`

## Request body

- object
  - `projectId` string, required
  - `firstName` string
  - `lastName` string
  - `email` string
  - `company` string
  - `companyDomain` string
  - `jobTitle` string
  - `linkedinUrl` string
  - `phone` string
  - `notes` string
  - `analysis` string
  - `outreachAngle` string: The opening the operator already chose, and why they chose the company. Both are read by the engine (`operatorAngle` governs the opening, `whyGoodLead` feeds research and the fit call). They were wired into the engine on 17.8.2026 and had no door on this mutation for an hour, which is the same shape of gap the analysis had for a week: read by a writer, unreachable by the caller.
  - `whyGoodLead` string
  - `externalUrl` string
  - `mediaImageUrl` string: A PICTURE FOR THIS ONE PERSON'S MESSAGE (15.9.2026). `mediaImageUrl` is a public https address, a gif rendered for this company, a screenshot of their own site. `mediaLinkUrl` makes it clickable, which is how a gif opens a video. It goes into THIS lead's draft when the draft is composed, so it is in the text the human approves, and it beats any picture set on the campaign as a whole. A picture never costs a lead: an address that cannot be used is dropped, the lead is created anyway, and the answer says in words what was not kept and why (`mediaRejected`).
  - `mediaLinkUrl` string
  - `mediaAlt` string
  - `mediaWidth` number
  - `source` string
  - `tags` string[]
  - `city` string
  - `country` string
  - `location` string
  - `customFields` unknown
  - `fields` object: Your own data for this company's personal page, e.g. `{ demo: "https://...", offer: "...", price: 49 }`.

## Response `200`

The call succeeded.

- object
  - `value` unknown, required
  - `request_id` string, required: Echoed in the `X-Request-Id` header.

## Other responses

- `400`: Unknown route, or arguments the validator refused.
- `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.
- `429`: Rate limited. Wait for the next minute window and retry.
- `500`: The function threw. The message says what, without leaking internals.

---

[API](https://skmtc.dev/selda/apis/selda-api.md) · [All operations](https://skmtc.dev/selda/apis/selda-api/llms.txt) · [OpenAPI document](https://skmtc.dev/selda/apis/selda-api/revisions/f0b115a94c0f?raw)
