Skip to content

mxlabs-sg/number-verify-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mxlabs-number-verify

Thin Python client for silent phone-number verificationCAMARA 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"}.

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

About

Silent phone verification (CAMARA) with SMS OTP fallback — Python SDK

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages