---
title: "runs.confirmCompanies"
method: POST
path: "/v1/runs/{runId}/confirm-companies"
tags: ["runs"]
---

# runs.confirmCompanies

`POST /v1/runs/{runId}/confirm-companies`

## Request

- Base URL: `https://api.selda.ai`
- URL: `https://api.selda.ai/v1/runs/{runId}/confirm-companies`
- Auth: HTTP bearer

Confirm 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.

Dispatched to `runs.confirmCompanies` 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 is refused: it runs the discovery and research engine: web search, crawls, model calls, per-lead credits
- MCP tools that wrap the same function: `selda_confirm_companies`
- Filled in from your API key, never sent by you: `orgId`
- A legacy entry. Its summary above says what that costs you.

## Path parameters

- `runId` string, required: A Convex document id in the `campaignRuns` table.

## Request body

- object
  - `language` string, required
  - `languageChosen` boolean: DID THE HUMAN NAME THAT LANGUAGE at this gate, or is it Selda's proposal riding through? The card sends a language on every confirm, whether or not anyone touched the field, and the engine read the mere presence of `confirmedProfile.language` as "the operator answered the question", then enforced it over every company's own evidence and told them they had chosen it. Absent = an older client that does not send it; the presence of a language is then read as before, and simply never attributed to the human.
  - `channel` string, required
  - `targetAudience` string
  - `targetRoles` string[]
  - `targetDecisionMakers` object[]
    - `title` string, required
    - `why` string
  - `targetMarket` string
  - `greeting` string
  - `tone` string
  - `leadsPerCompany` string
  - `excludedDomains` string[]
  - `reviewedCompanies` object[]: THE COMPANIES THAT WERE ON THE SCREEN WHEN THIS PRESS HAPPENED (14.9.2026). `excludedDomains` says what the human took OFF the list, which only describes their decision while the whole list is in front of them. On a run whose discovery is still arriving it does not: a company that had not been found yet was never removed, and the snapshot then recorded it as approved. Fifty asked for, fourteen walked, thirty-six researched unseen. So the client says which rows it had. Optional, and omitting it is the old behaviour exactly, see the long note on `confirmedCompanyList`, which is where the narrowing lives and where it is intersected with the run's own list so nothing can be approved into existence from here.
    - `name` string, required
    - `domain` string
  - `researchCount` number
  - `clarifyingAnswers` object[]
    - `question` string, required
    - `answer` string, required
  - `contactLookups` object: WHAT THIS SEARCH IS ASKED TO LOOK UP BEYOND AN ADDRESS, the human's answers, #127. Absent = an older client, and then `normalizeContactLookups` supplies the defaults, which are written to reproduce what those runs already did. Neither switch controls KEEPING anything. Every phone number and every profile URL a rung already running happens to publish is stored either way, dropping one to honour a switch would be Selda throwing away a fact nobody asked it to throw away. What they control is whether Selda spends anything going to look.
    - `phone` boolean
    - `linkedin` boolean
  - `contactResearchRequest` string: What else the human wants found out about the decision-maker, in their own words. A real query, not a note: `engineV3` runs one web search per named contact with exactly this question, stores the answer on the lead and hands it to the composer. Empty = nothing extra is searched for and nothing extra is spent.

## 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)
