Skip to main content

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 memo prefix based on your API key.
  • Allows filtering and tracking all spin/win/buyback activity by your key using the slug query 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; the won card is sent to this wallet instead of playerAddress
"altFundsRecipient": "wallet_public_key" // optional, turbo only; the auto-sell USDC is sent here instead of the card recipient
}

Response:

{
"memo": "slug-uuid",
"transaction": "base64_encoded_transaction"
}

Error Responses:

  • 400: Invalid request body, player address, alt player address, or alt funds recipient
  • 403: Player address, alt player address, or alt funds recipient is blocked
  • 500: Machine error — the details field gives the cause:
    • Machine is off balance — pool expected value is temporarily above the cap; retry shortly or try another machine
    • Machine is empty (non-turbo) / Machine is low (turbo) — a prize tier is low on inventory
    • Machine is off — machine toggled off or emergency stop active
  • 503: Machine has too many packs currently open (retry shortly)

Note: turbo mode will automatically process a buyback for Common wins when calling /api/openPack.

Funds split (turbo only): by default the auto-sell USDC and any non-Common card both go to altPlayerAddress (or playerAddress if it's unset). Provide altFundsRecipient to send the auto-sell USDC to a separate address while non-Common cards still go to altPlayerAddress. Recall that in turbo a given pack pays out either USDC (Common, auto-sold) or a card (non-Common) — never both — so altFundsRecipient only affects Common wins. It is ignored for non-turbo packs.


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
"altFundsRecipient": "wallet_public_key" // optional, turbo only; auto-sell USDC for Common wins is sent here instead of the NFT recipient (applies to every pack in the batch)
}

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: Player, alt player, or alt funds recipient address is blocked
  • 500: Machine error — the details field gives the cause:
    • Machine is off balance — pool expected value is temporarily above the cap; retry shortly or try another machine
    • Machine is low — a prize tier is low on inventory
    • Machine is off — machine toggled off 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. The VRF roll picks the prize tier from the machine's configured rarity weights (each machine has its own odds, so call /api/machines to read the live odds per machine), then a provable selection picks the card within that tier — see Provable Fairness (VRF). The NFT is transferred to the player and the metadata returned. 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"
}

buybackAmount is the auto-sell payout in USDC base units (6 decimals; e.g. 42500000 = 42.5 USDC). The USDC is sent to altFundsRecipient if it was supplied at /api/generatePack (or /api/generateYoloPacks), otherwise to altPlayerAddress, otherwise to playerAddress.

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
  • 403: Recipient address is blocked
  • 404: No on-chain purchase transaction found for this memo (not yet confirmed)
  • 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
  • 403: Player or alt recipient address is blocked
  • 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.


Buyback Available - /api/buyback/available

Checks whether a wallet has an eligible buyback for a given NFT and returns the USDC amount that would be refunded. Lean read-only lookup — no transaction is built. Eligibility matches the same rules as /api/buyback: the wallet must have received the NFT from the gacha within the last 72 hours and there must be no confirmed buyback for it yet.

Method: GET

Query Parameters:

  • wallet (required): wallet public key
  • nft (required): NFT mint address

Example Request:

GET /api/buyback/available?wallet=GfFAJnHnSgP7C2FQZLz6ogpdTV6Y7259f83qFFm9wxKm&nft=H9ZXYkudxn6qhyp5S25jm5SrA8Vnu8naSfvymm9TptLA

Response (Available):

{
"available": true,
"amount": 42500000
}

amount is in USDC base units (6 decimals), already adjusted for the pack type's buyback percentage and capped at 40,000 USDC.

Response (Not Available):

{
"available": false
}

Error Responses:

  • 400: Missing wallet or nft parameter
  • 500: Database query failed

Pack Status - /api/pack/status

Returns everything on record for a single memo across the three transaction tables: the pack purchase, the NFT send, and any buyback. Read-only lookup keyed entirely by the memo.

Method: GET

Query Parameters:

  • memo (required): The memo from the original pack purchase

Example Request:

GET /api/pack/status?memo=953cc94e-fd51-4f5f-bbcc-5a35faf7df65

Response:

{
"memo": "953cc94e-fd51-4f5f-bbcc-5a35faf7df65",
"pack": {
"wallet": "GfFAJnHnSgP7C2FQZLz6ogpdTV6Y7259f83qFFm9wxKm",
"transaction_signature": "5Nf9kZ...purchaseSignature",
"created_at": "2025-05-26T17:30:00.000Z",
"status": "confirmed",
"webhook_received": true,
"refunded": null,
"refund_transaction_signature": null,
"turbo_mode": false,
"pack_type": "pokemon_50",
"token_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
},
"send": {
"from_wallet": "prize_wallet_public_key",
"to_wallet": "GfFAJnHnSgP7C2FQZLz6ogpdTV6Y7259f83qFFm9wxKm",
"nft_address": "H9ZXYkudxn6qhyp5S25jm5SrA8Vnu8naSfvymm9TptLA",
"transaction_signature": "3Dq2kH65vqEzpBKczyDLzNYS8sXmtS5GS9MYTwjVaPM4tqVxjtPGnQG4mKpSPa5iTzqomhFgo9nYs3grRNPp68Fh",
"created_at": "2025-05-26T17:31:00.000Z",
"status": "confirmed",
"webhook_sent": true,
"points": 0,
"insured_value": 50000000,
"prize_tier": 4,
"vrf_proof": "vrf_proof_string"
},
"buyback": [
{
"user_wallet": "GfFAJnHnSgP7C2FQZLz6ogpdTV6Y7259f83qFFm9wxKm",
"refund_amount": "42500000",
"created_at": "2025-05-26T17:32:33.588Z",
"webhook_confirmed": true,
"transaction_signature": "3Dq2kH65vqEzpBKczyDLzNYS8sXmtS5GS9MYTwjVaPM4tqVxjtPGnQG4mKpSPa5iTzqomhFgo9nYs3grRNPp68Fh",
"status": "complete",
"alt_recipient": null
}
]
}

Field notes:

  • pack and send are the single row for that memo (or null if none exists yet).
  • buyback is an array — a memo can have more than one buyback attempt — ordered newest first; [] when there are none.
  • webhook_received (pack), webhook_sent (send), and webhook_confirmed (buyback) indicate the on-chain transaction was confirmed via webhook.
  • refunded is true when the pack was refunded, otherwise null; the refund's signature is in refund_transaction_signature.
  • refund_amount is returned as a string in USDC base units (6 decimals).
  • prize_tier values: 1 = Epic, 2 = Rare, 3 = Uncommon, 4 = Common.

Error Responses:

  • 400: Missing memo parameter
  • 500: Database query failed

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
},
"ev": 0
}
]
}

Field notes:

  • ev (number): the machine's current rarity-weighted expected insured value — each rarity tier's average insured value weighted by its odds — recomputed periodically by a background job. Defaults to 0 until it has first been calculated.

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 to pokemon_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 when page is 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
  • 403: Transaction not signed by the gacha wallet
  • 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": "cc-86646f0c-2a61-4a0a-89af-495d266684c3",
"transaction": "base64_encoded_transaction",
"totalCost": 50,
"count": 1,
"packType": "pokemon_50"
}

Note on the memo: The returned memo is prefixed with your API-key slug, or with cc- when no x-api-key is sent (e.g. cc-<uuid>) — it is not prefixed gift-. The gift marker is only appended to the on-chain memo (as a :gift suffix) and never appears in the memo field returned by this endpoint.

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. The response contains one entry per configured pack type — derived dynamically from the active machines — so the keys returned depend on which machines exist, not only the two shown below.

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

  1. Call /api/generatePurchasedPack to get a nonce + messageToSign.
  2. Have the user sign messageToSign with their wallet.
  3. Call /api/usePurchasedPack:
  • non-ledger: { signedMessage, signature, publicKey, nonce, packType }
  • ledger: { ledger: true, transactionSignature, publicKey, nonce, packType, turbo } to receive a memo.
  1. Call /api/openPack with the memo to send the NFT.

Reference UI (dev)

Method: POST

Request Body:

{
"publicKey": "daxsSgG4iPvhxWr2jrY76HKtRSjWjQLFveKHdmZKJFc",
"packType": "pokemon_50",
"ledger": true
}
  • ledger (optional, default: false): when true, 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 Recent Winners - /api/getRecentWinners

Returns the most recent winners for a single machine, newest first (up to 5).

Method: GET

Query Parameters:

  • packType (optional): The machine/pack code (e.g. pokemon_50, pokemon_250). Defaults to 50.

Example Request:

GET /api/getRecentWinners?packType=pokemon_50

Response:

{
"success": true,
"data": [
{
"winner": "wallet_address",
"prize_tier": 1,
"nft": {
"content": { "metadata": { "name": "Card Name", "attributes": [...] } },
"image": "https://..."
},
"timestamp": "2024-01-01T12:00:00Z",
"insuredValue": 50000000
}
]
}

The best available image URL is merged onto the nft object as image. prize_tier values: 1 = Epic, 2 = Rare, 3 = Uncommon, 4 = Common.

Error Responses:

  • 404: No recent winners found for the given pack type
  • 500: Database query failed

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 wins
  • packType (optional): Filter by pack type (e.g., "pokemon_50", "pokemon_250")
  • count (optional): Number of results to return (default: 10, max: 200)

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",
"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.


Get Winners (Paginated) - /api/getWinners

Cursor-paginates through all winners for a single memo slug, newest first. Use this instead of /api/getAllWinners when you need to walk a partner's entire win history page by page — it uses keyset pagination, so every page is fast regardless of how deep you go.

Each winner has the same shape as /api/getAllWinners; the response just adds the pagination fields nextCursor and hasMore.

Method: GET

Query Parameters:

  • slug (required): The memo slug to fetch winners for (your API key's slug).
  • cursor (optional): The nextCursor returned by the previous page. Omit it to fetch the first (most recent) page.
  • count (optional): Page size (default: 50, max: 200).

Example Request (first page):

GET /api/getWinners?slug=me&count=50

Example Request (next page):

GET /api/getWinners?slug=me&count=50&cursor=MjAyNS0wMS0wMVQxMjowMDowMC4wMDAwMDBafDEyMzQ1

Response:

{
"success": true,
"data": [
{
"winner": "wallet_address",
"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
}
],
"nextCursor": "MjAyNC0wMS0wMVQxMjowMDowMC4wMDAwMDBafDEyMzQ1",
"hasMore": true
}

Pagination:

  • nextCursor is an opaque token — pass it back as the cursor parameter to fetch the next page. Do not parse or construct it yourself.
  • hasMore is true when another page is available. When hasMore is false, nextCursor is null and you've reached the end.
  • Results are ordered most recent first and remain stable as new winners arrive, so you won't skip or double-count rows while paginating.

prize_tier values: 1 = Epic, 2 = Rare, 3 = Uncommon, 4 = Common.

Error Responses:

  • 400: Missing slug or invalid cursor
  • 500: Database query failed

Live Winners Token - /api/ably/token

Issues a short-lived Ably token so you can subscribe to live wins in realtime — the same feed that powers the Recent Winners section on the gacha site. Use this instead of polling /api/getAllWinners when you want wins to appear in your UI the moment they happen.

Any valid API key can subscribe to all winner channels; filter by gachaCode (or memo) on your end to show only the machines you care about.

Method: GET

Headers:

  • x-api-key (required): your API key.

Example Request:

curl "https://gacha.collectorcrypt.com/api/ably/token" \
-H "x-api-key: API_KEY"

Response: an Ably TokenRequest object. You don't parse this yourself — hand it straight to the Ably client (see below). It is scoped to subscribe-only on the winner channels and expires after 1 hour; the Ably client auto-renews by re-calling this endpoint.

Usage (browser / Node):

import * as Ably from "ably";

const ably = new Ably.Realtime({
authUrl: "https://gacha.collectorcrypt.com/api/ably/token",
authHeaders: { "x-api-key": "API_KEY" },
});

// One channel per pack type, e.g. recent-winners-pokemon_50
ably.channels.get("recent-winners-pokemon_50")
.subscribe("new-winner", (msg) => {
console.log(msg.data); // see payload below
});

The channel name is recent-winners-{packType} and the event name is new-winner. Each message's data is:

{
"winner": "wallet_address",
"prizeWallet": "wallet_address",
"nft": {
"address": "nft_mint_address",
"name": "Card Name",
"image": "https://...",
"certid": "1234567",
"gradingCompany": "PSA",
"grade": "10"
},
"timestamp": "2024-01-01T12:00:00Z",
"insuredValue": 50000000,
"weightedInsuredValue": 50000000,
"gachaCode": "pokemon_50",
"prizeTier": 1,
"memo": "me-abc123"
}

prizeTier values: 1 = Epic, 2 = Rare, 3 = Uncommon, 4 = Common.

memo is the unique pack identifier returned by /api/generatePack — use its slug prefix (e.g. me) to filter to your own machines, and the full value to attribute a win to the specific player/pack you created.

Error Responses:

  • 401: Missing or invalid API key

What it does: Mints a scoped Ably token tied to your key. Subscribe to the recent-winners-{packType} channels and react to new-winner events to render wins live.

Flow Summary

  1. Call /api/generatePack to create a purchase transaction
  2. Sign the transaction with your wallet
  3. Submit signed transaction via /api/submitTransaction
  4. Wait for confirmation
  5. Call /api/openPack with the memo to receive the NFT
  6. (Optional) Call /api/buyback within 72 hours to sell the NFT back for USDC
  7. Use /api/getAllWinners to display recent winners in your UI