Skip to main content

baridimob

Unofficial Python SDK for BaridiMob — Algérie Poste (بريد الجزائر)

PyPI Python License: MIT

Read your BaridiMob accounts, verify incoming payments, send transfers and pay bills from Python.

  • Two dependencies: httpx and cryptography
  • Fully typed (dataclasses)
  • Automatic session renewal

Status: version 0.x — the API may still change between minor versions.


Contents


Installation

pip install baridimob

Requires Python 3.10 or later.

Quick start

from baridimob import Config, FileStorage, create_client

bm = create_client(Config(storage=FileStorage("./.baridimob/session.json")))

# 1. Log in
result = bm.auth.login("your-login", "your-password")

# 2. First time on this machine only: confirm the device by SMS
if result.state == "otp_required":
    bm.auth.request_device_otp()        # an SMS code is sent to your phone
    bm.auth.confirm_device("123456")    # the code you received

# 3. Use the API
accounts = bm.accounts.list()
print(accounts[0].number, accounts[0].balance)

How login works

  1. First login on a machine: BaridiMob asks to confirm the new device. login() returns a result with state == "otp_required". Call request_device_otp(), then confirm_device(code) with the SMS code.
  2. The device keys and session are saved in the storage you provide (here a file).
  3. Next logins reuse the saved device. No SMS is needed.
  4. When the session expires, the SDK logs in again automatically and retries the request.
bm.auth.current_device_id()  # the registered device id, or None
bm.auth.logout()             # ends the session and deletes the saved device:
                             # the next login will ask for an SMS code again

Accounts and history

accounts = bm.accounts.list()             # all accounts
account  = bm.accounts.get(accounts[0].id)
balance  = bm.accounts.balance()          # balance of the first account

Transaction history

from datetime import date

# One page
page = bm.accounts.transactions(account.number, date(2026, 1, 1), date.today(), page=1)

# All pages at once
everything = bm.accounts.all_transactions(account.number, "2026-01-01")

# Stream page by page (memory-friendly)
for tx in bm.accounts.iter_transactions(account.number):
    print(tx.date, tx.amount, tx.description)

# Find one transaction by its reference number
tx = bm.accounts.find_transaction(account.number, "012345678901")

Each Transaction contains amount (positive = incoming, negative = outgoing), currency, description, date, fee, ref_num, and the original server data in raw.

Cards

cards = bm.cards.list()                 # regular and virtual
card = bm.cards.get(cards[0].id)
limits = bm.cards.limits(card.id)

page = bm.cards.history(card.id, from_value=date(2026, 1, 1))
movements = bm.cards.all_history(card.id)

for movement in bm.cards.iter_history(card.id):
    print(movement.date, movement.amount)

Card carries number, name, brand, status, exp_date (yyyyMM), type, balance, currency, embossed_name, linked_accounts and the raw payload. status is 0 valid, 20 temporary block, 21 permanent block, 99 expired, 12 not activated, 1 invalid PIN limit, 10 fraud block.

number is the full card number, exactly as BaridiMob sends it. Keep it out of logs and error reports; repr() leaves it out.

CardLimit carries type (0 amount, 1 number of operations), amount and used_amount or op_count and used_op_count, active, changeable, permanent, per_transaction and end_time.

history accepts state="all", "hold" or "processed".

BaridiMob ignores the category filter of /cards (verified live), so list() makes a single request that returns every card. Use card.category (regular or virtual) to tell them apart.

Verify an incoming payment

Useful for shops and services that accept payment by BaridiMob transfer. The customer gives you the transfer's reference number, and you check that the money has really arrived.

check = bm.payments.verify_payment(
    "012345678901",   # reference number given by the customer
    amount=2500,      # expected amount (optional)
)

if check.verified:
    ...  # payment received: deliver the order
else:
    print(check.reason)
reason Meaning
ok Payment found, incoming, and the amount matches
not_found No transaction with this reference number
not_incoming The transaction exists but is not money received
amount_mismatch The amount differs from the expected one
duplicate Several incoming transactions share this reference: check manually

By default, the search covers the last 30 days of the first account. Use account=, from_value= and to_value= to change this.

Transfers

Every transfer requires an SMS code. send() prepares the transfer and sends the SMS, then confirm() executes it:

pending = bm.transfers.send(
    1000,                       # amount
    account.number,             # your account
    "00799999000000000000",     # recipient's RIP
)

# ask the user for the SMS code, then:
receipt = pending.confirm("123456")

For full control, the same flow is available step by step: arm() → request_otp(trans_ref) → exec(..., trans_ref, otp).

Bill payments

# 1. Browse what can be paid
categories = bm.payments.categories()
providers = bm.payments.providers(categories[0].cid)

# 2. Prepare the payment
prepared = bm.payments.check(
    providers[0].pid,
    500,
    source_type="card",                 # "card" or "account"
    source_id="your-card-id",
    params={"phone": "0600000000"},     # fields required by this provider
)

# 3. Confirm with the SMS code
bm.payments.request_otp(prepared.trans_ref)
bm.payments.exec(
    providers[0].pid,
    500,
    source_type="card",
    source_id="your-card-id",
    trans_ref=prepared.trans_ref,
    otp="123456",
    params={"phone": "0600000000"},
)

source_type="account" is part of the API, but BaridiMob refuses an account as the payment source for paymentCheck (verified live); use a card.

Bills through the dynamic engine. Some services (Algérie Télécom, ADE, SEAL, Sonelgaz, AADL…) return the form themselves, step by step:

from baridimob import DynamicSource

groups = bm.payments.dynamic.groups()
operation = groups[0].operations[0]
card = bm.cards.list()[0]
source = DynamicSource.card(card.id)

# Fill each step until BaridiMob has none left (step.done). values() adds
# what BaridiMob expects besides what is typed: hidden fields (for an
# Algérie Télécom bill, billerServiceCode=FACTURE), the payment source and
# the amount. Later steps send the current step id and every value so far.
step = bm.payments.dynamic.step(operation.id)
values = {}
while not step.done:
    values.update(step.values(entered={"customerNumber": "044395258"}, source=source))
    step = bm.payments.dynamic.step(operation.id, values, step.id)

# check() moves no money and sends no SMS; for a bill, its details hold the
# subscriber, the bill and the amount due.
checked = bm.payments.dynamic.check(operation.id, values)
for detail in checked.details:
    print(detail.label, detail.amount.text if detail.amount else detail.text)
bm.payments.dynamic.request_otp(checked.trans_ref)
result = bm.payments.dynamic.exec(operation.id, checked.trans_ref, "123456", values)
print(result.recent_op_id, result.status)
  • Payment source: dynamic forms name it with DynamicSource (CLIENT_CARD_ID for a card, ACCOUNT_NUMBER for an account). BaridiMob answers result 2 to the 1/2 of payments.check.
  • Amounts: a money field may come filled (input.amount, a bill's amount due) or offer amounts to pick (input.amount_choices when input.amount_mode == "select"); pass the chosen one as values(amount=...). Read-only fields (input.readonly) keep BaridiMob's value. Fields come in the order of their ordinalNumber.
  • Refusals carry BaridiMob's own words, for example «لا توجد فواتير غير مدفوعة» (no unpaid bills): see Error handling.

check() returns DynamicCheckResult (trans_ref, confirmation_type, fee, plan_date, details, amount); exec() returns DynamicExecResult (recent_op_id, recent_op_type, status, details). complete() is for an extra confirmation step (such as 3D Secure) when the server asks for one. Values are sent as param.<id>.

groups(), operation(), step() and check() (an internet top-up with a card source) are verified against the live server. exec() and complete() use the endpoints of the official app but have not been executed live yet.

Withdrawals without a card

A cardless code takes cash out of an Algérie Poste ATM without the card.

from datetime import datetime, timedelta, timezone

from baridimob import CardlessSource

# The codes not used yet.
for code in bm.cardless.list():
    print(code.code, code.amount, code.currency, code.id)

account = bm.accounts.list()[0]
code = bm.cardless.create(
    amount=2000,
    source=CardlessSource.account(account.id),  # or CardlessSource.card(card.id)
    expires_at=datetime.now(timezone.utc) + timedelta(days=1),  # less than six months
)
print(code.code, code.id)  # id: the authorisation number

# While it is unused, it can be deleted.
bm.cardless.delete(code.id)

create() reads the form again each time (its hidden mb_model is signed per request) and is never retried automatically; the official app asks for no SMS code here. Treat code like cash. A naive expires_at is read as Algeria time.

Partly verified live: form() and list() are verified. create() reached BaridiMob's balance check (an account without funds was refused with «رصيد الحساب غير كافٍ»); delete() has not run against a real code yet.

Receipts and profile

from pathlib import Path

operations = bm.receipts.list()                       # recent operations
pdf = bm.receipts.download(operations[0].uid)         # PDF as bytes
Path("receipt.pdf").write_bytes(pdf)

# The receipt of a movement from the history, or None
movement = bm.accounts.transactions(account).transactions[0]
receipt = bm.receipts.download_for(movement)
if receipt:
    Path("movement.pdf").write_bytes(receipt)

profile = bm.profile.get()                            # account holder information

receipts.find(transaction) returns the recent operation behind a history movement, matched by its reference number (or by time and amount). BaridiMob keeps receipts only for the last few operations, so find() and download_for() return None for older ones.

Session storage

The storage keeps the device keys and session cookies between runs.

Storage Use case
FileStorage(path) Scripts and servers: saved to a JSON file
MemoryStorage() Tests and short-lived processes: lost when the process stops
Your own Database, Redis, secret manager…

To use your own storage, write a class with three methods (it follows the SessionStorage protocol):

from baridimob import SessionStorage, StoredSession

class MyStorage(SessionStorage):
    def load(self) -> StoredSession | None: ...   # return the saved session, or None
    def save(self, session: StoredSession) -> None: ...  # save it (encrypted!)
    def clear(self) -> None: ...                  # delete it

You can also move a session manually with bm.export_session() and bm.import_session(session).

Configuration

All options are optional:

bm = create_client(Config(
    storage=FileStorage("./session.json"),
    locale="fr",     # "ar" (default) | "fr" | "en"
    timeout=30.0,    # request timeout in seconds
    retries=2,       # retries on network errors
))
Option Default Description
storage None Where the session is saved. Without it, the device must be confirmed again at every start.
locale "ar" Language of server messages and labels
timeout 30.0 Request timeout in seconds
retries 2 Number of retries on network errors
device_name "BaridiMob SDK" Device name shown in BaridiMob
time_zone "GMT+01:00" Time zone sent to the server
base_url official server Server URL (must be HTTPS)
allow_insecure False Allow plain HTTP for non-local URLs (testing only)
http_client None Your own httpx.Client (proxy, logging…)

Error handling

All errors extend BaridiMobError, so you can catch them precisely:

from baridimob import InvalidCredentialsError, NetworkError

try:
    bm.auth.login(login, password)
except InvalidCredentialsError:
    ...  # wrong login or password
except NetworkError:
    ...  # no connection
Error When
InvalidCredentialsError Wrong login or password
InvalidOtpError / ExpiredOtpError Wrong or expired SMS code
TooManyAttemptsError Too many failed attempts
AccountLockedError The account is locked
PasswordExpiredError The password must be changed in the official app
SessionExpiredError Session expired and no credentials to log in again
NetworkError / TimeoutError Connection problem
ServerError The server returned an error
ApiResultError Any other server error. Inspect result_code and result_name.

When BaridiMob explains a refusal, the error message (str(error)) is its own words, in the session's language (for example «لا توجد فواتير غير مدفوعة»), also in description.

Security

  • The saved session is a live credential. It contains the session cookies and the device private key. Never commit it or log it. In production, encrypt it.
  • HTTPS is enforced. Plain HTTP is refused, except for localhost / 127.0.0.1 or with allow_insecure=True.
  • Moving money always requires an SMS code that you pass explicitly. Reading data never does.
  • Add .baridimob/ (or wherever you store the session) to your .gitignore.

Disclaimer

This is an independent community project. It is not affiliated with, endorsed by, or supported by Algérie Poste. BaridiMob may change its service at any time, which can break this SDK. Use it at your own risk.

License

MIT

Metadata

Release files for baridimob 0.4.0

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

Source distribution (sdist)

Source distribution for baridimob 0.4.0
File Size Uploaded Provenance
baridimob-0.4.0.tar.gz 35.9 kB Without provenance Details

Built distribution (wheel)

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

Total release size: 67.6 kB

Release files / baridimob-0.4.0.tar.gz

Download URL baridimob-0.4.0.tar.gz
Size 35.9 kB
Tags Source
SHA-256 checksum
How to use checksums
e200865a0afd88e6b2bdf97a49d6ccce99ef851e329b63a0d23c256d831f52ae
BLAKE2b-256 checksum
How to use checksums
f94c20e1bcee1e7e5887aa9c708deae4c017712b2b6334d53d3c4aae53ef767e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.11

Release files / baridimob-0.4.0-py3-none-any.whl

Download URL baridimob-0.4.0-py3-none-any.whl
Size 31.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ee5c2b1673fca7a6a6d495ef1ec196f0430621b593e9dc0383a810a8791393fd
BLAKE2b-256 checksum
How to use checksums
159ef07bee4e77089bca975570245a0d475ec2deb94113e56087ba2c5b4a2c48
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.11

Security for baridimob 0.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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