Developers

One key. Every channel.

SMS, Verify, Voice, Number Insight, List Hygiene and spam checks behind one base URL, one JSON envelope and one API key. Plain HTTPS — no SDK required.

Overview

One base URL. One JSON envelope.

Every endpoint on this page lives under the same base URL and returns the same envelope shape, whether it succeeds or fails.

Base URL
https://<console-host>/api/v1
Transport
HTTPS only — same host as your console account
Auth header
Authorization: Bearer <key> or X-API-Key: <key>
Content type
application/json (multipart for file uploads)
Timestamps
RFC 3339, e.g. 2026-09-24T08:00:00.412Z
  • Success: { "code": 0, "data": ... }. Spam-check lists return data.list and data.total; product-line lists return data.list and data.next_cursor.
  • Failure: { "code": <int>, "message": "...", "detail"?: {...} }. See Errors for the full table.
  • Spam-check pagination: page is 1-based (default 1); size defaults to 20 and accepts up to 500 — values outside 1..500 fall back to 20, they are not clamped to 500.
  • Timestamps are RFC 3339; parse any offset, don't assume UTC.
  • Ignore unknown response fields — new fields may be added over time.

Authentication

One key per project.

A project admin creates an API key in the console's Developer Center. The key (mp_live_…) is shown once, at creation — store it somewhere safe, it can't be retrieved again.

Two accepted headers

Send the key as either header — pick one, they work identically.

  • Authorization: Bearer <key>
  • X-API-Key: <key>

Scoped to one project

A key calls its own project's endpoints — the ones on this page — and nothing else.

  • Every product line
  • Never account / team / keys
  • 403 outside scope

Failure modes

Every request re-checks the key and the project's status.

  • 401 invalid / expired / revoked
  • 403 project suspended
  • Never in URLs or frontend code
curl "$BASE_URL/spam/capacity" -H "Authorization: Bearer $API_KEY"
Treat it like a password
API keys are bearer credentials — anyone who has one can call your project's endpoints. Keep it server-side.

Quickstart

Submit a check, then poll for the verdict.

Spam-check results are delivered by polling. Set your credentials, wrap requests in a small helper, then poll until the check settles.

ShellEnvironment
export BASE_URL="https://<console-host>/api/v1"
export API_KEY="mp_live_..."
cURLYour first request
# Submit a sender ID for classification
curl -X POST "$BASE_URL/spam/checks" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [{"kind": "sender", "value": "+15555550123"}]
  }'
PythonA small request helper
import os
import requests

BASE_URL = os.environ["BASE_URL"].rstrip("/")
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["API_KEY"]

def api(method, path, **kwargs):
    timeout = kwargs.pop("timeout", 15)
    response = session.request(method, BASE_URL + path, timeout=timeout, **kwargs)
    try:
        body = response.json()
    except ValueError:
        response.raise_for_status()
        raise
    if response.status_code == 429:
        raise requests.HTTPError(response=response)
    if not response.ok or body.get("code") != 0:
        detail = body.get("detail") if isinstance(body, dict) else None
        reason = detail.get("reason") if isinstance(detail, dict) else None
        msg = body.get("message", "API request failed")
        extra = f" ({reason})" if reason else ""
        raise RuntimeError(f'{response.status_code} {body.get("code")} {msg}{extra}')
    return body["data"]
PythonSubmit, then poll
import time

# Submit up to 500 items in one call.
created = api("POST", "/spam/checks", json={
    "items": [{"kind": "sender", "value": "+15555550123"}]
})
check_id = created["list"][0]["id"]
print("Accepted:", created["batch_id"], check_id)

# Poll until the check reaches a terminal status. A single check can take up
# to ~3 minutes under load, so give it a generous deadline. A 429 was not
# processed — resend after Retry-After. Never blindly resend the POST above
# if it times out; list /spam/batches first to see whether it already landed.
deadline = time.monotonic() + 300
while True:
    try:
        check = api("GET", f"/spam/checks/{check_id}")
    except requests.HTTPError as exc:
        if exc.response is not None and exc.response.status_code == 429:
            try:
                delay = max(1.0, float(exc.response.headers.get("Retry-After", "2")))
            except ValueError:  # HTTP-date form
                delay = 2.0
            if time.monotonic() + delay >= deadline:
                raise TimeoutError("Rate limit exceeds polling deadline") from exc
            time.sleep(delay)
            continue
        raise
    if check["status"] in {"completed", "inconclusive", "failed"}:
        print(check["status"], check["result"])
        break
    if time.monotonic() + 2 >= deadline:
        raise TimeoutError(f"Resume later with check id {check_id}")
    time.sleep(2)

Check polling

GET /spam/checks/{id} every ~2s until it settles.

  • completed
  • inconclusive
  • failed

Batch polling

GET /spam/batches/{id} for a whole submission.

  • completed
  • mixed
  • failed
  • retry → running

Large files

Poll the batch, then page through checks or download the export.

  • GET /spam/checks?batch_id=…
  • GET /spam/batches/{id}/export

API reference · Spam checks

Ten endpoints, one key.

Every endpoint below is scoped to your project. Ids from another project — or that don't exist — answer 404, never 403.

Capacity & Usage

Check whether the system can currently accept submissions, and pull daily counts for your project.

GET/spam/capacity

Check capacity

Advisory check for whether the system currently has capacity to accept new checks. It reserves nothing — a submit can still fail with 409 insufficient_capacity right after this returns true.

Response fields
availableboolean

True if new submissions can be accepted right now.

Errors
40140101

Missing or invalid API key.

40340301

Project is inactive, or the account is disabled.

50050001

Internal error.

50350301

Service temporarily unavailable.

GET /spam/capacity
curl "$BASE_URL/spam/capacity" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "available": true
  }
}
GET/project/usage

Daily usage

Daily counts of submitted checks by kind and outcome, for a UTC date window. Counted by submission day, not completion day. Usage uses normal/abnormal for the same outcomes checks report as clean/flagged. Your project is always the one your key belongs to — don't send project_id.

Query parameters
fromstring (YYYY-MM-DD)Required

Start of window, inclusive, UTC calendar day.

tostring (YYYY-MM-DD)Required

End of window, inclusive, UTC calendar day. Must not be before from; window spans at most 92 days.

Response fields
fromstring

Echo of the request from.

tostring

Echo of the request to.

project_idint

Your project id.

rowsarray

One entry per (day, kind) that had at least one check; days with none are omitted. Sorted by day, then kind.

rows[].daystring (YYYY-MM-DD)

UTC calendar day.

rows[].kind"sender" | "url"
rows[].normalint

Completed checks with result clean.

rows[].abnormalint

Completed checks with result flagged.

rows[].unclassifiedint

Completed with a verdict in neither group. Always 0 today.

rows[].unresolvedint

No verdict yet or ever (pending, inconclusive, or failed).

rows[].totalint

normal + abnormal + unclassified + unresolved.

totalsobject

The same five counters (normal, abnormal, unclassified, unresolved, total) summed over all rows.

Errors
40040002

from or to is missing.

40040002

from or to is not a valid YYYY-MM-DD date.

40040002

to is before from.

40040002

from..to spans more than 92 days.

40040002

project_id was sent; your project is implied by your key.

40140101

Missing or invalid API key.

40340301

Project is inactive, or the account is disabled.

50050001

Internal error.

50350301

Service temporarily unavailable.

GET /project/usage
curl "$BASE_URL/project/usage?from=2026-09-01&to=2026-09-30" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "from": "2026-09-01",
    "to": "2026-09-30",
    "project_id": 7,
    "rows": [
      {
        "day": "2026-09-24",
        "kind": "sender",
        "normal": 1780,
        "abnormal": 88,
        "unclassified": 0,
        "unresolved": 19,
        "total": 1887
      },
      {
        "day": "2026-09-24",
        "kind": "url",
        "normal": 0,
        "abnormal": 1,
        "unclassified": 0,
        "unresolved": 0,
        "total": 1
      }
    ],
    "totals": {
      "normal": 1780,
      "abnormal": 89,
      "unclassified": 0,
      "unresolved": 19,
      "total": 1888
    }
  }
}
GET/billing/balance

Balance

Your project's available balance, for monitoring: poll it and alert or top up when it runs low. Checks are charged when submitted, so nothing is held back. Counts toward your key's rate limit like any other call.

Response fields
currency"USD"

Currency of every amount.

balance_centsint

Available balance in cents.

unit_price_centsint

Your current price per check, in cents. 0 while checks are free.

estimated_checksint | null

How many checks the balance covers at unit_price_cents; null while checks are free.

Errors
40140101

Missing or invalid API key.

40340301

Project is inactive, or the account is disabled.

50050001

Internal error.

50350301

Service temporarily unavailable.

GET /billing/balance
curl "$BASE_URL/billing/balance" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "currency": "USD",
    "balance_cents": 12345,
    "unit_price_cents": 5,
    "estimated_checks": 2469
  }
}

Checks

Submit sender or URL values for spam screening, then read back status and results.

POST/spam/checks

Submit checks (JSON)

Submit up to 500 sender or URL values in one JSON batch. Duplicate values in items are dropped and counted as skipped_dupes. Pass an optional Idempotency-Key (16–128 ASCII letters, digits, hyphens or underscores) so a timed-out retry does not create a second batch. If a request times out without a key, list /spam/batches before resubmitting.

Request body
itemsarrayRequired

1 to 500 entries.

items[].kind"sender" | "url"Required
items[].valuestringRequired

Trimmed of surrounding whitespace; 1 to 2048 Unicode code points. A url value must start with http:// or https:// (case-sensitive). A sender value is opaque and kept verbatim.

Response fields
batch_idstring (UUID)

New batch id.

item_countint

= items.length

listCheck[]

The new checks, in the same order as items, all status:"pending", result:null.

list[].idint64

Check id.

list[].batch_idstring (UUID)

Owning batch.

list[].project_idint | null

Your project id.

list[].kind"sender" | "url"

As submitted.

list[].valuestring

As submitted, trimmed.

list[].status"pending"

Newly submitted checks are always pending.

list[].resultnull

Not settled yet.

list[].created_byint | null

Submitting user id, if any.

list[].created_atstring (RFC 3339)

Accepted time.

list[].completed_atnull

Not settled yet.

Errors
40040001

Body is not valid JSON, or a field has the wrong type.

40040002

items has the wrong length, or an item fails kind/value/url validation (first failing item only; nothing is stored).

40240201

Prepaid balance does not cover this submission. detail has balance_cents, required_cents, shortfall_cents. Nothing is stored.

40940901

No capacity (insufficient_capacity) or Idempotency-Key reused for a different body (idempotency_conflict). Nothing extra is stored.

40140101

Missing or invalid API key.

40340301

Project is inactive, or the account is disabled.

50050001

Internal error.

50350301

Service temporarily unavailable.

POST /spam/checks
curl -X POST "$BASE_URL/spam/checks" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: submit-senders-001" \
  -d '{"items":[{"kind":"sender","value":"+15555550123"},{"kind":"url","value":"https://example.com/promo"}]}'

Example response

{
  "code": 0,
  "data": {
    "batch_id": "d616fc6d-4c14-4dd9-997e-cbe844b6fa27",
    "item_count": 2,
    "list": [
      {
        "id": 42,
        "batch_id": "d616fc6d-4c14-4dd9-997e-cbe844b6fa27",
        "project_id": 7,
        "kind": "sender",
        "value": "+15555550123",
        "status": "pending",
        "result": null,
        "created_by": 12,
        "created_at": "2026-09-24T08:00:00.412Z",
        "completed_at": null
      },
      {
        "id": 43,
        "batch_id": "d616fc6d-4c14-4dd9-997e-cbe844b6fa27",
        "project_id": 7,
        "kind": "url",
        "value": "https://example.com/promo",
        "status": "pending",
        "result": null,
        "created_by": 12,
        "created_at": "2026-09-24T08:00:00.412Z",
        "completed_at": null
      }
    ]
  }
}
POST/spam/checks/upload

Submit checks (file upload)

Upload a plain-text file of sender or URL values, one per line — up to 20,000 unique items after dedupe. This is not CSV: a line a,b is a single sender value "a,b". Validation is all-or-nothing — the first invalid line rejects the whole file and nothing is stored.

Request body
filefile (multipart)Required

UTF-8 text, at most 8 MiB. One value per line. Each line is normalised first (leading BOM stripped, CRLF accepted, surrounding whitespace trimmed); blank lines and lines starting with # are skipped, and lines that are identical after this normalisation are dropped and counted. After dedupe: 1 to 20,000 items. Same per-value rules as the JSON submit.

kind"sender" | "url"

Omit to auto-detect per line (an http:// or https:// prefix is url, otherwise sender).

Response fields
batch_idstring (UUID)

New batch id.

item_countint

Unique items inserted.

skipped_dupesint

Duplicate lines dropped.

filenamestring

Sanitised filename ("upload.txt" if none given).

source"upload"
Errors
40040002

file field missing — also returned when the body exceeds the 8 MiB limit.

40040002

File is larger than 8,388,608 bytes.

40040002

kind is present but not sender, url, or empty.

40040002

A line fails validation (first invalid line only).

40040002

After dedupe, the file has 0 or more than 20,000 items.

40040001

The uploaded file could not be read.

40240201

Prepaid balance does not cover this submission. detail has balance_cents, required_cents, shortfall_cents. Nothing is stored.

40940901

No capacity (insufficient_capacity) or Idempotency-Key reused for a different body (idempotency_conflict). Nothing extra is stored.

40140101

Missing or invalid API key.

40340301

Project is inactive, or the account is disabled.

50050001

Internal error.

50350301

Service temporarily unavailable.

POST /spam/checks/upload
curl -X POST "$BASE_URL/spam/checks/upload" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: upload-senders-001" \
  -F "[email protected]" \
  -F "kind=sender"

Example response

{
  "code": 0,
  "data": {
    "batch_id": "5b0e3c1a-9f2d-4a7e-8c11-2f6e9d0b7a44",
    "item_count": 1873,
    "skipped_dupes": 12,
    "filename": "senders.txt",
    "source": "upload"
  }
}
GET/spam/checks

List checks

Paginated list of checks, newest id first. Filter by batch, kind, or outcome group.

Query parameters
batch_idstring (UUID)

Restrict to one batch.

kind"sender" | "url"
group"normal" | "abnormal"

normal = result clean, abnormal = result flagged. Implies status completed.

pageint

1-based. Default 1; values below 1 or non-numeric fall back to 1.

sizeint

Default 20, max 500. A value outside 1..500, or a non-numeric value, is rejected (see errors) rather than silently clamped.

Response fields
listCheck[]

Matching checks, newest id first.

list[].idint64

Check id.

list[].batch_idstring (UUID)

Owning batch.

list[].project_idint | null

Your project id.

list[].kind"sender" | "url"

As submitted.

list[].valuestring

As submitted, trimmed.

list[].status"pending" | "completed" | "inconclusive" | "failed"

pending: still queued or running, not retryable. completed: has an answer (see result). inconclusive: no definite answer, retryable. failed: did not complete, retryable.

list[].result"clean" | "flagged" | null

Non-null only when status is completed and a verdict exists.

list[].created_byint | null

Submitter user id; null if that user was deleted.

list[].created_atstring (RFC 3339)

Accepted time.

list[].completed_atstring (RFC 3339) | null

Settled time.

totalint

Full filtered count, independent of page/size.

Errors
40040002

batch_id is not a valid UUID.

40040002

kind is present but not sender or url.

40040002

group is present but not normal or abnormal.

40040002

size is outside 1..500, or not an integer.

40140101

Missing or invalid API key.

40340301

Project is inactive, or the account is disabled.

50050001

Internal error.

50350301

Service temporarily unavailable.

GET /spam/checks
curl "$BASE_URL/spam/checks?batch_id=d616fc6d-4c14-4dd9-997e-cbe844b6fa27&group=abnormal&page=1&size=100" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "list": [
      {
        "id": 43,
        "batch_id": "d616fc6d-4c14-4dd9-997e-cbe844b6fa27",
        "project_id": 7,
        "kind": "url",
        "value": "https://example.com/promo",
        "status": "completed",
        "result": "flagged",
        "created_by": 12,
        "created_at": "2026-09-24T08:00:00.412Z",
        "completed_at": "2026-09-24T08:00:07.9Z"
      }
    ],
    "total": 1
  }
}
GET/spam/checks/{id}

Get a check

Fetch one check by id.

Path parameters
idint64 (positive)Required

Check id.

Response fields
idint64

Check id.

batch_idstring (UUID)

Owning batch.

project_idint | null

Your project id.

kind"sender" | "url"

As submitted.

valuestring

As submitted, trimmed.

status"pending" | "completed" | "inconclusive" | "failed"

pending: still queued or running, not retryable. completed: has an answer (see result). inconclusive: no definite answer, retryable. failed: did not complete, retryable.

result"clean" | "flagged" | null

Non-null only when status is completed and a verdict exists.

created_byint | null

Submitter user id; null if that user was deleted.

created_atstring (RFC 3339)

Accepted time.

completed_atstring (RFC 3339) | null

Settled time.

Errors
40040001

id is not a positive integer.

40440401

No check with this id in your project (or it belongs to another project).

40140101

Missing or invalid API key.

40340301

Project is inactive, or the account is disabled.

50050001

Internal error.

50350301

Service temporarily unavailable.

GET /spam/checks/{id}
curl "$BASE_URL/spam/checks/42" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": 42,
    "batch_id": "d616fc6d-4c14-4dd9-997e-cbe844b6fa27",
    "project_id": 7,
    "kind": "sender",
    "value": "+15555550123",
    "status": "completed",
    "result": "clean",
    "created_by": 12,
    "created_at": "2026-09-24T08:00:00.412Z",
    "completed_at": "2026-09-24T08:00:05.1Z"
  }
}
POST/spam/checks/{id}/retry

Retry a check

Re-run a check that ended inconclusive or failed. The check is reset in place (same id, status:"pending", result:null, completed_at:null) and re-executed. If the batch had reached a terminal status, it goes back to running.

Path parameters
idint64 (positive)Required

Check id.

Response fields
idint64

Check id (unchanged).

batch_idstring (UUID)

Owning batch.

project_idint | null

Your project id.

kind"sender" | "url"

As submitted.

valuestring

As submitted, trimmed.

status"pending"

Reset to pending.

resultnull

Cleared.

created_byint | null

Original submitter user id.

created_atstring (RFC 3339)

Original accepted time (unchanged).

completed_atnull

Cleared.

Errors
40940901

status is pending or completed, or it changed concurrently. detail.reason is not_retryable.

40940901

No capacity to re-run the check right now. detail.reason is insufficient_capacity.

40440401

No check with this id in your project.

40040001

id is not a positive integer.

40140101

Missing or invalid API key.

40340301

Project is inactive, or the account is disabled.

50050001

Internal error.

50350301

Service temporarily unavailable.

POST /spam/checks/{id}/retry
curl -X POST "$BASE_URL/spam/checks/44/retry" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": 44,
    "batch_id": "d616fc6d-4c14-4dd9-997e-cbe844b6fa27",
    "project_id": 7,
    "kind": "sender",
    "value": "SHOPNOW",
    "status": "pending",
    "result": null,
    "created_by": 12,
    "created_at": "2026-09-24T08:00:00.412Z",
    "completed_at": null
  }
}

Batches

Track a submission's progress as a whole, and export finished results.

GET/spam/batches

List batches

Paginated list of batches, newest created first.

Query parameters
pageint

1-based. Default 1; values below 1 or non-numeric fall back to 1.

sizeint

Default 20, max 500. A value outside 1..500, or a non-numeric value, is rejected (see errors) rather than silently clamped.

Response fields
listBatch[]

Matching batches, newest created_at first.

list[].idstring (UUID)

Batch id.

list[].project_idint | null

Your project id.

list[].source"paste" | "upload"

paste = JSON submit, upload = file submit.

list[].filenamestring | null

Sanitised upload filename; null for paste.

list[].item_countint

Number of checks in the batch.

list[].created_byint | null

Submitter user id.

list[].created_by_namestring

Submitter username; empty string if that user is gone.

list[].created_atstring (RFC 3339)
list[].pendingint

Checks not yet settled.

list[].completedint

Checks settled with an answer.

list[].inconclusiveint

Checks with no definite answer.

list[].failedint

Checks that did not complete.

list[].flaggedint

Subset of completed whose result is flagged.

list[].status"running" | "completed" | "mixed" | "failed"

Computed live, not stored: running while any check is pending; failed if every check failed; mixed if at least one inconclusive or failed and none pending; completed if all checks completed. pending+completed+inconclusive+failed = item_count. Retrying a check moves a terminal batch back to running.

totalint

Full count.

Errors
40040002

size is outside 1..500, or not an integer.

40140101

Missing or invalid API key.

40340301

Project is inactive, or the account is disabled.

50050001

Internal error.

50350301

Service temporarily unavailable.

GET /spam/batches
curl "$BASE_URL/spam/batches?page=1&size=20" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "list": [
      {
        "id": "d616fc6d-4c14-4dd9-997e-cbe844b6fa27",
        "project_id": 7,
        "source": "paste",
        "filename": null,
        "item_count": 3,
        "created_by": 12,
        "created_by_name": "acme-admin",
        "created_at": "2026-09-24T08:00:00.41Z",
        "pending": 0,
        "completed": 2,
        "inconclusive": 1,
        "failed": 0,
        "flagged": 1,
        "status": "mixed"
      }
    ],
    "total": 1
  }
}
GET/spam/batches/{id}

Get batch progress

Fetch one batch's live progress. A batch can move back from a terminal status to running if you retry one of its checks.

Path parameters
idstring (UUID)Required

Batch id.

Response fields
idstring (UUID)

Batch id.

project_idint | null

Your project id.

source"paste" | "upload"

paste = JSON submit, upload = file submit.

filenamestring | null

Sanitised upload filename; null for paste.

item_countint

Number of checks in the batch.

created_byint | null

Submitter user id.

created_by_namestring

Submitter username; empty string if that user is gone.

created_atstring (RFC 3339)
pendingint

Checks not yet settled.

completedint

Checks settled with an answer.

inconclusiveint

Checks with no definite answer.

failedint

Checks that did not complete.

flaggedint

Subset of completed whose result is flagged.

status"running" | "completed" | "mixed" | "failed"

Computed live: running while any check is pending; failed if every check failed; mixed if at least one inconclusive or failed and none pending; completed if all checks completed.

Errors
40040002

id is not a valid UUID (note: 40002 here, vs 40001 for a bad check id).

40440401

No batch with this id in your project.

40140101

Missing or invalid API key.

40340301

Project is inactive, or the account is disabled.

50050001

Internal error.

50350301

Service temporarily unavailable.

GET /spam/batches/{id}
curl "$BASE_URL/spam/batches/5b0e3c1a-9f2d-4a7e-8c11-2f6e9d0b7a44" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": "5b0e3c1a-9f2d-4a7e-8c11-2f6e9d0b7a44",
    "project_id": 7,
    "source": "upload",
    "filename": "senders.txt",
    "item_count": 1873,
    "created_by": 12,
    "created_by_name": "acme-admin",
    "created_at": "2026-09-24T09:10:00Z",
    "pending": 1204,
    "completed": 650,
    "inconclusive": 3,
    "failed": 16,
    "flagged": 88,
    "status": "running"
  }
}
GET/spam/batches/{id}/export

Export batch results

Download all results for a batch as CSV — not a JSON envelope. UTF-8 BOM and a value,result header, then one CRLF-terminated line per check, ascending check id. Newlines inside value are replaced with spaces and a value containing a comma or quote is double-quoted; cells that Excel would treat as formulas are prefixed with a single quote. Can be called while the batch is still running; unsettled checks export as pending.

Path parameters
idstring (UUID)Required

Batch id.

Response fields
(body)text/csv

UTF-8 CSV with BOM and header row value,result. Each following line is "<value>,<result>". result is one of clean, flagged, pending, inconclusive, failed. (A completed check with no verdict still exports as inconclusive.)

Errors
40040002

id is not a valid UUID.

40440401

No batch with this id in your project.

40140101

Missing or invalid API key.

40340301

Project is inactive, or the account is disabled.

50050001

Internal error.

50350301

Service temporarily unavailable.

GET /spam/batches/{id}/export
curl "$BASE_URL/spam/batches/d616fc6d-4c14-4dd9-997e-cbe844b6fa27/export" \
  -H "Authorization: Bearer $API_KEY" \
  -o results.csv

CSV (text/csv)

value,result
+15555550123,clean
https://example.com/promo,flagged
SHOPNOW,inconclusive

API reference · Product lines

The same key, every product.

The Python and Node examples in the sections below call this small helper. It raises on any non-zero code, so an error arrives with its HTTP status, code and message.

Client helper
import os
import requests

BASE_URL = os.environ["BASE_URL"].rstrip("/")
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["API_KEY"]

def api(method, path, **kwargs):
    timeout = kwargs.pop("timeout", 15)
    response = session.request(method, BASE_URL + path, timeout=timeout, **kwargs)
    try:
        body = response.json()
    except ValueError:
        response.raise_for_status()  # e.g. an HTML 502 from a proxy
        raise
    if response.status_code == 429:
        raise requests.HTTPError(response=response)
    if not response.ok or body.get("code") != 0:
        detail = body.get("detail") if isinstance(body, dict) else None
        reason = detail.get("reason") if isinstance(detail, dict) else None
        msg = body.get("message", "API request failed")
        extra = f" ({reason})" if reason else ""
        raise RuntimeError(f'{response.status_code} {body.get("code")} {msg}{extra}')
    return body["data"]

Product lines are enabled per account. Calling one that is not enabled on yours answers 403 with code 40302 and detail.reason = feature_not_enabled, before any validation of the request:

JSON403 Forbidden
{
  "code": 40302,
  "message": "Feature not enabled for this account. Please contact sales.",
  "detail": {
    "reason": "feature_not_enabled",
    "product": "sms"
  }
}

API reference

SMS

Send single messages or batches over direct operator binds, and read back the operator's own delivery receipt — status, MCC-MNC, segments and cost.

POST/sms/messages

Send a message

Queues one message. The response is the accepted message; its delivery receipt arrives on the message (poll it) and as an sms.delivered / sms.failed webhook.

Request body
tostringRequired

Recipient in E.164, e.g. +447700900123.

textstringRequired

Message body, up to 1,600 characters. GSM-7 or UCS-2 is chosen automatically.

fromstring

A registered sender ID or number. Defaults to the project's default sender for the destination country.

routestring

transactional (default) or marketing. Marketing traffic runs on a separate, throttled queue.

callback_urlstring

HTTPS URL for this message's receipt, overriding the project's webhooks.

client_refstring

Your own reference, up to 64 characters; returned on the message and its webhooks.

Response fields
idstring

Message id (msg_…).

statusstring

queued → sent → delivered, or failed / rejected / expired.

segmentsinteger

Billable segments.

encodingstring

GSM-7 or UCS-2.

priceobject

amount (decimal string) and currency.

Errors
40040001

Malformed JSON or a wrong field type.

40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

POST /sms/messages
curl -X POST "$BASE_URL/sms/messages" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+447700900123",
    "from": "ELMETRY",
    "text": "Your Elmetry code is 482913",
    "route": "transactional",
    "client_ref": "signup-8841"
  }'

Example response

{
  "code": 0,
  "data": {
    "id": "msg_01J9Z3K6Q8T4",
    "to": "+447700900123",
    "from": "ELMETRY",
    "route": "transactional",
    "text": "Your Elmetry code is 482913",
    "status": "queued",
    "segments": 1,
    "encoding": "GSM-7",
    "mcc_mnc": null,
    "operator_status": null,
    "error_code": null,
    "price": {
      "amount": "0.0320",
      "currency": "USD"
    },
    "client_ref": "signup-8841",
    "created_at": "2026-09-28T08:00:00Z",
    "sent_at": null,
    "delivered_at": null
  }
}
GET/sms/messages/{id}

Retrieve a message

Current status and the operator's delivery receipt: raw operator status, MCC-MNC of the terminating network, segment count and cost.

Path parameters
idstringRequired

The message id (msg_…).

Response fields
statusstring

Normalised status.

operator_statusstring | null

The status string from the operator's DLR, e.g. DELIVRD, UNDELIV.

mcc_mncstring | null

Terminating network.

error_codestring | null

Operator error code when the message failed.

sent_atstring | null

When the operator accepted the message, RFC 3339.

delivered_atstring | null

When the operator confirmed delivery, RFC 3339.

Errors
40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

42942901

Rate limited; wait Retry-After seconds.

GET /sms/messages/{id}
curl "$BASE_URL/sms/messages/msg_01J9Z3K6Q8T4" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": "msg_01J9Z3K6Q8T4",
    "to": "+447700900123",
    "from": "ELMETRY",
    "route": "transactional",
    "text": "Your Elmetry code is 482913",
    "status": "delivered",
    "segments": 1,
    "encoding": "GSM-7",
    "mcc_mnc": "234-10",
    "operator_status": "DELIVRD",
    "error_code": null,
    "price": {
      "amount": "0.0320",
      "currency": "USD"
    },
    "client_ref": "signup-8841",
    "created_at": "2026-09-28T08:00:00Z",
    "sent_at": "2026-09-28T08:00:00.180Z",
    "delivered_at": "2026-09-28T08:00:03.410Z"
  }
}
GET/sms/messages

List messages

Newest first, cursor-paginated. Filter by time, status or your client_ref.

Query parameters
statusstring

One status, e.g. failed.

client_refstring

Exact match.

created_afterstring

RFC 3339; messages created at or after this time.

created_beforestring

RFC 3339; messages created before this time.

limitinteger

1–200, default 50.

cursorstring

next_cursor from the previous page.

Response fields
listMessage[]

Same shape as Retrieve a message.

next_cursorstring | null

null on the last page.

Errors
40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

GET /sms/messages
curl "$BASE_URL/sms/messages?status=failed&limit=50" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "list": [
      {
        "id": "msg_01J9Z3K6Q8T4",
        "to": "+447700900123",
        "from": "ELMETRY",
        "route": "transactional",
        "text": "Your Elmetry code is 482913",
        "status": "failed",
        "segments": 1,
        "encoding": "GSM-7",
        "mcc_mnc": "234-10",
        "operator_status": "UNDELIV",
        "error_code": "001",
        "price": {
          "amount": "0.0320",
          "currency": "USD"
        },
        "client_ref": "signup-8841",
        "created_at": "2026-09-28T08:00:00Z",
        "sent_at": "2026-09-28T08:00:00.180Z",
        "delivered_at": null
      }
    ],
    "next_cursor": "c2VxOjQ4MTI"
  }
}
POST/sms/batches

Send a batch

Up to 10,000 recipients in one call, with per-recipient template variables and an optional send window evaluated in each recipient's time zone.

Request body
recipientsobject[]Required

1–10,000 items of { to, vars? }.

textstringRequired

Template; {{name}} is replaced from each recipient's vars.

fromstring

Sender ID or number.

routestring

marketing (default for batches) or transactional.

send_atstring

RFC 3339 start time; omit to start now.

send_windowobject

{ start, end, timezone }, e.g. 09:00–20:00, recipient.

callback_urlstring

HTTPS URL for this batch's receipts.

client_refstring

Your reference, up to 64 characters.

Response fields
idstring

Batch id (bat_…).

statusstring

scheduled, sending, completed or canceled.

totalinteger

Accepted recipients after de-duplication.

Errors
40040001

Malformed JSON or a wrong field type.

40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

POST /sms/batches
curl -X POST "$BASE_URL/sms/batches" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "ELMETRY",
    "route": "marketing",
    "text": "Hi {{name}}, your autumn code is {{code}}. Reply STOP to opt out.",
    "recipients": [
      {
        "to": "+447700900123",
        "vars": {
          "name": "Ada",
          "code": "AUT-4471"
        }
      },
      {
        "to": "+447700900456",
        "vars": {
          "name": "Lin",
          "code": "AUT-4472"
        }
      }
    ],
    "send_window": {
      "start": "09:00",
      "end": "20:00",
      "timezone": "recipient"
    },
    "client_ref": "autumn-sale"
  }'

Example response

{
  "code": 0,
  "data": {
    "id": "bat_01J9Z4B2M7QX",
    "status": "scheduled",
    "from": "ELMETRY",
    "route": "marketing",
    "total": 2,
    "queued": 2,
    "sent": 0,
    "delivered": 0,
    "failed": 0,
    "canceled": 0,
    "price": {
      "amount": "0.0000",
      "currency": "USD"
    },
    "send_window": {
      "start": "09:00",
      "end": "20:00",
      "timezone": "recipient"
    },
    "client_ref": "autumn-sale",
    "created_at": "2026-09-28T08:00:00Z",
    "completed_at": null
  }
}
GET/sms/batches/{id}

Retrieve a batch

Progress counters and cost so far. delivered and failed are subsets of sent.

Path parameters
idstringRequired

The batch id (bat_…).

Response fields
queuedinteger

Not yet handed to an operator.

sentinteger

Handed to an operator.

deliveredinteger

Delivery confirmed; a subset of sent.

failedinteger

Failed, rejected or expired; a subset of sent.

canceledinteger

Canceled before sending.

priceobject

amount (decimal string) and currency.

completed_atstring | null

Set when nothing is left to send.

Errors
40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

42942901

Rate limited; wait Retry-After seconds.

GET /sms/batches/{id}
curl "$BASE_URL/sms/batches/bat_01J9Z4B2M7QX" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": "bat_01J9Z4B2M7QX",
    "status": "sending",
    "from": "ELMETRY",
    "route": "marketing",
    "total": 2400,
    "queued": 310,
    "sent": 2090,
    "delivered": 1985,
    "failed": 12,
    "canceled": 0,
    "price": {
      "amount": "64.1600",
      "currency": "USD"
    },
    "send_window": {
      "start": "09:00",
      "end": "20:00",
      "timezone": "recipient"
    },
    "client_ref": "autumn-sale",
    "created_at": "2026-09-28T08:00:00Z",
    "completed_at": null
  }
}
POST/sms/batches/{id}/cancel

Cancel a batch

Stops whatever has not been handed to an operator yet. Messages already sent are unaffected and still billed.

Path parameters
idstringRequired

The batch id (bat_…).

Response fields
canceledinteger

Recipients that will not be sent.

Errors
40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

40940901

detail.reason = not_cancelable: the batch already completed or was canceled.

42942901

Rate limited; wait Retry-After seconds.

POST /sms/batches/{id}/cancel
curl -X POST "$BASE_URL/sms/batches/bat_01J9Z4B2M7QX/cancel" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": "bat_01J9Z4B2M7QX",
    "status": "canceled",
    "from": "ELMETRY",
    "route": "marketing",
    "total": 2400,
    "queued": 0,
    "sent": 2090,
    "delivered": 1985,
    "failed": 12,
    "canceled": 310,
    "price": {
      "amount": "64.1600",
      "currency": "USD"
    },
    "send_window": {
      "start": "09:00",
      "end": "20:00",
      "timezone": "recipient"
    },
    "client_ref": "autumn-sale",
    "created_at": "2026-09-28T08:00:00Z",
    "completed_at": "2026-09-28T08:12:40Z"
  }
}
GET/sms/sender-ids

List sender IDs

Alphanumeric sender IDs registered for the project, per country, with their approval status.

Query parameters
countrystring

ISO 3166-1 alpha-2 filter.

Response fields
list[].sender_idstring

Up to 11 characters.

list[].statusstring

pending, approved or rejected.

Errors
40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

GET /sms/sender-ids
curl "$BASE_URL/sms/sender-ids?country=GB" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "list": [
      {
        "id": "sid_01J9Z9A1",
        "sender_id": "ELMETRY",
        "country": "GB",
        "use_case": "transactional",
        "status": "approved",
        "created_at": "2026-09-02T10:14:00Z"
      },
      {
        "id": "sid_01J9Z9A2",
        "sender_id": "ELMSALE",
        "country": "GB",
        "use_case": "marketing",
        "status": "pending",
        "created_at": "2026-09-28T08:00:00Z"
      }
    ]
  }
}
POST/sms/sender-ids

Register a sender ID

Requests an alphanumeric sender ID for one country. Registration rules and lead times vary by country; the ID is usable once status is approved.

Request body
sender_idstringRequired

3–11 characters, letters and digits, at least one letter.

countrystringRequired

ISO 3166-1 alpha-2.

use_casestringRequired

transactional or marketing.

sample_textstring

A representative message, required by most regulators.

Errors
40040001

Malformed JSON or a wrong field type.

40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

POST /sms/sender-ids
curl -X POST "$BASE_URL/sms/sender-ids" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sender_id": "ELMSALE",
    "country": "GB",
    "use_case": "marketing",
    "sample_text": "ELMSALE: 20% off this weekend. Reply STOP to opt out."
  }'

Example response

{
  "code": 0,
  "data": {
    "id": "sid_01J9Z9A2",
    "sender_id": "ELMSALE",
    "country": "GB",
    "use_case": "marketing",
    "status": "pending",
    "created_at": "2026-09-28T08:00:00Z"
  }
}

API reference

Verify

One-time passcodes without managing codes yourself. Start a session, check what the user typed. With `channel: auto`, an SMS that is not verified in time falls back to a voice call.

POST/verify/sessions

Start a verification

Generates a code and sends it. Only one pending session per number: starting a new one cancels the previous.

Request body
tostringRequired

Recipient in E.164.

channelstring

sms, voice or auto (default: SMS first, voice fallback).

code_lengthinteger

4–10, default 6.

localestring

Message and voice language, e.g. en, zh-CN, es. Default en.

sms_timeout_secondsinteger

auto only: seconds to wait for a check before calling. 30–300, default 60.

expires_in_secondsinteger

Session lifetime, 60–1,800, default 600.

client_refstring

Your reference, up to 64 characters.

Response fields
idstring

Session id (vs_…).

statusstring

pending, approved, expired or canceled.

attemptsobject[]

Each channel tried so far, with its delivery status.

Errors
40040001

Malformed JSON or a wrong field type.

40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

POST /verify/sessions
curl -X POST "$BASE_URL/verify/sessions" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+447700900123",
    "channel": "auto",
    "code_length": 6,
    "locale": "en",
    "sms_timeout_seconds": 60
  }'

Example response

{
  "code": 0,
  "data": {
    "id": "vs_01J9Z5C8R1HN",
    "to": "+447700900123",
    "channel": "auto",
    "status": "pending",
    "code_length": 6,
    "locale": "en",
    "attempts": [
      {
        "channel": "sms",
        "status": "delivered",
        "sent_at": "2026-09-28T08:00:00.200Z"
      }
    ],
    "checks": 0,
    "expires_at": "2026-09-28T08:10:00Z",
    "created_at": "2026-09-28T08:00:00Z"
  }
}
GET/verify/sessions/{id}

Retrieve a session

Status and the channels attempted. The code itself is never returned.

Path parameters
idstringRequired

The session id (vs_…).

Response fields
statusstring

pending, approved, expired or canceled.

attempts[].channelstring

sms or voice.

checksinteger

Codes checked so far (max 5).

Errors
40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

42942901

Rate limited; wait Retry-After seconds.

GET /verify/sessions/{id}
curl "$BASE_URL/verify/sessions/vs_01J9Z5C8R1HN" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": "vs_01J9Z5C8R1HN",
    "to": "+447700900123",
    "channel": "auto",
    "status": "pending",
    "code_length": 6,
    "locale": "en",
    "attempts": [
      {
        "channel": "sms",
        "status": "delivered",
        "sent_at": "2026-09-28T08:00:00.200Z"
      },
      {
        "channel": "voice",
        "status": "answered",
        "sent_at": "2026-09-28T08:01:00.310Z"
      }
    ],
    "checks": 0,
    "expires_at": "2026-09-28T08:10:00Z",
    "created_at": "2026-09-28T08:00:00Z"
  }
}
POST/verify/sessions/{id}/check

Check a code

Checks what the user entered. A wrong code is a normal 200 with valid: false; after five wrong codes the session is canceled.

Path parameters
idstringRequired

The session id (vs_…).

Request body
codestringRequired

The digits the user entered.

Response fields
validboolean

Whether this code matched.

statusstring

approved once a code matches.

remaining_checksinteger

Checks left before the session is canceled.

Errors
40040001

Malformed JSON or a wrong field type.

40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

40940901

detail.reason = not_pending: the session is already approved, expired or canceled.

42942901

Rate limited; wait Retry-After seconds.

POST /verify/sessions/{id}/check
curl -X POST "$BASE_URL/verify/sessions/vs_01J9Z5C8R1HN/check" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "482913"
  }'

Example response

{
  "code": 0,
  "data": {
    "id": "vs_01J9Z5C8R1HN",
    "valid": true,
    "status": "approved",
    "remaining_checks": 4
  }
}
POST/verify/sessions/{id}/cancel

Cancel a session

Ends a pending session, including a scheduled voice fallback. Idempotent.

Path parameters
idstringRequired

The session id (vs_…).

Errors
40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

42942901

Rate limited; wait Retry-After seconds.

POST /verify/sessions/{id}/cancel
curl -X POST "$BASE_URL/verify/sessions/vs_01J9Z5C8R1HN/cancel" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": "vs_01J9Z5C8R1HN",
    "to": "+447700900123",
    "channel": "auto",
    "status": "canceled",
    "code_length": 6,
    "locale": "en",
    "attempts": [
      {
        "channel": "sms",
        "status": "delivered",
        "sent_at": "2026-09-28T08:00:00.200Z"
      }
    ],
    "checks": 0,
    "expires_at": "2026-09-28T08:10:00Z",
    "created_at": "2026-09-28T08:00:00Z"
  }
}

API reference

Voice

Text-to-speech calls for passcodes and alerts, placed from a verified caller ID. Read the outcome from the call or from the `voice.completed` webhook.

POST/voice/calls

Place a call

Dials to and reads text aloud repeat times once answered.

Request body
tostringRequired

Callee in E.164.

textstringRequired

What to say, up to 1,000 characters.

caller_idstring

A verified number on the project. Defaults to a local number where available.

localestring

e.g. en-GB; see List languages.

voicestring

A voice id from List languages.

repeatinteger

1–3, default 2.

callback_urlstring

HTTPS URL for this call's voice.completed.

client_refstring

Your reference, up to 64 characters.

Response fields
idstring

Call id (call_…).

statusstring

Starts at queued.

Errors
40040001

Malformed JSON or a wrong field type.

40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

POST /voice/calls
curl -X POST "$BASE_URL/voice/calls" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+447700900123",
    "caller_id": "+442038087000",
    "text": "Your Elmetry code is 4 8 2 9 1 3.",
    "locale": "en-GB",
    "voice": "en-GB-f1",
    "repeat": 2
  }'

Example response

{
  "code": 0,
  "data": {
    "id": "call_01J9Z6D4V9KE",
    "to": "+447700900123",
    "caller_id": "+442038087000",
    "status": "queued",
    "locale": "en-GB",
    "voice": "en-GB-f1",
    "repeat": 2,
    "answered_at": null,
    "ended_at": null,
    "duration_seconds": 0,
    "hangup_cause": null,
    "price": {
      "amount": "0.0000",
      "currency": "USD"
    },
    "client_ref": "login-2fa-5521",
    "created_at": "2026-09-28T08:00:00Z"
  }
}
GET/voice/calls/{id}

Retrieve a call

Status, answered duration and why the call ended.

Path parameters
idstringRequired

The call id (call_…).

Response fields
statusstring

queued, ringing, answered, completed, busy, no_answer or failed.

duration_secondsinteger

Answered duration; billing is per started 6 seconds.

hangup_causestring | null

e.g. normal_clearing, user_busy, no_answer.

priceobject

amount (decimal string) and currency.

Errors
40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

42942901

Rate limited; wait Retry-After seconds.

GET /voice/calls/{id}
curl "$BASE_URL/voice/calls/call_01J9Z6D4V9KE" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": "call_01J9Z6D4V9KE",
    "to": "+447700900123",
    "caller_id": "+442038087000",
    "status": "completed",
    "locale": "en-GB",
    "voice": "en-GB-f1",
    "repeat": 2,
    "answered_at": "2026-09-28T08:00:06Z",
    "ended_at": "2026-09-28T08:00:24Z",
    "duration_seconds": 18,
    "hangup_cause": "normal_clearing",
    "price": {
      "amount": "0.0410",
      "currency": "USD"
    },
    "client_ref": "login-2fa-5521",
    "created_at": "2026-09-28T08:00:00Z"
  }
}
GET/voice/languages

List languages

Supported TTS locales and the voices available in each.

Response fields
list[].localestring

BCP 47 tag.

list[].voices[]object[]

{ id, gender }.

Errors
40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

GET /voice/languages
curl "$BASE_URL/voice/languages" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "list": [
      {
        "locale": "en-GB",
        "name": "English (United Kingdom)",
        "voices": [
          {
            "id": "en-GB-f1",
            "gender": "female"
          },
          {
            "id": "en-GB-m1",
            "gender": "male"
          }
        ]
      },
      {
        "locale": "zh-CN",
        "name": "Chinese (Mandarin)",
        "voices": [
          {
            "id": "zh-CN-f1",
            "gender": "female"
          }
        ]
      },
      {
        "locale": "es-ES",
        "name": "Español (España)",
        "voices": [
          {
            "id": "es-ES-m1",
            "gender": "male"
          }
        ]
      }
    ]
  }
}

API reference

Number Insight

Who a number belongs to right now: current and original carrier, porting, roaming, line type and reachability. One number synchronously, or up to 10,000 as a job.

GET/insight/numbers/{number}

Look up a number

A live network query for one number. Typically answers in under a second.

Path parameters
numberstringRequired

E.164, with the leading +.

Response fields
validboolean

Well-formed and allocated.

typestring

mobile, fixed, voip, virtual or unknown.

reachablestring

reachable, unreachable or unknown.

carrierobject

Current carrier, { name, mcc_mnc }.

original_carrierobject

Carrier the number range was allocated to; differs from carrier when ported.

portedboolean

Moved from its original carrier.

roamingobject

{ roaming, country }.

Errors
40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

GET /insight/numbers/{number}
curl "$BASE_URL/insight/numbers/+447700900123" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "number": "+447700900123",
    "valid": true,
    "type": "mobile",
    "country": "GB",
    "reachable": "reachable",
    "carrier": {
      "name": "EE",
      "mcc_mnc": "234-30"
    },
    "original_carrier": {
      "name": "Vodafone UK",
      "mcc_mnc": "234-15"
    },
    "ported": true,
    "roaming": {
      "roaming": false,
      "country": null
    },
    "checked_at": "2026-09-28T08:00:00Z"
  }
}
POST/insight/lookups

Start a bulk lookup

Queues up to 10,000 numbers. Poll the job, or subscribe to insight.lookup.completed.

Request body
numbersstring[]Required

1–10,000 E.164 numbers; duplicates are dropped.

callback_urlstring

HTTPS URL for this job's completion.

client_refstring

Your reference, up to 64 characters.

Response fields
idstring

Job id (lk_…).

statusstring

queued, running or completed.

Errors
40040001

Malformed JSON or a wrong field type.

40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

POST /insight/lookups
curl -X POST "$BASE_URL/insight/lookups" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "numbers": [
      "+447700900123",
      "+447700900456",
      "+14155550199"
    ],
    "client_ref": "crm-sync-0928"
  }'

Example response

{
  "code": 0,
  "data": {
    "id": "lk_01J9ZA2G7Y4D",
    "status": "queued",
    "total": 3,
    "processed": 0,
    "client_ref": "crm-sync-0928",
    "created_at": "2026-09-28T08:00:00Z",
    "completed_at": null
  }
}
GET/insight/lookups/{id}

Retrieve lookup results

Job progress plus a page of results, each in the single-lookup shape.

Path parameters
idstringRequired

The lookup job id (lk_…).

Query parameters
limitinteger

1–200, default 50.

cursorstring

next_cursor from the previous page.

Response fields
resultsobject[]

Same shape as Look up a number.

next_cursorstring | null

null on the last page.

Errors
40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

42942901

Rate limited; wait Retry-After seconds.

GET /insight/lookups/{id}
curl "$BASE_URL/insight/lookups/lk_01J9ZA2G7Y4D?limit=1" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": "lk_01J9ZA2G7Y4D",
    "status": "completed",
    "total": 3,
    "processed": 3,
    "results": [
      {
        "number": "+447700900123",
        "valid": true,
        "type": "mobile",
        "country": "GB",
        "reachable": "reachable",
        "carrier": {
          "name": "EE",
          "mcc_mnc": "234-30"
        },
        "original_carrier": {
          "name": "Vodafone UK",
          "mcc_mnc": "234-15"
        },
        "ported": true,
        "roaming": {
          "roaming": false,
          "country": null
        },
        "checked_at": "2026-09-28T08:00:00Z"
      }
    ],
    "next_cursor": "c2VxOjE",
    "created_at": "2026-09-28T08:00:00Z",
    "completed_at": "2026-09-28T08:00:04Z"
  }
}

API reference

List Hygiene

Clean a marketing list before you pay to send to it: invalid and unallocated numbers, disconnected lines and known spam traps are flagged, duplicates removed.

POST/hygiene/jobs

Start a cleaning job

Send numbers as a JSON array, or upload a CSV as multipart field file (first column is the number, up to 50 MiB). Up to 500,000 numbers per job.

Request body
numbersstring[]

JSON requests: E.164 numbers. Send either numbers or file.

filefile

Multipart requests (multipart/form-data): a CSV whose first column is the number. The other fields go alongside as form fields.

namestring

A label for the job.

default_countrystring

ISO 3166-1 alpha-2 used to read numbers without a +.

callback_urlstring

HTTPS URL for hygiene.completed.

Response fields
idstring

Job id (hj_…).

statusstring

queued, running or completed.

Errors
40040001

Malformed JSON or a wrong field type.

40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

POST /hygiene/jobs
curl -X POST "$BASE_URL/hygiene/jobs" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "october-newsletter",
    "numbers": [
      "+447700900123",
      "+447700900456",
      "07700900789"
    ],
    "default_country": "GB"
  }'

Example response

{
  "code": 0,
  "data": {
    "id": "hj_01J9Z7E5W3PA",
    "name": "october-newsletter",
    "status": "queued",
    "total": 3,
    "processed": 0,
    "valid": 0,
    "invalid": 0,
    "risky": 0,
    "duplicates": 0,
    "estimated_savings": null,
    "created_at": "2026-09-28T08:00:00Z",
    "completed_at": null
  }
}
GET/hygiene/jobs/{id}

Retrieve a job

Progress and the verdict counts. estimated_savings prices the invalid and risky numbers at your current SMS rate.

Path parameters
idstringRequired

The job id (hj_…).

Response fields
validinteger

Allocated, reachable numbers.

invalidinteger

Malformed, unallocated or disconnected numbers.

riskyinteger

Known spam traps and recently disconnected lines.

duplicatesinteger

Removed before checking.

estimated_savingsobject | null

{ messages, amount, currency } once completed.

Errors
40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

42942901

Rate limited; wait Retry-After seconds.

GET /hygiene/jobs/{id}
curl "$BASE_URL/hygiene/jobs/hj_01J9Z7E5W3PA" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": "hj_01J9Z7E5W3PA",
    "name": "october-newsletter",
    "status": "completed",
    "total": 48210,
    "processed": 48210,
    "valid": 44873,
    "invalid": 2918,
    "risky": 419,
    "duplicates": 1204,
    "estimated_savings": {
      "messages": 3337,
      "amount": "106.78",
      "currency": "USD"
    },
    "created_at": "2026-09-28T08:00:00Z",
    "completed_at": "2026-09-28T08:04:12Z"
  }
}
GET/hygiene/jobs/{id}/export

Export results

CSV with a header row, one line per unique number. Not wrapped in the JSON envelope.

Path parameters
idstringRequired

The job id (hj_…).

Query parameters
verdictstring

all (default), valid, invalid or risky.

Errors
40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

42942901

Rate limited; wait Retry-After seconds.

GET /hygiene/jobs/{id}/export
curl "$BASE_URL/hygiene/jobs/hj_01J9Z7E5W3PA/export?verdict=all" \
  -H "Authorization: Bearer $API_KEY" \
  -o results.csv

Example response (text/csv)

number,verdict,reason,type,carrier
+447700900123,valid,,mobile,EE
+447700900456,risky,spam_trap,mobile,Vodafone UK
+447700900789,invalid,unallocated,,

API reference

Webhooks

Receive delivery receipts and job results as signed HTTPS POSTs instead of polling. Every delivery is signed with Ed25519 — see Verifying signatures.

GET/webhooks

List webhooks

Every endpoint configured on the project, with its subscribed events and the public key its deliveries are signed with.

Response fields
list[].eventsstring[]

Subscribed event types.

list[].public_keystring

Hex Ed25519 public key for verifying deliveries.

list[].statusstring

active, or disabled after 24 hours of failed deliveries.

Errors
40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

GET /webhooks
curl "$BASE_URL/webhooks" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "list": [
      {
        "id": "wh_01J9Z8F6X2LC",
        "url": "https://api.example.com/hooks/elmetry",
        "events": [
          "sms.delivered",
          "sms.failed",
          "verify.approved"
        ],
        "description": "Production receiver",
        "status": "active",
        "public_key": "9f2c4a1be07d5c83e6b1a0f47d29c5e81b3a6f0d2e97c4b85a1f6e3d0c7b2a94",
        "created_at": "2026-09-28T08:00:00Z"
      }
    ]
  }
}
POST/webhooks

Create a webhook

Registers an HTTPS endpoint. Up to 10 per project; one endpoint may subscribe to any number of events.

Request body
urlstringRequired

Public HTTPS URL.

eventsstring[]Required

Event types, see Events below.

descriptionstring

Up to 200 characters.

Response fields
public_keystring

Store it with the endpoint; it does not change.

Errors
40040001

Malformed JSON or a wrong field type.

40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

42942901

Rate limited; wait Retry-After seconds.

POST /webhooks
curl -X POST "$BASE_URL/webhooks" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.example.com/hooks/elmetry",
    "events": [
      "sms.delivered",
      "sms.failed",
      "verify.approved"
    ],
    "description": "Production receiver"
  }'

Example response

{
  "code": 0,
  "data": {
    "id": "wh_01J9Z8F6X2LC",
    "url": "https://api.example.com/hooks/elmetry",
    "events": [
      "sms.delivered",
      "sms.failed",
      "verify.approved"
    ],
    "description": "Production receiver",
    "status": "active",
    "public_key": "9f2c4a1be07d5c83e6b1a0f47d29c5e81b3a6f0d2e97c4b85a1f6e3d0c7b2a94",
    "created_at": "2026-09-28T08:00:00Z"
  }
}
DELETE/webhooks/{id}

Delete a webhook

Stops deliveries to the endpoint immediately, including queued retries.

Path parameters
idstringRequired

The webhook id (wh_…).

Errors
40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

42942901

Rate limited; wait Retry-After seconds.

DELETE /webhooks/{id}
curl -X DELETE "$BASE_URL/webhooks/wh_01J9Z8F6X2LC" \
  -H "Authorization: Bearer $API_KEY"

Example response

{
  "code": 0,
  "data": {
    "id": "wh_01J9Z8F6X2LC",
    "deleted": true
  }
}
POST/webhooks/{id}/test

Send a test event

Delivers a test event, signed like a real one, and reports what your endpoint answered.

Path parameters
idstringRequired

The webhook id (wh_…).

Request body
eventstring

Event type to simulate; defaults to the first subscribed.

Response fields
status_codeinteger | null

Your endpoint's HTTP status; null if it could not be reached.

duration_msinteger

Round-trip time.

Errors
40040001

Malformed JSON or a wrong field type.

40040002

A value out of range or in the wrong format; message names the field.

40140101

Missing, invalid, expired or revoked API key.

40340301

The project is suspended.

40340302

feature_not_enabled: this product is not enabled on your account. Contact sales to enable it.

40440401

No such id in this project.

42942901

Rate limited; wait Retry-After seconds.

POST /webhooks/{id}/test
curl -X POST "$BASE_URL/webhooks/wh_01J9Z8F6X2LC/test" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "sms.delivered"
  }'

Example response

{
  "code": 0,
  "data": {
    "delivery_id": "dlv_01J9ZB3H8Z5F",
    "event": "sms.delivered",
    "status_code": 200,
    "duration_ms": 142
  }
}

Verifying signatures

Every delivery is an HTTPS POST with a JSON body, signed with Ed25519. Verify the signature over the timestamp header followed by the raw request body, using the webhook's public_key, and reject timestamps more than five minutes old. Answer 2xx within 10 seconds; anything else is retried with exponential backoff for up to 24 hours.

X-Signature-Ed25519
Hex Ed25519 signature of timestamp + raw body.
X-Signature-Timestamp
Unix seconds when the delivery was signed.
X-Webhook-Id
Delivery id; the same across retries — use it to de-duplicate.
Verify and receive
# pip install cryptography flask
import time
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from flask import Flask, abort, request

# A retry repeats X-Webhook-Id, so it doubles as a replay guard. In
# production record it with an atomic insert on a unique key, in the same
# transaction as your side effects; this in-process set is only a sketch.
handled_deliveries = set()

PUBLIC_KEY = Ed25519PublicKey.from_public_bytes(bytes.fromhex(
    "9f2c4a1be07d5c83e6b1a0f47d29c5e81b3a6f0d2e97c4b85a1f6e3d0c7b2a94"  # public_key from GET /webhooks
))
app = Flask(__name__)

@app.post("/hooks/elmetry")
def receive():
    signature = request.headers.get("X-Signature-Ed25519", "")
    timestamp = request.headers.get("X-Signature-Timestamp", "")
    body = request.get_data()  # raw bytes, before any JSON parsing
    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
        abort(401)  # stale or missing timestamp: possible replay
    try:
        PUBLIC_KEY.verify(bytes.fromhex(signature), timestamp.encode() + body)
    except (ValueError, InvalidSignature):
        abort(401)
    delivery_id = request.headers.get("X-Webhook-Id", "")
    event = request.get_json(silent=True)
    if not delivery_id or not isinstance(event, dict) or not isinstance(event.get("data"), dict):
        abort(400)  # nothing recorded: a later retry is still processed
    if delivery_id in handled_deliveries:
        return "", 204  # already handled: acknowledge, don't act twice
    print(event["type"], event["data"]["id"])  # your side effects
    handled_deliveries.add(delivery_id)  # only after they succeed
    return "", 204

Events

Each payload carries id (the event), type, created_at and a data object shaped like the resource it describes. Your client_ref comes back on it unchanged.

sms.deliveredThe operator confirmed delivery.
{
  "id": "evt_01J9ZC4J9A6G",
  "type": "sms.delivered",
  "created_at": "2026-09-28T08:00:03.600Z",
  "data": {
    "id": "msg_01J9Z3K6Q8T4",
    "to": "+447700900123",
    "status": "delivered",
    "operator_status": "DELIVRD",
    "mcc_mnc": "234-10",
    "segments": 1,
    "price": {
      "amount": "0.0320",
      "currency": "USD"
    },
    "client_ref": "signup-8841",
    "delivered_at": "2026-09-28T08:00:03.410Z"
  }
}
sms.failedThe message failed, was rejected or expired.
{
  "id": "evt_01J9ZC4J9A6G",
  "type": "sms.failed",
  "created_at": "2026-09-28T08:00:03.600Z",
  "data": {
    "id": "msg_01J9Z3K6Q8T4",
    "to": "+447700900123",
    "status": "failed",
    "operator_status": "UNDELIV",
    "error_code": "001",
    "mcc_mnc": "234-10",
    "client_ref": "signup-8841"
  }
}
sms.batch.completedNothing is left to send in a batch.
{
  "id": "evt_01J9ZC4J9A6G",
  "type": "sms.batch.completed",
  "created_at": "2026-09-28T08:00:03.600Z",
  "data": {
    "id": "bat_01J9Z4B2M7QX",
    "status": "completed",
    "from": "ELMETRY",
    "route": "marketing",
    "total": 2400,
    "queued": 0,
    "sent": 2400,
    "delivered": 2371,
    "failed": 29,
    "canceled": 0,
    "price": {
      "amount": "64.1600",
      "currency": "USD"
    },
    "send_window": {
      "start": "09:00",
      "end": "20:00",
      "timezone": "recipient"
    },
    "client_ref": "autumn-sale",
    "created_at": "2026-09-28T08:00:00Z",
    "completed_at": "2026-09-28T09:40:00Z"
  }
}
verify.approvedA correct code was checked.
{
  "id": "evt_01J9ZC4J9A6G",
  "type": "verify.approved",
  "created_at": "2026-09-28T08:00:03.600Z",
  "data": {
    "id": "vs_01J9Z5C8R1HN",
    "to": "+447700900123",
    "status": "approved",
    "channel": "sms",
    "approved_at": "2026-09-28T08:00:41Z"
  }
}
verify.expiredA session ended without a correct code.
{
  "id": "evt_01J9ZC4J9A6G",
  "type": "verify.expired",
  "created_at": "2026-09-28T08:00:03.600Z",
  "data": {
    "id": "vs_01J9Z5C8R1HN",
    "to": "+447700900123",
    "status": "expired",
    "checks": 1,
    "expired_at": "2026-09-28T08:10:00Z"
  }
}
voice.completedA call ended, answered or not.
{
  "id": "evt_01J9ZC4J9A6G",
  "type": "voice.completed",
  "created_at": "2026-09-28T08:00:03.600Z",
  "data": {
    "id": "call_01J9Z6D4V9KE",
    "to": "+447700900123",
    "status": "completed",
    "duration_seconds": 18,
    "hangup_cause": "normal_clearing",
    "price": {
      "amount": "0.0410",
      "currency": "USD"
    }
  }
}
insight.lookup.completedA bulk lookup finished.
{
  "id": "evt_01J9ZC4J9A6G",
  "type": "insight.lookup.completed",
  "created_at": "2026-09-28T08:00:03.600Z",
  "data": {
    "id": "lk_01J9ZA2G7Y4D",
    "status": "completed",
    "total": 3,
    "processed": 3
  }
}
hygiene.completedA cleaning job finished.
{
  "id": "evt_01J9ZC4J9A6G",
  "type": "hygiene.completed",
  "created_at": "2026-09-28T08:00:03.600Z",
  "data": {
    "id": "hj_01J9Z7E5W3PA",
    "status": "completed",
    "total": 48210,
    "valid": 44873,
    "invalid": 2918,
    "risky": 419
  }
}

Errors

Branch on code, not on message text.

Every non-2xx response carries the same envelope. These are all the codes the endpoints on this page return.

40040001

Malformed request — bad JSON, a wrong field type, a non-numeric id, or an unreadable upload file.

Fix the request shape and resubmit.

40040002

Semantic validation failed — an out-of-range value, a bad enum, or a bad date/UUID/URL format.

Read message for the specific field; safe to fix and resubmit.

40140101

Missing, invalid, revoked, or expired API key.

Send a valid, unexpired key.

40240201

insufficient_balance: your prepaid balance doesn't cover this submission (checks are paid for when submitted). detail has balance_cents, required_cents and shortfall_cents.

Nothing was stored. Top up in the console under Billing, then resubmit.

40340301

The route isn't open to a customer key, the project is suspended, or the account is disabled.

Not a request-shape problem — contact your account admin.

40340302

feature_not_enabled: this product is not enabled on your account. detail.product names it.

Contact sales to enable the product. Retrying won't change the answer.

40440401

The id doesn't exist, or belongs to another project.

Confirm the id; ids are scoped per project, never shared across projects.

40940901

Conflict — see detail.reason.

insufficient_capacity: nothing was stored, retry later with backoff. not_retryable, not_cancelable, not_pending: the record is in a state that can't take this action.

42942901

Rate limited (60 requests/min per key, burst 20). The request was not processed.

Wait Retry-After seconds, then resend. Never blindly resend a write that timed out — list it first.

50050001

Internal error.

Retry later; contact support if it persists.

50350301

Service temporarily unavailable.

Retry later with backoff.

JSONError body
{
  "code": 40901,
  "message": "<developer-facing text>",
  "detail": { "reason": "insufficient_capacity" }
}

message is written for a developer reading logs and is in English. Branch on code and detail.reason — never on the text of message.

Rate limits

60 requests a minute, per key.

60 requests/min per key with a burst of 20; over the limit you get HTTP 429 with Retry-After. Every endpoint on this page shares one bucket per API key.

Budget

Per API key, shared across every endpoint it can call.

  • 60 requests / min
  • Burst 20

Over the limit

A standard rate-limit response, not a silent drop.

  • HTTP 429 · code 42901
  • Retry-After header (seconds)

Client behavior

A busy poll loop can exhaust its own budget — back off, don't hammer.

  • Honor Retry-After
  • Never blindly resend a write
  • Poll every ~2s, not faster
Invalid keys are throttled too
Repeated attempts with keys that don't authenticate are limited per client IP and answered with the same 429 response, so a misconfigured client backs off instead of hammering the API.

Make your first request today.

Console
API
curl -X POST "$BASE_URL/spam/checks" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"items": [{"kind": "sender",
                 "value": "+15555550123"}]}'
Submit a check
Docs
Request a route test
Developer Documentation & API Reference — Elmetry