Python SDK for the Hotmart API
Project description
hotmart-python — Python SDK for the Hotmart API
hotmart-python is a typed Python SDK for the Hotmart API. Hotmart is a Brazilian digital products platform for selling courses, ebooks, subscriptions, and memberships — supporting payments via PIX, boleto, credit/debit card, and PayPal.
This SDK handles OAuth, token refresh, retries, rate limits, and pagination automatically. You write business logic; the SDK handles the API.
Documentação em Português disponível em README-ptBR.md.
v1.0 — complete rewrite. After two years without updates, the library was redesigned from scratch: resource-based API,
httpx, Pydantic v2, automatic retries, strict typing, and much more. This is a strong breaking change from v0.x — see the migration guide to update your code. If you're starting fresh, you can ignore this notice.
Features
- Fully typed responses — every API response is a Pydantic v2 model. Your IDE completes field names; no raw dicts, no guessing.
- Autopaginate iterators — every paginated endpoint ships a
*_autopaginatevariant that transparently walks all pages. Oneforloop, all records. - Automatic token management — OAuth token is acquired, cached, and proactively refreshed 5 minutes before expiry. Thread-safe with double-checked locking.
- Retry with exponential backoff — transient errors (5xx, 429) are retried automatically with jitter and
RateLimit-Resetawareness. Configurable viamax_retries. - Proactive rate limit tracking — monitors remaining requests per window and backs off before hitting the limit.
- Clean exception hierarchy — catch only what you care about:
AuthenticationError,RateLimitError,NotFoundError,BadRequestError, and more. - httpx under the hood — persistent connection pool, configurable timeouts, context manager support.
- Forward-compatible kwargs — extra
**kwargsare passed directly as query params, so you can use undocumented or newly added Hotmart parameters without waiting for an SDK update.
Design Principles
- One object, all resources. Instantiate
Hotmartonce and access every resource group as an attribute:client.sales,client.subscriptions,client.products, etc. - Fail loudly. Errors are typed exceptions, never silently swallowed or buried in return values.
- No boilerplate. Authentication, pagination, retries, and connection management are invisible by default. Opt in to configuration only when you need it.
- Strict typing.
mypy --strictpasses. All public APIs are fully annotated. Models useextra="allow"so new API fields don't break your code.
Table of Contents
- Installation
- Quick Start
- Authentication
- Resources
- Pagination
- Sandbox Mode
- Error Handling
- Logging
- Context Manager
- Extra Parameters (kwargs)
- Documentation
- Contributing
- License
Installation
pip install hotmart-python
Or with uv:
uv add hotmart-python
Requirements: Python 3.11+
Quick Start
from hotmart import Hotmart
client = Hotmart(
client_id="your_client_id",
client_secret="your_client_secret",
basic="Basic your_base64_credentials",
)
# Single page
page = client.sales.history(buyer_name="Paula")
for sale in page.items:
print(sale.purchase.transaction, sale.buyer.email)
# All pages — one iterator, no manual pagination
for sale in client.sales.history_autopaginate(transaction_status="APPROVED"):
print(sale.purchase.transaction)
Authentication
Hotmart uses OAuth 2.0 Client Credentials. The SDK handles token acquisition and refresh automatically — you only need to supply three values at startup.
Where to find your credentials
- Log in to Hotmart.
- Go to Tools → Developer Tools → Credentials.
- Generate a new credential set. You will receive:
client_id— your application client IDclient_secret— your application client secretbasic— the Base64-encodedclient_id:client_secretstring prefixed withBasic(Hotmart shows this value directly in the dashboard)
from hotmart import Hotmart
client = Hotmart(
client_id="abcdef12-1234-5678-abcd-abcdef123456",
client_secret="your_secret_here",
basic="Basic YWJjZGVmMTItMTIzNC01Njc4LWFiY2QtYWJjZGVmMTIzNDU2OnlvdXJfc2VjcmV0X2hlcmU=",
)
Tokens are valid for 24 hours. The SDK caches the token and proactively refreshes it 5 minutes before expiry using double-checked locking, so concurrent requests never race on token renewal.
Resources
Sales
# Single page
page = client.sales.history(buyer_name="Paula", transaction_status="APPROVED")
page = client.sales.summary(start_date=1700000000000, end_date=1710000000000)
page = client.sales.participants(buyer_email="paula@example.com")
page = client.sales.commissions(commission_as="PRODUCER")
page = client.sales.price_details(product_id=1234567)
# Refund a transaction
client.sales.refund("HP17715690036014")
# Autopaginate — iterates all pages automatically
for sale in client.sales.history_autopaginate(buyer_name="Paula"):
print(sale.purchase.transaction)
| Method | Description |
|---|---|
history(**kwargs) |
List all sales with detailed information |
history_autopaginate(**kwargs) |
Iterator over all pages |
summary(**kwargs) |
Total commission values per currency |
summary_autopaginate(**kwargs) |
Iterator over all pages |
participants(**kwargs) |
Sales user/participant data |
participants_autopaginate(**kwargs) |
Iterator over all pages |
commissions(**kwargs) |
Commission breakdown per sale |
commissions_autopaginate(**kwargs) |
Iterator over all pages |
price_details(**kwargs) |
Price and fee details per sale |
price_details_autopaginate(**kwargs) |
Iterator over all pages |
refund(transaction_code) |
Request a refund for a transaction |
Subscriptions
# List subscribers
page = client.subscriptions.list(status="ACTIVE", product_id=1234567)
# Summary
page = client.subscriptions.summary()
# Purchases and transactions for a single subscriber
purchases = client.subscriptions.purchases("SUB-ABC123")
transactions = client.subscriptions.transactions("SUB-ABC123")
# Cancel one or more subscriptions
result = client.subscriptions.cancel(["SUB-ABC123", "SUB-DEF456"], send_mail=True)
# Reactivate subscriptions (bulk)
result = client.subscriptions.reactivate(["SUB-ABC123"], charge=False)
# Reactivate a single subscription
result = client.subscriptions.reactivate_single("SUB-ABC123", charge=True)
# Change billing due day
client.subscriptions.change_due_day("SUB-ABC123", due_day=15)
# Autopaginate
for sub in client.subscriptions.list_autopaginate(status="ACTIVE"):
print(sub.subscriber_code)
| Method | Description |
|---|---|
list(**kwargs) |
List subscriptions with filters |
list_autopaginate(**kwargs) |
Iterator over all pages |
summary(**kwargs) |
Subscription summary |
summary_autopaginate(**kwargs) |
Iterator over all pages |
purchases(subscriber_code) |
Purchase history for a subscriber |
transactions(subscriber_code) |
Transactions for a subscriber |
cancel(subscriber_code, send_mail) |
Cancel one or more subscriptions |
reactivate(subscriber_code, charge) |
Reactivate subscriptions (bulk) |
reactivate_single(subscriber_code, charge) |
Reactivate a single subscription |
change_due_day(subscriber_code, due_day) |
Change the billing due day |
Products
# List products
page = client.products.list(status="ACTIVE")
# Offers for a product
page = client.products.offers("product-ucode-here")
# Plans for a product
page = client.products.plans("product-ucode-here")
# Autopaginate
for product in client.products.list_autopaginate():
print(product.name)
| Method | Description |
|---|---|
list(**kwargs) |
List all products |
list_autopaginate(**kwargs) |
Iterator over all pages |
offers(ucode, **kwargs) |
Offers for a product |
offers_autopaginate(ucode, **kwargs) |
Iterator over all pages |
plans(ucode, **kwargs) |
Plans for a product |
plans_autopaginate(ucode, **kwargs) |
Iterator over all pages |
Coupons
# Create a coupon (10% off) for product 1234567
client.coupons.create("1234567", "SUMMER10", discount=10.0)
# List coupons for a product
page = client.coupons.list("1234567")
# Delete a coupon by ID
client.coupons.delete("coupon-id-here")
# Autopaginate
for coupon in client.coupons.list_autopaginate("1234567"):
print(coupon.code)
| Method | Description |
|---|---|
create(product_id, coupon_code, discount) |
Create a discount coupon |
list(product_id, **kwargs) |
List coupons for a product |
list_autopaginate(product_id, **kwargs) |
Iterator over all pages |
delete(coupon_id) |
Delete a coupon |
Club (Members Area)
The Club resource requires a subdomain argument — the subdomain of your Members Area.
# Modules in the members area
modules = client.club.modules("my-course-subdomain")
# Pages within a module
pages = client.club.pages("my-course-subdomain", module_id="module-uuid")
# Students enrolled
students = client.club.students("my-course-subdomain")
# Student progress
progress = client.club.student_progress(
"my-course-subdomain",
student_email="student@example.com",
)
| Method | Description |
|---|---|
modules(subdomain, **kwargs) |
List modules in the members area |
pages(subdomain, module_id, **kwargs) |
List pages in a module |
students(subdomain, **kwargs) |
List enrolled students |
student_progress(subdomain, **kwargs) |
Student progress data |
Events
# Get event details
event = client.events.get("event-id-here")
# List tickets for a product
page = client.events.tickets(product_id=1234567)
# Autopaginate
for ticket in client.events.tickets_autopaginate(product_id=1234567):
print(ticket.name)
| Method | Description |
|---|---|
get(event_id) |
Get event details |
tickets(product_id, **kwargs) |
List tickets for a product |
tickets_autopaginate(product_id, **kwargs) |
Iterator over all pages |
Negotiation
# Create an installment negotiation for a subscriber
result = client.negotiation.create("SUB-ABC123")
| Method | Description |
|---|---|
create(subscriber_code) |
Create an installment negotiation |
Pagination
The Hotmart API uses cursor-based pagination. Each paginated response contains a page_info object with next_page_token.
Single-page call
Returns a PaginatedResponse[T] with .items and .page_info:
page = client.sales.history(max_results=50)
print(f"Got {len(page.items)} items")
print(f"Next token: {page.page_info.next_page_token}")
# Manually fetch next page
next_page = client.sales.history(page_token=page.page_info.next_page_token)
Autopaginate (recommended)
Every paginated method has a matching *_autopaginate variant that handles all page-fetching transparently:
for sale in client.sales.history_autopaginate(buyer_name="Paula"):
print(sale.purchase.transaction)
The iterator stops when there are no more pages — no token management, no loop conditions.
Sandbox Mode
Use sandbox=True to point all requests at Hotmart's sandbox environment. Sandbox and production credentials are not interchangeable — generate sandbox credentials in the Hotmart dashboard under the same Developer Credentials section, selecting "Sandbox" as the environment.
from hotmart import Hotmart
client = Hotmart(
client_id="your_sandbox_client_id",
client_secret="your_sandbox_client_secret",
basic="Basic your_sandbox_base64_credentials",
sandbox=True,
)
Note: Some endpoints behave differently or are not fully supported in the sandbox. See SANDBOX-GUIDE.md and HOTMART-API-BUGS.md for known issues.
Error Handling
All SDK errors inherit from HotmartError. Import and catch only the exceptions you need:
from hotmart import (
Hotmart,
HotmartError,
AuthenticationError,
RateLimitError,
NotFoundError,
BadRequestError,
InternalServerError,
)
try:
page = client.sales.history()
except AuthenticationError:
print("Check your credentials.")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds.")
except NotFoundError:
print("Resource not found.")
except HotmartError as e:
print(f"API error: {e}")
Exception hierarchy:
| Exception | HTTP status | Meaning |
|---|---|---|
AuthenticationError |
401, 403 | Invalid or missing credentials |
BadRequestError |
400 | Invalid parameters |
NotFoundError |
404 | Resource not found |
RateLimitError |
429 | Rate limit exceeded (500 req/min) |
InternalServerError |
500, 502, 503 | Hotmart server error |
APIStatusError |
other | Unexpected HTTP status |
HotmartError |
— | Base class for all SDK errors |
The SDK retries automatically on transient errors (5xx, 429) with exponential backoff (0.5 × 2^attempt + jitter, cap 30s). Configure via max_retries:
client = Hotmart(..., max_retries=5)
Logging
Logging is disabled by default. Enable it by passing log_level at construction time:
import logging
from hotmart import Hotmart
client = Hotmart(
client_id="...",
client_secret="...",
basic="Basic ...",
log_level=logging.INFO,
)
| Level | What is logged |
|---|---|
logging.DEBUG |
Request URLs, parameters — contains sensitive data, avoid in production |
logging.INFO |
High-level operation summaries |
logging.WARNING |
Warnings and unexpected conditions |
logging.ERROR |
Errors during API interactions |
logging.CRITICAL |
Critical failures |
Tokens and credentials are masked in all log output.
Context Manager
Hotmart supports the context manager protocol for automatic cleanup of the underlying HTTP connection pool:
from hotmart import Hotmart
with Hotmart(
client_id="...",
client_secret="...",
basic="Basic ...",
) as client:
for sale in client.sales.history_autopaginate():
print(sale.purchase.transaction)
Extra Parameters (kwargs)
All resource methods accept **kwargs and forward them directly to the API as query parameters. This lets you use undocumented or recently added Hotmart parameters without waiting for an SDK update:
# Pass any query parameter Hotmart supports, even if not in the method signature
page = client.sales.history(some_new_param="value")
Documentation
| Document | Description |
|---|---|
| README-ptBR.md | Esta documentação em Português |
| MIGRATION.md | Upgrading from v0.x to v1.0 — breaking changes and method mapping |
| CHANGELOG.md | Full version history |
| CONTRIBUTING.md | Development setup, code style, how to add endpoints |
| SANDBOX-GUIDE.md | Sandbox environment usage and known limitations |
| HOTMART-API-BUGS.md | Known Hotmart API bugs found during integration testing |
| HOTMART-API-REFERENCE.md | Complete API reference (agent/LLM-friendly — official docs are a JS SPA) |
Contributing
Contributions are welcome. See CONTRIBUTING.md for setup, coding style, how to add a new endpoint, and the PR checklist.
License
Apache License 2.0 — see LICENSE.txt for details.
This package is not affiliated with or officially supported by Hotmart.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hotmart_python-1.0.3.tar.gz.
File metadata
- Download URL: hotmart_python-1.0.3.tar.gz
- Upload date:
- Size: 100.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85220470ef152c3dd7ea218f71a02f79ea82cbd03c5876d5db872ee3d7327bb6
|
|
| MD5 |
16261e6dcc8e9eb1d1b1b0909a168cb8
|
|
| BLAKE2b-256 |
9efffe9dada5b9fa19646d48a8875adcf7bb6020249ffaf2a18996bc65e8f88e
|
Provenance
The following attestation bundles were made for hotmart_python-1.0.3.tar.gz:
Publisher:
publish.yml on im-voracity/hotmart-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hotmart_python-1.0.3.tar.gz -
Subject digest:
85220470ef152c3dd7ea218f71a02f79ea82cbd03c5876d5db872ee3d7327bb6 - Sigstore transparency entry: 1186449249
- Sigstore integration time:
-
Permalink:
im-voracity/hotmart-python@253ca76567854d55a383bb9bae4f3d33324fa230 -
Branch / Tag:
refs/tags/v1.0.3 - Owner: https://github.com/im-voracity
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@253ca76567854d55a383bb9bae4f3d33324fa230 -
Trigger Event:
push
-
Statement type:
File details
Details for the file hotmart_python-1.0.3-py3-none-any.whl.
File metadata
- Download URL: hotmart_python-1.0.3-py3-none-any.whl
- Upload date:
- Size: 26.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0550662299171d2c8d1eef912d98d42245dbc9ac9e32b1932c45f98672a36b21
|
|
| MD5 |
89f60942029a9de02c95e171830ac994
|
|
| BLAKE2b-256 |
2ecfaa1546ab2972575abdcef9eec1c657e53c3cf8b613d62a18d75d9436e9cc
|
Provenance
The following attestation bundles were made for hotmart_python-1.0.3-py3-none-any.whl:
Publisher:
publish.yml on im-voracity/hotmart-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hotmart_python-1.0.3-py3-none-any.whl -
Subject digest:
0550662299171d2c8d1eef912d98d42245dbc9ac9e32b1932c45f98672a36b21 - Sigstore transparency entry: 1186449253
- Sigstore integration time:
-
Permalink:
im-voracity/hotmart-python@253ca76567854d55a383bb9bae4f3d33324fa230 -
Branch / Tag:
refs/tags/v1.0.3 - Owner: https://github.com/im-voracity
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@253ca76567854d55a383bb9bae4f3d33324fa230 -
Trigger Event:
push
-
Statement type: