# Quickstart: your first report in 5 minutes

CallQA calls your voice agent like a real customer, then grades the call. Two ways to start:
let your coding agent do it (recommended), or do it by hand with `curl`.

Your first organization comes with **10 free test calls to try CallQA**, no card needed: enough
for this quickstart. See [Free trial](billing.md#free-trial).

## Set up with your coding agent (recommended)

Claude Code, Cursor, Codex or VS Code connect to the CallQA [MCP server](mcp.md). Your
agent then writes the test scenarios from your voice agent's code, verifies the phone number
with you, runs the calls and fixes what fails.

1. **Create an API key.** Open [Developer > MCP server](https://app.callqa.dev/developer/mcp/)
   in the app (an admin), click **Create key**. That page then shows the commands below
   **with your key already in them**: copy from there and skip step 2.
2. **Or paste your key** (it starts with `cqa_`) in place of `PASTE_YOUR_CALLQA_KEY_HERE` in
   the command below. Leave the word `Bearer` in front of it.
3. **Install the server** in your tool (one tab each), then **paste the prompt** that
   follows in your agent, opened in your voice agent's repository.

<!-- tabs -->

#### Claude Code

Run in a terminal (once, for all your projects), then start Claude Code in your voice
agent's repository (restart it if it was open):

```bash
claude mcp add --transport http --scope user callqa https://app.callqa.dev/mcp \
  --header "Authorization: Bearer PASTE_YOUR_CALLQA_KEY_HERE"
```

#### Cursor

Put this in `~/.cursor/mcp.json` (your user settings, not the repository; if the file
exists, add `callqa` to its `mcpServers`):

```json
{
  "mcpServers": {
    "callqa": {
      "url": "https://app.callqa.dev/mcp",
      "headers": {
        "Authorization": "Bearer PASTE_YOUR_CALLQA_KEY_HERE"
      }
    }
  }
}
```

#### Codex

Add this to `~/.codex/config.toml`:

```toml
[mcp_servers.callqa]
url = "https://app.callqa.dev/mcp"
http_headers = { "Authorization" = "Bearer PASTE_YOUR_CALLQA_KEY_HERE" }
```

#### VS Code

Run in a terminal (adds it to your VS Code user profile), then use Copilot Chat in Agent
mode:

```bash
code --add-mcp '{"name":"callqa","type":"http","url":"https://app.callqa.dev/mcp","headers":{"Authorization":"Bearer PASTE_YOUR_CALLQA_KEY_HERE"}}'
```

#### Other clients

Any MCP client with Streamable HTTP and a custom header. Clients that only start local
(stdio) servers can use the `mcp-remote` bridge:

```text
URL:    https://app.callqa.dev/mcp
Header: Authorization: Bearer PASTE_YOUR_CALLQA_KEY_HERE

npx mcp-remote https://app.callqa.dev/mcp --header "Authorization: Bearer PASTE_YOUR_CALLQA_KEY_HERE"
```

<!-- /tabs -->

The prompt to paste in your agent:

```text
Set up CallQA to test my voice agent (the one in this repository). The CallQA MCP server is
installed as "callqa": use its tools and follow its set-up-callqa steps.
1. Call get_setup_status first: it says what is done and which tool does each next step.
2. Environments (staging, production), then my agent's phone number and its verification.
3. Write 2 to 4 test scenarios from this repository (the agent's prompt, tools and flows),
   validate them, save them.
4. One first run: read the report, fix my agent (or the test) where it fails.
5. Schedule the tests every weekday morning and add the CI files.
Rules: before a run, tell me how many real calls it places. Stop and ask me for what only a
person can do (signing the number attestation, putting the verification phrase live,
billing). Never write an API key in a file of this repository. If no callqa tool is
available, tell me to install the MCP server first.
```

Your agent stops twice for you: to **sign the attestation** that you control the number,
and to **put the verification phrase live** if your agent's prompt is not in the repository.
In Claude Code, the same steps are also the slash command `/mcp__callqa__set-up-callqa`.
Details: [MCP server](mcp.md#onboarding-with-an-agent).

## Or by hand, with curl

You need `curl`, `jq`, and a phone number you own or are allowed to test.

### 1. Your API key

In the app, an admin opens [Developer > API keys](https://app.callqa.dev/developer/api-keys/)
and clicks **+ API key**, with the scopes `read`, `write` and `run`. The key is shown
**once** and looks like `cqa_1a2b3c4d_Zr0...`. Put **your** key in place of
`PASTE_YOUR_CALLQA_KEY_HERE`:

```bash
export CALLQA_API_KEY="PASTE_YOUR_CALLQA_KEY_HERE"
export CALLQA="https://app.callqa.dev/api/v1"

curl -sS -H "Authorization: Bearer $CALLQA_API_KEY" "$CALLQA/me" | jq '{org_name, scopes, plan: .plan.code}'
```

```json
{ "org_name": "Acme", "scopes": ["read", "run", "write"], "plan": "growth" }
```

A `401` means the key is wrong or still the placeholder.

### 2. Import a scenario

Save this as `reschedule.yaml`, with **your** phone number (E.164 format):

```yaml
name: "Reschedule a dentist appointment"
phone: "+14155550107"
persona: "Patient who needs to move Thursday's cleaning to next week"
goal: "Move the appointment to any morning next week"
must_include:
  - "confirm the new date and time"
max_latency_ms: 2000
variants:
  - title: "Polite patient"
  - title: "Patient in a hurry"
    persona:
      description: "Patient calling between two meetings, impatient, speaks fast"
```

Import it (the YAML is the raw request body):

```bash
curl -sS -X POST "$CALLQA/scenarios/import" \
  -H "Authorization: Bearer $CALLQA_API_KEY" \
  -H "Content-Type: application/yaml" \
  --data-binary @reschedule.yaml | tee scenario.json | jq '{id, name, target_id, variants}'

export SCENARIO=$(jq -r .id scenario.json)
export TARGET=$(jq -r .target_id scenario.json)
```

`201` means created; importing the same `name` again updates it (`200`). An invalid document
answers `422` with the fields at fault, for example
`{"loc": "target.phone", "msg": "Value error, phone must be E.164, e.g. +15550123456"}`.

### 3. Verify the number (one time)

CallQA only calls numbers you control. A new number is `unverified`: verify it once, in the
app (**Monitoring > Phone numbers**, open the number, **Verify ownership**) or with the API
(scopes `run` and `write`). You get a 6-digit code, add one sentence with it to the line's
greeting (or agent prompt), and we call once to hear it:

```bash
# 1. a code, shown once (the attestation is signed the first time)
curl -sS -X POST "$CALLQA/targets/$TARGET/verification/start" \
  -H "Authorization: Bearer $CALLQA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"attestation": {"accept": true, "signer_name": "Jane Doe, CTO"}}' \
  | jq '{phrase, code_expires_at}'

# 2. add the phrase ("Verification code 4 8 2 1 9 3.") to the greeting, then:
curl -sS -X POST "$CALLQA/targets/$TARGET/verification/call" \
  -H "Authorization: Bearer $CALLQA_API_KEY"

# 3. about 30 seconds later
curl -sS "$CALLQA/targets/$TARGET/verification" \
  -H "Authorization: Bearer $CALLQA_API_KEY" | jq '{status, last_attempt}'
```

`status` becomes `verified` (valid 180 days). The attestation says your organization owns the
number or is authorized to test it; it is kept as proof with the signer, IP and time. If your line only says the code after a
menu, add `"dtmf_gate": "1w2"` (the keys to press first) to the start request. Details:
[Number verification](number-verification.md#code-behind-an-ivr-menu).

### 4. Run the scenario

```bash
curl -sS -X POST "$CALLQA/scenarios/$SCENARIO/runs" \
  -H "Authorization: Bearer $CALLQA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tags": ["quickstart"]}' \
  | tee run.json | jq '{id, number, status, total, url}'

export RUN=$(jq -r .id run.json)
```

One call per enabled variant (here 2), in parallel within your plan's concurrency limit; a
call takes 1 to 5 minutes. Add `"environment": "staging"` and `"version": "v2.3.0"` to the
body to test a given deployment (see [Environments](environments.md)). While a call is in
progress, its page in the app has a **Listen live** button.

### 5. Wait for the result

```bash
while :; do
  curl -sS -H "Authorization: Bearer $CALLQA_API_KEY" "$CALLQA/runs/$RUN" > run.json
  status=$(jq -r .status run.json)
  echo "status: $status"
  [ "$status" = "completed" ] || [ "$status" = "canceled" ] && break
  sleep 15
done
jq '{verdict, score, passed, failed, warned, errored, blocked, cost_cents, url}' run.json
```

```json
{
  "verdict": "pass", "score": 91, "passed": 2, "failed": 0, "warned": 0,
  "errored": 0, "blocked": 0, "cost_cents": 0,
  "url": "https://app.callqa.dev/o/acme/runs/5c1e.../"
}
```

`cost_cents` stays `0` while calls fit in your monthly quota. The run is also in the app
under **Monitoring > Runs and calls**.

### 6. Read the report

```bash
curl -sS -H "Authorization: Bearer $CALLQA_API_KEY" "$CALLQA/calls?run_id=$RUN" \
  | jq '.items[] | {id, title, status, verdict, score, latency_p95_ms}'

CALL=$(curl -sS -H "Authorization: Bearer $CALLQA_API_KEY" "$CALLQA/calls?run_id=$RUN" | jq -r '.items[0].id')

curl -sS -H "Authorization: Bearer $CALLQA_API_KEY" "$CALLQA/calls/$CALL" \
  | jq '{verdict, score, verdicts: [.result.verdicts[] | {type, passed, evidence}], transcript: [.transcript[] | "\(.speaker): \(.text)"]}'
```

Each verdict quotes the transcript as evidence when there is one (`evidence` is `null` for
checks such as `answered` or `max_silence`; `latency_p95_ms` is `null` when no turn latency
was measured, as on a keypad-only menu). The full event trace is at `GET /calls/{id}/events`.
To try any endpoint from your browser, use the [API explorer](https://app.callqa.dev/api/v1/docs).

## If a call did not happen

A call that could not be placed ends with a `blocked_*` status and a `block_reason`:

| `status` | `block_reason` | Fix |
|---|---|---|
| `blocked_policy` | `number_not_verified` | Verify the number (step 3). |
| `blocked_policy` | `sandbox_no_batch`, `sandbox_daily_cap` | The number is not verified yet: only a few manual sandbox calls. Verify it. |
| `blocked_insufficient_credit` | `insufficient_credit` | Quota used up: top up the prepaid wallet. See [Billing](billing.md). |
| `blocked_policy` | `country_not_in_plan` | Your plan allows fewer countries. |

The full list is in [Errors](errors.md#call-statuses-and-block-reasons).

## Next steps

- Let your coding agent run the tests, read the failures and fix your agent:
  [MCP server](mcp.md).
- Gate your deployments on the score: [CI/CD integration](ci-cd.md).
- Get notified instead of polling: [Webhooks](webhooks.md).
- Write richer scenarios (IVR steps, typed assertions, test data): [Writing scenarios](scenarios.md)
  and the [grammar reference](reference/scenario-grammar.md).
