Skip to main content

ebarimt-pos-sdk

codecov Python License: MIT

A modern, async-first Python SDK for the Ebarimt POS API 3.0 — Mongolia's electronic receipting (e-баримт) platform. It wraps both the public OAuth2 API and the local REST API exposed by POS devices behind a single, typed, ergonomic interface.

📖 Full documentation: https://ebarimt-pos-sdk.readthedocs.io/mn/latest/ 📘 Ebarimt POS API 3.0 reference: https://developer.itc.gov.mn/docs/ebarimt-api/inbishdm2zj3x-pos-api-3-0-sistemijn-api-holbolt-zaavruud 🇲🇳 Mongolian version of this README: README_MN.md


Features

  • Async-first — every operation exposes both a synchronous and an async variant (method() / amethod()), built on httpx.
  • Strictly typed — Pydantic v2 models with camelCase ↔ snake_case aliasing; type-checked with ty.
  • Two focused clients — EbarimtApiClient for the public OAuth2 API, EbarimtRestClient for the local POS REST API.
  • Managed OAuth2 — built-in password-grant flow with automatic token refresh and proactive expiry handling.
  • Resilient transport — configurable retry with exponential backoff on 5xx and network errors, per-request timeouts, and TLS verification.
  • Structured errors — a clear exception hierarchy distinguishing transport, HTTP, decode, validation, and business failures. Sensitive headers and tokens are automatically redacted from error output.
  • Environment presets — one-line switch between STAGING and PRODUCTION endpoints via create_api_settings.

Installation

pip install ebarimt-pos-sdk

Or with uv:

uv add ebarimt-pos-sdk

Requires Python 3.10 or newer.


Quick Start

Local REST client — issue a receipt

from ebarimt_pos_sdk import EbarimtRestClient, RestClientSettings

settings = RestClientSettings(base_url="http://localhost:1234")

with EbarimtRestClient(settings) as client:
    receipt = client.receipt.create({
        "branch_no": "001",
        "total_amount": 10000,
        "merchant_tin": "1234567890",
        "pos_no": "POS001",
        "type": "B2C_RECEIPT",
        "bill_id_suffix": "A",
        "receipts": [{
            "total_amount": 10000,
            "tax_type": "VAT_ABLE",
            "merchant_tin": "1234567890",
            "items": [{
                "name": "Product",
                "measure_unit": "ш",
                "qty": 1,
                "unit_price": 10000,
                "total_amount": 10000,
            }],
        }],
    })
    print(receipt.id, receipt.qr_data)

Async variant: swap with for async with and call await client.receipt.acreate(...).

import asyncio
from ebarimt_pos_sdk import EbarimtRestClient, RestClientSettings

async def main() -> None:
    async with EbarimtRestClient(RestClientSettings(base_url="http://localhost:1234")) as client:
        receipt = await client.receipt.acreate(payload)
        print(receipt.id)

asyncio.run(main())

Public API client — look up a TIN

Use the factory to target a specific environment without hard-coding URLs:

from ebarimt_pos_sdk import (
    EbarimtApiClient,
    Environment,
    create_api_settings,
)

settings = create_api_settings(
    Environment.PRODUCTION,  # or Environment.STAGING
    client_id="your_client_id",
    username="your_username",
    password="your_password",
)

with EbarimtApiClient(settings=settings) as client:
    info = client.tin_info.read("1234567890")
    print(info.data)

Routing through a proxy

The public API may only be reachable from a specific region (e.g. Mongolia). Route the API client through an HTTP/SOCKS proxy with the proxy argument:

with EbarimtApiClient(settings, proxy="http://user:pass@mn-proxy:8080") as client:
    info = client.tin_info.read("1234567890")

proxy accepts a URL string or an httpx.Proxy; SOCKS proxies need the httpx[socks] extra. It cannot be combined with an injected sync_client/async_client — set the proxy on that client instead. The local EbarimtRestClient has no proxy argument (it talks to a POS device on your own network).


Clients at a glance

Client Authentication Available resources
EbarimtRestClient None (local network) receipt, info, send_data, bank_accounts
EbarimtApiClient OAuth2 password grant district_code, tin_info, merchant_info, product_tax_code

Both clients support synchronous and asynchronous context managers, share a common settings base (timeouts, TLS, headers, retry policy), and reuse the same error hierarchy.


Error handling

The SDK surfaces failures through a focused exception hierarchy, so you can react at the level of abstraction that matters to your code:

PosApiError
├── PosApiTransportError    # network / timeout / DNS / TLS
├── PosApiDecodeError       # response body was not valid JSON
├── PosApiHttpError         # non-2xx response from server
├── PosApiBusinessError     # 2xx, but domain-level failure in payload
└── PosApiValidationError   # Pydantic validation of request or response
from ebarimt_pos_sdk import (
    PosApiBusinessError,
    PosApiHttpError,
    PosApiTransportError,
    PosApiValidationError,
)

try:
    receipt = client.receipt.create(payload)
except PosApiValidationError as e:
    # Bad shape — fix the request before resending
    for err in e.errors:
        print(err["loc"], err["msg"])
except PosApiBusinessError as e:
    # Server accepted the request but rejected it on business grounds
    print(e.status, e.code, e.message)
except PosApiHttpError as e:
    # 4xx / 5xx — includes safe, redacted request/response context
    print(e)
except PosApiTransportError:
    # Network layer — safe to retry later
    raise

Authorization headers and token-bearing query parameters are redacted automatically in every error's string representation.


Configuration

All settings are immutable dataclasses — construct once and pass to the client. Timeouts, TLS verification, custom headers, and retry behaviour are shared across both clients via BaseSettings.

from ebarimt_pos_sdk import RestClientSettings
from ebarimt_pos_sdk.settings import RetrySettings

settings = RestClientSettings(
    base_url="http://localhost:1234",
    timeout_s=5.0,
    verify_tls=True,
    headers={"X-Request-Source": "pos-42"},
    retry=RetrySettings(
        max_retries=3,
        backoff_base_seconds=1.0,
        retryable_statuses=frozenset({500, 502, 503, 504}),
    ),
)

The default retry policy — 3 attempts with exponential backoff on 5xx and network errors — is suitable for most deployments.


Logging

The SDK logs through the standard library under the ebarimt_pos_sdk namespace and follows library-logging hygiene: a NullHandler is attached, so nothing is emitted until your application configures logging. The SDK never adds handlers or sets levels itself.

import logging

logging.basicConfig(level=logging.INFO)
logging.getLogger("ebarimt_pos_sdk").setLevel(logging.DEBUG)

What you get:

  • DEBUG — one line per request and response: → POST /rest/receipt [a1b2c3d4] / ← 200 in 42ms [a1b2c3d4].
  • WARNING — one line per retried attempt: retry 1/3 after 503, sleeping 1.00s [a1b2c3d4].
  • Failures are not logged — they raise a typed PosApiError. The same request_id is available as error.request_id, so an exception in production correlates with the log lines.

Each record also carries structured fields (request_id, http_method, http_status, duration_ms, attempt) via extra=, ready for JSON log pipelines.

Safety: records are metadata only — method, status, timing, ids. Headers and request/response bodies are never logged. URLs have sensitive query parameters (tokens, secrets) masked. Path segments are left intact; the only identifiers the SDK puts in a path are TINs, which are public.

To also see httpx's own output, configure its loggers directly — httpx at INFO emits an HTTP Request: line (note: the raw URL, unredacted), and httpcore at DEBUG emits connection/wire detail:

import logging.config

logging.config.dictConfig({
    "version": 1,
    "handlers": {"default": {"class": "logging.StreamHandler"}},
    "loggers": {
        "ebarimt_pos_sdk": {"handlers": ["default"], "level": "DEBUG"},
        "httpx": {"handlers": ["default"], "level": "INFO"},
        "httpcore": {"handlers": ["default"], "level": "WARNING"},
    },
})

Validation philosophy

The SDK validates structure, not policy:

  • ✅ Field shapes, regex for stable identifiers (TIN, branch codes), enums, and basic numeric constraints (>= 0).
  • ❌ Business rules, cross-field dependencies, reference-table lookups, or any government policy that changes out of band.

This boundary is intentional. The server owns business rules; the SDK owns shape correctness. That keeps the SDK stable when rules change.


Development

# Install all dependencies, including dev extras
uv sync --dev

# Run the unit test suite
uv run pytest -m "not integration"

# Run with coverage
uv run pytest -m "not integration" --cov

# Lint and format
uv run ruff check
uv run ruff format

# Type check
uv run ty check

Integration tests (marked @pytest.mark.integration) require a live PosAPI server and credentials; they are excluded from CI by default.

See CONTRIBUTING.md for contribution guidelines and CHANGELOG.md for release notes.


License

Released under the MIT License.

Metadata

Release files for ebarimt-pos-sdk 0.4.1

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

Source distribution (sdist)

Source distribution for ebarimt-pos-sdk 0.4.1
File Size Uploaded
ebarimt_pos_sdk-0.4.1.tar.gz 23.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ebarimt-pos-sdk 0.4.1
File Interpreter ABI Platform
ebarimt_pos_sdk-0.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 64.1 kB

Release files / ebarimt_pos_sdk-0.4.1.tar.gz

Download URL ebarimt_pos_sdk-0.4.1.tar.gz
Size 23.0 kB
Tags Source
SHA-256 checksum
How to use checksums
488ad84a4b5038a29ec64291ea928b07a74649668bc6cff33eb1b5b095adcd74
BLAKE2b-256 checksum
How to use checksums
471ebf6d3448a247f95d0d94a2b72fc71ae6383b38dc0b368f634ee573c50d28
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Release files / ebarimt_pos_sdk-0.4.1-py3-none-any.whl

Download URL ebarimt_pos_sdk-0.4.1-py3-none-any.whl
Size 41.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
54edaa22c2a152534f6497a2afaf887de7ea92c95b2e0098c816d28f0db0f8f0
BLAKE2b-256 checksum
How to use checksums
25e37e9694e2f5330923ce6e391aecbb1caf8de3ef439558c326d9dd6055ad59
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.1 This release

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.10

2 release files

0.2.9

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

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