# AIBulk Wholesale API > Buy redemption codes for AI subscription products programmatically. Pay from a prepaid > balance or via hosted crypto checkout. Gift cards are delivered directly after procurement. ## Getting started 1. Register at https://aibulk.uk, then self-activate an API key at https://aibulk.uk/account — no approval needed (shown once, prefix `awk_`). Pricing is level-based: each level (Lv1–Lv5) has its own fixed price per product; levels rise automatically with cumulative spending + prepaid balance. Qty 1–1000 per order for everyone. 2. All requests: `Authorization: Bearer awk_...` 3. Machine-readable spec: https://aibulk.uk/openapi.json 4. MCP endpoint (Streamable HTTP): https://aibulk.uk/mcp — same Authorization header. Claude Code: `claude mcp add --transport http aibulk https://aibulk.uk/mcp --header "Authorization: Bearer awk_..."` ## Conventions - Amounts are decimal strings with at most 2 decimals ("12.50"), never floats. Currencies: USDT, USDC. - Gift-card variants expose productGroupId, denomination (decimal face value in faceCurrency units: USD cards "25.00", Robux codes "800" with faceCurrency ROBUX), faceCurrency and region (US or GLOBAL). Group for display, but purchase the selected leaf id. stockMode=limited hides exact stock; call availability with the desired qty. Checkout repeats a fresh quantity/price check and accounts for outstanding orders before debit/payment-link creation. A GET does not reserve stock. - Direct-code fulfillmentStatus=needs_attention means manual delivery/refund review, not an automatic refund. fulfilledQuantity reports progress; codes remain withheld until all units are delivered. Keep the original order and contact support; never replace it. - Address products by `id` (stable). `slug` is a display/SEO identifier and may change. - POST /wholesale/v1/orders is idempotent on externalOrderId — reuse the same id (with the SAME productId/qty) on retry; never double-charges. Replays return the order's CURRENT status; codes[] only when status=delivered. Mismatched params → 409 idempotency_conflict. - POST /wholesale/v1/topup is idempotent on externalTopupId (or the Idempotency-Key header); reuse it on retry. - To reject a changed quote instead of silently accepting it, pass expectedUnitPrice from the latest products/availability response; a mismatch returns 409 price_changed with the current price in details. - payment=balance → instant debit; direct_code gift cards may remain paid with codes=[] until procurement completes. payment=intent → checkoutUrl for a human to pay (give it only to the payer; do not log or publish it), then poll GET /wholesale/v1/orders/{id} until status=delivered. Poll every 10–30s: payment sync is throttled to once per 10s per order, faster polling adds nothing. - Unpaid intent/top-up orders expire after ~15 minutes; start over with a new externalOrderId (externalTopupId). Money received after local expiry is still honored — reconciliation delivers/credits anyway. - paymentUrl and checkoutUrl are aliases; order detail uses paymentUrl. Replaying an intent order's externalOrderId with payment=balance returns it as pending WITHOUT checkoutUrl — fetch it via GET /wholesale/v1/orders/{id}. - On a 5xx payment-provider error no dangling order is left — retry safely with the same externalOrderId. - deliveryType=redemption means platform codes plus redeemUrl for activation. deliveryType=direct_code means actual gift card codes, redeemUrl=null, and no platform redemption. Delivered means every item is ready. - Errors include {"error": "", "message": "", "requestId": "...", "retryable"?: true}; branch on error, not message. retryable=true means the same request may be retried as-is. Send requestId to support when reporting a failure. Codes: unauthorized, invalid_input, invalid_cursor, not_found, out_of_stock, insufficient_stock, stock_unavailable, fulfillment_unavailable, insufficient_balance, duplicate_order, idempotency_conflict, price_changed, rate_limited, webhook_not_configured, payment_provider_error, payment_provider_timeout, internal_error. Redemption endpoints add: invalid_code, already_redeemed, retry_conflict, missing_field, invalid_account_id, code_expired, test_code_only. - Redeeming on the customer's behalf (resellers): every product carries redeemFields (what to collect; [] for direct_code). POST /wholesale/v1/redemptions {code, redeemInfo} submits one of YOUR codes (others → 404 invalid_code); GET /wholesale/v1/redemptions/{redemptionId} or ?code= returns status not_redeemed|pending|processing|fulfilled|failed plus retryable/failureCode; a failed code can be re-POSTed with corrected redeemInfo. Limited per API key (60 submits/min), not per IP. Test the loop with prod_api_order_test: its codes complete instantly via this endpoint (no benefits) and emit redemption.fulfilled; the redemption site itself rejects them (test_code_only). - Codes issued through the API have no expiry date. USDC top-ups are credited 1:1 to the USDT balance. Full human-readable reference (Chinese): https://aibulk.uk/developers/api - Rate limit: 300 req/min per key (plus a per-IP limit). Read X-RateLimit-* and Retry-After headers. - Compliance: pay only from wallets with no direct or indirect ties to sanctioned platforms (e.g. Binance's Aug 2026 blocklist: HTX, BitPapa, EXMO and others — https://www.binance.com/en/support/announcement/detail/af2be67dc03c4673b4f56c42db948253). Addresses linked to them risk exchange freezes; this platform accepts no association with those entities, and any resulting freeze or loss is borne by the payer. - For direct_code gift cards, deliver codes[] as-is and never create another order while the original is paid. ## Webhooks (optional push — polling stays the source of truth) - Configure: PUT /wholesale/v1/webhook {"url": "https://..."} → returns the HMAC secret ONCE (reconfiguring rotates it). POST /wholesale/v1/webhook/test sends a test event. Redirects are not followed (a 3xx counts as a failed attempt), so register the final URL. - Events (JSON body, envelope fields merged flat with the payload): - order.delivered {eventId, event, timestamp, orderId, externalOrderId, productId, qty, codes[], redeemUrl} — emitted when all customer codes are ready, including asynchronous direct-code delivery - order.delivery_attention {eventId, event, timestamp, orderId, externalOrderId, productId, qty, deliveryType, fulfillmentStatus, fulfilledQuantity, errorCode, resolution, codes:[], redeemUrl:null} — manual review; query current order because events can arrive out of order - topup.credited {eventId, event, timestamp, orderId, externalTopupId, amount, currency} - redemption.fulfilled / redemption.failed {eventId, event, timestamp, orderId, externalOrderId, productId, redemptionId, code, status, failureCode, retryable, fulfilledAt} — once per redemption attempt reaching a terminal state (a resubmitted code starts a new attempt with its own event); only for codes from your wholesale orders; no deliverable or submitted data included - webhook.test {eventId, event, timestamp, message} - Headers: X-AIBulk-Signature: sha256=HMAC_SHA256(secret, rawBody) — verify before trusting; X-AIBulk-Event-Id — stable across redeliveries, dedupe on it; X-AIBulk-Delivery-Id — unique per attempt; X-AIBulk-Timestamp — unix seconds. - Delivery: respond with any 2xx within 5s; otherwise retried with exponential backoff (30s doubling, capped 6h) up to 10 attempts. Inspect/retry at GET /wholesale/v1/webhook/deliveries. ## Endpoints - GET /wholesale/v1/products — products, your unit price, stock info (stockMode=inventory: stock = available units, cached ≤60s; on_demand: fulfilled per redemption, stock=null; limited: finite stock, check qty before checkout) - GET /wholesale/v1/products/{id}/availability?qty=N — real-time "can this qty be fulfilled" check before ordering - GET /wholesale/v1/balance — prepaid balance - POST /wholesale/v1/topup — idempotent balance top-up (human pays at checkoutUrl); 20/hour per account, at most 5 unpaid top-ups at a time (429 too_many_pending_topups) - POST /wholesale/v1/orders — purchase codes (idempotent) - GET /wholesale/v1/orders?externalOrderId= — find your order (list includes top-up orders; rows carry channel = wholesale_api | topup) - GET /wholesale/v1/orders/{id} — order detail + codes when delivered - GET /wholesale/v1/products/{id} — one product (uncached) incl. redeemFields - POST /wholesale/v1/redemptions — redeem one of your codes server-side {code, redeemInfo} - GET /wholesale/v1/redemptions/{redemptionId} | ?code= — redemption progress (poll every 30–60s; upstream refresh is throttled to once per 60s) - PUT /wholesale/v1/webhook — durable HMAC-signed notifications; POST /webhook/test validates the integration - GET /wholesale/v1/webhook/deliveries — inspect/retry delivery history - GET /wholesale/v1/transactions — cursor-paginated balance ledger