G2Bulk API documentation — REST API and SMM panel reference

G2Bulk
Overview

Introduction

The G2Bulk API lets you resell game top-ups, voucher codes and digital goods straight from your own code. Every call is plain JSON over HTTPS, so there's no SDK to install. Orders are idempotent, and whatever you buy comes back instantly or after a short poll.

Every request goes to the versioned base URL below. You fund your wallet and get an API key through the Telegram bot @G2BULKBOT, so there's no separate dashboard to sign up for.

BASEhttps://api.g2bulk.com/v1/

How a purchase flows

  • Browse /category and /products to find what to sell.
  • Call the purchase or order endpoint with your API key.
  • Get codes back instantly, or poll the delivery URL until ready.
  • Reconcile against /transactions, which records every balance change.
Prices move with exchange rates. Always read the current unit price from the product or catalogue endpoint right before you charge.
Security

Authentication

Protected endpoints expect your secret key in an X-API-Key header. Public catalog endpoints (categories, products, games, catalogue) need no key at all.

Keys are tied to your wallet balance — treat them like a password and never ship one in client-side code.

Brute-force protection. Repeated failed authentication attempts trigger a permanent IP ban. Cache your key; don't loop on 401s.
Request header
header
X-API-Key: your_api_key_here
Verify your key
cURL
curl -X GET https://api.g2bulk.com/v1/getMe \
  -H "X-API-Key: your_api_key_here"
User

Current user

GET/v1/getMeauth

Returns the profile and live wallet balance for the key holder. Call it on boot to confirm credentials and check available funds before charging.

response200 OK
{
  "success": true,
  "user_id": 123456789,
  "username": "johndoe",
  "first_name": "John Doe",
  "balance": 8.74
}
Catalog

Categories

GET/v1/category
GET/v1/category/:id

List every product category, or pass an :id to get the products inside one. Public — no key required. Each category carries a live product_count.

response200 OK
{
  "success": true,
  "categories": [
    {
      "id": 1,
      "title": "PUBG Mobile UC Vouchers",
      "description": "",
      "image_url": "https://example.com/pubg.png",
      "product_count": 11
    },
    {
      "id": 2,
      "title": "Razer Gold Accounts",
      "description": "",
      "image_url": null,
      "product_count": 5
    }
  ]
}
Catalog

Products

GET/v1/products
GET/v1/products/:id

Every product with its current unit_price, printed face_value (the denomination on the voucher — null when not set) and live stock. Fetch the single-product route before a purchase so you charge against the freshest price.

response200 OK
{
  "success": true,
  "products": [
    {
      "id": 1,
      "title": "60 UC Voucher",
      "description": "",
      "category_id": 1,
      "category_title": "PUBG Mobile UC Vouchers",
      "unit_price": 0.84,
      "face_value": 1,
      "image_url": null,
      "stock": 1006
    }
  ]
}
Purchases

Buy a product

POST/v1/products/:id/purchaseauth

Charges your wallet and reserves stock. Send the quantity in the body. The response is either COMPLETED with codes attached, or PENDING with a poll_url to follow.

BodyTypeDescription
quantity reqintegerNumber of units to buy.
Idempotency. Optionally pass an X-Idempotency-Key to dedupe retries — the same key within a 30-minute window returns the original response instead of charging twice. The key must be a 36-character UUID (e.g. 550e8400-e29b-41d4-a716-446655440000); keys of any other length are rejected with an error. Omit the header entirely to skip idempotency.

When the status is PENDING, follow poll_url until it returns 200 with the codes — typically within 5–10 seconds. A pre-order mode for non-instant products is in the works and will reuse the same poll_url pattern.

cURL
curl -X POST https://api.g2bulk.com/v1/products/1/purchase \
  -H "X-API-Key: your_api_key_here" \
  -H "X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{"quantity": 5}'
completed200 OK
{
  "success": true,
  "order_id": 123,
  "transaction_id": 456,
  "product_id": 1,
  "product_title": "60 UC Voucher",
  "status": "COMPLETED",
  "delivery_items": ["KEY1", "KEY2", "KEY3"]
}
pending200 PENDING
{
  "success": true,
  "order_id": 124,
  "transaction_id": 457,
  "product_id": 2,
  "product_title": "PSN $20 Gift Card",
  "status": "PENDING",
  "delivery_items": null,
  "poll_url": "/v1/orders/124/delivery"
}
Purchases

Orders

GET/v1/ordersauth
GET/v1/orders/:id

Paginated order history, newest first. Pass page, limit (max 100) and an optional search query, or fetch a single order by id.

cURL
curl -X GET "https://api.g2bulk.com/v1/orders?page=1&limit=50" \
  -H "X-API-Key: your_api_key_here"
response200 OK
{
  "success": true,
  "orders": [
    {
      "id": 1,
      "user_id": 123456789,
      "product_id": 1,
      "product_title": "60 UC Voucher",
      "quantity": 1,
      "total_price": "0.840",
      "status": "COMPLETED",
      "description": "Purchase from G2Bulk Bot",
      "created_at": "2025-10-19T05:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 120,
    "total_pages": 3
  }
}
Purchases

Delivery polling

GET/v1/orders/:id/deliveryauth

For PENDING orders, poll this every 2–5 seconds. The HTTP status tells you where things stand without parsing the body:

CodeMeaning
200Codes are ready in delivery_items.
202Still PROCESSING — try again shortly.
410Terminal failure — auto-refunded.
404Order not found, or not yours.
Most orders finish within 5–10 seconds. Stop polling on any 200 or 410. Delivery items stay available for 30 days after created_at — save them on your side before then.
ready200 OK
{
  "success": true,
  "order_id": 124,
  "product_id": 2,
  "product_title": "PSN $20 Gift Card",
  "quantity": 1,
  "status": "COMPLETED",
  "delivery_items": ["XXXX-XXXX-XXXX"]
}
processing202
{
  "success": true,
  "order_id": 124,
  "status": "PROCESSING",
  "message": "Order is still being fulfilled. Try again shortly."
}
refunded410
{
  "success": false,
  "order_id": 124,
  "status": "REFUNDED",
  "refunded": true,
  "message": "Order failed and was refunded automatically."
}
Purchases

Transactions

GET/v1/transactionsauth

The ledger behind your wallet — every charge and top-up with balance_before / balance_after, so reconciliation is exact. Paginated like orders, with an optional search filter.

TypeMeaning
add_balanceBalance added — manual addition or refund.
charge_balanceBalance deducted — purchase or top-up.
response200 OK
{
  "success": true,
  "data": [
    {
      "id": 45,
      "user_id": 123456789,
      "transaction_type": "charge_balance",
      "amount": "1.126",
      "balance_before": "10.000",
      "balance_after": "8.874",
      "status": "success",
      "description": "Purchase PUBG Mobile 60 UC",
      "created_at": "2025-10-19T05:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 200,
    "total_pages": 4
  }
}
Game top-up

Games & fields

GET/v1/games
POST/v1/games/fields
POST/v1/games/servers

Direct top-up writes credits straight to a player account — no voucher code. Start with /games for the supported list, then ask /games/fields which inputs a title needs (user id, server, charname).

Some games also require a server. /games/servers returns them, or a 403 meaning no server needed — that's not an error, just check /games/fields for the real requirements.

BodyTypeDescription
game reqstringGame code identifier, e.g. mlbb.
GET /games200 OK
{
  "success": true,
  "games": [
    {
      "id": 1,
      "code": "pubg_mobile",
      "name": "PUBG Mobile",
      "image_url": "/images/pubg_mobile.png"
    },
    {
      "id": 2,
      "code": "free_fire",
      "name": "Free Fire",
      "image_url": "/images/free_fire.png"
    }
  ]
}
POST /games/fields200 OK
{
  "code": "200",
  "info": {
    "fields": ["userid", "serverid"],
    "notes": "Not available for Indonesia users"
  }
}
POST /games/servers200 OK
{
  "code": "200",
  "servers": {
    "SouthEast Asia": "SouthEast Asia",
    "America": "America",
    "Europe": "Europe"
  }
}
Game top-up

Validate player

POST/v1/games/checkPlayerId

Confirm a player id resolves to a real account before you charge — it returns the in-game name so the buyer can sanity-check the recipient.

BodyTypeDescription
game reqstringGame code identifier.
user_id reqstringPlayer's user ID.
server_id condstringServer identifier, if the game uses one.
charname condstringGame-specific account/character identifier.
charname means different things per game (character, server or account id). Read the notes field from /games/fields to know which.
request
{
  "game": "mlbb",
  "user_id": "123456789",
  "server_id": "2001",
  "charname": "charname"
}
valid200 OK
{
  "valid": "valid",
  "name": "John Doe",
  "openid": "41581795132966184"
}
Game top-up

Catalogue & ETA

GET/v1/games/:code/catalogue
POST/v1/games/eta

The catalogue lists every denomination with its live amount. The ETA endpoint estimates how long a top-up takes so you can set buyer expectations.

ETA labels

LabelRange
instant< 30 seconds
less_than_1_minute30s – 1 min
less_than_2_minutes1 – 2 min
less_than_5_minutes2 – 5 min
less_than_10_minutes5 – 10 min
less_than_30_minutes10 – 30 min
more_than_30_minutesover 30 min
no_datanot enough data yet
catalogue200 OK
{
  "success": true,
  "game": {
    "code": "pubgm",
    "name": "PUBG Mobile",
    "image_url": "/images/pubgm.png"
  },
  "catalogues": [
    { "id": 1, "name": "60 UC",  "amount": 0.88 },
    { "id": 2, "name": "120 UC", "amount": 1.75 }
  ]
}
eta200 OK
{
  "success": true,
  "estimated_time": {
    "label": "less_than_5_minutes",
    "display": "Less than 5 minutes",
    "median_seconds": 187
  }
}
Game top-up

Place a top-up order

POST/v1/games/:code/orderauth
POST/v1/games/order/status

The player id is validated and balance checked before the order is created. It comes back PENDING; track it with the status endpoint or a webhook.

BodyTypeDescription
catalogue_name reqstringDenomination, e.g. "60 UC".
player_id reqstringPlayer's in-game ID.
server_id condstringServer identifier if applicable.
charname condstringGame-specific character identifier.
remark optstringYour own order note.
callback_url optstringWebhook for status updates.
Idempotency. Optionally pass an X-Idempotency-Key to dedupe retries — the same key within a 30-minute window returns the original response instead of charging twice. The key must be a 36-character UUID (e.g. 550e8400-e29b-41d4-a716-446655440000); keys of any other length are rejected with an error. Omit the header entirely to skip idempotency.

Status values

PENDING queued   PROCESSING in flight   COMPLETED delivered   FAILED refunded

request body
{
  "catalogue_name": "60 UC",
  "player_id": "12345678",
  "server_id": "2001",
  "charname": "charname",
  "remark": "Optional note",
  "callback_url": "https://your-domain.com/webhook/order-status"
}
response200 OK
{
  "success": true,
  "message": "Order created successfully. We are processing your order.",
  "order": {
    "order_id": 42,
    "game": "PUBG Mobile",
    "catalogue": "60 UC",
    "player_id": "12345678",
    "player_name": "PlayerName",
    "price": 0.88,
    "status": "PENDING",
    "callback_url": "https://your-domain.com/webhook/order-status"
  }
}
Game top-up

Webhooks

Pass a callback_url on an order and we'll POST to it the moment the order reaches a terminal state — no polling required.

PropertyDetail
MethodPOST with JSON body
Timeout10 seconds
RetrySingle retry on failure
Fires onCOMPLETED or FAILED
Respond 2xx within 10s. We retry once on failure, so make your handler idempotent against duplicate notifications.
callback body → you
{
  "order_id": 42,
  "game_code": "pubgm",
  "game_name": "PUBG Mobile",
  "player_id": "12345678",
  "player_name": "PlayerName",
  "server_id": "2001",
  "denom_id": "60 UC",
  "price": 0.88,
  "status": "COMPLETED",
  "message": "Order completed successfully",
  "remark": "your order remark",
  "timestamp": "2024-01-15T10:30:00Z"
}
GET /games/orders200 OK
{
  "success": true,
  "orders": [
    {
      "order_id": 42,
      "game_code": "pubgm",
      "game_name": "PUBG Mobile",
      "player_id": "12345678",
      "player_name": "PlayerOne",
      "denom_id": "60",
      "price": 0.84,
      "status": "completed",
      "is_refunded": false,
      "created_at": "2025-10-19T05:30:00Z",
      "completed_at": "2025-10-19T05:31:00Z"
    }
  ],
  "pagination": { "page": 1, "limit": 50, "total": 85, "total_pages": 2 }
}
Reference

Status & limits

Standard HTTP semantics throughout — the body always carries a success boolean and a human message on errors.

LimitValue
Rate limit1000 requests / 10s per key
Idempotency window30 minutes
Pending poll cadenceevery 2–5 seconds
Typical completion5–10 seconds
Delivery window30 days after created_at
Page sizemax 100 items
Auth failurespermanent IP ban

HTTP status codes

CodeMeaning
200OK — request successful.
202Accepted — still pending/processing, poll again.
400Bad request — invalid format or parameters.
401Unauthorized — auth failed or key invalid.
404Not found — resource does not exist.
410Gone — failed, refunded or cancelled (terminal).
429Too many requests — rate limit exceeded.
500Internal server error.
Use exponential backoff on 429 and 5xx responses. Repeated failed auth attempts trigger a permanent IP ban — validate your key before sending.
error shape4xx
{
  "success": false,
  "message": "Order failed and was refunded automatically."
}
no servers403
{
  "detail": {
    "code": "403",
    "message": "Game does not requires any servers"
  }
}
SMM Panel

SMM panel API

If you already run an SMM panel, G2Bulk speaks the same protocol you're used to. There's one endpoint, every action is a POST, and you pick what to do with an action field in the body.

POST/api/v2

The base URL is https://api.g2bulk.com/api/v2. Send the body as JSON or form-urlencoded, whichever your panel already uses.

Authentication

Your key goes in the request body as key, not in a header. Grab one from the Telegram bot @G2BULKBOT.

This is the one spot G2Bulk differs from the main API: the SMM endpoint reads key from the body, not the X-API-Key header.

Topping up a game

For top-ups, put the player details in link and keep quantity at 1. Depending on the game, link is the player ID on its own, Player ID|Server ID, or Player ID|Server ID|Charname.

cURL
curl https://api.g2bulk.com/api/v2 \
  -d key=your_api_key_here \
  -d action=balance
SMM Panel

Get services

POST/api/v2key

Lists every service you can order, each with its live rate in USD. Pass category to narrow it to one game.

BodyTypeDescription
key reqstringYour API key.
action reqstringMust be services.
category optstringFilter by game name.
cURL
curl https://api.g2bulk.com/api/v2 \
  -d key=your_api_key_here \
  -d action=services
response200 OK
[
  {
    "service": 1,
    "name": "PUBG Mobile 60 UC",
    "type": "Package",
    "category": "PUBG Mobile",
    "rate": "0.88",
    "min": "1",
    "max": "1",
    "refill": false,
    "cancel": false
  }
]
SMM Panel

Add order

POST/api/v2key

Places a top-up. The order comes back with an order id you poll for status. Balance is checked first, so a failure returns an error instead.

BodyTypeDescription
key reqstringYour API key.
action reqstringMust be add.
service reqintegerService ID from services.
link reqstringPlayer ID, or Player ID|Server ID|Charname.
quantity reqintegerAlways 1 for game top-ups.
cURL
curl https://api.g2bulk.com/api/v2 \
  -d key=your_api_key_here \
  -d action=add \
  -d service=1 \
  -d "link=12345678|2001" \
  -d quantity=1
created200 OK
{ "order": 12345 }
rejectederror
{ "error": "Insufficient balance" }
SMM Panel

Order status

POST/api/v2key

Processing happens in the background, so poll this to follow an order. Send one order, or a comma-separated orders list (up to 100) to check a batch in one call.

BodyTypeDescription
key reqstringYour API key.
action reqstringMust be status.
order condintegerA single order ID.
orders condstringComma-separated IDs, up to 100.
single200 OK
{
  "charge": "0.88",
  "start_count": "0",
  "status": "Completed",
  "remains": "0",
  "currency": "USD"
}
batch · orders=12345,12346200 OK
{
  "12345": {
    "charge": "0.88",
    "start_count": "0",
    "status": "Completed",
    "remains": "0",
    "currency": "USD"
  },
  "12346": {
    "charge": "1.75",
    "start_count": "0",
    "status": "Pending",
    "remains": "0",
    "currency": "USD"
  }
}
SMM Panel

Check balance

POST/api/v2key

Returns your current wallet balance and currency.

BodyTypeDescription
key reqstringYour API key.
action reqstringMust be balance.
cURL
curl https://api.g2bulk.com/api/v2 \
  -d key=your_api_key_here \
  -d action=balance
response200 OK
{
  "balance": "150.5000",
  "currency": "USD"
}
SMM Panel

Status & errors

Order status is one of these values:

StatusMeaning
PendingWaiting to be processed.
In progressBeing processed now.
CompletedDelivered successfully.
PartialPartially completed.
CanceledCanceled or failed.
Errors still come back as HTTP 200 with an error field — check for error before reading the rest of the response.
error shapeerror
{ "error": "Insufficient balance" }
common errors
"Invalid API key"
"Invalid action"
"Service ID is required"
"Link (Player ID) is required"
"Service not found or inactive"
"Game is currently unavailable"
"Insufficient balance"
"Order not found"