GET /phone/resolve
Live phone fraud and SMS deliverability intelligence in a single call: from a real-time HLR lookup it derives whether a number carries SIM-swap / port-out risk, is a non-fixed VoIP or disposable line, and whether it is reachable right now for an SMS — alongside the current carrier, number portability (MNP) and roaming state behind those signals.
Use it to screen for fraud at onboarding, score a risk signal before
extending trust, or clean an SMS list before a campaign — on live network
data that cannot be reproduced from a static dataset. Where
GET /phone/validate is an offline, structural check
(format and line type, the local loss-leader), resolve reaches the network: it
tells you not just whether a number could be valid, but who actually operates
it after porting and roaming and how risky and reachable it looks. See the
live /catalog for the authoritative price.
Deliverability signal: active together with line_type tells you whether
an SMS can land on the handset right now, while a non_fixed_voip line flags a
throwaway number unfit for a verification code.
x402 golden rule: the agent pays for the answer to its question. An invalid or non-mobile number is a successful answer → 200. The 4xx/5xx range is reserved for requests the service cannot answer (missing input, upstream failure).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
number | string | yes | Number to resolve; E.164 (+33…) or national form |
country | string | no | Default region (ISO 3166-1 alpha-2) for non-E.164 national numbers |
Pipeline & billing
The service avoids charging an HLR lookup when one is pointless:
- Structurally invalid → 200
valid: false+issue, no HLR call,freshness.kind = "snapshot". - Valid but non-mobile (only
mobile,fixed_line_or_mobileandvoipare HLR-eligible) → 200 structure only, network fieldsnull, no HLR call,snapshot. - Mobile-like → HLR lookup: a cache hit returns 200 with
freshness.kind = "cached"(+age_secs); a miss returns 200"live"(and is cached); an upstream failure returns a typed 5xx (never a 200).
200 response — UnifiedResponse
{
"data": { ... },
"provenance": {
"source": "hlr-lookups",
"fetched_at": "2026-06-12T09:30:00Z",
"freshness": { "kind": "live" }
}
}
provenance.source: always the effective HLR provider that actually served the answer (e.g.hlr-lookupsortelnyx) — never the name of the failover chain, and honest even when served from cache (a cached hit reports the provider that originally produced it). For cases 1–2 above it islibphonenumberwith asnapshotfreshness.freshness.kind:live(fresh lookup),cached(withage_secs) orsnapshot(answered locally, no HLR).
Fields of data
| Field | Type | Description |
|---|---|---|
input | string | The number as received (after trimming) |
valid | bool | Structural validity for its region |
e164 | string | null | Canonical E.164 form if valid, otherwise null |
country | string | null | ISO 3166-1 alpha-2 region code if valid, otherwise null |
number_type | string | null | Structural line type if valid, otherwise null |
active | bool | null | Subscriber reachable at lookup time; null if the HLR can’t tell |
line_type | string | null | Line type reported by the HLR (mobile, landline, voip, unknown) |
carrier | object | null | Current carrier (see below); null without a lookup |
mnp | object | null | Portability info; null without a lookup |
roaming | object | null | Roaming info; null without a lookup |
risk | object | null | Derived risk assessment; null without a lookup |
coverage | object | null | Completeness of the live verdict (see below); null without a lookup |
issue | string | null | Diagnostic code when valid: false |
Network fields (active, line_type, carrier, mnp, roaming, risk,
coverage) are null whenever no HLR lookup was billed (cases 1 and 2).
risk — fraud & deliverability signals
The headline of the verdict: deterministically derived from the HLR signals,
provider-agnostic. non_fixed_voip flags a disposable / throwaway line,
recently_ported a possible SIM-swap or port-out, absent_subscriber a number
that cannot currently receive an SMS — folded into a single level.
| Field | Type | Description |
|---|---|---|
non_fixed_voip | bool | Non-geographic VoIP line (potential disposable number) |
recently_ported | bool | Number was ported (possible SIM-swap / port-out) |
absent_subscriber | bool | Subscriber momentarily unreachable |
level | string | high on any strong signal (VoIP / absent), medium if only ported, else low |
coverage — completeness of the live verdict
Present only when an HLR lookup was billed. It tells the agent whether the
live core it paid for was actually delivered, so a degraded fallback answer
is not mistaken for a full HLR verdict. The live core counts as delivered as soon
as either signal comes back — subscriber presence (active) or the
current carrier — so complete: true does not imply that active is
known: a carrier-only verdict returns the operator with active: null and is
still complete: true.
| Field | Type | Description |
|---|---|---|
complete | bool | true when the live core was delivered (presence or current carrier); false for a partial answer |
reason | string | null | Stable code when complete: false; null when complete |
When complete is false, reason is one of:
reason | Meaning |
|---|---|
FALLBACK_PROVIDER | A fallback provider served the answer and does not report live presence |
NO_LIVE_PRESENCE | The primary provider answered but returned neither presence nor a live carrier |
active and coverage.complete are independent — read them together. Four
combinations occur on the data actually returned:
| Verdict | active | coverage.complete | Meaning |
|---|---|---|---|
| Present | true | true | Subscriber reachable; full live core |
| Absent (known) | false | true | Subscriber genuinely unreachable; full live core |
| Carrier-only | null | true | Current carrier known, presence not reported |
| Partial fallback | null | false | Neither presence nor carrier; degraded answer (reason) |
The agent must not conflate the last two: a carrier-only verdict is a
complete answer the provider simply could not enrich with presence, whereas a
partial fallback never delivered the live core at all. The price is the same
across all four — the live depth depends on the serving provider’s coverage, and
coverage is how a partial answer is signalled honestly.
carrier
| Field | Type | Description |
|---|---|---|
mcc | string | Mobile Country Code reported by the HLR |
mnc | string | Mobile Network Code reported by the HLR |
operator | string | null | Operator name; null if the (MCC, MNC) is unknown |
country | string | null | Operator country; null if the pair is unknown |
mnp — portability
| Field | Type | Description |
|---|---|---|
ported | bool | Whether the number was ported out of its origin net |
original_carrier | object | null | Original allocation carrier (same shape as carrier) |
roaming
| Field | Type | Description |
|---|---|---|
roaming | bool | Whether the subscriber is roaming abroad |
country | string | null | Roaming country (ISO 3166-1 alpha-2), if known |
Case 1 — active mobile, clean
{
"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
}
Case 2 — ported number (medium risk)
{
"input": "+33612345678",
"valid": true,
"e164": "+33612345678",
"country": "FR",
"number_type": "mobile",
"active": true,
"line_type": "mobile",
"carrier": { "mcc": "208", "mnc": "10", "operator": "SFR", "country": "FR" },
"mnp": {
"ported": true,
"original_carrier": { "mcc": "208", "mnc": "01", "operator": "Orange France", "country": "FR" }
},
"roaming": { "roaming": false, "country": null },
"risk": {
"non_fixed_voip": false,
"recently_ported": true,
"absent_subscriber": false,
"level": "medium"
},
"coverage": { "complete": true, "reason": null },
"issue": null
}
Case 3 — absent subscriber (high risk, inactive)
{
"input": "+33612345678",
"valid": true,
"e164": "+33612345678",
"country": "FR",
"number_type": "mobile",
"active": false,
"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": true,
"level": "high"
},
"coverage": { "complete": true, "reason": null },
"issue": null
}
Here the subscriber is genuinely absent but the HLR verdict is complete
(coverage.complete: true) — a known result, not to be confused with a
carrier-only verdict (case 4) or a degraded fallback (case 5).
Case 4 — carrier-only (complete, presence not reported)
The provider returns the current carrier but no presence flag, so active is
null while the live core is still delivered: coverage.complete is true. The
agent learns who operates the number even though reachability is unknown.
{
"input": "+33612345678",
"valid": true,
"e164": "+33612345678",
"country": "FR",
"number_type": "mobile",
"active": null,
"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
}
Case 5 — degraded fallback response (partial, still billed)
A fallback provider served the lookup but does not report live presence, so the
core live fields (active, carrier, line_type) are null while
freshness.kind is still live. coverage.complete is false with a stable
reason, and the call is billed at the full price like any live answer.
{
"input": "+33612345678",
"valid": true,
"e164": "+33612345678",
"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": "FALLBACK_PROVIDER" },
"issue": null
}
Case 6 — valid but non-mobile (no HLR billed → snapshot)
Structure only; all network fields are null.
{
"input": "+33123456789",
"valid": true,
"e164": "+33123456789",
"country": "FR",
"number_type": "fixed_line",
"active": null,
"line_type": null,
"carrier": null,
"mnp": null,
"roaming": null,
"risk": null,
"coverage": null,
"issue": null
}
Case 7 — invalid number (successful answer → 200, no HLR billed)
{
"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"
}
Possible issue codes: BAD_FORMAT, TOO_SHORT, TOO_LONG, NOT_A_NUMBER,
UNKNOWN_REGION, INVALID_FOR_REGION.
Errors
A failed HLR lookup is never turned into a 200: the provider is only required for the live path (case 3), so invalid and non-mobile numbers still answer without it.
| Status | code | Case |
|---|---|---|
| 400 | MISSING_PARAMETER | number parameter missing or empty |
| 502 | BAD_GATEWAY | HLR provider unreachable, upstream error or bad decode |
| 503 | SERVICE_UNAVAILABLE | No HLR provider configured |
| 504 | GATEWAY_TIMEOUT | HLR provider timed out |
| 500 | INTERNAL | Internal error (e.g. provider auth misconfiguration) |
{ "error": "missing or empty required parameter: number", "code": "MISSING_PARAMETER" }
See also
GET /phone/validate— the offline, structural companion (format and line type, no network call) used as the local loss-leader before reaching for a live lookup.- For agents — discovery surfaces, the live
/catalogand the authoritative price for this endpoint.