Wholesale API reference

The machine-readable contract is openapi.json; agents can start from llms.txt.

AIBulk Developer / Wholesale API

Base URL: https://api.aibulk.uk/wholesale/v1. The machine-readable contract is https://api.aibulk.uk/openapi.json; the online version of this document is at aibulk.uk/en/developers/api, with an overview and code samples at aibulk.uk/en/developers.

Conventions

Every request carries the API key generated in the account center:

Authorization: Bearer awk_xxxxxxxx...
  • Amounts are decimal strings with at most two decimals (for example "22.00"); never use floats.
  • Default rate limit: 300 requests per minute. Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a 429 also carries Retry-After.
  • Every response carries X-Request-Id. Error bodies include requestId where possible; include it when reporting a problem.
  • Branch on the stable error code, not on the human-readable message. Some conflicts also return details; 5xx responses are marked retryable: true. The full list is under "Error codes" below.
  • Codes issued through the API have no expiry date. payment=intent orders and top-ups expire after about 15 minutes without payment. USDC top-ups are credited 1:1 to the USDT balance.

Payment compliance

  • Since August 2026 Binance has been blocking transactions with 17 sanctioned platforms (including HTX, BitPapa, EXMO, Rapira, Aifory Pro, ABCeX, WhiteBird, Shelbit, Aban Tether and A7). Wallets with direct or indirect fund flows to these entities may be frozen or have funds held for compliance review; see the official Binance announcement.
  • Only pay top-ups and single orders from wallets with no fund flows to those entities or their associated addresses; any freeze, hold or loss caused by such addresses is borne by the payer.
  • This platform has no association of any kind with sanctioned entities.

Levels and pricing

User levels Lv1 to Lv5 are raised automatically by "cumulative spending + current balance" and never go down. The web, the REST API and MCP all settle at the product's price for the user's level; price in GET /products is the caller's current unit price.

  1. GET /products for products, your current unit price and fulfilment status. Order by the stable id, never by slug.
  2. Call GET /products/:id/availability?qty=N before large orders.
  3. Send your own unique externalOrderId with POST /orders; on network errors, timeouts or 5xx, retry with the same value and the same product and quantity.
  4. If a silent price change is unacceptable, pass the latest quote (price from GET /products or unitPrice from availability) as expectedUnitPrice; a change returns 409 price_changed with the current price.
  5. payment=balance debits immediately and platform codes are usually returned synchronously; direct-code products may first return paid, so query the original order or wait for the webhook. payment=intent returns a checkout URL; after payment, use the same query flow.
  6. Deliver according to deliveryType: redemption sends the code plus redeemUrl; direct_code sends the actual gift card code with redeemUrl=null and no platform redemption.

0.01 USDT live order test

The test product API order test (no real benefits) has the ID prod_api_order_test. It is hidden from the shop and from product lists; use the ID directly.

  • It is a real charge in production, 0.01 USDT per unit at every level; it is not a free sandbox.
  • REST API only, payment=balance only, qty=1 only, and at least 0.01 USDT balance is required; it cannot be bought on the web, through MCP or with a single online payment.
  • It verifies balance debit, code delivery, order queries, externalOrderId idempotency and any configured order.delivered webhook. Standard API rate limits apply.
  • The returned codes are test codes with no real benefits and cannot be resold as subscriptions. Submitting one on the redemption site returns 400 test_code_only; it is not for testing real subscription activation.
  • A test code can be submitted to POST /redemptions (any redeemInfo, {} recommended): it completes in the same transaction (status=fulfilled, a fixed deliverable text) and emits redemption.fulfilled. This lets you exercise "order → server-side redemption → polling / webhook" end to end for 0.01 USDT; resubmitting the same test code returns 409 already_redeemed.
  • Retrying the same order must reuse externalOrderId and the same parameters, which never charges twice; a different ID creates a new order and charges again.
curl https://api.aibulk.uk/wholesale/v1/orders \
  -H "Authorization: Bearer $AIBULK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"externalOrderId":"integration-test-001","productId":"prod_api_order_test","qty":1,"payment":"balance","expectedUnitPrice":"0.01"}'

You can first call GET /products/prod_api_order_test/availability?qty=1 to check purchasability. A quantity other than 1 returns 400 api_test_quantity; an unsupported channel or payment method returns 400 api_test_balance_only.

Direct-code gift cards

deliveryType on products and orders distinguishes delivery: redemption uses platform codes plus a redemption URL; direct_code delivers the gift card code itself, redeemUrl is null, and the platform redemption site is not involved.

US App Store gift cards, Roblox USD gift cards and Robux codes are direct_code. After a balance payment or a completed single payment, the order may temporarily return status: "paid" with codes: [] while codes are being obtained. Query the original order ID or wait for the order.delivered webhook; the status becomes delivered only after the full quantity is delivered, and codes[] then holds the actual card codes. Do not create a new order or change the idempotency ID while waiting. Order downloads likewise contain only actual card codes. US Apple cards work only with US-region Apple Accounts.

Group productGroupId Example value faceCurrency region Official redemption page
apple-gift-card-us "25.00" USD US https://apps.apple.com/redeem
roblox-gift-card-us "25.00" USD US https://www.roblox.com/redeem
robux "800" (Robux amount) ROBUX GLOBAL https://www.roblox.com/redeem

Multi-value gift cards (Apple Gift Card / Roblox / Robux)

The catalog keeps the original list of individual SKUs, and every row can still be ordered by id. New fields let you merge several values for display:

Field Example Meaning
productGroupId apple-gift-card-us Display group; cannot be used as the order productId
denomination "25.00" / "800" Actual face value, a decimal string in faceCurrency units
faceCurrency USD / ROBUX Unit of the face value, distinct from the payment currency
region US / GLOBAL Card region
price "24.99" The current account's actual unit purchase price
stockMode limited Finite stock; the quantity must be checked separately and stock is null

For ordinary products the four group/value fields are null. SKUs that share a productGroupId can form a value picker; show only options with inStock && available, sorted by numeric value. When buying, pass the selected row's id, not the page slug, and do not pass denomination instead of choosing a SKU.

For limited products, availability?qty=N reads the latest purchasability and subtracts this platform's pending orders; ordering re-validates before debiting or creating a payment link and prevents concurrent requests on this platform from double-claiming the remainder. The availability query itself reserves nothing, so stock can still run out before or after payment. 409 insufficient_stock, stock_unavailable or fulfillment_unavailable mean nothing was debited and no payment link was created; reduce the quantity or check again later. On a network timeout or an unclear result, look up the order by the original externalOrderId first.

Example: select the $25 value and buy one card from the balance. API_KEY comes from your own secure configuration; externalOrderId must be persisted by your system and reused on timeout retries.

const base = "https://api.aibulk.uk/wholesale/v1";
const headers = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" };
async function api(path, init = {}) {
  const response = await fetch(base + path, { ...init, headers });
  const data = await response.json();
  if (!response.ok) throw Object.assign(new Error(data.message), { status: response.status, data });
  return data;
}
const { products } = await api("/products");
const selected = products.find(p => p.productGroupId === "apple-gift-card-us"
  && p.denomination === "25.00" && p.faceCurrency === "USD" && p.region === "US"
  && p.inStock && p.available);
if (!selected) throw new Error("This denomination is unavailable");
const quote = await api(`/products/${encodeURIComponent(selected.id)}/availability?qty=1`);
if (!quote.available) throw new Error("This quantity is unavailable");
const result = await api("/orders", {
  method: "POST",
  body: JSON.stringify({ externalOrderId, productId: selected.id, qty: 1,
    payment: "balance", expectedUnitPrice: quote.unitPrice })
});
// Persist result.orderId. Reuse it for GET /orders/:id and webhook reconciliation.
// payment=intent instead returns checkoutUrl; inspect the same order after payment.

Direct-code orders additionally expose, in the create response, the order list and the order detail:

fulfillmentStatus Client behaviour
awaiting_payment Waiting for payment to complete
processing Query the original order every 10–30 seconds, or wait for the notification
needs_attention Show "under manual review", slow down polling, contact support with the original order number; do not buy again
delivered Deliver codes[] from the detail to the buyer
expired Expired unpaid; if a payment was started with an unclear result, check the original order first
refunded The order has a recorded refund status

fulfilledQuantity is the number of card codes obtained so far; a partially completed order still returns no codes, and everything is delivered together once the whole order completes. needs_attention also returns errorCode: "delivery_needs_attention" and resolution: "manual_review". It means support is reviewing the remaining delivery and any refund; it does not mean a refund has been paid. Orders with codes already obtained, or with an unclear result, are not refunded in full automatically. Judge the payment status paid separately from the fulfilment status. After delivery, give the buyer the matching official redemption page from the table above.

The exception notification is order.delivery_attention, with the same signature, retry and eventId deduplication rules as order.delivered; it is sent once when an order first enters manual review. Notifications may arrive out of order; query the original order on arrival to confirm the latest state.

{
  "eventId": "order:example-order:order.delivery_attention",
  "event": "order.delivery_attention",
  "timestamp": 1787500000,
  "orderId": "example-order",
  "externalOrderId": "your-order-123",
  "productId": "selected-sku-id",
  "qty": 2,
  "deliveryType": "direct_code",
  "fulfillmentStatus": "needs_attention",
  "fulfilledQuantity": 1,
  "errorCode": "delivery_needs_attention",
  "resolution": "manual_review",
  "codes": [],
  "redeemUrl": null
}

Endpoints

GET /products — products and quotes

Returns products, the current user's unit price, the stock mode and the fulfilment status:

{
  "products": [{
    "id": "prod_chatgpt_plus",
    "name": "ChatGPT Plus (1个月)",
    "price": "21.56",
    "currency": "USDT",
    "stockMode": "on_demand",
    "availabilityStatus": "available",
    "deliveryMode": "automatic"
  }]
}

Common fields:

  • id: stable product ID, used for ordering and availability checks.
  • slug: display/SEO identifier that may change; not recommended as a basis for programmatic ordering.
  • price: the unit price for the level of the user behind the API key.
  • stockMode=inventory: limited by the platform's own inventory; stock is cached for at most 60 seconds and shown with a cap.
  • stockMode=on_demand: fulfilled per redemption request, stock=null.
  • stockMode=limited: finite stock whose quantity is not published, stock=null; run the availability check for the desired quantity, which is validated again at order time.
  • deliveryMode=automatic means automatic fulfilment after redemption; manual / manual_fallback means manual handling may be needed.
  • availabilityStatus=degraded means the automatic route is unavailable and a manual fallback applies; it does not promise instant automatic delivery.
  • redeemFields / redeemFieldsEn: what the end user must provide at redemption (identical to the redemption site's check response; [] for direct_code gift cards). Downstreams redeeming on behalf of customers should collect this before purchasing; key is the key name in redeemInfo.

GET /products/:id returns a single product in the same shape (bypassing the list cache); unknown or delisted products return 404 not_found. For example, Claude Pro:

{
  "id": "prod_claude_pro",
  "price": "22.00",
  "deliveryType": "redemption",
  "redeemFields": [
    { "key": "account_id", "label": "Claude Organization ID", "type": "text", "required": true }
  ]
}

GET /products/:id/availability?qty=50 — purchasability and quote

{
  "productId": "prod_chatgpt_plus",
  "qty": 50,
  "available": true,
  "unitPrice": "21.56",
  "stockMode": "on_demand",
  "availabilityStatus": "available",
  "deliveryMode": "automatic",
  "checkedAt": 1787500000
}

The result reflects this platform's current stock and routing configuration; it is not an SLA guarantee from upstream services.

GET /balance — balance

{ "balance": "1250.00", "currency": "USDT" }

POST /topup — idempotent top-up

New integrations must send externalTopupId, or put it in the Idempotency-Key header. Timeout retries must reuse the same value:

{ "externalTopupId": "topup-20260824-001", "amount": "500.00", "currency": "USDT" }
{
  "orderId": "...",
  "externalTopupId": "topup-20260824-001",
  "amount": "500.00",
  "status": "pending",
  "checkoutUrl": "https://checkout.tariapay.com/...",
  "reused": false
}

Reusing an idempotency key with a different amount or currency returns 409 idempotency_conflict. A top-up expires after about 15 minutes without payment; once credited, GET /balance is the source of truth, and you can also subscribe to topup.credited. USDC top-ups are credited 1:1 to the USDT balance.

Each account may start at most 20 top-up requests per hour (idempotent replays count; the account center, the REST API and MCP share the quota) and may have at most 5 unpaid top-ups at a time; beyond that the API returns 429 too_many_pending_topups: pay or let them expire before creating a new one.

During the compatibility window, legacy clients that send only amount / currency can still create a top-up, but the response carries HTTP Warning: 299 and idempotencyWarning; such requests cannot be retried safely. Add the idempotency key as soon as possible; a future major version will make it mandatory.

POST /orders — idempotent bulk order

{
  "externalOrderId": "your-order-123",
  "productId": "prod_chatgpt_plus",
  "qty": 50,
  "payment": "balance",
  "expectedUnitPrice": "21.56"
}

Balance mode returns immediately:

{
  "orderId": "...",
  "externalOrderId": "your-order-123",
  "amount": "1078.00",
  "status": "delivered",
  "codes": ["AB4K-9XQ2-..."],
  "redeemUrl": "https://redeemhub.uk",
  "reused": false
}

Single-payment mode returns checkoutUrl (the compatibility field paymentUrl may also appear); the order is usually pending at this point and no code is returned:

{
  "orderId": "...",
  "externalOrderId": "your-order-123",
  "amount": "1078.00",
  "status": "pending",
  "checkoutUrl": "https://checkout.tariapay.com/...",
  "paymentUrl": "https://checkout.tariapay.com/...",
  "redeemUrl": "https://redeemhub.uk",
  "reused": false
}

Give checkoutUrl only to the payer; never log or publish it.

Once the user has paid and Tariapay has confirmed it, the platform starts delivery; direct-code orders may stay paid, so also check fulfillmentStatus. Integrators should query GET /orders/:id or receive the order.delivered webhook, and ship downstream only after confirming all codes are usable. An order not confirmed within the payment window becomes expired; to restart an unpaid order, use a new externalOrderId.

Insufficient balance returns 402 insufficient_balance; stock, idempotency conflicts and price changes use 409 with the matching error codes.

GET /orders / GET /orders/:id — order queries

The list supports externalOrderId, limit=1..100 and cursor, and returns:

{ "orders": [], "nextCursor": null, "hasMore": false }

Pass the previous page's nextCursor through unchanged for the next page; do not parse the cursor. An invalid or expired cursor returns 400 invalid_cursor. List rows are summaries without codes or paymentUrl; the detail carries codes when status=delivered. Deliver both the code and redeemUrl for redemption products; direct_code products deliver the actual card code with redeemUrl=null.

GET /orders/:id example (a balance-paid platform-code order):

{
  "id": "…",
  "external_order_id": "your-order-123",
  "channel": "wholesale_api",
  "product_id": "prod_claude_pro",
  "order_number": "…",
  "qty": 2,
  "unit_price": "22.00",
  "amount": "44.00",
  "currency": "USDT",
  "status": "delivered",
  "deliveryType": "redemption",
  "created_at": 1787500000,
  "paid_at": 1787500000,
  "product_name": "Claude Pro (1个月)",
  "redeemUrl": "https://redeemhub.uk",
  "paymentUrl": null,
  "codes": ["AB4K-9XQ2-XXXX-XXXX", "CD7M-2PQ8-XXXX-XXXX"]
}

Direct-code orders also carry fulfillmentStatus, fulfilledQuantity, errorCode and resolution (see above). paymentUrl is returned only on pending single-payment orders.

Order statuses:

  • pending: awaiting payment or payment confirmation; no code in intent mode at this point.
  • paid: payment confirmed; for direct-code orders also check fulfillmentStatus, where needs_attention means manual handling.
  • delivered: codes issued and ready to deliver downstream.
  • expired: the payment window passed without confirmation; order again with a new externalOrderId.
  • refunded: refunded / reversed.

PUT /webhook — configure notifications

Only public HTTPS URLs are accepted; an empty string clears the webhook. A successful configuration returns a one-time secret (reconfiguring rotates it). The platform persists events first, then delivers immediately; the receiver must return 2xx within 5 seconds, otherwise delivery is retried with intervals starting at 30 seconds, doubling each time and capped at 6 hours, up to 10 attempts. Redirects are not followed and a 3xx counts as a failed delivery, so register the final URL (including details such as a trailing slash). X-AIBulk-Timestamp is the event creation time and can be used to reject stale replays.

Delivery body example:

{
  "eventId": "evt_...",
  "event": "order.delivered",
  "timestamp": 1787500000,
  "orderId": "...",
  "externalOrderId": "your-order-123",
  "codes": ["..."],
  "redeemUrl": "https://redeemhub.uk"
}

Request headers:

  • X-AIBulk-Signature: sha256=<HMAC-SHA256(secret, rawBody)>
  • X-AIBulk-Event-Id
  • X-AIBulk-Delivery-Id
  • X-AIBulk-Timestamp

Receivers should be idempotent on eventId and return 2xx after successful processing.

  • POST /webhook/test: creates and delivers a persisted test event.
  • GET /webhook/deliveries?limit=50&cursor=...: delivery history.
  • POST /webhook/deliveries/:id/retry: manually retry a failed or cancelled delivery.

Current events: order.delivered, order.delivery_attention, topup.credited, redemption.fulfilled, redemption.failed and webhook.test.

order.delivered means all customer codes are ready. For redemption products it does not mean the end user has completed the subscription redemption; redemption progress is checked on the redemption site at redeemUrl, or tracked by the downstream through the redemption endpoints / redemption.* events below. direct_code products deliver actual gift card codes and need no platform redemption.

POST /redemptions — server-side redemption on behalf of customers

For downstreams that buy codes and then redeem for end users themselves (for example, the buyer pays and submits a Claude Organization ID, and the downstream system redeems directly). It is the same validation and state machine as the redemption site's submit; the only differences:

  • Authenticated with the same Bearer API key and rate-limited per key (60 submissions per minute; queries share the 300 per minute limit), not per egress IP. Server-side downstreams sharing an egress IP with other tenants do not crowd each other out.
  • Only codes issued by orders of the user who owns this key are accepted; any other code returns 404 invalid_code without distinguishing "does not exist" from "not yours". direct_code gift card orders need no redemption and their codes are outside this endpoint's scope.

The redeemInfo keys come from the product's redeemFields[].key:

{ "code": "AB4K-9XQ2-XXXX-XXXX", "redeemInfo": { "account_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } }
{ "redemptionId": "6f3c…", "status": "pending", "code": "AB4K-9XQ2-XXXX-XXXX", "orderId": "…" }

Fulfilment is asynchronous: afterwards poll GET /redemptions/:redemptionId, or receive redemption.fulfilled / redemption.failed. Error codes match the redemption site:

HTTP error Meaning
400 missing_field A required field from redeemFields is empty
400 invalid_account_id The Claude Organization ID is not a valid UUID
400 invalid_session_json / invalid_telegram_username The product's details are malformed
400 test_code_only Codes of the test product cannot be redeemed
404 invalid_code The code is invalid, voided, or not from this account's orders
409 already_redeemed Already processing or completed; cannot be resubmitted
409 retry_conflict The previous failed attempt still holds supplier resources; check progress / retry later
410 code_expired The code has expired
503 session_validation_unavailable The ChatGPT session pre-check is temporarily unavailable; retryable

GET /redemptions/:redemptionId, GET /redemptions?code=… — redemption progress

{
  "redemptionId": "6f3c…",
  "code": "AB4K-9XQ2-XXXX-XXXX",
  "orderId": "…",
  "externalOrderId": "your-order-123",
  "productId": "prod_claude_pro",
  "status": "fulfilled",
  "retryable": false,
  "failureCode": null,
  "deliverable": "充值完成:ab***@example.com",
  "createdAt": 1787500000,
  "fulfilledAt": 1787500600
}
  • status: not_redeemed (the code has not been submitted, redemptionId=null; only appears on ?code= queries) → pending → processing → fulfilled | failed.
  • When failed, retryable=true and the same code can be POST /redemptions again with corrected redeemInfo; failureCode is fulfillment_failed (automatic fulfilment did not complete) or rejected_by_operator (rejected by manual review).
  • deliverable is returned only when fulfilled and is a masked completion note (for example a partially masked account); it never contains raw data other than card codes.
  • While processing, the server refreshes from upstream at most once every 60 seconds per redemption, so faster polling gains nothing; poll every 30–60 seconds, or rely on webhooks with polling as the fallback.
  • Querying a redemptionId that does not belong to this account returns 404 not_found.

redemption.fulfilled / redemption.failed use the same signature, retry and eventId deduplication rules as order.delivered; each redemption emits once every time it reaches a terminal state (a resubmission after failure starts a new attempt and emits again, with a different eventId, when it reaches a terminal state). They are sent only to accounts whose codes came from wholesale API orders, never for retail web orders or gift card orders. The payload contains neither the deliverable nor the submitted details:

{
  "eventId": "redemption:6f3c…:0:redemption.failed",
  "event": "redemption.failed",
  "timestamp": 1787500000,
  "orderId": "…",
  "externalOrderId": "your-order-123",
  "productId": "prod_claude_pro",
  "redemptionId": "6f3c…",
  "code": "AB4K-9XQ2-XXXX-XXXX",
  "status": "failed",
  "failureCode": "fulfillment_failed",
  "retryable": true,
  "fulfilledAt": null
}

For redemption.fulfilled, status is fulfilled, failureCode is null, retryable is false and fulfilledAt is the completion time. Events may arrive out of order or later than a polling result; on receipt, treat GET /redemptions/:redemptionId as the source of truth.

GET /transactions — balance ledger

Supports limit, cursor and type=topup|purchase|refund|adjust:

{
  "transactions": [{ "type": "purchase", "amount": "-1078.00", "balanceAfter": "172.00", "order_id": "..." }],
  "nextCursor": null,
  "hasMore": false
}

Error codes

HTTP error Where Handling
401 unauthorized All endpoints Key missing or invalid; regenerate it in the account center
400 invalid_input All endpoints Invalid parameters; see message
400 invalid_cursor List endpoints The cursor is invalid; start again from the first page
404 not_found Products, orders, redemptions The ID does not exist or does not belong to this account
402 insufficient_balance POST /orders in balance mode Top up or reduce the quantity
409 out_of_stock / insufficient_stock / stock_unavailable / fulfillment_unavailable POST /orders Nothing was debited; reduce the quantity or retry later
409 price_changed POST /orders Reconfirm with details.actualUnitPrice, then order again
409 duplicate_order POST /orders Concurrent collision; look up the order by externalOrderId
409 idempotency_conflict POST /orders, POST /topup The idempotency key was used with different parameters; use a new business ID
400 api_test_quantity / api_test_balance_only Test product The test product supports only REST, balance payment and qty=1
404 invalid_code /redemptions The code is invalid, voided or not from this account
400 missing_field / invalid_account_id / invalid_session_json / invalid_telegram_username POST /redemptions Fix the details according to redeemFields
409 already_redeemed / retry_conflict POST /redemptions Switch to polling; for retry_conflict, resubmit later
410 code_expired POST /redemptions The code has expired (API codes have no expiry, so this is rare)
409 webhook_not_configured POST /webhook/test Configure the webhook first
429 rate_limited All endpoints Back off according to Retry-After
429 too_many_pending_topups POST /topup 5 unpaid top-ups already exist; pay or wait about 15 minutes for them to expire
502 / 504 payment_provider_error / payment_provider_timeout intent orders, top-ups No order was left behind; retry with the same idempotency key
503 session_validation_unavailable POST /redemptions The ChatGPT session pre-check is temporarily unavailable; retryable
500 internal_error All endpoints retryable: true; contact support with the requestId

Retry guidance

  • Order creation timeout, disconnect or 5xx: retry with the same externalOrderId, the same productId and the same qty, or recover the original order with GET /orders?externalOrderId=....
  • duplicate_order: usually a concurrent retry collision; query GET /orders?externalOrderId=....
  • idempotency_conflict: the same idempotency ID was used with a different product, quantity or amount; stop retrying and use a new business ID.
  • price_changed: show or confirm the latest details.actualUnitPrice, then proceed with a new business decision.
  • rate_limited: back off according to Retry-After.
  • retry_conflict on server-side redemption: do not buy a new code; query GET /redemptions?code=… after 30–60 seconds and resubmit once status=failed and retryable=true.

Redemption site delivery notes

The following applies to platform codes with deliveryType=redemption; send both the code and redeemUrl to the end user. direct_code gift cards deliver the actual card code and skip these platform redemption steps:

  1. The end user opens redeemUrl.
  2. Enters or pastes the code.
  3. Submits the redemption details the product requires, as prompted on the page.
  4. For automatically fulfilled products, processing may take a few minutes after redemption; the end user can check the status on the redemption site.

The redemption site is a neutral-brand domain suited to resale scenarios. Never deliver only the code without redeemUrl.

If the downstream collects the details itself and redeems on behalf of end users, use POST /redemptions and GET /redemptions/:redemptionId above instead of the redemption site's public endpoints (those are rate-limited per client IP and will be throttled quickly when a server egress IP is shared with other tenants).

MCP

https://api.aibulk.uk/mcp uses the same Bearer key. Tools include list_products, check_availability, get_balance, create_topup, purchase_codes, get_order, list_orders and list_transactions.

Grok redemption account

Super Grok and Grok Heavy redemptions require the full Session JSON, or the UUID from its session.userId. Sign in to Grok and open Grok Session to get it; do not use sessionId, xUserId or an email address as the top-up account.

When using the redemption API / MCP, call check first and use the returned redeemFields. The grok_user_id field accepts a UUID string, or the full JSON string containing status: "authenticated" and session.userId. The page shows the recognised userId; confirm the target account before submitting.

The input format is validated on both client and server, and the server keeps only the normalised userId. A malformed input returns 400 before the code is consumed, and can be retried after correction. This check does not prove the account's live login state or top-up eligibility; a successful submission means the redemption request was accepted, not that the subscription has been applied.