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

ParameterTypeRequiredDescription
numberstringyesNumber to resolve; E.164 (+33…) or national form
countrystringnoDefault 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:

  1. Structurally invalid200 valid: false + issue, no HLR call, freshness.kind = "snapshot".
  2. Valid but non-mobile (only mobile, fixed_line_or_mobile and voip are HLR-eligible) → 200 structure only, network fields null, no HLR call, snapshot.
  3. 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-lookups or telnyx) — 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 is libphonenumber with a snapshot freshness.
  • freshness.kind: live (fresh lookup), cached (with age_secs) or snapshot (answered locally, no HLR).

Fields of data

FieldTypeDescription
inputstringThe number as received (after trimming)
validboolStructural validity for its region
e164string | nullCanonical E.164 form if valid, otherwise null
countrystring | nullISO 3166-1 alpha-2 region code if valid, otherwise null
number_typestring | nullStructural line type if valid, otherwise null
activebool | nullSubscriber reachable at lookup time; null if the HLR can’t tell
line_typestring | nullLine type reported by the HLR (mobile, landline, voip, unknown)
carrierobject | nullCurrent carrier (see below); null without a lookup
mnpobject | nullPortability info; null without a lookup
roamingobject | nullRoaming info; null without a lookup
riskobject | nullDerived risk assessment; null without a lookup
coverageobject | nullCompleteness of the live verdict (see below); null without a lookup
issuestring | nullDiagnostic 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.

FieldTypeDescription
non_fixed_voipboolNon-geographic VoIP line (potential disposable number)
recently_portedboolNumber was ported (possible SIM-swap / port-out)
absent_subscriberboolSubscriber momentarily unreachable
levelstringhigh 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.

FieldTypeDescription
completebooltrue when the live core was delivered (presence or current carrier); false for a partial answer
reasonstring | nullStable code when complete: false; null when complete

When complete is false, reason is one of:

reasonMeaning
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

active and coverage.complete are independent — read them together. Four combinations occur on the data actually returned:

Verdictactivecoverage.completeMeaning
PresenttruetrueSubscriber reachable; full live core
Absent (known)falsetrueSubscriber genuinely unreachable; full live core
Carrier-onlynulltrueCurrent carrier known, presence not reported
Partial fallbacknullfalseNeither 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

FieldTypeDescription
mccstringMobile Country Code reported by the HLR
mncstringMobile Network Code reported by the HLR
operatorstring | nullOperator name; null if the (MCC, MNC) is unknown
countrystring | nullOperator country; null if the pair is unknown

mnp — portability

FieldTypeDescription
portedboolWhether the number was ported out of its origin net
original_carrierobject | nullOriginal allocation carrier (same shape as carrier)

roaming

FieldTypeDescription
roamingboolWhether the subscriber is roaming abroad
countrystring | nullRoaming 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.

StatuscodeCase
400MISSING_PARAMETERnumber parameter missing or empty
502BAD_GATEWAYHLR provider unreachable, upstream error or bad decode
503SERVICE_UNAVAILABLENo HLR provider configured
504GATEWAY_TIMEOUTHLR provider timed out
500INTERNALInternal 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 /catalog and the authoritative price for this endpoint.