POST /phone/resolve/batch

Resolves a list of phone numbers in a single call: up to 50 numbers, settled with one x402 payment for the whole batch. Each entry runs through the same live HLR engine as GET /phone/resolve — fraud and deliverability intelligence (SIM-swap / port-out risk, non-fixed VoIP and disposable-number detection, SMS reachability), current carrier, portability (MNP) and roaming.

One settlement for N numbers amortises the per-call payment overhead for agents that process lists: clean an SMS list / dedup before a campaign, or score a batch for fraud in one shot. The call is priced per number with a single x402 settlement for the whole batch (a base plus a per-number unit), so cost scales with volume but settlement happens once — see the live /catalog for the authoritative price.

x402 golden rule: the agent pays for the answer to its question. A well-formed batch is a successful answer → 200, even when some of its numbers are invalid (each is returned with valid: false, exactly as in the single call). The 4xx range is reserved for requests the service cannot answer (missing or malformed body, empty list, batch over the cap). A total upstream failure, where no number could be resolved, returns a typed 5xx and is not settled.

Request

POST with a JSON body. Set Content-Type: application/json.

POST /phone/resolve/batch
Content-Type: application/json
{
  "numbers": [
    { "number": "+33612345678" },
    { "number": "0612345678", "country": "FR" }
  ]
}
FieldTypeRequiredDescription
numbersobject[]yesNumbers to resolve, 1 to 50 entries (see item)

Each item has the same two inputs as the single call:

FieldTypeRequiredDescription
numberstringyesNumber to resolve; E.164 (+33…) or national form
countrystringnoDefault region (ISO 3166-1 alpha-2) for non-E.164 national numbers

A structurally invalid entry inside the array is not a request error: it is resolved as an invalid number (valid: false with an issue), just like in the single call.

200 response — UnifiedResponse

{
  "data": {
    "count": 2,
    "results": [ { ... }, { ... } ]
  },
  "provenance": {
    "source": "hlr-lookups",
    "fetched_at": "2026-06-12T09:30:00Z",
    "freshness": { "kind": "live" }
  }
}
  • count: number of resolved numbers, equal to results.length and to the number of items submitted.
  • results: one verdict per number, in the same order as the input. Each element has exactly the same shape as the data of GET /phone/resolvevalid, e164, active, line_type, carrier, mnp, roaming, risk, coverage, issue. See the single-call reference for the full field tables and per-case examples.
  • A single provenance block covers the whole batch. Per-number completeness of the live core is carried inline by each result’s coverage (below), so a partial or failed number is signalled in its own verdict, not in the envelope.

Per-number honesty — coverage

Like the single call, every resolved item carries a coverage marker telling the agent whether the live core it paid for was actually delivered for that number. In a batch, coverage.reason extends the single-call codes with two batch-specific ones:

reasonMeaning
LOOKUP_FAILEDThe HLR lookup for this number failed at the provider (the rest of the batch still answered)
BUDGET_EXCEEDEDThis number was not resolved within the batch’s time/lookup budget
FALLBACK_PROVIDERA fallback provider served the answer and does not report live presence
NO_LIVE_PRESENCEThe primary provider answered but returned neither presence nor a live carrier

When coverage.complete is false, the live fields (active, carrier, line_type) are null for that entry while the rest of the verdict still applies. The batch is billed even when partial — partial numbers are charged like any other; only a total upstream failure (no number resolved) escapes the 200 range as a 5xx.

Example — mixed batch (clean, invalid and partial entries → 200)

{
  "data": {
    "count": 3,
    "results": [
      {
        "input": "+33612345678",
        "valid": true,
        "e164": "+33612345678",
        "country": "FR",
        "number_type": "mobile",
        "active": true,
        "line_type": "mobile",
        "carrier": { "mcc": "208", "mnc": "01", "operator": "Orange France", "country": "FR" },
        "mnp": { "ported": false, "original_carrier": null },
        "roaming": { "roaming": false, "country": null },
        "risk": { "non_fixed_voip": false, "recently_ported": false, "absent_subscriber": false, "level": "low" },
        "coverage": { "complete": true, "reason": null },
        "issue": null
      },
      {
        "input": "not a phone",
        "valid": false,
        "e164": null,
        "country": null,
        "number_type": null,
        "active": null,
        "line_type": null,
        "carrier": null,
        "mnp": null,
        "roaming": null,
        "risk": null,
        "coverage": null,
        "issue": "NOT_A_NUMBER"
      },
      {
        "input": "+33712345678",
        "valid": true,
        "e164": "+33712345678",
        "country": "FR",
        "number_type": "mobile",
        "active": null,
        "line_type": null,
        "carrier": null,
        "mnp": { "ported": false, "original_carrier": null },
        "roaming": { "roaming": false, "country": null },
        "risk": { "non_fixed_voip": false, "recently_ported": false, "absent_subscriber": false, "level": "low" },
        "coverage": { "complete": false, "reason": "BUDGET_EXCEEDED" },
        "issue": null
      }
    ]
  },
  "provenance": {
    "source": "hlr-lookups",
    "fetched_at": "2026-06-12T09:30:00Z",
    "freshness": { "kind": "live" }
  }
}

The invalid entry answers valid: false with no HLR billed (coverage: null), while the third number is a valid mobile that the batch could not resolve within budget — signalled honestly with coverage.complete: false rather than a silent gap.

Errors

Only requests the service cannot answer leave the 200 range. The cap is checked before any resolution, so an oversized batch is rejected without being billed an answer. A total HLR failure (no number resolved) is never turned into a 200.

StatuscodeCase
400INVALID_BODYBody missing or not JSON, not an object, or numbers missing
400EMPTY_BATCHnumbers is an empty array ([])
400BATCH_TOO_LARGEMore than 50 numbers in a single call
502BAD_GATEWAYEvery HLR lookup failed upstream (total failure, not settled)
503SERVICE_UNAVAILABLENo HLR provider configured
504GATEWAY_TIMEOUTHLR provider timed out for the whole batch
500INTERNALInternal error (detail logged, not exposed)
{ "error": "batch too large: 51 numbers, the maximum is 50", "code": "BATCH_TOO_LARGE" }

See also

  • GET /phone/resolve — single-number reference and full field documentation (risk, coverage, carrier, mnp, roaming).
  • For agents — discovery surfaces, the live /catalog and how settlement works.