Upload resource that you can poll for status or pull processed results from.
The Upload object
Every endpoint in this section returns or includes anUpload. Most fields are populated incrementally as the upload moves through its lifecycle.
result_summary
Populated once the upload iscompleted. Lets you see the shape of a list
without downloading it.
format is "identity" when the list was processed with name columns mapped and
"answer_intent" otherwise. It tells you which vocabulary status_counts keys —
and which values statuses accepts on the download endpoint.
Upload statuses
The
pending_mapping, validating, validation_failed, and awaiting_confirmation statuses are only reachable from the in-app upload flow; API-created uploads jump straight to ready_for_processing.
OptimalDial Identity
OptimalDial Identity is in public beta. It is available to every account and
the output format is stable, but we’re still tuning the thresholds against real
call outcomes. We’d genuinely like your feedback — especially how the identity
bands compare to what your reps report from the field. Get in touch at
support@optimaldial.com.
- Answer Intent — will this number pick up?
- Identity — is it actually your contact?
full_name_column or first_name_column + last_name_column. Without a
name, identity is unavailable for that list.
OptimalDial_Status — the nine tiers
When identity is enabled,OptimalDial_Status holds the two axes crossed into one
value. Take the Answer Intent value; if identity is Medium or better, insert
“Right-Party”; if identity is Low, replace it with “Likely Wrong Number”.
Each description states what the number is likely to do, then who it’s likely to
belong to — in that order. Every tier except
7 - Non-Mobile is a prediction,
which is why they all carry “Likely”; line type is a carrier attribute, so tier 7
states it outright. Ordering is answer-intent-major, and odd tiers are the
identity-backed ones.
The numeric prefix is there so the column sorts correctly as plain text in Sheets,
Excel, and dialer imports.
Treat this as a fixed vocabulary produced upstream — filter or sort on the numeric
prefix rather than re-deriving the tier from the two axes, which will drift.
Without identity, OptimalDial_Status holds the Answer Intent value instead
(Likely Answer, Likely Call Screening, Likely Voicemail, Non-Mobile,
Unknown), exactly as it always has.
The OptimalDial_Identity column
Opt in viainclude_identity on the download endpoint. Values are Very High,
High, Medium, Low, and Unknown.
Validation tiers
Each upload runs at one of two tiers. Passvalidation_tier on Create an upload; it defaults to standard.
Credits are always whole numbers —
max rounds the total up, so a 1,001-number list at max is charged 1502 credits. Check credits_charged on the response to see the exact amount deducted.
Multiple phone columns
Most CRM exports carry more than one number per contact — a mobile, a direct line, a switchboard. Map up to 10 of them in a single upload withphone_columns and every number is validated.
Billing is per number. A contact with a number in two mapped columns costs
2 credits at standard, or ceil(2 × 1.5) = 3 at max — the same as two
single-number contacts would. The same number listed in more than one column is
de-duplicated and charged once, and so is a number that already appeared on an
earlier contact. valid_number_count on the response is what you were charged
for; valid_row_count is how many contacts those numbers belong to.
What comes back
The processed file keeps one row per contact — your file’s shape is unchanged. Each mapped column gets its own result columns, prefixed with that column’s header:{column}_OptimalDial_Answer_Intent and {column}_OptimalDial_Identity.
Filtering a processed download by status keeps a
contact when any of their numbers matches, and sorting ranks each contact by
their best number.
Map a single column — with
phone_column, or a one-entry phone_columns — and
nothing changes: you get the plain OptimalDial_Status column exactly as before.
The prefixed shape only appears when you map more than one.Create an upload
multipart/form-data— upload a CSV file you already have on disk. Limit: 100 MB.application/json— submit phone numbers (or contact objects) directly in the request body.
- Minimum 100 valid phone numbers per upload. This counts numbers, not contacts — 50 contacts with two mapped phone columns each qualifies.
- Maximum 250,000 phone numbers per request.
- Numbers must resolve to a US or Canadian region (E.164-normalized server-side).
CSV mode (multipart/form-data)
Supply either
phone_column or phone_columns. Passing both is fine as long as
they agree — if phone_column isn’t among phone_columns, the request returns
400 rather than us guessing which one you meant to be charged for.
Supply either full_name_column, or both first_name_column and
last_name_column; supplying only one half of the pair returns 400.
- cURL
- Node.js
- Python
JSON mode (application/json)
Send either a flatphone_numbers array or a list of contacts objects with a phone_column field name. You must include exactly one of the two.
OptimalDial Identity needs a name to compare against, so it requires
contacts —
a flat phone_numbers array carries none. Passing a name column with
phone_numbers returns 400.- cURL
- Node.js
- Python
Response
200 OK returns a fully populated Upload with status: "ready_for_processing".
Errors
List uploads
source client-side if you only care about one.
Query parameters
- cURL
- Node.js
- Python
Response
next_cursor is null when there are no more pages. Order is created_at DESC, then id DESC to break ties.
Retrieve an upload
- cURL
- Node.js
- Python
Errors
Cancelling an upload
Uploads can’t be cancelled. Once you submit a list we take ownership of the work and the credits are committed, so there is noDELETE on this resource — DELETE /api/v1/uploads/{id} now returns 410 Gone.
If you submitted something in error, email support@optimaldial.com with the upload id. We’ll refund the credits where the work hasn’t been done.
Download endpoints
Filtering the processed file
…/download/processed accepts optional query parameters. With none of them the
response is exactly what it has always been — a URL to the full processed file.
With any of them, we build the slice you asked for and point the URL at that
instead. The response body is the same shape either way.
400 listing the valid values for that file.
All three responses share the same shape:
expires_at is a Unix timestamp; the signed URL is valid for 1 hour. Always re-fetch from this endpoint rather than caching the URL — once it expires, the URL returns 403.
- cURL
- Node.js
- Python