Skip to main content

mrxsim

Official Python client for MRXSIM.COM — purchase virtual numbers and retrieve SMS OTPs for every catalog app/social service (Telegram, WhatsApp, Google, Instagram, Discord, Snapchat, Other (SMS), and more).

Brand: MRXSIM · Domain: https://mrxsim.com · Package: mrxsim


Security first

  • No hardcoded API keys in library source or examples.
  • Prefer environment variable MRXSIM_API_KEY.
  • Or load a local config.json that is gitignored.
  • Never commit live keys. Revoke immediately if exposed.

Install

pip install mrxsim

From this repository (editable):

pip install -e .

Quick start (environment variable)

export MRXSIM_API_KEY="mrxs_your_key_here"   # Linux / macOS
# setx MRXSIM_API_KEY "mrxs_your_key_here"  # Windows (new shell)
from mrxsim import Client

with Client() as client:
    # Discover the catalog — no country/service needed to browse.
    countries = client.list_countries()
    services = client.list_services("england")
    options = client.list_options(country="england", service="telegram")

    order = client.get_number(
        country="england", service="telegram",
        operator=options[0]["operator"],  # a stable MRXSIM option code — never a provider ID
        idempotency_key="my-own-stable-id-for-this-purchase",  # optional, see "Retry safety" below
    )
    print(order["phone_number"], order["id"])

    sms = client.wait_for_sms(order["id"])
    print(sms["sms_code"])

    # client.cancel(order["id"])  # if you need to cancel before the OTP arrives

One-shot purchase + OTP:

from mrxsim import Client

with Client(country="egypt", service="whatsapp") as client:
    result = client.buy_and_wait()
    print(result["phone_number"], result["sms_code"])

Quick start (config file)

cp config.example.json config.json
# edit config.json → set api_key, country, service
from mrxsim import Client

client = Client.from_config("config.json")
order = client.get_number()
sms = client.wait_for_sms(order["id"])
client.close()

MRXSIM_API_KEY overrides api_key in the file when set.


Get your API key

  1. Open https://mrxsim.com and create an account.
  2. Top up with Crypto (USDT / supported networks).
  3. Profile → Get API KEY (shown once at create/regenerate).
  4. Export MRXSIM_API_KEY or paste into gitignored config.json.

API surface

Method Endpoint Client method
GET /api/v1/balance Client.balance()
GET /api/v1/sms/catalog Client.list_countries()
GET /api/v1/sms/services?country=… Client.list_services()
GET /api/v1/sms/operators?country=…&service=… Client.list_options()
POST /api/v1/get_number Client.get_number()
GET /api/v1/get_sms?order_id=… Client.get_sms() / wait_for_sms()
POST /api/v1/cancel Client.cancel()

Rental Numbers, when enabled for your account (a disabled product answers 404, exactly like an unknown route):

Method Endpoint Client method
GET /api/v1/rental_countries client.rentals.list_countries()
GET /api/v1/rental_plans?country=… client.rentals.list_plans()
POST /api/v1/rental_quote client.rentals.quote()
POST /api/v1/rental_purchase (requires Idempotency-Key) client.rentals.purchase()
GET /api/v1/rental_list client.rentals.list()
GET /api/v1/rental_status?rental_id=… client.rentals.get()
GET /api/v1/rental_messages?rental_id=… client.rentals.messages()
POST /api/v1/rental_close (requires Idempotency-Key) client.rentals.close()
POST /api/v1/rental_renew (requires Idempotency-Key; currently always refused) client.rentals.renew()

Residential Proxies, when enabled for your account:

Method Endpoint Client method
GET /api/v1/proxy_locations client.proxies.list_locations()
GET /api/v1/proxy_plans client.proxies.list_plans()
POST /api/v1/proxy_quote client.proxies.quote()
POST /api/v1/proxy_purchase (requires Idempotency-Key) client.proxies.purchase()
GET /api/v1/proxy_list client.proxies.list()
GET /api/v1/proxy_detail?proxy_id=… client.proxies.get()
GET /api/v1/proxy_usage?proxy_id=… client.proxies.usage()
GET /api/v1/proxy_credentials?proxy_id=… client.proxies.credentials()
POST /api/v1/proxy_stop (requires Idempotency-Key) client.proxies.stop()

Travel eSIM is not part of the API-key Developer API: it is offered through the signed-in website / Mini App only, so client.esims.* raises MrxsimUnsupportedError before any network call. See "Travel eSIM" below.

Catalog discovery (list_countries/list_services/list_options) is public MRXSIM data — no purchase, no debit — and does not consume your purchase-tier rate budget. An operator value from list_options() is a stable, MRXSIM-owned option code (e.g. "opt1", "opt2" for a service with more than one genuinely distinct price tier) — never an upstream provider identifier — pass it straight to get_number().

list_options() lists every option MRXSIM has ever had genuine backing for on that country/service, not just what's purchasable right now — a row can carry available: False / reliability: "Temporarily unavailable" and still be a real, normally priced product; don't assume every row is instantly purchasable, and don't drop one from your own UI/cache just because it's temporarily unavailable (MRXSIM doesn't either — it resumes automatically once real backing returns). Buying such a row fails cleanly with a 409 (reason == "no_honorable_candidate"), never a silent substitution or a 500. Compare price_usdt numerically, not as a string — the same price can render with a different number of decimal places between calls (e.g. "0.26" vs. "0.2600").

Header on every call:

X-API-Key: YOUR_KEY

Public order fields (zero-knowledge)

Successful responses expose retail fields only — aligned with the MRXSIM server OrderPublicOut contract. This is the complete PUBLIC_ORDER_FIELDS allowlist the client enforces client-side (see "Retry safety" and the changelog below for the newer fields in context):

Field Meaning
id MRXSIM order UUID
phone_number Assigned number
service / country Catalog codes
status Order status
price Retail USDT price charged
sms_code OTP when received
substituted_operator Set when Smart Auto-Fallback delivered a different (never worse) operator than requested
balance_usdt Wallet balance remaining after this purchase
cancel_allowed Whether this order is currently cancelable at all
cancel_available_at ISO timestamp cancellation becomes available, if in cooldown
cancel_remaining_seconds Server-computed seconds left in a cancel cooldown, if any
stars_refund_status / stars_refunded_amount Only for an order funded through Telegram Stars: the state and amount of its refund after a cancel; otherwise null

Order status values: PROCESSING (purchase confirming, no number yet) then PENDING (number issued, waiting for the code) then RECEIVED | EXPIRED | CANCELED. wait_for_sms() keeps polling through PROCESSING and PENDING.

Internal operational metrics and upstream routing identifiers are not part of the public API. The client also sanitizes responses client-side if any such fields ever appear (defense in depth).

Docs: https://mrxsim.com/docs

Retry safety

Client never automatically retries get_number() — not on a timeout, not on a network error, not on a 503. A hidden automatic retry on a purchase call is exactly how a client library accidentally causes a real second charge for one logical purchase, so this client simply doesn't do it. What it gives you instead, so you can retry safely when you choose to:

  • idempotency_key on get_number() — pass the same string across your own retry attempts (e.g. after catching MrxsimTimeoutError) and MRXSIM guarantees at most one real purchase for that key, returning the original order on a repeat instead of buying a second number. Omit it for a normal, independent purchase.
  • MrxsimRateLimitError.retry_after (429) and MrxsimServiceUnavailableError.retry_after (503, MRXSIM's own capacity backpressure — distinct from a per-key rate limit) — both carry the server's real Retry-After value in seconds (None if the response didn't include one). Prefer it over a fixed client-side delay:
import time
from mrxsim import Client, MrxsimRateLimitError, MrxsimServiceUnavailableError

client = Client()
try:
    order = client.get_number(idempotency_key="my-own-stable-id-for-this-purchase")
except (MrxsimRateLimitError, MrxsimServiceUnavailableError) as exc:
    if exc.retry_after:
        time.sleep(exc.retry_after)
        order = client.get_number(idempotency_key="my-own-stable-id-for-this-purchase")
    else:
        raise

Plain read calls (get_sms(), status polling inside wait_for_sms()) are naturally safe to retry on your own — they never mutate anything — so this client doesn't add speculative auto-retry logic there either; keep your own retry loop as simple or as sophisticated as your application needs.

MrxsimAmbiguousPurchaseError — never retry this one with a new key

For the rare case where a real charge/vendor purchase may already have happened but MRXSIM's own record of it failed to save, get_number() raises MrxsimAmbiguousPurchaseError (a subclass of MrxsimAPIError, so a generic except MrxsimAPIError still catches it — but it needs the opposite handling from every other error above):

from mrxsim import Client, MrxsimAmbiguousPurchaseError

client = Client()
try:
    order = client.get_number(idempotency_key="my-own-stable-id-for-this-purchase")
except MrxsimAmbiguousPurchaseError as exc:
    # Do NOT retry with a new idempotency_key — that risks a genuine second
    # real purchase. Retry with the EXACT SAME key, or hold and contact
    # support with exc.purchase_intent_id.
    order = client.get_number(idempotency_key="my-own-stable-id-for-this-purchase")

exc.purchase_intent_id is MRXSIM's own internal correlation id (never provider-derived, safe to log) — keep it if you need to contact support instead of retrying immediately.


Rentals lifecycle

import uuid
from mrxsim import Client

client = Client()
countries = client.rentals.list_countries()
plans = client.rentals.list_plans("US")
plan = next(p for p in plans if p["purchasable"])           # never offer a plan with purchasable=False
print(plan["refund_disclosure"])                            # show this to your buyer before paying
quote = client.rentals.quote("US", plan["duration_type"], plan["duration_time"])
rental = client.rentals.purchase(quote["quote_id"], idempotency_key=str(uuid.uuid4()))
inbox = client.rentals.messages(rental["id"])               # a rental inbox can hold many SMS
if client.rentals.get(rental["id"])["can_close"]:
    client.rentals.close(rental["id"], idempotency_key=str(uuid.uuid4()))

Statuses: ACTIVE, EXPIRED, CANCELED, CLOSED. A quote is short-lived and single-use. close() is only valid while can_close is true (409 otherwise); renew() is currently always refused (409, reason == "renewal_not_supported"). An unknown id and someone else's id are both 404.

Proxy lifecycle

import uuid
from mrxsim import Client

client = Client()
plans = client.proxies.list_plans()
plan = next(p for p in plans if p["available"])
quote = client.proxies.quote("US", "ROTATING", plan["plan_id"])
lease = client.proxies.purchase(quote["quote_id"], idempotency_key=str(uuid.uuid4()))
creds = client.proxies.credentials(lease["id"])             # only while ACTIVE or EXHAUSTED
usage = client.proxies.usage(lease["id"])
client.proxies.stop(lease["id"], idempotency_key=str(uuid.uuid4()))   # revokes now, no auto-refund

Statuses: ACTIVE, EXHAUSTED, STOP_REQUESTED, STOPPING, STOPPED, EXPIRED. Credentials are never shown again once a lease is STOPPED or EXPIRED (409, reason == "not_active"). Treat username and password as secrets.

Travel eSIM

Travel eSIM is offered through the MRXSIM website and Mini App, not through the API-key Developer API, so there is no eSIM method in this SDK. Purchase can be temporarily unavailable; on the signed-in eSIM surface that is signalled by a 404 (product switched off, identical to an unknown route), a 503 with reason provider_unavailable, or a 503 daily-capacity reason. A 202 with reason == "ambiguous_purchase_pending_reconciliation" means the outcome is still being confirmed: your wallet stays debited until it is resolved.

Ambiguous states

A purchase can end in a state MRXSIM is still reconciling. Never treat it as a failure and never retry it with a new Idempotency-Key:

  • HTTP 202, or 503 carrying purchase_intent_id (numbers) or attempt_id (rentals), raises MrxsimAmbiguousPurchaseError. Funds are reserved, not lost.
  • What to do: wait, then check get_sms() / rentals.list() / your order history; retry only with the exact same Idempotency-Key; otherwise contact support with exc.purchase_intent_id or exc.rental_attempt_id.
  • A number order in PROCESSING is the same idea: keep polling get_sms().

Rate limits and retry guidance

Every call shares a system-wide ceiling; each key also has per-action budgets (purchase, reads, and for rentals/proxies quote and close/stop), and an approved high-volume key gets a larger multiple. A 429 carries Retry-After, exposed as MrxsimRateLimitError.retry_after: wait at least that long. Retry 429, 500 and 503 with backoff; never retry 401, 402, 404 or 422 unchanged. 409 means read exc.details["reason"] first (for example fetch a fresh quote). This client never auto-retries a purchase.

Error semantics

Status Exception Meaning
401 MrxsimAuthError missing or invalid key
402 MrxsimAPIError insufficient wallet balance
404 MrxsimAPIError not found, not yours, or product not enabled
409 MrxsimAPIError quote expired/invalid, action not allowed now, idempotency conflict; see details["reason"]
422 MrxsimAPIError invalid request, or a stock-out (details["stock_race"])
429 MrxsimRateLimitError rate limited, see retry_after
202 / 503 with an id MrxsimAmbiguousPurchaseError ambiguous, see above
503 MrxsimServiceUnavailableError temporarily unavailable, retry with backoff

Error bodies never contain an upstream service name. Routing is internal to MRXSIM: there is no parameter to choose or influence the upstream source.


Changelog

Note on Rental Numbers / Travel eSIM / Residential Proxies (added 2026-09-29): the dated entries below track this SDK's ORIGINAL single-product (Temporary Numbers) iteration history. client.rentals, client.esims, and client.proxies were each added later as fully separate product domains — see their own sections above ("Rental Numbers", "Travel eSIM", "Proxy lifecycle") for what each one does and how to use it. None of the three ever got its own dated changelog entry when it landed, and this SDK has never been published to PyPI in any state that lacked any of them — there is no real prior published version a consumer could have been missing them from — so backfilling precise per-domain "added in version X" entries here would just be fabricating history this package never actually had. What matters for a real consumer is simple: version 1.0.8 (the current, not-yet-published candidate) includes all four product domains (SMS, Rentals, eSIM, Proxy) as documented above, in full.

Docs-only clarification (2026-09-04, MRXSIM Major Rework Program, Agent 20, "Docs Final Update" pass) — no code or version change. This pass runs after the owner-directed Telegram/Website "always clickable" UX corrections and the final red-team revalidation, and re-verified this package's docstrings against the actual current server code rather than re-stating the prior pass's wording. Those two UX corrections were a Telegram-bot/Website presentation-layer fix only — they never touched this Developer API's wire contract, which had no client-side gate to begin with. One small wording fix made: get_number()'s docstring described a stale quote as "no longer honorable" (an internal-jargon phrase); reworded to plain English ("no longer valid"). The literal, correct reason value the server actually returns (no_honorable_candidate) is unchanged and still documented verbatim.

Docs-only clarification (2026-09-04, MRXSIM Major Rework Program, Agent 20) — no code or version change. list_options()'s docstring and the catalog-discovery note above were corrected/clarified: the previous reliability example values ("High", "Unrated") did not match the labels the server actually sends ("Available" / "Limited availability" / "Temporarily unavailable"), and neither this README nor the docstring explained that an available: False row is a real, permanently listed product rather than one about to vanish — the "always-visible catalog" contract. Also added: a note to compare price_usdt numerically, never as a string (the same price can render with a different decimal-place count between calls). See docs/rework_agent20_docs_sdk_findings.md in the main MRXSIM repository for the full reasoning; nothing here changes this package's behavior, so the version stays 1.0.6.

1.0.8

  • cancel_reason/cancel_state now correctly returned by get_sms() and cancel(), not just get_number() — real bug found and fixed server-side (2026-09-28): the cancellation-policy fields added in 1.0.6 were only ever wired into POST /get_number's response; GET /get_sms and POST /cancel kept returning null/null for both fields regardless of the order's real state, so a caller polling get_sms() got a different (wrong) answer than calling get_number() on the same order — a real parity break against this file's own "a developer must never see a different answer than the retail UI would" principle. Both fields were already in this client's own PUBLIC_ORDER_FIELDS allowlist (added in 1.0.6) and needed no client-side change once the server started sending them consistently — this version bump exists because that server-side fix genuinely changed what real API responses contain, and the version number had never been updated to reflect it.
  • Trimmed wait_for_sms()'s internal terminal-failure status set to match the real, documented public status vocabulary exactly (PROCESSING/PENDING/RECEIVED/EXPIRED/CANCELED) — real gap found (Phase 9 API-freeze audit): the set also listed "CANCELLED" (double-L), "TIMEOUT", and "FAILED", none of which the server has ever actually assigned to a real order. Those were unreachable dead branches, not a wider real vocabulary — no behavior change for any real order, since those statuses never occurred.
  • MrxsimUnsupportedError now exported from the package root (mrxsim.MrxsimUnsupportedError) — real gap found (external-developer validation pass): the public docs already name this as what every client.esims.* method raises, but it was only reachable via the undocumented internal path from mrxsim.esims import MrxsimUnsupportedError. client.esims/EsimsAPI itself remains intentionally unexported (there is no live eSIM endpoint to use it against).
  • MrxsimAuthError is now a real MrxsimAPIError subclass, with a real status_code — real gap found (external-developer validation pass): every other exception representing an actual HTTP response (MrxsimRateLimitError, MrxsimServiceUnavailableError, MrxsimAmbiguousPurchaseError) already inherited from MrxsimAPIError and carried status_code; MrxsimAuthError did not, so a reasonable except MrxsimAPIError as e: handle(e.status_code) catch-all for any HTTP error silently never caught a 401/403. status_code is 401 or 403 for a real server response, None for this client's own pre-flight placeholder-key check (no HTTP request made). No import path changed — from mrxsim import MrxsimAuthError still works exactly as before.
  • Omitting idempotency_key entirely (not just passing "") on rentals.purchase/close/renew/proxies.purchase/stop now raises MrxsimConfigError, not a bare Python TypeError — real gap found (external-developer validation pass): these five methods required idempotency_key as a keyword-only argument with no default, so Python's own call machinery raised TypeError before this SDK's code — and its documented MrxsimConfigError — ever ran, breaking the "always catch a documented MrxsimXxxError" idiom this package otherwise upholds everywhere else. Now typed str | None = None; omitting it or passing "" both raise the same MrxsimConfigError.
  • Two documentation corrections on the live Developer Docs page (not an SDK code change, no version-relevant behavior change): list_options()'s name field is a permanent per-tier label assigned once and never recomputed, not "assigned fresh from current rank" as the page previously (and incorrectly) said — the live behavior was already correct, only the description was wrong; and the Rental Numbers rental_quote example now explicitly notes it currently returns 409 insufficient_stock given today's genuinely empty inventory, rather than looking like a copy-paste mistake.
  • User-Agent bumped to mrxsim-python/1.0.8.

1.0.7

  • MrxsimAmbiguousPurchaseError — real gap found and closed: this exception class has existed in mrxsim.exceptions since the ambiguous -purchase safety work landed, but was never re-exported from the top-level mrxsim package or listed in __all__ — a caller following this README's own documented pattern (from mrxsim import MrxsimAmbiguousPurchaseError) would get an ImportError. Now correctly exported; see "MrxsimAmbiguousPurchaseError — never retry this one with a new key" below for the full retry-safety guidance this exception exists for. No behavior change — _raise_for_status() was already raising this exception type correctly; only its discoverability was broken.
  • User-Agent bumped to mrxsim-python/1.0.7.

1.0.6

  • Client.balance() — real gap found and closed: the server's GET /api/v1/balance endpoint has existed since 2026-09-01, but this client never exposed a dedicated method for it. Returns {"balance_usdt": "..."}; cheap, idempotent, does not consume your purchase-tier rate budget.
  • PUBLIC_ORDER_FIELDS widened to match the real server OrderPublicOut exactly — real bug found and fixed: balance_usdt, cancel_allowed, cancel_available_at, and cancel_remaining_seconds (all real fields the server has sent on every get_number()/get_sms()/cancel() response since 2026-09-01) were being silently stripped by this client's own sanitizer before this fix — contradicting get_sms()'s and cancel()'s own documented return values. Every prior version back to 1.0.3 was affected. If you're on an older version, upgrade — no code changes needed on your end, you'll simply start receiving fields the server was already sending.
  • User-Agent bumped to mrxsim-python/1.0.6.

1.0.3

  • get_number(idempotency_key=...) — optional, forwarded as an Idempotency-Key header; a retry with the same key (and the same country/service/operator) returns the original order instead of a second purchase. Omitted by default — this client never generates or reuses a key on your behalf.
  • MrxsimRateLimitError/new MrxsimServiceUnavailableError now expose a real retry_after (seconds) parsed from the server's Retry-After header, None if absent. MrxsimServiceUnavailableError is new — raised on 503 (MRXSIM's own capacity backpressure, distinct from the per-key 429).
  • New "Retry safety" section above — documents that this client never automatically retries a purchase call, by design.
  • list_countries()/list_services()/list_options() — real catalog discovery, added 2026-08-31 (this version was never published while this gap existed, so it's folded into 1.0.3 rather than bumping to 1.0.4). Calls MRXSIM's existing public catalog endpoints directly — same canonical data Website/Mini App/Bot render, zero provider identity ever included.
  • cancel(order_id) — added 2026-08-31, alongside a real server-side developer-API cancel endpoint (there was none before). Reuses the same proven cancel/refund logic the web/bot surface already uses — idempotent, provider-neutral errors, refunds only on a confirmed provider outcome.
  • User-Agent bumped to mrxsim-python/1.0.3.

1.0.2

  • Sanitize client responses to strip internal operational metrics and upstream routing identifiers.
  • Harden public documentation for white-label / zero-knowledge API alignment.
  • User-Agent bumped to mrxsim-python/1.0.2.

1.0.1

  • Document and enforce zero-knowledge public order fields.
  • User-Agent bumped to mrxsim-python/1.0.1.

Examples

export MRXSIM_API_KEY="…"
python examples/buy_number_example.py
python examples/get_otp_example.py <order_id>

Development

python -m venv .venv
# Windows: .venv\Scripts\activate
source .venv/bin/activate
pip install -e ".[dev]"
pytest
python -m build

Support

© MRXSIM · Secure SMS Infrastructure

Metadata

Release files for mrxsim 1.0.8

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

Source distribution (sdist)

Source distribution for mrxsim 1.0.8
File Size Uploaded
mrxsim-1.0.8.tar.gz 61.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mrxsim 1.0.8
File Interpreter ABI Platform
mrxsim-1.0.8-py3-none-any.whl Python 3 none any Details

Total release size: 101.7 kB

Release files / mrxsim-1.0.8.tar.gz

Download URL mrxsim-1.0.8.tar.gz
Size 61.9 kB
Tags Source
SHA-256 checksum
How to use checksums
c218a6acbb7707d2d2fcedfaa3debb1776abcdfbe9902a4e98d176239da27576
BLAKE2b-256 checksum
How to use checksums
f6165787174120dfb1a39f72b09794080f202e744ddee46bc10a0344250db6c4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.2

Release files / mrxsim-1.0.8-py3-none-any.whl

Download URL mrxsim-1.0.8-py3-none-any.whl
Size 39.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
660fab7011afe7a7e63cab006bb0b9c125287491dcebf5610db3d20820bcb706
BLAKE2b-256 checksum
How to use checksums
53d500e9dd5e2ab38541e83ebcb8a975c97e1a9d662ea5fc2b41a21c267bbe5f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.2

Release history Release notifications | RSS feed

1.0.9

2 release files

This release

1.0.8 This release

2 release files

1.0.7

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.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