Gacha Machine API Documentation
🎮 Demo Repository: Check out the Gacha Starter Demo for a working example implementation.
Overview
The gacha machine API allows users to purchase mystery packs containing NFTs, open them to receive random NFTs, and optionally sell them back for USDC. This guide covers the standard $50 pack flow.
Devnet USDC Faucet
Use this faucet to get the USDC token used on devnet: https://spl-token-faucet.com/?token-name=USDC-Dev
Devnet
You can see all of the API calls in action on the live devnet site: https://dev-gacha.collectorcrypt.com
🔐 Authentication
Each request to the API must include a valid x-api-key header.
Why It's Required
- Ensures only authorized partners can access the machine.
- Automatically tags your transactions with a unique
memoprefix based on your API key. - Allows filtering and tracking all spin/win/buyback activity by your key using the
slugquery parameter.
Example Header
curl -X POST "https://gacha.collectorcrypt.com/api/generatePack" \
-H "Content-Type: application/json" \
-H "x-api-key: API_KEY" \
-d '{
"playerAddress": "HWPRgtDGpBm8mByTGS57BWCsijMo53qPPSbskWDukfTc"
}'
Endpoints
Transactions can be submitted directly to the blockchain after they've been signed.
Generate Pack - /api/generatePack
Creates a transaction for purchasing a pack. The user needs to sign this transaction to begin the purchase. Generates a partially signed transaction that transfers 50 USDC from the player to the gacha wallet. Returns a unique memo identifier and the transaction that needs to be signed by the player.
Method: POST
Request Body:
{
"playerAddress": "wallet_public_key",
"packType": "pokemon_50", // optional, defaults to "pokemon_50"; use "pokemon_250" for legendary packs
"turbo": true, // optional; when true, Common wins are auto-sold during openPack
"altPlayerAddress": "wallet_public_key" // optional parameter that will send the card to another wallet.
}
Response:
{
"memo": "slug-uuid",
"transaction": "base64_encoded_transaction"
}
Error Responses:
- 400: Invalid request body or player address
- 403: Access denied (for legendary packs without proper NFT ownership)
- 500: Machine empty, too many packs open, or emergency stop active
Note: turbo mode will automatically process a buyback for Common wins when calling /api/openPack.
Generate Yolo Packs - /api/generateYoloPacks
Creates multiple transactions for purchasing multiple packs at once. Each pack gets its own transaction and memo that needs to be signed individually by the player.
Method: POST
Request Body:
{
"count": 2,
"packType": "pokemon_50",
"playerAddress": "5Kp9FbQ2oEKt6XnEvbovsnkpewW3wm9bg4ff3c3hAPbt",
"turbo": true, // optional; applies to all generated packs, auto-sells Common wins during openPack
"altPlayerAddress": "wallet_public_key" // optional; the won NFT is sent to this wallet instead of playerAddress
}
Response:
{
"yoloId": "uuid",
"count": 2,
"transactions": [
{
"memo": "slug-uuid-1",
"transaction": "base64_encoded_transaction_1"
},
{
"memo": "slug-uuid-2",
"transaction": "base64_encoded_transaction_2"
}
]
}
Error Responses:
- 400: Invalid request body, player address, or count (must be between 1 and 100)
- 403: Access denied (for legendary packs without proper NFT ownership)
- 500: Machine empty, too many packs open, or emergency stop active
What it does:
Generates multiple pack purchase transactions at once (up to 100). Each transaction must be signed separately using /api/submitTransaction, then each memo can be used with /api/openPack to receive the NFTs.
Note: When turbo is true, all generated packs will auto-sell Common wins during /api/openPack.
Open Pack - /api/openPack
Opens a purchased pack and sends an NFT to the player. Randomly selects an NFT based on rarity tiers (1% Epic, 4% High, 15% Mid, 80% Low), transfers it to the player, and returns the NFT metadata. For turbo mode with low-tier wins, automatically processes a buyback.
Method: POST
Request Body:
{
"memo": "slug-uuid-string"
}
Response (Success):
{
"success": true,
"transactionSignature": "nft_transfer_transaction_hash",
"nft_address": "nft_mint_address",
"nftWon": {
"content": {
"metadata": {
"name": "Card Name",
"description": "Card Description",
"attributes": [...]
}
}
},
"points": 0,
"roll": 12345678,
"rarity": "Epic"
}
rarity is one of "Epic", "Rare", "Uncommon", or "Common".
Response (Turbo Mode Buyback): When turbo mode is enabled and the pack rolls Common, the NFT is auto-sold for USDC and the response includes additional fields:
{
"success": true,
"transactionSignature": "nft_transfer_transaction_hash",
"nft_address": "nft_mint_address",
"nftWon": { /* NFT metadata */ },
"points": 0,
"code": "TURBO_MODE_BUYBACK",
"buybackAmount": 42500000,
"roll": 87654321,
"rarity": "Common"
}
Response (Waiting for Payment):
{
"success": true,
"code": "WAITING_FOR_WEBHOOK",
"memo": "slug-uuid-string"
}
Response (Already Opened):
Returns the original response with the same fields, including the existing transactionSignature, nftWon, points, and rarity.
Error Responses:
- 400: No spin transaction found or no NFTs available
- 500: Machine empty or processing error
Buyback - /api/buyback
Generates a transaction to sell an NFT back for a percentage of its insured value in USDC. Verifies the player won the NFT within 72 hours, then creates a transaction that transfers the NFT back to the original prize wallet and sends USDC to the player (or to altRecipient if provided). The transaction needs to be signed by the player and submitted to the blockchain.
Method: POST
Request Body:
{
"playerAddress": "wallet_public_key",
"nftAddress": "nft_mint_address",
"altRecipient": "wallet_public_key" // optional; if provided, USDC refund is sent here instead of playerAddress
}
Response:
{
"success": true,
"serializedTransaction": "base64_encoded_transaction",
"refundAmount": 42500000,
"memo": "original-memo-string"
}
Error Responses:
- 400: Invalid addresses, NFT not found, or outside 72-hour buyback window
- 500: Transaction building failed
Buyback Check - /api/buyback/check
Checks if an NFT buyback has completed.
Method: GET
Query Parameters:
memo(required): The memo from the original pack purchase
Example Request:
GET /api/buyback/check?memo=953cc94e-fd51-4f5f-bbcc-5a35faf7df65
Response (Buyback Exists):
{
"exists": true,
"playerWallet": "GfFAJnHnSgP7C2FQZLz6ogpdTV6Y7259f83qFFm9wxKm",
"nft": "H9ZXYkudxn6qhyp5S25jm5SrA8Vnu8naSfvymm9TptLA",
"transactionSignature": "3Dq2kH65vqEzpBKczyDLzNYS8sXmtS5GS9MYTwjVaPM4tqVxjtPGnQG4mKpSPa5iTzqomhFgo9nYs3grRNPp68Fh",
"buybackAmount": "90000",
"createdAt": "2025-05-26T17:32:33.588Z",
"status": "complete"
}
status is "complete" when the buyback has been confirmed via webhook, or empty string "" when still pending.
Response (Buyback Not Found):
{
"exists": false,
"message": "No buyback transaction found for this memo"
}
Error Responses:
- 400: Missing memo parameter
- 500: Database query failed
What it does: Checks if a buyback transaction has been completed for the given memo. Returns the buyback details if it exists, or indicates that no buyback was found.
Get Status - /api/status
Checks the current operational status of all gacha machines.
Method: GET
Request: No parameters required
Response:
{
"machineStatus": "running",
"legendaryStatus": "open",
"eliteStatus": "open",
"freePacksStatus": "closed",
"sportsStatus": "closed",
"gachas": [
{
"code": "pokemon_50",
"name": "Elite Pack",
"price": 50,
"status": "open",
"isOpen": true
}
]
}
machineStatus values:
"running"— no emergency stop active"stopped"— emergency stop active, no purchases possible
status / individual machine values:
"open"— machine is accepting purchases"closed"— machine is temporarily closed
Get Stock - /api/stock
Returns current NFT inventory counts by rarity for each machine.
Method: GET
Response:
{
"elite": { "common": 34, "uncommon": 42, "rare": 86, "epic": 124 },
"legendary": { "common": 98, "uncommon": 85, "rare": 34, "epic": 24 },
"sports": { "common": 10, "uncommon": 5, "rare": 3, "epic": 1 },
"pokemon_50": { "common": 34, "uncommon": 42, "rare": 86, "epic": 124 },
"pokemon_250": { "common": 98, "uncommon": 85, "rare": 34, "epic": 24 }
}
The response includes both legacy named keys (elite, legendary, sports) and dynamic per-code keys for all machines.
Get Machines - /api/machines
Returns full configuration and live stock for every machine. This is the recommended single call to build a machine-picker UI — it includes odds, pricing, and current inventory all at once.
Method: GET
Response:
{
"machines": [
{
"code": "pokemon_50",
"name": "Elite Pack",
"shortName": "Elite",
"image": "image_url",
"thumbnailUrl": "thumbnail_url",
"videoSrc": "video_url",
"videoHevc": "hevc_video_url",
"public": true,
"price": 50,
"contains": "description of contents",
"instantBuyback": 85,
"freeSpins": false,
"turboMode": false,
"pointsMultiplier": 1,
"odds": {
"epic": 0.01,
"rare": 0.04,
"uncommon": 0.15,
"common": 0.80
},
"tierRanges": {
"common": { "start": 0, "end": 25 },
"uncommon": { "start": 25, "end": 75 },
"rare": { "start": 75, "end": 200 },
"epic": { "start": 200, "end": 99999 }
},
"stock": {
"common": 34,
"uncommon": 42,
"rare": 86,
"epic": 124
}
}
]
}
Get NFTs - /api/getNfts
Retrieves NFTs available in the gacha machine filtered by code and optionally by rarity.
Method: GET
Query Parameters:
code(optional): The pack/collection code (e.g., "pokemon_50"). Defaults topokemon_50.rarity(optional): Filter by rarity tier - "common", "uncommon", "rare", or "epic"page(optional): Page number for pagination (1-indexed). When provided, the response is paginated.limit(optional): Number of NFTs per page. Only used whenpageis provided.
Example Requests:
GET /api/getNfts?code=pokemon_50
GET /api/getNfts?code=pokemon_50&rarity=epic
GET /api/getNfts?code=pokemon_50&rarity=rare&page=1&limit=50
Response (non-paginated):
{
"nfts": [
{
"nft_address": "nft_mint_address",
"name": "Card Name",
"description": "Card Description",
"rarity": "epic",
"attributes": [...],
"image": "image_url",
"insured_value": 50000000
}
]
}
Response (paginated, when page is provided):
{
"nfts": [ /* ... */ ],
"hasMore": true,
"page": 1,
"limit": 50
}
Error Responses:
- 500: Database query failed
Submit Transaction - /api/submitTransaction
Submits a signed transaction to the Solana blockchain.
Method: POST
Request Body:
{
"signedTransaction": "base64_encoded_signed_transaction"
}
Response (Success):
{
"success": true,
"signature": "transaction_signature_hash",
"confirmationStatus": "confirmed"
}
confirmationStatus is one of "confirmed", "finalized", or "submitted" (the last one means the transaction was sent but not yet confirmed within the valid block height window).
Example Request:
curl -X POST "/api/submitTransaction" \
-H "Content-Type: application/json" \
-H "x-api-key: API_KEY" \
-d '{
"signedTransaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABBqF55bPp..."
}'
Error Responses:
- 400: Missing or invalid signed transaction
- 500: Transaction submission failed or blockchain error
What it does:
Takes a fully signed transaction (from /api/generatePack or /api/buyback after user signing) and submits it to the Solana blockchain. Returns the transaction signature for tracking.
Generate Gift - /api/generateGift
Creates a transaction to gift packs to another wallet. The sender pays for the packs; after submitting the signed transaction, the receiver will have gifted packs available to open on gacha.collectorcrypt.com.
Method: POST
Request Body:
{
"sender": "HWPRgtDGpBm8mByTGS57BWCsijMo53qPPSbskWDukfTc",
"receiver": "daxsSgG4iPvhxWr2jrY76HKtRSjWjQLFveKHdmZKJFc",
"packType": "pokemon_50",
"count": 1 // optional; must be an integer between 1 and 20, defaults to 1
}
Response:
{
"giftId": 2,
"memo": "gift-86646f0c-2a61-4a0a-89af-495d266684c3",
"transaction": "base64_encoded_transaction",
"totalCost": 50,
"count": 1,
"packType": "pokemon_50"
}
Example Request:
curl -X POST "https://gacha.collectorcrypt.com/api/generateGift" \
-H "Content-Type: application/json" \
-d '{
"sender":"HWPRgtDGpBm8mByTGS57BWCsijMo53qPPSbskWDukfTc",
"receiver":"daxsSgG4iPvhxWr2jrY76HKtRSjWjQLFveKHdmZKJFc",
"packType":"pokemon_50",
"count":1
}'
What it does:
Returns a transaction. Sign it, then POST it to /api/submitTransaction. Once confirmed, the receiver will have gifted packs to open on gacha.collectorcrypt.com.
Get Gifted - /api/getGifted
Fetches gift history for a wallet — both sent and received gift packs, totals grouped by pack type, and a per-pack opening history for received gifts.
Method: GET
Query Parameters:
wallet(required): wallet public key
Example Request:
GET /api/getGifted?wallet=HWPRgtDGpBm8mByTGS57BWCsijMo53qPPSbskWDukfTc
Response:
{
"wallet": "HWPRgtDGpBm8mByTGS57BWCsijMo53qPPSbskWDukfTc",
"sent": [
{
"id": 1,
"memo": "gift-cc-uuid",
"sender": "wallet_public_key",
"receiver": "wallet_public_key",
"pack_type": "pokemon_50",
"count": 1,
"cost": 50,
"webhook_confirmed": true,
"status": "confirmed",
"transaction_signature": "tx_signature",
"created_at": "2025-01-01T00:00:00.000Z"
}
],
"received": [ /* same shape as sent */ ],
"totals": {
"sent": [
{ "pack_type": "pokemon_50", "count": 5, "cost": 250 }
],
"received": [
{ "pack_type": "pokemon_50", "count": 3, "cost": 150 }
]
},
"perpack": [
{
"pack_opened_id": 123,
"purchase_type": "gift",
"purchase_id": 1,
"wallet": "receiver_wallet",
"pack_type": "pokemon_50",
"opened_date": "2025-01-02T00:00:00.000Z",
"purchase_signature": "gift_tx_signature",
"send_nft_txn": "send_nft_signature",
"memo": "cc-uuid",
"insured_value": 50000000,
"pack_quantity": 1
}
]
}
The perpack array tracks each individual gift pack opening for the receiver wallet.
Purchased Packs Summary - /api/purchasedPacks
Returns pack counts for a wallet (purchased vs gifted, used vs unused) grouped by packType.
Method: GET
Query Parameters:
wallet(required): wallet public key
Example Request:
GET /api/purchasedPacks?wallet=daxsSgG4iPvhxWr2jrY76HKtRSjWjQLFveKHdmZKJFc
Response:
{
"pokemon_50": {
"purchased": 0,
"gifted": 2,
"used_purchased": 0,
"used_gifted": 1
},
"pokemon_250": {
"purchased": 0,
"gifted": 0,
"used_purchased": 0,
"used_gifted": 0
}
}
Generate Purchased Pack Challenge - /api/generatePurchasedPack
Generates a message-to-sign challenge for opening a purchased/gifted pack.
Gift opening flow
- Call
/api/generatePurchasedPackto get anonce+messageToSign. - Have the user sign
messageToSignwith their wallet. - Call
/api/usePurchasedPack:
- non-ledger:
{ signedMessage, signature, publicKey, nonce, packType } - ledger:
{ ledger: true, transactionSignature, publicKey, nonce, packType, turbo }to receive amemo.
- Call
/api/openPackwith thememoto send the NFT.
Reference UI (dev)
Method: POST
Request Body:
{
"publicKey": "daxsSgG4iPvhxWr2jrY76HKtRSjWjQLFveKHdmZKJFc",
"packType": "pokemon_50",
"ledger": true
}
ledger(optional, default:false): whentrue, the server returns a Ledger-compatible challenge flow.
Response:
{
"success": true,
"nonce": "23a8bb98-d975-454f-a410-28e072247afa",
"expiry": "2026-02-25T20:48:42.317Z",
"messageToSign": "Open my purchased pokemon_50 pack!\n\nNonce:23a8bb98-d975-454f-a410-28e072247afa",
"ledger": true
}
Use Purchased Pack - /api/usePurchasedPack
Consumes a purchased/gifted pack after the wallet signs the challenge message. Returns a memo you can open like normal (via /api/openPack).
Method: POST
Request Body:
{
"ledger": true,
"nonce": "7b24cb5b-f3f6-4f6d-b039-6d1bc05b6060",
"packType": "pokemon_50",
"publicKey": "HWPRgtDGpBm8mByTGS57BWCsijMo53qPPSbskWDukfTc",
"transactionSignature": "signed_transaction",
"turbo": true
}
Request Body (non-ledger):
{
"signedMessage": "Open my purchased pokemon_50 pack!\n\nNonce:89c3e1b3-9d50-4cdd-95e6-444ef3fd68e4",
"signature": "base58_signature",
"publicKey": "daxsSgG4iPvhxWr2jrY76HKtRSjWjQLFveKHdmZKJFc",
"turbo": false,
"packType": "pokemon_50",
"nonce": "89c3e1b3-9d50-4cdd-95e6-444ef3fd68e4"
}
Response:
{
"success": true,
"memo": "cc-83f67f2e-7b53-45fd-9d81-ab4f8243475c",
"packsRemaining": 0
}
Get All Winners - /api/getAllWinners
Retrieves recent winners from the gacha machine.
Method: GET
Query Parameters:
timestamp(optional): ISO 8601 timestamp to get winners after this time. If omitted, returns the latest winners with no cutoff.slug(optional): Filter by memo prefix (e.g., "me")epic(optional): Set to "true" to get only epic/legendary winspackType(optional): Filter by pack type (e.g., "pokemon_50", "pokemon_250")count(optional): Number of results to return (default: 10, max: 10000)
Example Request:
GET /api/getAllWinners?timestamp=2024-01-01T00:00:00Z&slug=me&epic=false&packType=pokemon_50&count=20
Response:
{
"success": true,
"data": [
{
"winner": "wallet_address",
"prizeWallet": 1,
"nft_address": "nft_mint_address",
"nft": {
"content": {
"metadata": {
"name": "Card Name",
"attributes": [...]
}
}
},
"insuredValue": 50000000,
"created_at": "2024-01-01T12:00:00Z",
"memo_slug": "me",
"pack_type": "pokemon_50",
"prize_tier": 1
}
]
}
prize_tier values: 1 = Epic, 2 = Rare, 3 = Uncommon, 4 = Common.
Error Responses:
- 400: Invalid timestamp
- 500: Database query failed
What it does: Returns a list of recent NFT winners optionally filtered by timestamp, memo prefix (slug), rarity (epic), and pack type. Results are ordered by most recent first.
Flow Summary
- Call
/api/generatePackto create a purchase transaction - Sign the transaction with your wallet
- Submit signed transaction via
/api/submitTransaction - Wait for confirmation
- Call
/api/openPackwith the memo to receive the NFT - (Optional) Call
/api/buybackwithin 72 hours to sell the NFT back for USDC - Use
/api/getAllWinnersto display recent winners in your UI