Base path: `https://api.1toolring.com/api/v1/email/verify`

Auth: `Authorization: Bearer <key>`. Price: 105 credits per chargeable address. A `catch_all` or `unknown` verdict, a cache hit, and a failed call charge 0.

## Endpoints

| Method | Path | Price | Description |
| --- | --- | --- | --- |
| POST | `/` | 105 per chargeable address | Real-time verdict for one address, with a 7-day result cache. |
| POST | `/bulk` | 105 per chargeable address | Upload a CSV or TXT list (multipart field `file`, up to 1M rows) and verify it as a job. |
| GET | `/jobs` | free | Your bulk jobs, newest first, with `page` and `per_page`. |
| GET | `/jobs/{job_id}` | free | Status and result counters of one job, refreshed live while it runs. |
| GET | `/jobs/{job_id}/report` | free | Download a finished job's result CSV. `filter=all\|ok\|ok_and_catch_all\|unknown\|invalid`. |
| POST | `/jobs/{job_id}/cancel` | free | Stop an in-flight job. A finished job is never re-billed by a late cancel. |
| GET | `/credits` | free | The wallet balance and how many addresses it still covers at the current price. |

## Verify one address

```bash
curl -X POST https://api.1toolring.com/api/v1/email/verify \
  -H "Authorization: Bearer $TOOLRING_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com"}'
```

```json
{
  "data": {
    "email": "user@example.com",
    "quality": "good",
    "result": "ok",
    "resultcode": 1,
    "subresult": "",
    "is_free": false,
    "is_role": false,
    "is_disposable": false,
    "did_you_mean": "",
    "credits_used": 105,
    "cached": false,
    "checked_at": "2026-11-01T09:00:00Z"
  }
}
```

## What is charged

| `resultcode` | `result` | Charge |
| --- | --- | --- |
| 1 | `ok` | 105 |
| 2 | `catch_all` | 0 |
| 3 | `unknown` | 0 |
| 4 | `error` | 0 |
| 5 | `disposable` | 105 |
| 6 | `invalid` | 105 |

A repeat of the same address within 7 days is served from cache with `cached: true` and charged 0. The `X-ToolRing-Credits` response header carries the amount actually charged.

## Bulk jobs

```bash
curl -X POST https://api.1toolring.com/api/v1/email/verify/bulk \
  -H "Authorization: Bearer $TOOLRING_KEY" \
  -F "file=@emails.csv"
```

```json
{
  "data": {
    "id": "vjob_abc123",
    "status": "running",
    "filename": "emails.csv",
    "row_count": 1200,
    "counts": { "ok": 0, "catch_all": 0, "disposable": 0, "invalid": 0, "unknown": 0, "verified": 0 },
    "credits_charged": "0",
    "created_at": "2026-11-01T09:00:00Z",
    "finished_at": null,
    "report_ready": false
  }
}
```

A job is capped by the wallet: it never verifies more addresses than the balance covers. Poll `GET /jobs/{job_id}` until `report_ready` is true, then download `GET /jobs/{job_id}/report?filter=ok`.

## Errors

| Code | Status | Meaning |
| --- | --- | --- |
| `VALIDATION_ERROR` | 400 | `email` missing or not an address; bulk upload missing the `file` field. |
| `INSUFFICIENT_CREDITS` | 402 | The prepaid wallet cannot cover one address. These endpoints never fall back to a free allowance; top up and retry. |
| `NOT_FOUND` | 404 | Unknown `job_id`, or a job that belongs to another key. |
| `UPSTREAM_ERROR` / `UPSTREAM_UNAVAILABLE` | 502 | The verifier failed; nothing was charged. |