# Send calls

`POST /v1/calls` sends one finished call. Chambr saves it, finds the rep's
side of the conversation and queues it for scoring.

## Request fields

```json
{
  "external_id": "00T5f00000ABCdE",
  "provider": "salesforce",
  "occurred_at": "2026-09-28T15:02:14Z",
  "call_type": "discovery",
  "scorecard_id": null,
  "rep": { "email": "sarah.reid@yourcompany.com", "name": "Sarah Reid" },
  "participants": [
    { "role": "rep", "name": "Sarah Reid", "email": "sarah.reid@yourcompany.com" },
    { "role": "customer", "name": "Dana Whitfield", "company": "Acme Freight" }
  ],
  "transcript": {
    "segments": [
      { "speaker": "Dana Whitfield", "text": "Morning. I have about twenty minutes.", "start_ms": 0 },
      { "speaker": "Sarah Reid", "text": "Understood, I will keep it tight.", "start_ms": 4100 }
    ]
  },
  "metadata": { "salesforce": { "task_id": "00T5f00000ABCdE", "opportunity_id": "0065f00000XyZ" } }
}
```

| Field | Required | Notes |
|---|---|---|
| `external_id` | yes | Your stable id for the call, such as the Salesforce Task id. 1 to 200 characters. |
| `provider` | yes | Where the call comes from: letters, digits, `-` and `_`, up to 32 characters. Chambr lower-cases it. `(provider, external_id)` is the [idempotency key](https://developers.staging.chambr.ai/idempotency.md). |
| `occurred_at` | yes | RFC 3339 with `Z` or a colon offset (`+00:00`, `-05:30`). `+0000` without a colon is rejected. Between 2000-01-01 and one day from now. |
| `rep` | no | Who the call is scored for. See [Identify the rep](#identify-the-rep) and [Send a call without a rep](#send-a-call-without-a-rep). |
| `rep.email` | when `rep` is sent | The rep's Chambr login email. |
| `rep.name` | no | Helps match the rep's speaker label. |
| `transcript` | yes | Exactly one of `segments` or `text`, plus optional `rep_speaker`. See [Transcript formats](#transcript-formats). |
| `call_type` | no, recommended | `discovery`, `cold_call`, `warm_call`, `customer_success`, `cross_selling`, `evaluation`, `negotiation`, `gatekeeper`, `inbound_call` or `technical_discovery`. |
| `scorecard_id` | no | A published scorecard of the rep's team. |
| `participants` | no | Up to 50, each with `role` (`rep`, `customer` or `other`), `name`, and optional `email`, `company` and `speaker_label`. The first `customer` becomes the call's title in Chambr. |
| `recording_id` | no | From [Attach a recording](https://developers.staging.chambr.ai/guides/recordings.md). |
| `metadata` | no | Any JSON object for your reference, up to 16 KB and 5 levels deep. Stored with the call, never used for scoring. Send only the personal data you need. |

Optional fields may be omitted or `null`. Text must not contain the NUL
character. Chambr rejects any other field with `422 unknown_field`; put custom
data in `metadata`.

| Limit | Value |
|---|---|
| Request body | 1 MiB (`413 payload_too_large`) |
| Transcript | 2,000 turns after merging consecutive turns by one speaker, and 400,000 characters (`422 transcript_too_long`, never truncated) |
| `segments` | 10,000 segments of up to 20,000 characters |
| `text` | 600,000 characters |
| `participants` | 50 |

## Identify the rep

`rep.email` must be the rep's Chambr login email. The rep must be active, have
signed in once, and belong to exactly one team. Otherwise Chambr returns
`422 rep_*` and saves nothing. Invite the rep in Chambr, or leave `rep` out.

Chambr scores only the rep's side and never assumes the first speaker is the
rep. It finds the rep's speaker label in this order, ignoring case and extra
spaces:

1. `transcript.rep_speaker`, if sent. It must match one speaker label.
2. The one label that matches `rep.email`, `rep.name`, the rep's name in
   Chambr, or the name, email or `speaker_label` of a `role: "rep"`
   participant.
3. Inference from the dialogue. It never picks a `customer` or `other`
   participant.

If none decides, Chambr returns `422 rep_speaker_undetermined` (send
`rep_speaker`) or `503 inference_unavailable` (retry after `Retry-After`).
Every other speaker counts as the customer side. `attribution.speaker_source`
says which rule applied.

## Send a call without a rep

Leave `rep` out when your system does not know the Chambr user. Chambr files
the call under the API key's team, owned by the admin who created the key,
until a manager reassigns it in Chambr. `attribution.rep_source` is
`api_key_owner`. If the key's team was deleted, Chambr returns
`422 team_not_configured`.

```json
{
  "external_id": "00T5f00000ABCdF",
  "provider": "salesforce",
  "occurred_at": "2026-09-28T16:10:00Z",
  "call_type": "discovery",
  "participants": [
    { "role": "rep", "name": "Sarah Reid" },
    { "role": "customer", "name": "Dana Whitfield", "company": "Acme Freight" }
  ],
  "transcript": {
    "segments": [
      { "speaker": "Dana Whitfield", "text": "Thanks for calling back." },
      { "speaker": "Sarah Reid", "text": "Of course. Did the lane numbers come through?" }
    ]
  }
}
```

## Transcript formats

`segments` (preferred) is an ordered array of
`{ "speaker": "...", "text": "...", "start_ms": 1234 }`; `start_ms` is
optional. `text` is a plain transcript with a speaker label on each turn:

```text
Sarah Reid: Thanks for making the time.
Dana Whitfield: Of course.
```

```text
[00:12] Sarah Reid
Thanks for making the time.
00:15 - Dana Whitfield
Of course.
```

A transcript without speaker labels returns `422 transcript_unlabeled`.

## Scorecard and call type

| Field | If you omit it |
|---|---|
| `call_type` | Chambr classifies the call from the transcript, which slows the request, and falls back to `discovery`. |
| `scorecard_id` | The team's scorecard rule for the call type decides, else the team default. With neither, Chambr returns `422 scorecard_not_configured`. If the rule or default points to a scorecard that is no longer available, `422 scorecard_rule_unavailable`. |

## Check status

Scoring is asynchronous and takes minutes. Poll `GET /v1/calls/{id}` at most
once a minute. It works for any call in your account.

| `status` | Meaning | What to do |
|---|---|---|
| `queued` | Waiting to be scored | Nothing |
| `scoring` | Being scored | Nothing |
| `scored` | Done | Open `app_url` |
| `skipped` | Not scored; `skip_reason` is `too_short`, `no_scorecard`, `empty_scorecard` or `all_criteria_na` | Fix the scorecard, or nothing for a short call |
| `failed` | Scoring did not finish | Nothing; a manager can re-run it in Chambr |

`app_url` needs a Chambr login with access to the
rep's team. Store it on your record, for example the Salesforce Task.
