Skip to main content

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

  1. OneMax switches API access on for your business.
  2. The owner opens the partner panel, goes to API and creates a key.
  3. 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_usage with a reference is 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)

Source distribution for onemax 0.1.0
File Size Uploaded
onemax-0.1.0.tar.gz 63.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for onemax 0.1.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

0.1.1

2 release files

This release

0.1.0 This release

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