Skip to main content

A typed, fully asynchronous client for the Monzo Developer API

Project description

aiomonzo

[!IMPORTANT] Personal-use, unofficial package. aiomonzo is an independent community project. It is not developed, maintained, supported, approved, or endorsed by Monzo Bank.

Monzo states that its Developer API is not suitable for public applications and may be used only with your own account or a small set of users you explicitly allow. This package is therefore intended for personal projects and small private integrations, not public customer-facing banking applications. See the Monzo Developer API introduction.

aiomonzo is an unofficial, fully asynchronous and typed Python client for the Monzo Developer API. It provides API, OAuth, retry, validation, and resource-lifecycle primitives while leaving credential storage, tenancy, user interfaces, and deployment policy to the application using it.

The package supports Python 3.12, 3.13, and 3.14.

Features

  • Fully asynchronous API and OAuth operations using httpx.
  • Strict, frozen Pydantic models that preserve new provider fields.
  • Static tokens, caller-owned OAuth token storage, and custom token providers.
  • Automatic refresh with rotated refresh-token replacement.
  • Optional distributed refresh locking for multi-process deployments.
  • Bounded timeouts and retries that respect mutation idempotency.
  • Typed, secret-safe exceptions that do not retain provider response bodies.
  • Explicit async resource ownership and a py.typed marker.

Installation

python -m pip install aiomonzo

With uv:

uv add aiomonzo

Documentation

Guide Contents
Getting started Installation, API Playground tokens, and first requests
OAuth setup Creating a Monzo client and completing authorization
Token storage Durable storage, refresh rotation, and multi-process locking
API reference Constructor options, methods, models, and pagination
Errors and retries Exception hierarchy, retry behavior, and recovery
Security Credential, state, transport, webhook, and logging guidance
Troubleshooting Common Monzo authorization and API failures
Development Local setup, validation, packaging, and release workflow

The same guides are published in the GitHub Wiki.

Static access token

A static token is useful for short-lived development and API Playground experiments. Generate one in the Monzo developer tools, approve access in the Monzo mobile app when prompted, and avoid hard-coding or committing it.

import asyncio
import os

from aiomonzo import MonzoClient


async def main() -> None:
    async with MonzoClient(
        access_token=os.environ["MONZO_ACCESS_TOKEN"],
    ) as monzo:
        identity = await monzo.who_am_i()
        accounts = await monzo.list_accounts()
        print(identity.user_id, [account.id for account in accounts])


asyncio.run(main())

For long-lived access, follow the OAuth setup guide.

OAuth with caller-owned storage

Applications using Monzo OAuth supply an OAuthClientConfig and a TokenStore. The store controls encryption, tenant isolation, persistence, and atomic token replacement. aiomonzo never creates durable token storage.

from aiomonzo import MonzoClient, OAuthClientConfig, OAuthToken, TokenStore


class ApplicationTokenStore(TokenStore):
    async def load(self) -> OAuthToken | None:
        ...  # Load and decrypt for the current application user.

    async def save(self, token: OAuthToken) -> None:
        ...  # Atomically replace access and rotated refresh tokens.

    async def clear(self) -> None:
        ...  # Remove the current user's token set.


oauth = OAuthClientConfig(
    client_id="monzo-client-id",
    client_secret=load_monzo_client_secret(),
    redirect_uri="https://app.example.com/oauth/monzo/callback",
)
client = MonzoClient(oauth=oauth, token_store=ApplicationTokenStore())

authorization = client.create_authorization_request()
# Store authorization.state in a user-bound, short-lived session before
# redirecting the browser to authorization.url.

At the callback, pass the code and both state values to exchange_authorization_code. State comparison is constant-time. When a token expires or Monzo rejects it, refresh is serialized within the provider and the complete replacement token set is passed to TokenStore.save.

Monzo can rotate refresh tokens. A durable save implementation must replace the complete token set atomically; saving only the access token can make the next refresh impossible.

Multi-process refresh coordination

By default, aiomonzo serializes refreshes within one process. If multiple processes or hosts share the same durable token record, pass a refresh_lock_factory backed by a distributed lock, such as a database advisory lock or Redis lock. The lock must be scoped to the same user and OAuth client as the token record and remain held while the token is reloaded, refreshed, and atomically replaced.

The factory returns an async context manager:

from contextlib import AbstractAsyncContextManager

from aiomonzo import MonzoClient


def refresh_lock_factory() -> AbstractAsyncContextManager[None]:
    return application_locks.monzo_oauth(user_id)


client = MonzoClient(
    oauth=oauth,
    token_store=ApplicationTokenStore(),
    refresh_lock_factory=refresh_lock_factory,
)

All processes sharing that token record must use the same lock key. A single-process application can omit this option.

Custom access-token providers

For multi-user applications, gateways, or private credential brokers, implement the narrow AccessTokenProvider protocol:

from aiomonzo import AccessTokenProvider, MonzoClient


class BrokerAccessTokenProvider(AccessTokenProvider):
    async def get_access_token(self) -> str:
        return await obtain_short_lived_token()

    async def refresh_after_rejection(self, rejected_access_token: str) -> str:
        return await replace_rejected_token(rejected_access_token)


client = MonzoClient(access_token_provider=BrokerAccessTokenProvider())

The provider owns storage and refresh policy. MonzoClient requests a token only when sending a Monzo API request and never exposes it through model data.

Supported operations

MonzoClient currently provides:

  • who_am_i
  • list_accounts
  • get_balance
  • list_pots
  • deposit_into_pot
  • withdraw_from_pot
  • get_transaction
  • list_transactions
  • annotate_transaction
  • register_webhook
  • list_webhooks
  • delete_webhook
  • create_authorization_request
  • exchange_authorization_code
  • refresh_access_token
  • logout

Amounts use Monzo's integer minor units. Transaction listing returns one bounded page and accepts since, before, limit, and merchant expansion controls; the caller owns pagination policy.

HTTP client ownership

By default, MonzoClient owns one pooled httpx.AsyncClient. Close it with an async context manager or await client.aclose(). If an application injects an existing httpx.AsyncClient, the application retains ownership and must close it.

Default connection limits, timeouts, redirects, and retries are bounded. Read-only requests can be retried for transient failures. Mutating requests are not replayed unless the operation has an idempotency contract.

Errors

Failures derive from MonzoClientError, with typed subclasses for configuration, request validation, authentication, permission, rate limiting, timeouts, transport, provider response decoding, provider response validation, and token-store failures.

Exceptions deliberately avoid access tokens, OAuth secrets, provider bodies, and credential-bearing URLs. Applications should still avoid logging arbitrary financial model data.

Development

uv sync --dev --frozen
make check
uv run pre-commit run --all-files

make check runs Ruff, strict mypy, the full test suite, distribution metadata checks, artifact-content checks, and a clean installation smoke test.

Security

Do not report vulnerabilities with real Monzo credentials or financial data. Use GitHub private vulnerability reporting when available.

Read the security guide before deploying OAuth token storage or using the client in a multi-process service.

Official Monzo resources

License

MIT. This project is not affiliated with or endorsed by Monzo Bank.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiomonzo-0.1.0.tar.gz (29.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aiomonzo-0.1.0-py3-none-any.whl (21.1 kB view details)

Uploaded Python 3

File details

Details for the file aiomonzo-0.1.0.tar.gz.

File metadata

  • Download URL: aiomonzo-0.1.0.tar.gz
  • Upload date:
  • Size: 29.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for aiomonzo-0.1.0.tar.gz
Algorithm Hash digest
SHA256 dfd18ad7858773d16d50e13be4e2363b997ddebd981e7132418c7316ebf2bd9e
MD5 7c8635a8706c41ae4b03ef23defefcde
BLAKE2b-256 b646f4dbb2ffecba7a10b4a896323b987d5e44151fb871832a6408d9042b8027

See more details on using hashes here.

File details

Details for the file aiomonzo-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: aiomonzo-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 21.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for aiomonzo-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f1320799d3f77b4470c243d555951c210cc972040ab7d48bfe98e71c149cfecd
MD5 3cb40a11f5f0e5150621e07f23d3b832
BLAKE2b-256 921ea5031d760db469bdf872cfe770884899c727e5e832a0e022fa6bd1178cbe

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page