Skip to main content

mxlabs-number-verify

Thin Python client for silent phone-number verification — CAMARA Number Verification delivered through the MX Labs Open Gateway — with an automatic SMS OTP fallback.

Your app never touches telco keys, JWT signing, or the OIDC/CIBA dance. You call two methods; the hosted backend brokers every operator's auth and gives you back a yes/no.

┌──────────────┐  start / verify / sms_*   ┌────────────────────────┐   CAMARA   ┌────────────┐
│  your server │ ────────────────────────► │  MX Labs Open Gateway  │ ─────────► │  operator  │
│  (this SDK)  │ ◄──────────────────────── │  (keys stay here)      │            │  network   │
└──────────────┘           JSON            └────────────────────────┘            └────────────┘
  • Zero dependencies. Standard library only. Python 3.8+.
  • Silent when it can be, SMS when it can't. One flow, graceful fallback.
  • Open-core. This client is MIT. Point it at the MX Labs hosted backend (recommended) or your own backend running against your own Bridge Alliance credentials — same wire contract.

Install

pip install mxlabs-number-verify

Quick start

One call. The SDK handles the session and polling; you just open the auth URL on the user's device (that cellular fetch is what silently proves the number).

import os
from mxlabs_number_verify import MXNumberVerify

nv = MXNumberVerify(api_key=os.environ["MXLABS_API_KEY"])

result = nv.verify_number(
    "+60123456789",
    on_auth_url=lambda url, _sid: redirect_user_to_url(url),  # device opens over cellular
)

if result["status"] == "verified":
    grant_access(result["phone"])
else:
    nv.sms_send(result["session_id"])   # fallback: OTP by SMS

Advanced — full control

verify_number is sugar over four primitives; use them directly to drive the session, redirect, and fallback yourself:

started = nv.start("+60123456789")
# have the device open started["auth_url"] over cellular, then:
result = nv.poll_verify(started["session_id"])
if result["status"] != "verified":
    sent = nv.sms_send(started["session_id"])
    code = input(f"Enter the code sent to {sent['masked']}: ")
    nv.sms_verify(started["session_id"], code)

Why "open the URL over cellular"?

Silent Number Verification works by having the device's mobile network confirm the SIM's number — no SMS, no code, no user action. That only fires when the authorize URL is fetched over the cellular data path (not Wi-Fi). If the device is on Wi-Fi or the operator can't verify, the call returns nv_unavailable / not_verified with fallback: "sms_otp" — switch to the SMS flow. The SDK surfaces this for you; you never guess.

API

MXNumberVerify(api_key, backend_url=..., timeout=30.0, transport=None)

arg notes
api_key required — issued by MX Labs
backend_url hosted backend (default https://api.mxlab.sg/sdk.php), or your own
timeout per-request network timeout in seconds
transport optional callable(url, headers, body, timeout) -> (status, dict) for tests / a custom HTTP stack

Methods

  • ping() → {"status": "ok", "partner": ...}.
  • start(phone) → {"session_id", "auth_url", "callback_url", ...}. phone may be E.164 (+60...) or local digits.
  • verify(session_id, code=None) → one check; status is pending, verified, not_verified, or nv_unavailable.
  • poll_verify(session_id, interval=2.0, timeout=30.0) → polls verify until a terminal status or timeout. On timeout returns {"status": "nv_unavailable", "reason": "timeout", "fallback": "sms_otp"}.
  • sms_send(session_id) → {"status": "sms_sent", "masked", "expires_in"}.
  • sms_verify(session_id, code) → {"status": "verified" | "invalid" | "expired"}.

Status values

verify / poll_verify return a status. Poll until it is terminal:

status Meaning What to do
pending The silent check is still running, waiting on the operator. Not terminal. Keep polling verify (poll_verify does this for you).
verified The number is confirmed. method shows how (number_verification or sms_otp); phone echoes it back. Grant access.
not_verified The operator checked and the number is not active on this device. Do not grant access; offer the SMS fallback (fallback: "sms_otp").
nv_unavailable The silent check could not run (e.g. Wi-Fi, timeout, or sandbox). reason says why. Fall back to an SMS one-time code (sms_send → sms_verify).

SMS backstop statuses: sms_send → sms_sent (with masked, expires_in); sms_verify → verified / invalid (wrong code) / expired (code timed out).

Errors

Transport failures, non-2xx HTTP, and {"status": "error"} bodies raise MXNumberVerifyError with a machine-readable .code (e.g. invalid_api_key, invalid_phone, session_not_found_or_expired), plus .http_status and .response. Non-error verification outcomes (not_verified, nv_unavailable, invalid, expired) are returned, not raised — they're normal results.

from mxlabs_number_verify import MXNumberVerifyError
try:
    nv.start("nope")
except MXNumberVerifyError as e:
    print(e.code)  # "invalid_phone"

Try it in your browser

Live interactive demo (sandbox, no signup): https://api.mxlab.sg/

Sandbox — try it in 10 minutes, no real phone

Ask MX Labs for a sandbox key (sbx_…). Sandbox keys are fully simulated: they never touch a real operator or send a real SMS, and only accept these magic test numbers:

Number Simulates
+10000000001 silent verification succeeds
+10000000002 silent NV unavailable → SMS OTP fallback
+10000000003 NV reachable but number doesn't match

In sandbox the SMS OTP is always 000000. Everything else is identical to production — same methods, same responses (with an extra "sandbox": true). Swap in a live key for real numbers; no code changes.

nv = MXNumberVerify(api_key="sbx_...")
started = nv.start("+10000000001")
nv.verify(started["session_id"])   # -> {"status": "verified", "sandbox": True}

API contract

The raw HTTP contract is published as an OpenAPI 3.1 spec: mxlabs/number-verify-openapi. Use it to generate a client in any other language.

License

MIT © MX Labs

Metadata

Release files for mxlabs-number-verify 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mxlabs-number-verify 0.1.2
File Size Uploaded
mxlabs_number_verify-0.1.2.tar.gz 11.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mxlabs-number-verify 0.1.2
File Interpreter ABI Platform
mxlabs_number_verify-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 20.0 kB

Release files / mxlabs_number_verify-0.1.2.tar.gz

Download URL mxlabs_number_verify-0.1.2.tar.gz
Size 11.1 kB
Tags Source
SHA-256 checksum
How to use checksums
971fd89344a5b3f62b7c0d372e347c7b243ee0858accfffe16998d0fdc94e5ee
BLAKE2b-256 checksum
How to use checksums
309a683efd493741f18761b65dffdc666bf80104fe73629c2c10902f617a7161
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.12

Release files / mxlabs_number_verify-0.1.2-py3-none-any.whl

Download URL mxlabs_number_verify-0.1.2-py3-none-any.whl
Size 8.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
03df711c74e73ee9c896bd48bbfcdf7ba235f3ba46367ccd26b054c2586c4d6d
BLAKE2b-256 checksum
How to use checksums
8c9334b5347c6b085de8a03f2ac08c8cc0301e43f86f5b628d1f1fb88e154bc6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.12

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page