onemax
Python client for the OneMax partner API.
OneMax is a 1+1 membership club in Baku. Members pay for a plan and show a code at the counter to use a partner's offer. This library lets a partner's own system do what the counter does: check that someone is a member and record that they used the offer.
Sync and async, fully typed, one dependency.
pip install onemax
Getting a key
- OneMax switches API access on for your business.
- The owner opens the partner panel, goes to API and creates a key.
- The key is shown once. It belongs to one venue and acts there like a cashier.
Keep the key on your server. It must never reach a browser or a mobile app.
Quick start: a member's code
A member opens their OneMax card and reads you the six digit code.
from onemax import OneMaxClient
client = OneMaxClient("omx_live_...")
check = client.verify_code("482915")
if not check.usable:
print("No discount:", check.reason)
else:
order = place_order(discounted=True)
client.confirm_usage(check.usage_id, reference=order.id)
verify_code only answers the question. Nothing counts against the member until you call
confirm_usage. If the order falls through, call void_usage(check.usage_id) instead.
Pass your own order id as reference. Confirming again with the same reference returns the same
usage, so a retried request is safe.
Account linking
A member links their OneMax account to your app once. After that you check them without a code. Linking is the OAuth 2.0 authorization code flow with PKCE, so the same steps work for a website and a mobile app.
Register the addresses your app may return to in the partner panel, under API, Account linking. Your client id is shown there.
1. Send the member to the permission page.
from onemax import generate_pkce
pkce = generate_pkce()
session["onemax_verifier"] = pkce.verifier
url = client.authorize_url(
client_id="omx_client_...",
redirect_uri="https://example.az/onemax/return",
code_challenge=pkce.challenge,
state=session_id,
)
In a mobile app, open url in the system browser and use your app link as the redirect_uri,
for example taksi://onemax/return.
2. The member returns to your address with code and state, or with error=access_denied
if they declined. Check that state is the one you sent.
3. Exchange the code on your server and store the token.
link = client.exchange_code(
code,
code_verifier=session["onemax_verifier"],
redirect_uri="https://example.az/onemax/return",
)
save(user_id, link.link_token)
The code works once and for five minutes.
4. Check the member whenever you need to.
status = client.link_status(link_token)
if not status.linked:
forget(user_id)
check = client.verify_link(link_token)
if check.usable:
client.confirm_usage(check.usage_id, reference=order.id)
A member can unlink at any time in their OneMax profile. From then on link_status answers
linked=False and verify_link answers reason="not_linked". Neither raises.
You learn only whether the subscription is active. The member's name, email, phone and photo are never shared.
Async
AsyncOneMaxClient has the same methods, awaited:
from onemax import AsyncOneMaxClient
async with AsyncOneMaxClient("omx_live_...") as client:
check = await client.verify_code("482915")
Results
verify_code and verify_link return a Verification:
| Field | Meaning |
|---|---|
active |
The member has a subscription that works today |
usable |
They may use the offer right now |
reason |
Why, as a Reason |
usage_id |
Set when usable is true. Confirm it or void it |
daily_limit |
How many times a day a member may use this venue |
Reason |
Meaning |
|---|---|
OK |
Go ahead |
NOT_SUBSCRIBED |
No subscription, or it has ended |
LIMIT_REACHED |
The member already used today's allowance at this venue |
OFFER_UNAVAILABLE |
The venue or the offer is not open right now |
INVALID_CODE |
The code is wrong, expired or already used |
NOT_LINKED |
The link token no longer works |
Reason and UsageStatus are string enums, so check.reason == "ok" works as well. A value this
version does not know is returned as a plain string.
If a venue has several offers open, pass offer_id. client.context() lists them.
Errors
Everything the library raises is a OneMaxError.
| Class | When |
|---|---|
TransportError |
No usable answer: a connection failure, a timeout, or a body that is not JSON |
ApiError |
The API answered with an error. Has status, code, message, details, request_id |
AuthenticationError |
401. The key is missing, wrong or switched off |
AccessDisabledError |
403. API access is switched off for the partner |
NotFoundError |
404. No such usage for this key |
ConflictError |
409. For example usage_limit_reached, already_applied, reference_used |
InvalidRequestError |
400 or 422. For example offer_required, link_code_invalid |
RateLimitError |
429. Has retry_after in seconds |
ServerError |
500 and above |
from onemax import ConflictError, OneMaxError
try:
client.confirm_usage(usage_id, reference=order.id)
except ConflictError as error:
if error.code == "usage_limit_reached":
charge_full_price(order)
else:
raise
except OneMaxError:
retry_later(order)
message is in Azerbaijani and meant for your logs. Decide on code.
Limits
- The member's daily limit applies exactly as it does at a counter.
- 120 requests a minute per key.
- The library never retries by itself.
confirm_usagewith areferenceis safe to retry.
Development
uv sync
uv run ruff format --check .
uv run ruff check .
uv run mypy src
uv run pytest
Releasing
Set the version in src/onemax/_version.py, add a ## x.y.z section to CHANGELOG.md, commit, then push a tag
vx.y.z. The Publish workflow checks that the tag equals the package version, runs the checks,
publishes to PyPI and creates the GitHub release from the changelog section. A tag that does not match the
version publishes nothing.
License
MIT
Metadata
Release files for onemax 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| onemax-0.1.0.tar.gz | 63.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| onemax-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 75.9 kB
Release files / onemax-0.1.0.tar.gz
| Download URL | onemax-0.1.0.tar.gz |
|---|---|
| Size | 63.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6b6d48ada4813d3dd3d8d3c9b689ff84d6d27eed96c0b92ba6f2547c8bf29be1
|
|
BLAKE2b-256 checksum How to use checksums |
60d36ff0e27ce3893e5e3eec3784a8348c908f94f54b18802499f30aa9871c4f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / onemax-0.1.0-py3-none-any.whl
| Download URL | onemax-0.1.0-py3-none-any.whl |
|---|---|
| Size | 11.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8a6d13bcbb52bfa0b11803fa2c318bfff3230cd1fb19ae80ab58330e5437a5b9
|
|
BLAKE2b-256 checksum How to use checksums |
89b72dd6490c29d6aa5047441762f69e6fff7d7eb35b1db389d76bd7f0d66f69
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|