Portals API Python client
A typed synchronous and asynchronous Python client for the Portals Partners API, the partner-facing programmatic API of the Portals marketplace.
Features
- All 35 operations from the Portals Partners OpenAPI contract
- Matching
PortalsClientandAsyncPortalsClientAPIs - Typed Pydantic v2 models for every schema in the contract (strict requests, forward-compatible responses)
- httpx2 connection pooling and injectable HTTP clients
- Exact
Decimalamounts and parseddatetimetimestamps - Typed API errors and rate-limit metadata
- Python 3.11+ and a
py.typedmarker
Installation
pip install portals-api
Or with uv:
uv add portals-api
From a local checkout:
pip install .
The distribution is named portals-api; the import package is portals.
Quickstart
Synchronous
from portals import PortalsClient
with PortalsClient("your-api-key") as client:
config = client.market.config()
wallet = client.wallet.get()
results = client.nfts.search(max_price="20", limit=20)
print(config.deposit_wallet)
print(wallet.balance)
print(results.total_count)
Asynchronous
import asyncio
from portals import AsyncPortalsClient
async def main() -> None:
async with AsyncPortalsClient("your-api-key") as client:
wallet = await client.wallet.get()
state = await client.arena.state()
print(wallet.balance)
print(state.server_time)
asyncio.run(main())
Get an API key from the Portals mini app under Profile → Partner API. Keep the key on your server and never commit it to source control.
Resources
| Resource | Operations |
|---|---|
client.arena |
Arena state, inventory, and bets |
client.offers |
Create, update, cancel, inspect, and accept collection offers |
client.collections |
Collection previews, metrics, and attribute floor prices |
client.market |
Market configuration and activity |
client.nfts |
Search, owned NFTs, purchases, listings, transfers, and withdrawals |
client.users |
User existence and authenticated user activity |
client.wallet |
Balance, deposits, TON withdrawals, and withdrawal statuses |
Methods return typed models. For example:
from decimal import Decimal
from portals import BuyDetail, PortalsClient
with PortalsClient("your-api-key") as client:
purchase = client.nfts.buy(
[
BuyDetail(id="nft-id", price=Decimal("12.50")),
]
)
print(purchase.total_purchased)
print(purchase.total_failed)
print(purchase.total_spent)
Purchases may partially succeed. Inspect each item in purchase.purchase_results rather than assuming the whole request succeeded or failed.
Amounts and timestamps
Pass amounts as decimal strings or Decimal instances. Floats are rejected to avoid binary floating-point errors. Amounts are serialized as strings on the wire and exposed as Decimal values by response models.
RFC 3339 timestamps are exposed as datetime instances. Arena timestamps remain integers because the API returns Unix milliseconds.
Errors
from portals import APIError, PortalsClient, RateLimitError
with PortalsClient("your-api-key") as client:
try:
client.market.actions()
except RateLimitError as exc:
if exc.rate_limit is not None:
print(exc.rate_limit.reset_seconds)
except APIError as exc:
print(exc.status_code, exc.code, exc.message)
Specific exceptions cover authentication, access denial, rate limits, an unavailable market feed, and unknown Arena bet outcomes. Transport failures, invalid JSON, and response-validation failures have separate exception types.
Write operations are not automatically retried. In particular, an Arena bet that returns 504 BET_OUTCOME_UNKNOWN or fails during network transmission may already have been placed; check client.arena.state() before trying again.
Custom HTTP clients
Pass an httpx2.Client or httpx2.AsyncClient to customize transports, proxies, or test behavior. The library closes only clients it creates itself.
import httpx2
from portals import PortalsClient
http_client = httpx2.Client(timeout=10)
client = PortalsClient("your-api-key", client=http_client)
client.close()
http_client.close()
Low-level requests
Use request() for new endpoints that do not yet have a typed method:
payload = client.request("GET", "/partners/example", params={"limit": 10})
Pass response_model=YourModel to validate the response with Pydantic.
Development
Install the project and development tools with uv:
uv sync
Run the checks used by CI:
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run pytest
uv build
The OpenAPI contract is vendored at specs/partners.en.json. Refresh it and regenerate the
models, then review the resulting changes:
uv run python scripts/update_spec.py
uv run python scripts/generate_models.py
uv run ruff check --fix src/portals/models.py
uv run ruff format src/portals/models.py
License
MIT
Metadata
Release files for portals-api 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| portals_api-0.1.1.tar.gz | 28.4 kB | With provenance | Details for portals_api-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| portals_api-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for portals_api-0.1.1-py3-none-any.whl |
Total release size: 53.8 kB
Release files / portals_api-0.1.1.tar.gz
| Download URL | portals_api-0.1.1.tar.gz |
|---|---|
| Size | 28.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4be47e7742d024fab1db4a5f69ebed7044dd088dbcace6ec9a4318ef4c7ad221
|
|
BLAKE2b-256 checksum How to use checksums |
7b504a9a2f20f33407df9f497700019c07b6a3d1f3fd4a08f9fca67f7f80e44b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 31, 2026.
Transparency logRelease files / portals_api-0.1.1-py3-none-any.whl
| Download URL | portals_api-0.1.1-py3-none-any.whl |
|---|---|
| Size | 25.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0a29253fd9ab2f0560c2b871002b6dd116b69404b3805ef900fa0b4c70f24b45
|
|
BLAKE2b-256 checksum How to use checksums |
f0121a8599a18b6da773a4b5de237b1de081b2d1a6d51464c934f96225e7c1b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 31, 2026.
Transparency logSecurity for portals-api 0.1.1
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for portals_api-0.1.1.tar.gz
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 Oct 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.