> ## Documentation Index
> Fetch the complete documentation index at: https://docs.optimaldial.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> Notable changes to the OptimalDial public API.

All meaningful, customer-facing changes to the OptimalDial REST API and webhook contract are listed here. The `api_version` field on webhook payloads changes only when the payload shape itself changes.

## 2026-08-26 — Multiple phone columns per upload

* Map up to **10** phone columns in one upload with the new `phone_columns` field — comma-separated in CSV mode, an array in JSON mode. See [Multiple phone columns](/api-reference/uploads#multiple-phone-columns).
* **Billing is per number.** A contact with a number in two mapped columns costs 2 credits at `standard` and `ceil(2 × 1.5) = 3` at `max`. The same number appearing in more than one column — or already seen on an earlier contact — is de-duplicated and charged once.
* New `valid_number_count` on the `Upload` object and in the `upload.created` webhook: the numbers you were charged for. `valid_row_count` keeps its meaning of contact rows, so **do not** derive an invalid count by subtracting one from the other on a multi-column upload.
* The processed file keeps **one row per contact** and carries one `{column}_OptimalDial_Status` per mapped column (plus `{column}_OptimalDial_Answer_Intent` and `{column}_OptimalDial_Identity` on identity lists). A blank cell means that column held no number for that contact.
* The 100-number minimum now counts numbers rather than contacts, so 50 contacts with two mapped columns each qualifies.
* **Nothing changes for single-column uploads.** `phone_column` still works on its own, the result column is still the plain `OptimalDial_Status`, and `valid_number_count` equals `valid_row_count`. No `api_version` bump.

## 2026-08-10 — Upload cancellation removed

* **Breaking:** `DELETE /api/v1/uploads/{id}` is removed. It now returns `410 Gone` and no longer changes the upload or refunds credits. Submitting an upload is final — once we accept a list, we own the work and the refund decision.
* The endpoint's guarantees never held in practice: a cancel that arrived after processing began still flipped the upload to `cancelled` without refunding anything, and a repeated call could erase the record of a refund that had already been paid out.
* If you submitted an upload in error, email [support@optimaldial.com](mailto:support@optimaldial.com) with the upload id and we'll refund the credits where the work hasn't been done.
* The `cancelled` status stays in the `Upload` schema so historical uploads still parse. No new upload can enter it.

## 2026-07-03 — Credit balance endpoint

* New [Credit Balance API](/api-reference/credit-balance) — `GET /api/v1/credits/balance` returns your organization's current credit balance, split into `subscription` and `purchased` pools plus a `total`. Read-only, costs no credits, and available to every account with an API key. Use it to check remaining credits before submitting uploads or contacts.

## 2026-06-26 — Spam Monitoring API

* New [Spam Monitoring API](/api-reference/spam-monitoring) — track your outbound numbers for carrier spam/scam-likely flagging across AT\&T, T-Mobile, and Verizon. Add a number, complete verification, then read per-carrier status, run history, and screenshots, or get pushed alerts over webhooks. Available to every account with an API key and an active subscription.
* New endpoints: `POST /api/v1/spam/numbers`, `GET /api/v1/spam/numbers`, `GET /api/v1/spam/numbers/{id}`, `DELETE /api/v1/spam/numbers/{id}`, `POST /api/v1/spam/numbers/{id}/verify`, `POST /api/v1/spam/numbers/{id}/verify/resend`, `GET /api/v1/spam/numbers/{id}/history`, and `GET /api/v1/spam/numbers/{id}/screenshot` (streams `image/png`).
* **SMS-or-call verification.** Adding a number auto-initiates verification; submit the code to `…/verify`. Choose `call` (default) or `sms` — SMS isn't deliverable to landlines, so fall back to `call`. Verification is required for monitoring to run, not a security gate.
* Two new webhook events on the existing `POST /api/v1/webhooks` endpoint: [`spam.detected`](/guides/webhooks#event-types) — **edge-triggered**, fires once on a clean→spam transition per carrier (not on every daily re-test) — and `number.verified`. Same HMAC signing, headers, and retry semantics as existing events; no `api_version` bump.

## 2026-06-25 — Higher rate limits + per-row throughput

* Write-endpoint rate limits were raised and restructured into token-bucket tiers: **600/min per API key** (was 60) and **1,500/min per organization** (was 600). `POST /api/v1/contacts` is now covered too, so high-volume per-row integrations (e.g. a Clay "Run column") pace cleanly.
* `429` responses gained a `scope` field (`per_key` | `per_org`) and `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset` headers; the `observed` field was removed. `Retry-After` is unchanged.
* Buckets now refill continuously rather than resetting on a clock-aligned minute — burst up to the limit, then steady-state. Honor `Retry-After` and large jobs drain without errors.
* High-volume partner integrations can have their per-organization limit raised on request.

## 2026-06-24 — MAX validation tier on the Contacts API

* `POST /api/v1/contacts` accepts an optional `validation_tier` field (`standard` | `max`). Defaults to `standard`, so existing integrations are unaffected. An unrecognized value returns `400`.
* **`max` has no minimum submission size here** — the 100-number floor applies only to `POST /api/v1/uploads`. Per-row pipelines can request `max` one contact at a time.
* Because credits are whole numbers, `max` bills marginally on this endpoint: successive `max` contacts are charged `2, 1, 2, 1, …`, so a queued batch of N totals `ceil(N × 1.5)` — the same price as those N numbers submitted as one `max` upload. Read `credits_charged` per response for the exact amount. See [Validation tiers](/api-reference/contacts#validation-tiers).
* The contact object and the `contact.completed` / `contact.failed` webhook payloads now include `validation_tier`. No `api_version` bump — the field is additive.
* **Documentation note:** this field shipped on the date above but was missing from the Contacts API reference until 2026-08-11. The endpoint accepted `max` throughout; only the docs were incomplete.

## 2026-06-24 — Contacts API generally available

* The [Contacts API](/api-reference/contacts) (`POST /api/v1/contacts`, `GET /api/v1/contacts`, `GET /api/v1/contacts/{contact_id}`) is now available to every account with an API key. It was previously limited to Enterprise organizations.
* This access change altered no request or response shapes. Calls no longer return `403 feature_not_enabled`; access is gated only by your API key and credit balance.

## 2026-06-05 — MAX validation tier

* `POST /api/v1/uploads` accepts an optional `validation_tier` field (`standard` | `max`) in both CSV and JSON modes. Defaults to `standard`, so existing integrations are unaffected.
* `max` is more thorough and is charged at 1.5 credits/number (billed as `ceil(valid_row_count × 1.5)`). See [Validation tiers](/api-reference/uploads#validation-tiers).
* The upload object now includes a `validation_tier` field.

## 2026-04-23 — v1 GA

Initial general availability of the public API.

**New endpoints**

* `POST /api/v1/uploads` — submit phone-number lists in CSV (multipart) or JSON form.
* `GET /api/v1/uploads`, `GET /api/v1/uploads/{id}` — list and retrieve uploads.
* `DELETE /api/v1/uploads/{id}` — cancel an upload that hasn't started processing.
* `GET /api/v1/uploads/{id}/download/{original|processed|processed-filtered}` — short-lived signed download URLs.
* `POST /api/v1/webhooks`, `GET /api/v1/webhooks`, `GET /api/v1/webhooks/{id}`, `PATCH /api/v1/webhooks/{id}`, `DELETE /api/v1/webhooks/{id}` — manage webhook endpoints.
* `GET /api/v1/webhooks/{id}/deliveries` — recent delivery attempts for one endpoint.

**Authentication**

* `od_live_*` API keys, scoped to a single organization. Owner-only minting via the in-app developer panel; SHA-256 hashed at rest; auto-revoked when the creating user leaves the org.

**Webhooks**

* Three event types: `upload.created`, `upload.completed`, `upload.failed`.
* HMAC-SHA256 signatures over `{timestamp}.{body}`, in Stripe-compatible `t=…,v1=…` format.
* 5-minute replay window.
* Up to 6 delivery attempts with backoff (1m, 5m, 30m, 2h, 12h between attempts).
* Endpoints auto-disabled after 20 consecutive failures.
* SSRF hardening: HTTPS-only, public IPs only, redirects not followed.

**Rate limits**

* 60 requests/minute per API key, 600 requests/minute per organization, on write endpoints.
