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.