# Estimate and Results

> GET /v1/campaigns/{id}/estimate for cost, duration and wallet check before launch, and GET /v1/campaigns/{id}/results for a per-contact CSV of outcomes.

Source: https://edesy.in/docs/voice-api/campaigns/estimate-and-results

Before you start a campaign, `GET /v1/campaigns/{id}/estimate` prices every pending contact at your rates, estimates how long the list will take, and checks your wallet. After it runs, `GET /v1/campaigns/{id}/results` downloads a CSV with one row per contact: status, attempts, duration, AMD verdict, SIP status and call SID.

## Estimate

`GET https://voice-api.edesy.in/v1/campaigns/{id}/estimate?assumed_duration_sec=45`

| Query parameter | Description |
|---|---|
| `assumed_duration_sec` | Talk time per answered call to price. The estimate is only as good as this number |

```json
{
  "data": {
    "campaign_id": "...",
    "currency": "INR",
    "pending_contacts": 4800,
    "total_contacts": 5000,
    "rated_contacts": 4800,
    "unrated_contacts": 0,
    "assumed_duration_sec": 45,
    "estimated_cost": 2400,
    "estimated_minutes": 96,
    "wallet_balance": 3000,
    "sufficient_balance": true,
    "rates_unavailable": false
  }
}
```

| Field | Description |
|---|---|
| `estimated_cost` | Cost if every pending contact answers for `assumed_duration_sec`: the worst case for one pass. Retries are not added, because unanswered calls are free |
| `estimated_minutes` | Wall-clock time to work the list at `max_concurrent`, ignoring window closures |
| `sufficient_balance`, `shortfall_inr` | Whether the wallet covers `estimated_cost`, and by how much it falls short |
| `unrated_contacts` | Contacts that matched no rate. Shown rather than priced at zero |
| `rates_unavailable` | `true` when no rates resolve; the cost fields are then meaningless |

Starting a campaign that the wallet cannot cover returns `402` with the required amount and your balance.

## Live progress

`GET /v1/campaigns/{id}/contacts?status=no-answer&page=1&per_page=50` lists contacts by status:

| Contact status | Meaning |
|---|---|
| `pending` | Not dialed yet |
| `dialing` | Being called |
| `answered` | Answered, call in progress |
| `completed` | Answered and finished |
| `no-answer` | Not answered |
| `busy` | Busy |
| `failed` | Could not connect |
| `retrying` | Waiting for a retry |
| `suppressed` | Skipped (DNC or duplicate) |

## Results CSV

```bash
curl -o results.csv https://voice-api.edesy.in/v1/campaigns/{id}/results \
  -H "Authorization: Bearer $EDESY_KEY"
```

Columns:

| Column | Description |
|---|---|
| `phone_number`, `name` | The contact |
| `status` | Final contact status |
| `attempts` | Calls made |
| `duration_sec` | Answered seconds |
| `answered_by` | `human`, `machine` or `unknown` when AMD ran |
| `sip_status`, `sip_reason` | Final SIP status |
| `call_sid` | The last call, readable with `GET /v1/calls/{CallSid}` |
| `error` | Error detail, if any |
| `suppression_reason` | Why a contact was skipped |
| `consent_source`, `consent_timestamp` | Consent you supplied |

To call the people you missed, `POST /v1/campaigns/{id}/duplicate` with `{"audience": "unreached"}` and start the copy.
