Skip to main content

UQPAY Python SDK

License: MIT

Official Python SDK for the UQPAY API.

Requirements

  • Python 3.11+

Installation

pip install uqpay

Quick Start

from uqpay import UQPayClient

# Sandbox (for testing)
client = UQPayClient(client_id="your-client-id", api_key="your-api-key", environment="sandbox")

# Production
client = UQPayClient(client_id="your-client-id", api_key="your-api-key", environment="production")

Authentication

The SDK handles authentication automatically. It retrieves a UQPAY Access Token using your client_id and api_key, caches it, and retrieves a new Token before the current one expires. You do not need to manage Tokens manually.

Configuration

client = UQPayClient(
    client_id="your-client-id",
    api_key="your-api-key",
    environment="sandbox",       # "sandbox" (default) or "production"
    timeout=30.0,                # request timeout in seconds
    max_retries=2,               # automatic retries on transient errors
    log_level="none",            # "none" | "error" | "warn" | "info" | "debug"
    redact_fields=["card_number", "cvc"],
)

Per-request options:

result = client.banking.payouts.create(
    {...},
    request_options={
        "idempotency_key": "unique-key",
        "on_behalf_of": "sub-account-id",
        "timeout": 60,
        "max_retries": 0,
    },
)

Environment Variables

Store credentials in your environment and pass them explicitly when you create the client. The SDK does not load environment variables or .env files automatically.

UQPAY_CLIENT_ID=your-client-id
UQPAY_API_KEY=your-api-key
import os

client = UQPayClient(
    client_id=os.environ["UQPAY_CLIENT_ID"],
    api_key=os.environ["UQPAY_API_KEY"],
    environment="sandbox",
)

API Coverage

Banking API

Resource Operations
Balances List, Retrieve, ListTransactions
Transfers Create, List, Retrieve
Deposits List, Retrieve
Beneficiaries Create, List, Retrieve, Update, Delete, Check
Payouts Create, List, Retrieve
Virtual Accounts Create, List
Conversions CreateQuote, Create, List, Retrieve, ListDates, ListCurrentRates
Payment Methods List

Issuing API

Resource Operations
Cardholders Create, List, Retrieve, Update
Cards Create, List, Retrieve, Update, UpdateStatus, Recharge, Withdraw, RetrieveOrder, CreatePanToken, GetSecureIframeUrl
Transactions List, Retrieve
Products List
Balances List, Retrieve, ListTransactions
Transfers Create, Retrieve
Reports Create, Download
Auth Decision PGP-based real-time authorization

Connect API

Resource Operations
Accounts Create, List, Retrieve
Sub-Accounts Create
Additional Docs Get

Payment API

Resource Operations
Payment Intents Create, Confirm, Capture, Cancel, List, Retrieve, Update
Refunds Create, List, Retrieve
Bank Accounts Create, List, Retrieve, Update
Payouts Create, List, Retrieve
Balances List, Retrieve
Attempts List, Retrieve
Settlements List

Supporting API

Resource Operations
Files Upload, DownloadLinks

Simulator (sandbox only)

Resource Operations
Issuing Authorize, Reverse
Deposits Simulate

Resources

Banking

# Balances
balances = client.banking.balances.list({"page_number": 1, "page_size": 20})
balance = client.banking.balances.retrieve("SGD")
txns = client.banking.balances.list_transactions({"page_number": 1, "page_size": 20})

# Transfers
transfer = client.banking.transfers.create({
    "source_account_id": "acc-123",
    "target_account_id": "acc-456",
    "currency": "SGD",
    "amount": "100.00",
    "reason": "Fund sub-account",
})
transfers = client.banking.transfers.list({"page_number": 1, "page_size": 20})
transfer = client.banking.transfers.retrieve("transfer-id")

# Deposits
deposits = client.banking.deposits.list({"page_number": 1, "page_size": 20})
deposit = client.banking.deposits.retrieve("deposit-id")

# Beneficiaries
beneficiary = client.banking.beneficiaries.create({...})
beneficiaries = client.banking.beneficiaries.list({"page_number": 1, "page_size": 20})
beneficiary = client.banking.beneficiaries.retrieve("ben-id")
client.banking.beneficiaries.update("ben-id", {})
client.banking.beneficiaries.delete("ben-id")
client.banking.beneficiaries.check({...})

# Payouts
payout = client.banking.payouts.create({
    "beneficiary_id": "ben-123",
    "currency": "SGD",
    "amount": "50.00",
    "purpose_code": "PERSONAL",
})
payouts = client.banking.payouts.list({"page_number": 1, "page_size": 20})
payout = client.banking.payouts.retrieve("payout-id")

# Virtual Accounts
# Create returns an accepted application (HTTP 200), not ready bank details.
application = client.banking.virtual_accounts.create(
    {
        "country": "BH",
        "currency": "USD",
        "payment_method": "SWIFT",  # omit/None/"" to evaluate LOCAL and SWIFT
        "nickname": "USD collections",
    },
    {
        "idempotency_key": "merchant-va-application-42",
        # "on_behalf_of": "connected-account-id",
    },
)

# Applications and issued Virtual Accounts are separate collections.
applications = client.banking.virtual_account_applications.list({
    "page_number": 1,
    "page_size": 20,
    "status": "SUBMITTED",
    "country": "BH",
    "currency": "USD",
})
application = client.banking.virtual_account_applications.retrieve(
    application["data"]["application_id"]
)
vas = client.banking.virtual_accounts.list({"page_number": 1, "page_size": 20})

# Conversions
quote = client.banking.conversions.create_quote({
    "buy_currency": "SGD",
    "sell_currency": "USD",
    "buy_amount": "100.00",
    "conversion_date": "2026-04-15",
})
conversion = client.banking.conversions.create({...})
rates = client.banking.conversions.list_current_rates()
dates = client.banking.conversions.list_dates({"currency_from": "USD", "currency_to": "SGD"})

# Payment Methods
methods = client.banking.payment_methods.list({"country": "US", "currency": "USD"})

Issuing

Cardholders

cardholder = client.issuing.cardholders.create({
    "first_name": "Jane",
    "last_name": "Doe",
    "email": "jane@example.com",
    "phone_number": "+6591234567",
    "country_code": "SG",
    "date_of_birth": "1990-01-15",
})
cardholders = client.issuing.cardholders.list({"page_number": 1, "page_size": 20})
cardholder = client.issuing.cardholders.retrieve("ch-id")
client.issuing.cardholders.update("ch-id", {"first_name": "Updated"})

Cards

card = client.issuing.cards.create({
    "cardholder_id": "ch-id",
    "product_id": "prod-id",
    "card_type": "VIRTUAL",
    "currency": "SGD",
})
cards = client.issuing.cards.list({"page_number": 1, "page_size": 20})
card = client.issuing.cards.retrieve("card-id")
client.issuing.cards.update("card-id", {"metadata": {"order_id": "ord-001"}})
client.issuing.cards.update_status("card-id", {"status": "FROZEN"})

# Recharge and withdraw
client.issuing.cards.recharge("card-id", {"amount": "100.00", "currency": "SGD"})
client.issuing.cards.withdraw("card-id", {"amount": "50.00", "currency": "SGD"})

# Secure card info
result = client.issuing.cards.create_pan_token("card-id")
result = client.issuing.cards.get_secure_iframe_url("card-id")

# Card orders
order = client.issuing.cards.retrieve_order("card-order-id")

Transactions, Products, Balances, Transfers

# Transactions
txns = client.issuing.transactions.list({"page_number": 1, "page_size": 20})
txn = client.issuing.transactions.retrieve("txn-id")

# Products
products = client.issuing.products.list({"page_number": 1, "page_size": 20})

# Issuing balances
balances = client.issuing.balances.list({"page_number": 1, "page_size": 20})
balance = client.issuing.balances.retrieve("SGD")
client.issuing.balances.list_transactions({"page_number": 1, "page_size": 20})

# Issuing transfers
transfer = client.issuing.transfers.create({
    "source_account_id": "master-acc-id",
    "destination_account_id": "sub-acc-id",
    "currency": "SGD",
    "amount": 100,
})
transfer = client.issuing.transfers.retrieve("transfer-id")

# Reports
report = client.issuing.reports.create({
    "report_type": "SETTLEMENT",
    "start_time": "2026-01-01T00:00:00Z",
    "end_time": "2026-04-10T23:59:59Z",
})
csv_data = client.issuing.reports.download(report["report_id"])

Connect

# Accounts
accounts = client.connect.accounts.list({"page_number": 1, "page_size": 20})
account = client.connect.accounts.retrieve("acc-id")
account = client.connect.accounts.create({...})

# Sub-accounts
sub_account = client.connect.sub_accounts.create({...})

# Additional documents
docs = client.connect.additional_docs.get()

Payment

# Payment Intents
intent = client.payment.payment_intents.create({
    "amount": "100.00",
    "currency": "SGD",
    "merchant_order_id": "order-001",
    "return_url": "https://example.com/return",
})
client.payment.payment_intents.confirm(intent["payment_intent_id"], {"payment_method": {...}})
client.payment.payment_intents.capture("pi-id")
client.payment.payment_intents.cancel("pi-id", {"cancellation_reason": "requested_by_customer"})
intents = client.payment.payment_intents.list({"page_number": 1, "page_size": 20})

# Refunds
refund = client.payment.refunds.create({
    "payment_intent_id": "pi-id",
    "amount": "50.00",
    "reason": "requested_by_customer",
})
refunds = client.payment.refunds.list({"page_number": 1, "page_size": 20})
refund = client.payment.refunds.retrieve("refund-id")

# Bank Accounts
bank_account = client.payment.bank_accounts.create({...})
bank_accounts = client.payment.bank_accounts.list({"page_number": 1, "page_size": 20})

# Payouts
payout = client.payment.payouts.create({...})
payouts = client.payment.payouts.list({"page_number": 1, "page_size": 20})

# Balances and Attempts
balances = client.payment.balances.list({"page_number": 1, "page_size": 20})
balance = client.payment.balances.retrieve("SGD")
attempts = client.payment.attempts.list({"page_number": 1, "page_size": 20})
attempt = client.payment.attempts.retrieve("attempt-id")

# Settlements
settlements = client.payment.settlements.list({"page_number": 1, "page_size": 20})

Supporting (File Upload / Download)

with open("document.pdf", "rb") as f:
    result = client.supporting.files.upload(
        file_data=f.read(),
        filename="document.pdf",
        mime_type="application/pdf",
    )

links = client.supporting.files.download_links([result["file_id"]])

Simulator (sandbox only)

The simulator is only available in the sandbox environment and raises SimulatorNotAvailableError in production.

# Simulate a card authorization
result = client.simulator.issuing.authorize({
    "card_id": "card-id",
    "amount": 25.0,
    "currency": "SGD",
    "transaction_status": "APPROVED",
})

# Simulate a reversal
client.simulator.issuing.reverse({
    "card_id": "card-id",
    "transaction_id": result["transaction_id"],
})

# Simulate a deposit
client.simulator.deposits.simulate({
    "currency": "SGD",
    "amount": 500.0,
})

Pagination

All list methods accept page_number and page_size:

page = 1
while True:
    result = client.issuing.cards.list({"page_number": page, "page_size": 50})
    items = result.get("items") or result.get("list") or []
    if not items:
        break
    for card in items:
        print(card["card_id"])
    page += 1

Webhooks

client = UQPayClient(
    client_id="your-client-id",
    api_key="your-api-key",
    webhook_secret="your-webhook-secret",
)

# In your HTTP handler:
from uqpay import UQPayWebhookError

try:
    event = client.webhooks.construct_event(
        raw_body=request.get_data(),
        headers=dict(request.headers),
    )
    print(event["event_type"], event["data"])
except UQPayWebhookError as e:
    return str(e), 400

For virtual.account.create, virtual.account.update, and virtual.account.closed, data.account_id and data.direct_id identify the owning account context. account_id is the UUID of the account that owns the application. direct_id is a string: it is "0" for a main account, or the connected account's main account ID. source_id equals data.application_id. The same required account_id and direct_id fields are typed on Create and Retrieve application details and on every List application summary. Deduplicate retries by event_id, then apply the complete data object only when its public_version is greater than the version stored for that application. The application event shape is exported as VirtualAccountApplicationWebhookEvent and is used by webhook versions V1.5.1, V1.5.2, and V1.6.0.

Use is_virtual_account_application_event(event) to narrow the generic verified dictionary before typed access. Unknown older Virtual Account event versions stay available as generic dictionaries and are not reclassified as application data.

Synchronous Create failures raise UQPayError and create no application. Once a request is accepted, method-level asynchronous failures appear in data.results[].error. Every issued bank detail always includes close_reason; use status == "CLOSED" rather than requiring a non-empty reason.

The verifier checks HMAC-SHA512(secret, raw_body + timestamp) and rejects requests with a timestamp outside the default 300-second replay window.

Authorization Decision (PGP)

Handle real-time card authorization decisions. UQPAY sends PGP-encrypted transactions to your endpoint; the SDK decrypts them, calls your handler, and returns an encrypted response.

from uqpay.crypto import generate_auth_decision_key_pair

# Generate a key pair once and store securely
keys = generate_auth_decision_key_pair(name="MyApp", email="ops@example.com")

# Configure at startup
client.issuing.auth_decision.configure(
    private_key=keys["private_key"],
    uqpay_public_key="<UQPAY public key from dashboard>",
)

# Define your decision function
def decide(transaction: dict) -> dict:
    if transaction.get("amount", 0) > 10000:
        return {"response_code": "05"}  # Decline
    return {"response_code": "00", "partner_reference_id": "ref-001"}

# In your HTTP handler:
encrypted_response = client.issuing.auth_decision.handle(
    body=request.get_data(),
    headers=dict(request.headers),
    decide=decide,
)

Error Handling

from uqpay import (
    UQPayError,
    AuthenticationError,
    ForbiddenError,
    NotFoundError,
    ValidationError,
    RateLimitError,
    ServerError,
)

try:
    balance = client.banking.balances.retrieve("SGD")
except AuthenticationError as e:
    print(f"Authentication failed: {e.message}")
except ForbiddenError as e:
    print(f"Access denied: {e.message}")
except NotFoundError as e:
    print(f"Not found: {e.message}")
except ValidationError as e:
    print(f"Validation error [{e.code}]: {e.message}")
except RateLimitError as e:
    print(f"Rate limited (HTTP {e.http_status})")
except ServerError as e:
    print(f"Server error: {e.message}")
except UQPayError as e:
    print(f"{e.type}: {e.message} (HTTP {e.http_status})")

All errors expose: message, code, type, http_status, idempotency_key.

Testing

pip install -e ".[dev]"
cp .env.example .env  # fill in sandbox credentials
pytest tests/ -v

# Skip integration tests
SKIP_INTEGRATION_TESTS=true pytest tests/ -v

Documentation

License

This project is licensed under the MIT License — see the LICENSE file for details.

Download files

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

Source Distribution

uqpay-2.0.0.tar.gz (47.9 kB view details)

Uploaded Source

Built Distribution

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

uqpay-2.0.0-py3-none-any.whl (69.4 kB view details)

Uploaded Python 3

File details

Details for the file uqpay-2.0.0.tar.gz.

File metadata

  • Download URL: uqpay-2.0.0.tar.gz
  • Upload date:
  • Size: 47.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for uqpay-2.0.0.tar.gz
Algorithm Hash digest
SHA256 64964b17f845ab4f3d4ad53e28b60bd9a8b4056d0529117a46b249e60be57a93
MD5 3049aa1953af5134536c064fd7fce301
BLAKE2b-256 cc9825300bcec1f6bf584048de3ce1329ee796ee203deaba5d6b14fd6b16d723

See more details on using hashes here.

File details

Details for the file uqpay-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: uqpay-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 69.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for uqpay-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 15355410a883318713a516a401733a33b58eaa60ba04bf48257b95e2b2a354dd
MD5 a5c02faee8c27dab956dca0fc8bfa4ad
BLAKE2b-256 99a1fc8a5379e311d0cae66e89933778a6d2a59bb42a98d38dfc3915aff4ba3d

See more details on using hashes here.

Release history Release notifications | RSS feed

2.1.0

2 files

This release

2.0.0 This release

2 files

1.2.0

2 files

0.2.0

2 files

0.1.0

2 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