API Reference
Generated directly from the published OpenAPI contract (version 1.0.0). This page is never hand-maintained — it always reflects exactly what the API ships.
reseller-account
Your own account profile — read-only.
GET /api/v1/reseller/account
Get Account
reseller-catalog
Browse the live, publicly purchasable catalogue — read-only.
GET /api/v1/reseller/catalog/categories
List Categories
Unpaginated by design: there is no admin category CRUD today, so this list is empty-or-tiny in practice (same truth the unauthenticated public catalog router already reflects) — cursor machinery would be pure overhead for a resource with no realistic deep-page scenario.
GET /api/v1/reseller/catalog/packages/{package_id}
Get Package
package_id is ProductVariant.id (RESELLER_API_CONTRACT.md
§3's stable public package identifier). A variant belonging to a
non-ACTIVE product, or that is itself not in a publicly-visible status,
reads as not-found — never a different error — matching the existing
"never leak existence of a hidden product" rule
(CatalogService.get_public_product_by_slug's exact same discipline,
applied here at the variant level since no existing method does this
for a single variant looked up by its own id).
GET /api/v1/reseller/catalog/products
List Products
GET /api/v1/reseller/catalog/products/{product_id}
Get Product
reseller-meta
This machine-readable API description — no authentication required.
GET /api/v1/reseller/openapi.json
Public Reseller API OpenAPI document
The machine-readable OpenAPI 3 document for this API — only the /api/v1/reseller/* paths, never admin/internal/website-session surfaces. No Authorization header required.
reseller-orders
Create orders (idempotent) and read their status/delivery.
GET /api/v1/reseller/orders
List Orders
Paginated order history for the authenticated account's own wallet — may include orders created via Telegram or the website too (the owner-approved shared-wallet decision), never another account's. Accepts no account/user-identifier query parameter at all; there is nothing here a caller could tamper with to list someone else's orders.
POST /api/v1/reseller/orders
Create Order
Buy body.quantity units of body.package_id from the
authenticated reseller account's own Brenoz wallet.
Always returns 201 for a freshly-created OR a safely-replayed order
(same Idempotency-Key + same body) — the order resource now exists
either way, whatever its current pipeline status. A terminal failed
status is still a successful CREATE from the HTTP layer's perspective:
the wallet was already debited at the catalog-time price before the
live supplier re-check ran (same existing, documented platform
behavior every website/Telegram wallet purchase has always had —
RESELLER_API_SECURITY.md §4), and the automatic wallet-order refund
sweep (Phase 8, Batch 3.5, enabled in production) now resolves exactly
this case without any reseller action. status_message explains a
non-obvious terminal/held status in plain language; nothing here ever
implies the attempt was free or that fulfillment is instant.
Client-facing failures: catalog.variant_not_found (404 — unknown
package_id), checkout.product_unavailable (409 — no eligible
offer right now), checkout.insufficient_balance (402),
checkout.invitation_email_required/invitation_email_invalid
(400 — this package's fulfillment mode needs an invitation email,
which this endpoint has no field for in v1; such a package cannot be
bought through the Reseller API yet), orders.idempotency_payload_ mismatch (409 — Idempotency-Key reused with a different body).
GET /api/v1/reseller/orders/{order_id}
Get Order
One of the authenticated account's own orders. An order owned by a
different account reads back as orders.not_found (404) — never a
403 — so order ids cannot be probed across accounts, the exact same
ownership discipline routers/orders.py::get_order already uses for
the website.
GET /api/v1/reseller/orders/{order_id}/delivery
Get Order Delivery
The stored delivery artifact for one of the authenticated account's
own orders — the EXACT sanitized bytes persisted at fulfillment time,
same source table the website's own delivery view reads, never
re-derived/re-sanitized/re-fetched from a supplier. Ownership enforced
by get_order_for_user (404, never 403). Three distinct response
states — see ResellerOrderDeliveryResponse's own docstring for why
this deliberately does not collapse "no delivery yet" and "needs
manual review" into one available=False the way the website's
older endpoint still does.
Rate-limited per delivery with the SAME limiter the website/Telegram
redelivery paths use; the gate sits AFTER the "is there anything to
return" check, so a pending/pending_manual_review response
never burns cooldown budget.
reseller-wallet
Your own Brenoz wallet balance — read-only. Orders debit this balance; there is no separate reseller billing relationship.
GET /api/v1/reseller/wallet
Get Wallet
reseller-webhooks
Register callback URLs for asynchronous order-lifecycle notifications — optional, additive, never required for correctness.
GET /api/v1/reseller/webhooks
List Webhooks
Every endpoint on the authenticated account — never another
account's, never the secret. Unpaginated: the per-account cap (10)
already bounds this to a small set, same reasoning
ApiKeyService.list_for_owner already established for its own
analogous list.
POST /api/v1/reseller/webhooks
Register Webhook
Registers a new callback URL. The signing secret is returned ONCE, in this response only — it cannot be retrieved again; losing it means revoking this endpoint and registering a new one.
DELETE /api/v1/reseller/webhooks/{webhook_id}
Revoke Webhook
Idempotent — revoking an already-revoked endpoint returns it unchanged rather than erroring, safe to retry.
GET /api/v1/reseller/webhooks/{webhook_id}/deliveries
List Webhook Deliveries
Delivery attempt history for one of the account's own endpoints — newest first, the same keyset-cursor convention every other reseller list endpoint already uses. Never the payload body, never a secret (RESELLER_API_CONTRACT.md §6).
POST /api/v1/reseller/webhooks/{webhook_id}/deliveries/{delivery_id}/replay
Replay Webhook Delivery
Manual, owner-initiated re-attempt of a dead-lettered delivery
(RESELLER_API_SECURITY.md §6) — re-attempts exactly once per call,
never a runaway retry storm; the delivery sweep picks it up on its
next tick at the reset next_retry_at (immediate). Also enforces
the shared, stricter outbound-action cooldown (see module docstring)
since each replay makes one real HTTP attempt against the endpoint's
URL.
POST /api/v1/reseller/webhooks/{webhook_id}/test
Test Webhook
Sends a synthetic test.ping event immediately, synchronously —
the caller is explicitly waiting on the result. Never creates a
delivery-history row (RESELLER_API_CONTRACT.md §6; see
WebhookDeliveryService.send_test_ping's own docstring for why).
Also enforces the shared, stricter outbound-action cooldown (see
module docstring) since every call makes one real HTTP attempt
against the endpoint's URL, independent of whether automatic webhook
delivery is enabled.