{"openapi":"3.1.0","info":{"title":"AIBulk Wholesale API","version":"1.4.0","description":"Programmatic purchase of subscription redemption codes and fixed-denomination gift cards.\nIntegration test product: prod_api_order_test (API Order Test, No Real Benefits). Hidden from all product lists. Real 0.01 USDT charge at every level, REST API balance payment only, qty=1. Use expectedUnitPrice=0.01 and reuse externalOrderId on retries. Supports order queries, idempotency and order.delivered webhooks. Its codes are rejected by the redemption site (400 test_code_only) but can be submitted to POST /wholesale/v1/redemptions, where they complete instantly (status fulfilled, fixed deliverable text, no benefits) and emit redemption.fulfilled — use this to test the server-side redemption loop end to end. Not available via web checkout, MCP or payment=intent (400 api_test_balance_only); other quantities return 400 api_test_quantity. Availability returns deliveryMode=test_code_only.\n\nAuthentication: `Authorization: Bearer awk_...` (create your API key in the shop portal).\nAmounts are decimal strings with at most 2 decimals (e.g. \"12.50\"), never floats.\n`POST /wholesale/v1/orders` is idempotent on `externalOrderId`: retries with the same id return the original order (with its current `status`) and never double-charge. The retry must repeat the same `productId` and `qty` — mismatched parameters are rejected with `409 idempotency_conflict`.\n`POST /wholesale/v1/topup` is idempotent on `externalTopupId` (or the `Idempotency-Key` header). Reuse it on every retry.\nWith payment=balance, debit is immediate after quantity/price checks; direct_code gift cards can return paid with an empty codes array. Check fulfillmentStatus as well as payment status.\nGift-card variants share productGroupId and expose denomination, faceCurrency and region. Order the selected leaf id; neither the group slug nor a denomination parameter is accepted as productId. stockMode=limited uses fresh quantity checks and outstanding orders before checkout; an availability GET does not reserve stock.\nIf fulfillmentStatus=needs_attention (or order.delivery_attention arrives), stop fast polling and ask support to review the original order. Never create a replacement order or assume a refund. Partial deliveries and uncertain outcomes are reconciled manually.\nWith `payment=intent` you receive a `checkoutUrl` for a hosted crypto checkout (human pays), then poll `GET /wholesale/v1/orders/{id}` until `status=delivered` to collect codes. Give the `checkoutUrl` only to the payer; do not log or publish it. Poll every 10–30 seconds: server-side payment sync is throttled to once per 10 seconds per order, so faster polling adds no freshness.\nAn unpaid intent or top-up order expires after ~15 minutes; create a new order with a new `externalOrderId` (or `externalTopupId`). If the buyer completes payment after local expiry, the received payment is still honored: reconciliation delivers the codes (or credits the balance) anyway.\nOn a 5xx payment-provider error no dangling order is left behind — retry safely with the SAME `externalOrderId`.\n`paymentUrl` and `checkoutUrl` are equivalent aliases in responses; prefer `checkoutUrl` where both appear (order detail responses use `paymentUrl`).\nA delivered order means all customer codes were issued. deliveryType=redemption: give each code plus redeemUrl to the end customer for activation. deliveryType=direct_code: codes are the actual gift card codes, redeemUrl is null, and paid means procurement is still processing. Poll with the original order ID or await order.delivered; never create a replacement order.\nRate limit: 300 requests/minute per API key (plus a per-IP limit shared with unauthenticated traffic). On `429 rate_limited`, back off per the `Retry-After` / `X-RateLimit-*` headers.\nCodes issued through this API have no expiry date. USDC top-ups are credited 1:1 to the USDT balance.\nCompliance: pay only from wallets with no direct or indirect ties to sanctioned platforms (see Binance's Aug 2026 blocklist: https://www.binance.com/en/support/announcement/detail/af2be67dc03c4673b4f56c42db948253). Linked addresses risk exchange freezes; this platform accepts no association with those entities, and any resulting freeze or loss is borne by the payer.\nOutbound delivery notifications are available — configure them with `PUT /wholesale/v1/webhook` and see the top-level `webhooks` section for event payloads, signing and retry semantics.\nServer-side redemption for resellers who activate on behalf of their customers: products expose `redeemFields` (what to collect before purchase), `POST /wholesale/v1/redemptions` submits a code you bought together with the customer's `redeemInfo`, `GET /wholesale/v1/redemptions/{redemptionId}` (or `?code=`) reports progress, and `redemption.fulfilled` / `redemption.failed` webhooks push the terminal result. These endpoints are limited per API key, not per source IP, and only accept codes issued by your own orders (`404 invalid_code` otherwise).\nAn MCP endpoint mirroring this API is available at `/mcp` (Streamable HTTP, same Authorization header)."},"servers":[{"url":"https://aibulk.uk"}],"security":[{"apiKey":[]}],"paths":{"/wholesale/v1/products":{"get":{"operationId":"listProducts","summary":"List purchasable products with your unit price","responses":{"200":{"description":"Active products","content":{"application/json":{"schema":{"type":"object","properties":{"products":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Stable product id — use this in orders/availability calls; slug may change"},"slug":{"type":"string","description":"Display/SEO identifier, not for API addressing"},"name":{"type":"string","description":"Chinese product title (legacy field)"},"nameEn":{"type":["string","null"],"description":"English product title"},"price":{"$ref":"#/components/schemas/Decimal","description":"Your unit price at your current level"},"retailPrice":{"$ref":"#/components/schemas/Decimal","description":"Lv1 list price, for reference"},"currency":{"type":"string","enum":["USDT","USDC"]},"fulfillmentMode":{"type":"string","enum":["manual","auto","code_only"]},"deliveryType":{"type":"string","enum":["redemption","direct_code"]},"productGroupId":{"type":["string","null"],"description":"Group related denominations for display. Not a purchasable productId."},"denomination":{"type":["string","null"],"description":"Printed face value as a decimal string in faceCurrency units, e.g. 25.00 (USD) or 800 (ROBUX). Not the selling price."},"faceCurrency":{"type":["string","null"],"description":"Face-value unit, e.g. USD or ROBUX. Independent of payment currency."},"region":{"type":["string","null"],"description":"Card region, e.g. US or GLOBAL."},"inStock":{"type":"boolean","description":"Listing-level availability switch; false means not purchasable at all"},"available":{"type":"boolean","description":"Whether a qty=1 order would be accepted right now"},"stockMode":{"type":"string","enum":["inventory","on_demand","limited"],"description":"inventory: bounded by own stock. on_demand: fulfilled per redemption. limited: finite stock, exact count hidden; check availability for the desired quantity"},"stock":{"type":["integer","null"],"description":"Available units when stockMode=inventory (cached up to 60s, display-capped). null when on_demand"},"stockCapped":{"type":"boolean","description":"true if actual stock exceeds the displayed cap"},"availabilityStatus":{"type":"string","enum":["available","manual","degraded","unavailable"]},"deliveryMode":{"type":"string","enum":["automatic","manual","manual_fallback","unavailable"]},"redeemFields":{"type":"array","items":{"$ref":"#/components/schemas/RedeemField"},"description":"What the end customer must provide at redemption (Chinese labels); [] for direct_code gift cards. Collect these before purchasing when you redeem on the customer's behalf."},"redeemFieldsEn":{"type":"array","items":{"$ref":"#/components/schemas/RedeemField"},"description":"Same fields with English labels (may be empty when no translation is configured)"}}}}}}}}},"401":{"description":"Missing or invalid API key (`unauthorized`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/products/{id}":{"get":{"operationId":"getProduct","summary":"Get one product (uncached) with your unit price and its redeemFields","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Product id"}],"responses":{"200":{"description":"Same shape as one listProducts row","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown or inactive product (`not_found`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/redemptions":{"post":{"operationId":"submitRedemption","summary":"Redeem one of your codes on behalf of the end customer","description":"Equivalent to the redemption site's submit, authenticated with your API key and rate-limited per key (60/min) instead of per source IP. Only codes issued by your own orders are accepted; anything else returns 404 invalid_code without revealing whether the code exists. redeemInfo keys come from the product's redeemFields (e.g. account_id = Claude Organization ID for prod_claude_pro). prod_api_order_test codes complete instantly here (fulfilled, no benefits) and emit redemption.fulfilled, so the whole loop can be tested for 0.01 USDT. A code whose previous redemption failed (status=failed, retryable=true) may be submitted again with corrected redeemInfo; 409 already_redeemed means it is in progress or done, 409 retry_conflict means the previous attempt still holds supplier resources — poll and retry later. Fulfillment is asynchronous: poll getRedemption or subscribe to redemption.fulfilled / redemption.failed.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"A code from one of your delivered orders (XXXX-XXXX-XXXX-XXXX)"},"redeemInfo":{"type":"object","additionalProperties":{"type":"string"},"description":"Values keyed by redeemFields[].key"}}}}}},"responses":{"200":{"description":"Redemption accepted and queued","content":{"application/json":{"schema":{"type":"object","properties":{"redemptionId":{"type":"string","description":"Use with getRedemption; also carried by redemption.* webhooks"},"status":{"type":"string","enum":["pending"]},"code":{"type":"string"},"orderId":{"type":"string"}}}}}},"400":{"description":"`invalid_input` | `missing_field` — a required redeemFields entry is empty | `invalid_account_id` — Claude Organization ID is not a UUID | `invalid_session_json` | `invalid_telegram_username`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`invalid_code` — unknown, voided, or not issued by your orders","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`already_redeemed` — in progress or completed | `retry_conflict` — previous attempt still holds supplier resources; poll and retry later","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"`code_expired`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`session_validation_unavailable` — ChatGPT session preflight temporarily unavailable; retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"getRedemptionByCode","summary":"Redemption status for one of your codes","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"status=not_redeemed when the code has not been submitted yet (redemptionId=null)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Redemption"}}}},"400":{"description":"`invalid_input` — missing code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`invalid_code`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/redemptions/{redemptionId}":{"get":{"operationId":"getRedemption","summary":"Redemption status by id (source of truth; webhooks are a push convenience)","description":"While pending/processing the server refreshes the upstream status at most once per 60s per redemption, so polling faster than that adds nothing. Poll every 30–60s.","parameters":[{"name":"redemptionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current redemption state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Redemption"}}}},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — unknown id or not issued by your orders","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/products/{id}/availability":{"get":{"operationId":"checkAvailability","summary":"Check if a quantity can be fulfilled right now (real-time, uncached)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Product id"},{"name":"qty","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":1}}],"responses":{"200":{"description":"Availability verdict","content":{"application/json":{"schema":{"type":"object","properties":{"productId":{"type":"string"},"qty":{"type":"integer"},"available":{"type":"boolean"},"stockMode":{"type":"string","enum":["inventory","on_demand","limited"]},"productGroupId":{"type":["string","null"],"description":"Group related denominations for display. Not a purchasable productId."},"denomination":{"type":["string","null"],"description":"Printed face value as a decimal string in faceCurrency units, e.g. 25.00 (USD) or 800 (ROBUX). Not the selling price."},"faceCurrency":{"type":["string","null"],"description":"Face-value unit, e.g. USD or ROBUX. Independent of payment currency."},"region":{"type":["string","null"],"description":"Card region, e.g. US or GLOBAL."},"deliveryType":{"type":"string","enum":["redemption","direct_code"]},"unitPrice":{"$ref":"#/components/schemas/Decimal"},"checkedAt":{"type":"integer","description":"Unix timestamp"},"availabilityStatus":{"type":"string","enum":["available","manual","degraded","unavailable"]},"deliveryMode":{"type":"string","enum":["automatic","manual","manual_fallback","unavailable","test_code_only"]}}}}}},"400":{"description":"qty out of range (`invalid_input`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown or inactive product (`not_found`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/balance":{"get":{"operationId":"getBalance","summary":"Get prepaid balance","responses":{"200":{"description":"Current balance","content":{"application/json":{"schema":{"type":"object","properties":{"balance":{"$ref":"#/components/schemas/Decimal"},"currency":{"type":"string"}}}}}},"401":{"description":"Missing or invalid API key (`unauthorized`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/topup":{"post":{"operationId":"createTopup","summary":"Create a balance top-up; a human completes payment at checkoutUrl","description":"Provide the idempotency key as externalTopupId in the body or as the Idempotency-Key header, and reuse it on every retry. Legacy requests without a key are temporarily accepted but cannot be safely retried and return a Warning header. Limits: 20 top-up requests per hour per account (idempotent replays count), and at most 5 unpaid top-up orders at a time (`429 too_many_pending_topups`; pay or let them expire after ~15 minutes).","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":64},"description":"Alternative to body.externalTopupId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"externalTopupId":{"type":"string","maxLength":64,"description":"Your idempotency key. May instead be sent as Idempotency-Key header."},"amount":{"$ref":"#/components/schemas/Decimal","description":"Must be greater than 0, at most 2 decimals"},"currency":{"type":"string","enum":["USDT","USDC"],"default":"USDT"}}}}}},"responses":{"200":{"description":"Top-up order created. Balance is credited after payment is confirmed (check with getBalance).","content":{"application/json":{"schema":{"type":"object","properties":{"orderId":{"type":"string"},"externalTopupId":{"type":["string","null"]},"amount":{"$ref":"#/components/schemas/Decimal"},"status":{"type":"string","enum":["pending","delivered","expired","refunded"]},"checkoutUrl":{"type":["string","null"],"description":"Hosted checkout URL while pending"},"paymentUrl":{"type":["string","null"],"description":"Alias of checkoutUrl (compatibility)"},"reused":{"type":"boolean"},"idempotencyWarning":{"type":"string","description":"Present only for a legacy request without an idempotency key"}}}}}},"400":{"description":"`invalid_input`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — externalTopupId was reused with a different amount or currency","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"`payment_provider_error` — no order was left behind; retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"504":{"description":"`payment_provider_timeout` — no order was left behind; retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/orders":{"post":{"operationId":"purchaseCodes","summary":"Purchase codes (idempotent on externalOrderId)","description":"payment=balance: debits instantly; direct_code gift cards may return paid with codes=[] until procurement finishes. payment=intent: returns checkoutUrl; poll getOrder every 10-30s until status=delivered (an unpaid intent expires after ~15 minutes). Idempotent replay returns the original order with its CURRENT status (not necessarily delivered) — codes[] is only populated when status=delivered. A replay must repeat the same productId/qty, otherwise 409 idempotency_conflict. The payment mode is NOT compared: replaying with payment=balance an externalOrderId that was created as an intent order returns that pending order WITHOUT a checkoutUrl — fetch it via getOrder (paymentUrl). If a pending intent order lost its checkoutUrl (rare provider failure), replaying the same externalOrderId in intent mode repairs it and returns a fresh checkoutUrl.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["externalOrderId","productId","qty"],"properties":{"externalOrderId":{"type":"string","maxLength":64,"description":"Your unique order id. Reuse the SAME value when retrying — this is the idempotency key."},"productId":{"type":"string"},"qty":{"type":"integer","minimum":1,"maximum":1000},"payment":{"type":"string","enum":["balance","intent"],"default":"balance"},"expectedUnitPrice":{"$ref":"#/components/schemas/Decimal","description":"Optional optimistic price lock; mismatch returns 409 price_changed"}}}}}},"responses":{"200":{"description":"Order result","content":{"application/json":{"schema":{"type":"object","properties":{"orderId":{"type":"string"},"externalOrderId":{"type":"string"},"amount":{"$ref":"#/components/schemas/Decimal"},"status":{"type":"string","enum":["pending","paid","delivered","expired","refunded"]},"codes":{"type":"array","items":{"type":"string"},"description":"Customer codes, present only when every item is delivered. For direct_code products these are the actual gift card codes."},"checkoutUrl":{"type":"string","description":"Present in intent mode while payment is pending"},"paymentUrl":{"type":"string","description":"Alias of checkoutUrl (compatibility)"},"redeemUrl":{"type":["string","null"],"description":"Redemption site for redemption products; null for direct_code gift cards"},"deliveryType":{"type":"string","enum":["redemption","direct_code"]},"fulfillmentStatus":{"type":"string","enum":["awaiting_payment","processing","needs_attention","delivered","expired","refunded"],"description":"Direct-code orders only. needs_attention requires support review; paid alone does not imply automatic progress."},"fulfilledQuantity":{"type":"integer","minimum":0,"description":"Units obtained so far. Partial codes remain withheld until the whole order is delivered."},"errorCode":{"type":["string","null"],"enum":["delivery_needs_attention",null]},"resolution":{"type":["string","null"],"enum":["manual_review",null],"description":"Support reconciles remaining delivery and any refund. This is not confirmation of a customer refund."},"reused":{"type":"boolean","description":"true if this externalOrderId was already processed (idempotent replay)"}}}}}},"400":{"description":"`invalid_input`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"`insufficient_balance` — top up or lower qty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — unknown productId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`duplicate_order` — concurrent retry raced; re-query with GET /wholesale/v1/orders?externalOrderId=... | `idempotency_conflict` — externalOrderId reused with different productId/qty; use a new externalOrderId | `out_of_stock` — product currently unavailable | `insufficient_stock` — qty exceeds available stock after outstanding orders; no debit or payment link is created | `stock_unavailable` — a fresh stock check could not confirm availability; no debit or payment link is created | `fulfillment_unavailable` — delivery route or live product/price validation is unavailable; no debit or payment link is created | `price_changed` — current level price differs from expectedUnitPrice; refresh products and confirm the new price","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"`payment_provider_error` (intent mode) — no order was left behind; retry with the SAME externalOrderId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"504":{"description":"`payment_provider_timeout` (intent mode) — no order was left behind; retry with the SAME externalOrderId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listOrders","summary":"List recent orders including top-ups (filter by externalOrderId)","description":"Returns both purchase orders and balance top-up orders, newest first; distinguish them by the per-row channel field (wholesale_api | topup). Rows are summaries: no codes and no paymentUrl — fetch GET /wholesale/v1/orders/{id} for those.","parameters":[{"name":"externalOrderId","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{orders, nextCursor, hasMore}, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/OrderSummary"}},"nextCursor":{"type":["string","null"]},"hasMore":{"type":"boolean"}}}}}},"400":{"description":"`invalid_cursor` or `invalid_input`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/orders/{id}":{"get":{"operationId":"getOrder","summary":"Get one order; contains codes once status=delivered","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Order detail; inspect fulfillmentStatus for direct-code delivery or manual review. codes[] only when delivered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderDetail"}}}},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/webhook":{"put":{"operationId":"configureWebhook","summary":"Set delivery webhook URL (empty string clears)","description":"Returns a signing secret ONCE (reconfiguring rotates it). Each delivery is a POST with JSON body and headers: X-AIBulk-Signature: sha256=HMAC_SHA256(secret, rawBody), X-AIBulk-Event-Id (stable across redeliveries of one event — dedupe on it), X-AIBulk-Delivery-Id (unique per attempt), X-AIBulk-Timestamp (unix seconds of the event). Respond with any 2xx within 5 seconds to acknowledge; anything else is retried with exponential backoff (30s doubling, capped at 6h) for up to 10 attempts. Event payloads are documented in the top-level `webhooks` section. Webhooks are a convenience push — polling getOrder remains the source of truth.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"https:// URL or empty string"}}}}}},"responses":{"200":{"description":"{ok, url, secret?}"},"400":{"description":"`invalid_input`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/webhook/test":{"post":{"operationId":"testWebhook","summary":"Send and persist a webhook.test delivery","responses":{"200":{"description":"{ok, deliveryId, eventId}"},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`webhook_not_configured`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/webhook/deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"Cursor-paginated outbound webhook delivery history","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{deliveries, nextCursor, hasMore}"},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/webhook/deliveries/{id}/retry":{"post":{"operationId":"retryWebhookDelivery","summary":"Reset and immediately retry a failed/cancelled delivery","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ok:true}"},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/wholesale/v1/transactions":{"get":{"operationId":"listTransactions","summary":"Balance ledger (topup/purchase/refund/adjust) with balanceAfter snapshots","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["topup","purchase","refund","adjust"]}}],"responses":{"200":{"description":"{transactions, nextCursor, hasMore}"},"400":{"description":"`invalid_cursor`, `invalid_input`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — back off per the Retry-After / X-RateLimit-* response headers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{"order.delivery_attention":{"post":{"summary":"A direct-code order requires manual delivery/refund review","description":"Emitted once per order when fulfillment first requires attention. Events may arrive out of order: query the original order for current status. Never create a replacement purchase. No partial codes or private diagnostic details are sent.","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEnvelope"},{"type":"object","properties":{"orderId":{"type":"string"},"externalOrderId":{"type":["string","null"]},"productId":{"type":"string"},"qty":{"type":"integer"},"deliveryType":{"const":"direct_code"},"fulfillmentStatus":{"type":"string","enum":["awaiting_payment","processing","needs_attention","delivered","expired","refunded"],"description":"Direct-code orders only. needs_attention requires support review; paid alone does not imply automatic progress."},"fulfilledQuantity":{"type":"integer","minimum":0,"description":"Units obtained so far. Partial codes remain withheld until the whole order is delivered."},"errorCode":{"type":["string","null"],"enum":["delivery_needs_attention",null]},"resolution":{"type":["string","null"],"enum":["manual_review",null],"description":"Support reconciles remaining delivery and any refund. This is not confirmation of a customer refund."},"codes":{"type":"array","maxItems":0},"redeemUrl":{"type":"null"}},"required":["orderId","fulfillmentStatus","fulfilledQuantity","errorCode","resolution"]}]}}}},"responses":{"2XX":{"description":"Acknowledged; same signature and retry rules as other events"}}}},"order.delivered":{"post":{"summary":"All customer codes are ready (direct-code orders wait for delivery after payment)","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEnvelope"},{"type":"object","properties":{"orderId":{"type":"string"},"externalOrderId":{"type":["string","null"]},"productId":{"type":"string"},"qty":{"type":"integer"},"codes":{"type":"array","items":{"type":"string"},"description":"Customer codes; actual gift card codes for direct_code products"},"redeemUrl":{"type":["string","null"],"description":"Redemption site; null for direct_code gift cards"},"deliveryType":{"type":"string","enum":["redemption","direct_code"]}}}]}}}},"responses":{"2XX":{"description":"Acknowledged — any 2xx stops retries"}}}},"topup.credited":{"post":{"summary":"A top-up payment was confirmed and credited to your balance","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEnvelope"},{"type":"object","properties":{"orderId":{"type":"string"},"externalTopupId":{"type":["string","null"]},"amount":{"$ref":"#/components/schemas/Decimal"},"currency":{"type":"string","enum":["USDT","USDC"]}}}]}}}},"responses":{"2XX":{"description":"Acknowledged — any 2xx stops retries"}}}},"redemption.fulfilled":{"post":{"summary":"A redemption you submitted (or your customer submitted on the redemption site) completed","description":"Emitted once per redemption attempt when it reaches fulfilled; eventId is stable as redemption:{redemptionId}:{attempt}:redemption.fulfilled. Only for codes issued by your wholesale orders. No deliverable or submitted data is included — fetch getRedemption if you need the masked deliverable.","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEnvelope"},{"$ref":"#/components/schemas/RedemptionEventPayload"},{"type":"object","properties":{"status":{"const":"fulfilled"},"failureCode":{"type":"null"},"retryable":{"const":false}}}]}}}},"responses":{"2XX":{"description":"Acknowledged — any 2xx stops retries"}}}},"redemption.failed":{"post":{"summary":"A redemption reached failed; the same code may be resubmitted with corrected redeemInfo","description":"Emitted once per redemption attempt when it reaches failed. After you resubmit the code, a new attempt starts and its own terminal event will follow (distinct eventId). failureCode: fulfillment_failed (automatic fulfillment could not complete) | rejected_by_operator (declined by support).","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEnvelope"},{"$ref":"#/components/schemas/RedemptionEventPayload"},{"type":"object","properties":{"status":{"const":"failed"},"failureCode":{"type":["string","null"],"enum":["fulfillment_failed","rejected_by_operator",null]},"retryable":{"const":true}}}]}}}},"responses":{"2XX":{"description":"Acknowledged — any 2xx stops retries"}}}},"webhook.test":{"post":{"summary":"Manual test delivery triggered via POST /wholesale/v1/webhook/test","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEnvelope"},{"type":"object","properties":{"message":{"type":"string"}}}]}}}},"responses":{"2XX":{"description":"Acknowledged"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key starting with awk_"}},"schemas":{"OrderSummary":{"type":"object","description":"List row: never carries codes or paymentUrl; use GET /wholesale/v1/orders/{id}","properties":{"id":{"type":"string"},"external_order_id":{"type":["string","null"]},"channel":{"type":"string","enum":["wholesale_api","topup"]},"product_id":{"type":["string","null"]},"qty":{"type":"integer"},"amount":{"$ref":"#/components/schemas/Decimal"},"status":{"type":"string","enum":["pending","paid","delivered","expired","refunded"]},"deliveryType":{"type":"string","enum":["redemption","direct_code"]},"fulfillmentStatus":{"type":"string","enum":["awaiting_payment","processing","needs_attention","delivered","expired","refunded"],"description":"Direct-code orders only. needs_attention requires support review; paid alone does not imply automatic progress."},"fulfilledQuantity":{"type":"integer","minimum":0,"description":"Units obtained so far. Partial codes remain withheld until the whole order is delivered."},"errorCode":{"type":["string","null"],"enum":["delivery_needs_attention",null]},"resolution":{"type":["string","null"],"enum":["manual_review",null],"description":"Support reconciles remaining delivery and any refund. This is not confirmation of a customer refund."},"redeemUrl":{"type":["string","null"]}}},"OrderDetail":{"type":"object","properties":{"id":{"type":"string"},"external_order_id":{"type":["string","null"]},"product_id":{"type":["string","null"]},"qty":{"type":"integer"},"amount":{"$ref":"#/components/schemas/Decimal"},"status":{"type":"string","enum":["pending","paid","delivered","expired","refunded"]},"deliveryType":{"type":"string","enum":["redemption","direct_code"]},"fulfillmentStatus":{"type":"string","enum":["awaiting_payment","processing","needs_attention","delivered","expired","refunded"],"description":"Direct-code orders only. needs_attention requires support review; paid alone does not imply automatic progress."},"fulfilledQuantity":{"type":"integer","minimum":0,"description":"Units obtained so far. Partial codes remain withheld until the whole order is delivered."},"errorCode":{"type":["string","null"],"enum":["delivery_needs_attention",null]},"resolution":{"type":["string","null"],"enum":["manual_review",null],"description":"Support reconciles remaining delivery and any refund. This is not confirmation of a customer refund."},"codes":{"type":"array","items":{"type":"string"},"description":"Detail only; empty until every item is delivered"},"paymentUrl":{"type":["string","null"]},"redeemUrl":{"type":["string","null"]}}},"Decimal":{"type":"string","pattern":"^\\d+(\\.\\d{1,2})?$","description":"Decimal amount as string, at most 2 decimals, e.g. \"12.50\""},"RedeemField":{"type":"object","description":"One input the end customer must provide at redemption time; identical to the redemption site's check response","properties":{"key":{"type":"string","description":"Use as the redeemInfo key"},"label":{"type":"string"},"type":{"type":"string","enum":["text","email","password","select","textarea"]},"required":{"type":"boolean"},"options":{"type":"array","items":{"type":"string"}},"placeholder":{"type":"string"}},"required":["key","label","type","required"]},"Product":{"type":"object","description":"One product row; identical to listProducts items","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"nameEn":{"type":["string","null"]},"price":{"$ref":"#/components/schemas/Decimal"},"retailPrice":{"$ref":"#/components/schemas/Decimal"},"currency":{"type":"string","enum":["USDT","USDC"]},"fulfillmentMode":{"type":"string","enum":["manual","auto","code_only"]},"deliveryType":{"type":"string","enum":["redemption","direct_code"]},"productGroupId":{"type":["string","null"],"description":"Group related denominations for display. Not a purchasable productId."},"denomination":{"type":["string","null"],"description":"Printed face value as a decimal string in faceCurrency units, e.g. 25.00 (USD) or 800 (ROBUX). Not the selling price."},"faceCurrency":{"type":["string","null"],"description":"Face-value unit, e.g. USD or ROBUX. Independent of payment currency."},"region":{"type":["string","null"],"description":"Card region, e.g. US or GLOBAL."},"inStock":{"type":"boolean"},"available":{"type":"boolean"},"stockMode":{"type":"string","enum":["inventory","on_demand","limited"]},"stock":{"type":["integer","null"]},"stockCapped":{"type":"boolean"},"availabilityStatus":{"type":"string","enum":["available","manual","degraded","unavailable"]},"deliveryMode":{"type":"string","enum":["automatic","manual","manual_fallback","unavailable","test_code_only"]},"redeemFields":{"type":"array","items":{"$ref":"#/components/schemas/RedeemField"},"description":"What the end customer must provide at redemption (Chinese labels); [] for direct_code gift cards. Collect these before purchasing when you redeem on the customer's behalf."},"redeemFieldsEn":{"type":"array","items":{"$ref":"#/components/schemas/RedeemField"},"description":"Same fields with English labels (may be empty when no translation is configured)"}}},"Redemption":{"type":"object","properties":{"redemptionId":{"type":["string","null"],"description":"null while status=not_redeemed"},"code":{"type":"string"},"orderId":{"type":"string"},"externalOrderId":{"type":["string","null"]},"productId":{"type":"string"},"status":{"type":"string","enum":["not_redeemed","pending","processing","fulfilled","failed"]},"retryable":{"type":"boolean","description":"true only when status=failed: resubmit the code with corrected redeemInfo"},"failureCode":{"type":["string","null"],"enum":["fulfillment_failed","rejected_by_operator",null],"description":"Set only when status=failed"},"deliverable":{"type":["string","null"],"description":"Masked completion note when fulfilled (e.g. the recharged account, partially masked); null otherwise"},"createdAt":{"type":["integer","null"],"description":"Unix seconds the redemption was submitted"},"fulfilledAt":{"type":["integer","null"]}},"required":["redemptionId","code","orderId","productId","status","retryable","failureCode","deliverable","createdAt","fulfilledAt"]},"RedemptionEventPayload":{"type":"object","properties":{"orderId":{"type":"string"},"externalOrderId":{"type":["string","null"]},"productId":{"type":"string"},"redemptionId":{"type":"string"},"code":{"type":"string"},"status":{"type":"string","enum":["fulfilled","failed"]},"failureCode":{"type":["string","null"]},"retryable":{"type":"boolean"},"fulfilledAt":{"type":["integer","null"]}},"required":["orderId","redemptionId","code","status","retryable"]},"WebhookEnvelope":{"type":"object","description":"Common fields merged flat into every webhook body (no nesting). Verify X-AIBulk-Signature (sha256=HMAC_SHA256(secret, rawBody)) before trusting it; dedupe on eventId — retries of one event reuse it.","properties":{"eventId":{"type":"string","description":"Stable per event, shared by redeliveries — idempotency key for your handler"},"event":{"type":"string","enum":["order.delivered","order.delivery_attention","topup.credited","redemption.fulfilled","redemption.failed","webhook.test"]},"timestamp":{"type":"integer","description":"Unix seconds when the event was created"}},"required":["eventId","event","timestamp"]},"Error":{"type":"object","properties":{"error":{"type":"string","description":"Stable machine-readable code — branch on this, not on message"},"message":{"type":"string","description":"Human-readable, may be localized"},"requestId":{"type":"string","description":"Also returned in X-Request-Id; include it in support requests"},"retryable":{"type":"boolean"},"details":{"type":"object","additionalProperties":true}},"required":["error","requestId"]}}}}