Marketplace API
Overview
The CollectorCrypt Marketplace V2 API lets you build clients on top of the CollectorCrypt V2 marketplace program on Solana. Every request follows the same three-step pattern:
- Build — POST to a builder endpoint (
/marketplace/list,/buy, etc.) to receive an unsigned transaction. The backend automatically checks the wallet's on-chain SOL balance and, if it can't cover network fees, co-signs as fee payer and returns a partially-signed transaction. - Sign — the user's wallet signs the transaction.
- Broadcast — POST the signed transaction to
/marketplace/broadcast.
- Program ID:
CcmRKTuZCGJBWQwMHvDYApBRvSZNHqGJXkznqpDTSQUr - Currency: USDC (SPL Token)
- NFT types: pNFTs (Programmable NFTs), cNFTs (Compressed NFTs), Standard NFTs (legacy Token Metadata 1/1s), and MPL Core assets
Pricing and fees
The buyer always pays exactly the listed price (or, for an offer, the offer amount). A platform fee (currently 2%) is deducted from the seller's proceeds — it is not added on top of the buyer's payment. So for a price sale the buyer pays price, the seller receives price − fee, and the fee goes to the platform treasury. This is the same for direct buys and accepted offers.
Base URL
| Environment | Base URL |
|---|---|
| Production | https://api.collectorcrypt.com |
| Devnet | https://dev-api.collectorcrypt.com |
All endpoints below are relative to the base URL.
Devnet USDC faucet
For testing on devnet: https://spl-token-faucet.com/?token-name=USDC-Dev
Request format
All endpoints accept JSON bodies and include a required wallet field — the caller's Solana wallet address (base58). The backend builds an unsigned transaction addressed to that wallet; the wallet's signature on the returned transaction is what authorizes the action on-chain.
curl -X POST "https://api.collectorcrypt.com/marketplace/list" \
-H "Content-Type: application/json" \
-d '{ "wallet": "YOUR_WALLET_ADDRESS", "cardId": "...", "nftAddress": "...", "price": 25.00, "currency": "USDC" }'
Endpoints
1. List an NFT — POST /marketplace/list
Creates a transaction for listing an NFT for sale. The seller must sign and broadcast the transaction via /marketplace/broadcast.
Request Body:
{
"wallet": "SELLER_WALLET_ADDRESS",
"cardId": "card_db_id",
"currency": "USDC",
"price": 25.00,
"nftAddress": "NFT_MINT_ADDRESS",
"tokenStandard": "Pnft",
"userHasSol": true
}
| Field | Type | Required | Description |
|---|---|---|---|
wallet | string | Yes | The seller's wallet public key |
cardId | string | Yes | The CollectorCrypt card ID |
currency | string | Yes | Currency for the listing (use "USDC") |
price | number | Yes | Listing price in USDC |
nftAddress | string | Yes | The NFT mint address |
tokenStandard | "Pnft" | "Cnft" | "StandardNft" | "CoreNft" | No | Token standard. Optional; resolved via DAS if omitted. |
userHasSol | boolean | No | Advisory hint only. The backend decides whether to act as fee payer by checking the wallet's on-chain SOL balance (cached briefly); when it does, the returned transaction is pre-signed by the backend. |
Response: The unsigned transaction as a base64 string.
BASE64_ENCODED_UNSIGNED_TRANSACTION
Error Responses:
400: On-chain error while building the listing transaction409: NFT is already listed on the marketplace
2. Cancel a Listing — POST /marketplace/cancel-listing
Creates a transaction to cancel an active listing. The seller must sign and broadcast it.
Request Body:
{
"wallet": "CALLER_WALLET_ADDRESS",
"tokenMint": "NFT_MINT_ADDRESS",
"seller": "SELLER_WALLET_ADDRESS"
}
| Field | Type | Required | Description |
|---|---|---|---|
wallet | string | Yes | The caller's wallet public key (the signer) |
tokenMint | string | Yes | The NFT mint address |
seller | string | No | The listing seller — defaults to wallet if omitted |
Response: The unsigned transaction as a base64 string.
BASE64_ENCODED_UNSIGNED_TRANSACTION
Error Responses:
400: On-chain error while building the cancel transaction404: No active listing found on-chain for this NFT
3. Update a Listing Price — POST /marketplace/update-listing
Creates a transaction to update the price of an active listing.
Request Body:
{
"wallet": "SELLER_WALLET_ADDRESS",
"tokenMint": "NFT_MINT_ADDRESS",
"newPrice": 30.00,
"coin": "USDC"
}
| Field | Type | Required | Description |
|---|---|---|---|
wallet | string | Yes | The seller's wallet public key |
tokenMint | string | Yes | The NFT mint address |
newPrice | number | Yes | Updated listing price in USDC |
coin | string | Yes | Currency (use "USDC") |
Response: The unsigned transaction as a base64 string.
BASE64_ENCODED_UNSIGNED_TRANSACTION
Error Responses:
400:newPriceequals the current listing price, or on-chain error while building the update transaction404: No listing found on-chain for this NFT
4. Buy an NFT — POST /marketplace/buy
Creates a transaction for purchasing a listed NFT. The buyer must sign and broadcast it.
Request Body:
{
"wallet": "BUYER_WALLET_ADDRESS",
"nftAddress": "NFT_MINT_ADDRESS",
"price": 25.00,
"currency": "USDC",
"tokenStandard": "Pnft",
"sellerWallet": "SELLER_WALLET_ADDRESS",
"fundingSource": "wallet"
}
| Field | Type | Required | Description |
|---|---|---|---|
wallet | string | Yes | The buyer's wallet public key |
nftAddress | string | Yes | The NFT mint address |
price | number | Yes | The listed price in USDC |
currency | string | Yes | Currency (use "USDC") |
tokenStandard | "Pnft" | "Cnft" | "StandardNft" | "CoreNft" | No | Token standard. Optional; resolved via DAS if omitted. |
sellerWallet | string | No | Seller wallet. Optional; resolved from the DB if omitted. |
fundingSource | "wallet" | "escrow" | No | Payment source. Defaults to "wallet". Set to "escrow" to pay from the buyer's on-chain shared escrow balance. |
Response: The unsigned transaction as a base64 string.
BASE64_ENCODED_UNSIGNED_TRANSACTION
When fundingSource is "escrow", the response is a JSON object with two transactions (a withdraw from escrow, then the buy) to sign together:
{
"transactions": ["BASE64_WITHDRAW_TX", "BASE64_BUY_TX"],
"fundingSource": "escrow"
}
Escrow mode requires the buyer's escrow to hold at least price USDC. The buyer pays exactly price; the platform fee is taken from the seller's proceeds (see Pricing and fees).
Error Responses:
400: On-chain error, or insufficient escrow balance (escrow mode only)404: Card, seller wallet, or listing not found409: Card owner changed since the listing was built (someone else bought it first) — refetch and retry
5. Make an Offer
An offer must be backed by the buyer's on-chain escrow balance — when the seller accepts, the funds are pulled from escrow (see the Offer flow). Because of this, an offer can be funded in three different ways, and you pick the one that matches the buyer's current escrow balance. This funding choice is the key decision when building an offer.
| You want to… | Use | Deposits | Returns |
|---|---|---|---|
| Top up only what's missing, then offer (recommended / what the CollectorCrypt UI uses) | createMakeOfferWithDepositTxs | max(0, price − escrowBalance) | multiple txs |
| Use funds already in escrow, no deposit | createMakeOfferTxV2NoDeposit | nothing | single tx |
Always deposit the full price, then offer | POST /marketplace/make-offer (a.k.a. createMakeOfferTxV2) | price | single tx |
All three move USDC into the buyer's escrow as needed; nothing is paid to the seller until the offer is accepted. The fee model is the same as a buy — on acceptance the seller receives price − fee (see Pricing and fees).
Calling the
createMakeOffer*builders. The two escrow-aware variants are exposed through the JSON-RPC-style dispatcher, not as their own REST path:POST /v2with a body of{ "method": "<name>", "params": { ... } }. The response is whatever the method'sresultis (a base64 string, or an object). All three builders are public — passwalletinparams.
5a. createMakeOfferWithDepositTxs (deposit shortfall + offer)
Computes the escrow shortfall (depositAmount = max(0, price − currentEscrowBalance)), then returns a deposit transaction (only if needed) plus the offer transaction, to sign together with signAllTransactions.
POST /v2
{
"method": "createMakeOfferWithDepositTxs",
"params": {
"wallet": "BUYER_WALLET_ADDRESS",
"cardId": "card_db_id",
"nftAddress": "NFT_MINT_ADDRESS",
"price": 20.00,
"currency": "USDC"
}
}
Response:
{
"transactions": ["BASE64_TX", "BASE64_TX"],
"blockhash": "RECENT_BLOCKHASH",
"depositAmount": 5.00,
"needsDeposit": true
}
When the buyer's escrow already covers price, needsDeposit is false, depositAmount is 0, and only the offer transaction is returned.
5b. createMakeOfferTxV2NoDeposit (use existing escrow)
Builds the offer transaction without depositing. The buyer's escrow must already hold at least price.
POST /v2
{
"method": "createMakeOfferTxV2NoDeposit",
"params": {
"wallet": "BUYER_WALLET_ADDRESS",
"cardId": "card_db_id",
"nftAddress": "NFT_MINT_ADDRESS",
"price": 20.00,
"currency": "USDC",
"tokenStandard": "Cnft",
"ownerWallet": "OWNER_WALLET_ADDRESS",
"escrowBalance": 25.00,
"collectionHash": "MERKLE_TREE_COLLECTION_HASH"
}
}
Response: the unsigned transaction as a base64 string.
Error Responses:
400: no escrow account ("You do not have a shared escrow account…"), or escrow balance< price("Insufficient escrow balance…"), or could not determine NFT owner, or on-chain build error
5c. POST /marketplace/make-offer (deposit full price)
Single transaction that deposits the full price into escrow and creates the offer. Simplest path, but it always deposits — use createMakeOfferTxV2NoDeposit / createMakeOfferWithDepositTxs if the buyer may already have escrow funds.
Request Body:
{
"wallet": "BUYER_WALLET_ADDRESS",
"cardId": "card_db_id",
"nftAddress": "NFT_MINT_ADDRESS",
"price": 20.00,
"currency": "USDC",
"tokenStandard": "Cnft",
"userHasSol": true,
"ownerWallet": "OWNER_WALLET_ADDRESS",
"collectionHash": "MERKLE_TREE_COLLECTION_HASH"
}
| Field | Type | Required | Description |
|---|---|---|---|
wallet | string | Yes | The buyer's wallet public key |
cardId | string | Yes | The CollectorCrypt card ID |
nftAddress | string | Yes | The NFT mint address |
price | number | Yes | Offer price in USDC (also the amount deposited into escrow) |
currency | string | Yes | Currency (use "USDC") |
tokenStandard | "Pnft" | "Cnft" | "StandardNft" | "CoreNft" | No | Token standard. Optional; resolved via DAS if omitted. |
userHasSol | boolean | No | Advisory hint only. The backend decides whether to act as fee payer by checking the wallet's on-chain SOL balance (cached briefly); when it does, the returned transaction is pre-signed by the backend. |
ownerWallet | string | No | Current NFT owner. Optional; resolved via RPC if omitted. |
collectionHash | string | No | cNFT collection hash. Optional; resolved via DAS if omitted (cNFTs only). |
Response: the unsigned transaction as a base64 string.
Error Responses:
400: Could not determine NFT owner from the chain, or on-chain error while building the offer transaction
Managing escrow directly. You can also fund or drain escrow on its own via the
POST /v2builderscreateDepositToUserEscrowTxandcreateWithdrawFromUserEscrowTx(params:{ amount, wallet }), each returning a base64 transaction.
6. Cancel an Offer — POST /marketplace/cancel-offer
Creates a transaction to cancel an active offer. Optionally keeps funds in escrow for future offers.
Request Body:
{
"wallet": "BUYER_WALLET_ADDRESS",
"nftAddress": "NFT_MINT_ADDRESS",
"keepInEscrow": false
}
| Field | Type | Required | Description |
|---|---|---|---|
wallet | string | Yes | The buyer's wallet public key (offer maker) |
nftAddress | string | Yes | The NFT mint address |
keepInEscrow | boolean | No | If true, released USDC stays in your escrow account. If false (default), USDC is withdrawn to your wallet. |
Response: The unsigned transaction as a base64 string.
BASE64_ENCODED_UNSIGNED_TRANSACTION
If the wallet has insufficient SOL for rent, the backend automatically falls back to keepInEscrow: true (funds stay in escrow).
Error Responses:
404: No active offer found on-chain for this buyer/NFT pair
7. Update an Offer — POST /marketplace/update-offer
Creates a transaction to change the price of an existing offer.
Request Body:
{
"wallet": "BUYER_WALLET_ADDRESS",
"nftAddress": "NFT_MINT_ADDRESS",
"currency": "USDC",
"buyer": "BUYER_WALLET_ADDRESS",
"price": 22.00
}
| Field | Type | Required | Description |
|---|---|---|---|
wallet | string | Yes | The buyer's wallet public key (offer maker, signer) |
nftAddress | string | Yes | The NFT mint address |
currency | string | Yes | Currency (use "USDC") |
buyer | string | Yes | The buyer's wallet public key (typically equal to wallet) |
price | number | Yes | New offer price in USDC |
Response: The unsigned transaction as a base64 string.
BASE64_ENCODED_UNSIGNED_TRANSACTION
The program requires the buyer's escrow to already hold the full new amount — this transaction does not transfer additional USDC into escrow. To raise an offer above the current escrow balance, use createUpdateOfferWithDepositTxs (via POST /v2, params { wallet, nftAddress, buyer, price, currency }); like its make-offer counterpart it deposits only the shortfall and returns { transactions, blockhash, depositAmount, needsDeposit }.
Error Responses:
400: New price equals the current offer price, buyer has no escrow account, escrow balance is less than the new price, or on-chain error while building the update transaction404: Offer not found on-chain (the DB record, if any, is markedCancelledin the background)
8. Accept an Offer — POST /marketplace/accept-offer
Creates a transaction for the NFT owner to accept a buyer's offer. The seller must sign and broadcast it.
Request Body:
{
"wallet": "SELLER_WALLET_ADDRESS",
"nftAddress": "NFT_MINT_ADDRESS",
"buyer": "BUYER_WALLET_ADDRESS",
"currency": "USDC",
"price": 20.00,
"tokenStandard": "Pnft",
"userHasSol": true
}
| Field | Type | Required | Description |
|---|---|---|---|
wallet | string | Yes | The seller's wallet public key (NFT owner, signer) |
nftAddress | string | Yes | The NFT mint address |
buyer | string | Yes | The buyer's wallet public key (the offer maker) |
currency | string | Yes | Currency (use "USDC") |
price | number | Yes | The offer price in USDC |
tokenStandard | "Pnft" | "Cnft" | "StandardNft" | "CoreNft" | No | Token standard. Optional; resolved via DAS if omitted. |
userHasSol | boolean | No | Advisory hint only. The backend decides whether to act as fee payer by checking the wallet's on-chain SOL balance (cached briefly); when it does, the returned transaction is pre-signed by the backend. |
Response: The unsigned transaction as a base64 string.
BASE64_ENCODED_UNSIGNED_TRANSACTION
If the NFT is a cNFT that's currently listed, the program requires the listing to be cancelled first. In that case the response is a JSON object containing a cancel-listing transaction — broadcast it, then call /marketplace/accept-offer again to get the settlement transaction:
{
"requiresCancelListing": true,
"cancelListingTx": "BASE64_CANCEL_LISTING_TX",
"message": "This cNFT is currently listed. Please sign the cancel listing transaction first, then accept the offer."
}
Error Responses:
400: Offer price on-chain has drifted from the submittedprice(race: the buyer updated or cancelled), or on-chain error while building the accept transaction404: Offer not found on-chain for this buyer/NFT pair
9. Browse Listings — GET /marketplace
Returns a paginated, filterable list of cards on the marketplace. This is a read endpoint — no wallet signature is required. Use it to discover listings (and the offers on them) before calling any of the transaction builders above.
The endpoint is fully public and cached at the CDN (Cache-Control: public, s-maxage=60, max-age=30).
Pagination
| Param | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | 1-indexed page number |
step | integer | 100 | Page size. Max 100. |
Filter parameters (all optional unless noted)
CSV parameters accept either a comma-separated string (?cardType=Card,Sealed) or repeated query keys.
| Param | Type | Description |
|---|---|---|
search | string | Substring match against itemName and gemrateCardName; exact match against nftAddress and gradingID |
ownerAddress | string | Solana wallet — return only cards owned by this address |
marketplaceStatus | CSV | Buy now — only currently-listed cards |
marketplaceSource | CSV | CC (CollectorCrypt V2), ME (Magic Eden) |
marketplaceTags | CSV | Filter by one or more global tag names (e.g. ?marketplaceTags=Hot,Featured) |
listPriceMin / listPriceMax | number | Listed price range in USDC (SOL listings are auto-converted) |
insuredValueMin / insuredValueMax | number | Filter by insuredValue |
yearMin / yearMax | integer | Card year range |
categories | CSV | One or more of Baseball, Basketball, Football, Hockey, Soccer, Golf, Tennis, Racing, Boxing & MMA, Misc Sports, Pokemon, Magic The Gathering, Yu-Gi-Oh!, Lorcana, One Piece, Collectible Card Games, Non-Sport, Azuki, Vibes |
cardType | CSV | Card, Comic, ComicRaw, Game, Merch, Raw, Sealed |
blockchain | CSV | Solana, Base, Monad, ApeChain, Ethereum, XLayer |
autographed | boolean | Only autographed cards |
burnt | boolean | Include burned cards. Default excludes them. |
grade | string | ILIKE match against grade (caller supplies any % wildcards) |
genericGradeMin / genericGradeMax | number | Numeric gradeNum range |
gradingCompany | CSV | PSA, PSA/DNA, Beckett, CGC, CBCS, CGS, CSG, SGC, TAG, KSA, UDA, Steiner, BBCE, Rare Edition, Not graded |
set / format / productCode | string | Substring match for sealed/comic metadata |
language | string | Language filter (e.g. English); matched on word boundaries, not as a plain substring |
pokemon | string | Word-boundary match against gemrateCardName |
ccBuyback | boolean | Only cards with an active CollectorCrypt buyback offer |
gemrateCardName | string | Substring match against gemrateCardName |
gemrateGrade | string | CSV | Exact match (or IN list) against the gemrate grade |
gemrateSet / gemrateSpecId / gemrateParallel | string | Substring match against the card's set / spec id / parallel |
orderBy | enum | See below. Default listedDateDesc. |
orderBy values
dateAsc, dateDesc, nameAsc, nameDesc, priceAsc, priceDesc (insured value), yearAsc, yearDesc, listedDateAsc, listedDateDesc, listedPriceAsc, listedPriceDesc.
Example
# First page of Pokémon cards listed on CC for under $100, newest first
curl "https://api.collectorcrypt.com/marketplace?categories=Pokemon&marketplaceSource=CC&listPriceMax=100&orderBy=listedDateDesc&page=1&step=50"
Response
{
"filterNFtCard": [
{
"id": "card_db_id",
"itemName": "...",
"nftAddress": "NFT_MINT_ADDRESS",
"nftStandard": "Pnft",
"blockchain": "Solana",
"category": "Pokemon",
"type": "Card",
"year": 1999,
"grade": "10",
"gradeNum": 10,
"gradingCompany": "PSA",
"gradingID": "...",
"insuredValue": "...",
"status": "Transferred",
"createdAt": "2026-04-01T00:00:00.000Z",
"updatedAt": "2026-05-10T00:00:00.000Z",
"listing": {
"createdAt": "2026-05-01T00:00:00.000Z",
"updatedAt": "2026-05-01T00:00:00.000Z",
"currency": "USDC",
"price": "25",
"receiptId": "...",
"sellerId": "...",
"marketplace": "CC"
},
"offers": [{ "id": "offer_id" }],
"owner": {
"id": "user_id",
"name": "...",
"wallet": "OWNER_WALLET_ADDRESS"
},
"images": {
"front": "https://...",
"frontM": "https://...",
"frontS": "https://...",
"back": "https://...",
"backM": "https://...",
"backS": "https://..."
}
}
],
"findTotal": 1234,
"total": 50000,
"totalPages": 25,
"cardsQtyByCategory": {
"Pokemon": 800,
"Baseball": 200,
"...": 0
}
}
| Field | Description |
|---|---|
filterNFtCard | The current page of cards. listing is null when the card isn't listed; offers is an array of active-offer ids. |
findTotal | Total cards matching the filter (excludes the categories filter — used to compute cardsQtyByCategory). |
total | Total cards in the marketplace overall (status = 'Transferred'). |
totalPages | ceil(findTotal / step). |
cardsQtyByCategory | Count of matching cards per category — useful for sidebar facet counts. |
10. Broadcast a Transaction — POST /marketplace/broadcast
Broadcasts a signed transaction to the Solana network. Use this after the user signs any transaction returned by the endpoints above.
Request Body:
{
"wallet": "SIGNER_WALLET_ADDRESS",
"signedTransaction": "base64_encoded_signed_transaction",
"nftAddress": "NFT_MINT_ADDRESS"
}
| Field | Type | Required | Description |
|---|---|---|---|
wallet | string | Yes | The wallet that signed the transaction — used for fee-payer cache hints |
signedTransaction | string | Yes | The base64-encoded signed transaction |
nftAddress | string | No | If provided, triggers a background card metadata refresh after 5 seconds |
Response:
{
"success": true,
"signature": "SOLANA_TRANSACTION_SIGNATURE",
"message": "Transaction broadcast successfully"
}
The transaction is validated against an allow-list of program IDs (the CollectorCrypt marketplace program and related system programs) before being submitted.
Retryable insufficient-funds error:
If the signer wallet has insufficient SOL for fees, the endpoint returns 400 with a structured JSON error body in message:
{
"code": "INSUFFICIENT_FUNDS_RETRYABLE",
"message": "Insufficient funds for transaction fees",
"retryable": true,
"useBackendPayer": true
}
Rebuild the transaction by calling the original builder endpoint with userHasSol: false — the backend will set itself as fee payer.
Error Responses:
400: Invalid or undeserializable transaction, insufficient funds (retryable — see above), or submission failure403: Transaction contains a program ID that isn't on the allow-list
Errors
All error responses use this shape:
{
"statusCode": 400,
"message": "human-readable error",
"error": "Bad Request"
}
| Status | Meaning |
|---|---|
400 | Bad request — missing wallet, on-chain failure, price mismatch, insufficient escrow, or invalid signed transaction |
403 | Transaction contains a program ID outside the broadcast allow-list |
404 | Listing, offer, card, or seller wallet not found |
409 | NFT is already listed, or the on-chain owner changed during /buy |
429 | Rate limit exceeded |
For /marketplace/broadcast, the insufficient-funds case returns a JSON-encoded string inside message with code: "INSUFFICIENT_FUNDS_RETRYABLE" — see the broadcast section above.