Troubleshoot number lookup and messaging-service resolution failures
Two families of failures stop a send before it enqueues:LOOKUP_FAILED— the number-lookup endpoints (GET /api/v1/numbers/lookup/:phone, the trust-package dip, and bulk HLR) lost their upstream carrier-intelligence feed and could not answer.- Messaging-service resolution codes —
MESSAGING_SERVICE_NOT_FOUND(404) andMESSAGING_SERVICE_RESOLUTION_FAILED(500), raised when the sender-resolution chain cannot bind the service id the request named.
numbers/lookup dip or indirectly when the
sender-resolution chain consults lookup for country/MCC heuristics — so a repeated 502 there
can block your whole launch.
1. LOOKUP_FAILED on GET /api/v1/numbers/lookup
The number-lookup service tries the primary HLR provider first and falls back to the
secondary Number Lookup provider when HLR throws. When the primary provider is unreachable
and the fallback is also down — or when no provider is configured at all — the service
cannot answer at all and the route returns a 502:
Cause table
The
lineStatus gate is the important fallback: when a lookup returns
lineStatus: "unknown" you should not treat the number as inactive, because a “no signal”
result must stay distinguishable from a true “inactive” — treat the number as possibly
valid until a confirmed lookup returns inactive.
Retry matrix
In all transient branches the response carries
meta.request_id; capture it so support
can trace the exact upstream call that failed.
A bulk lookup (
POST /api/v1/numbers/lookup/bulk-hlr) must do the same per-row backoff — retrying the whole batch on one row’s failure re-charges the rows that succeeded. Filter to failed[] first, then resubmit those E.164s only.2. Messaging-service resolution — MESSAGING_SERVICE_NOT_FOUND and MESSAGING_SERVICE_RESOLUTION_FAILED
Every outbound send must resolve a sender. The resolution chain checks the messaging-service
pointer first, then a sender-pool override, then the tenant fallback. Two codes cover the
failure modes that stop the chain before it even reaches pool lookup.
A send naming nothing at all — neither
messaging_service_id, nor a sender_pool_id, nor a
from E.164 / sender ID — fails earlier with 422 SENDER_REQUIRED (covered in the
sender-resolution errors runbook). The two codes
above are the failure modes after you do name a service.
MESSAGING_SERVICE_NOT_FOUND — pointer is stale
The id you sent does not match a service that your tenant owns. Check:
- List your services.
GET /api/v1/messaging/messaging-servicesreturns the current inventory; pick the live id and update the client / template / campaign that is pointing at the deleted one. - Re-create if needed. When a service was deleted, any sender-pool override or campaign bound to it keeps hitting this 404 until you repoint it.
- Test-mode / sandbox. In sandbox modes the service list is per-environment — an id copied from a live production call will not resolve against the sandbox organisation.
meta.request_id — an orphaned pointer cluster usually means the row is still present
but the tenant scoping failed, which is a platform-side problem.
MESSAGING_SERVICE_RESOLUTION_FAILED — transient resolution error
The service row was found, but a downstream storage error (for example a Drizzle-wrapped
Postgres error) interrupted the resolution before any of the optional overrides could be
resolved. The fix:
- Retry once. This code is retryable; the wire message is deliberately static so the client never receives raw storage text, and a single retry usually succeeds when the cause was a transient database blip.
- Probe the pools. If the failure recurs and always on the same service, run
GET /api/v1/messaging/sender-pools?messaging_service=<id>to see whether a broken pool override (country_sender_poolspointing at a deleted pool) is what the chain is dying on. Fix that override on the service.
3. Who decides, and when to escalate
For aLOOKUP_FAILED: you check whether the failure is request-shape (lookup the wrong
E.164, an empty body) or upstream (both providers down). The first branch is a client
fix; the second needs Support.
For a messaging-service resolution code: you decide which selector your integration should
actually carry. The tenant’s default fallback chain only runs when the request named
nothing. Either you repoint the messaging service, or you repoint the sender pool, or the
fallback from picks up — but you cannot decide that the platform owns the pointer until
you have run
GET /api/v1/messaging/messaging-services and
confirmed the id is not there. If the id is listed and the send still fails, escalate.
When you escalate, include:
- The exact
meta.request_idof one failed call. - For
LOOKUP_FAILED: the E.164, the branch (HLR-only or fallback) and, if present, theprovider_messagehint. - For messaging-service codes: the service id and the step in the chain the error came
from (
messaging_service_idpointer or sender-pool override).
Related references
- Sender resolution — the precedence chain every send walks:
pool over bare
from,messaging_service_idfills in only when you named neither, and the fallback order. - Messaging services model — the entity the
messaging_service_idpointer targets. - Live number testing (LNT) — the other send pre-flight that can land a lookup failure: when a lookup rejects in LNT the live test never bills.
- Number lookup — the lookup surface the
LOOKUP_FAILEDcode protects. - Sender-resolution errors — the other codes
the same chain can throw (
SENDER_REQUIRED,SENDER_POOL_NOT_FOUND,NO_SENDER_CONFIGURED). - Error codes reference — the wire surface all these codes are registered on.