Sell a drop end-to-end
From approved vault items to a paid sale: create the drop, share the link, poll for the sale, read the earnings.
A drop is a paid media bundle with a checkout page. This guide walks the whole loop: pick content → create the drop → share the link → learn about the sale.
1. Pick approved vault items#
Only APPROVED items can go into a drop. The default vault listing returns approved items only, so anything it gives you is usable:
curl "https://www.dropfans.io/api/external/vault?limit=10" \
-H "Authorization: Bearer $DROPFANS_API_KEY"const res = await fetch('https://www.dropfans.io/api/external/vault?limit=10', {
headers: { Authorization: 'Bearer ' + process.env.DROPFANS_API_KEY },
});
const { items } = await res.json();
const ids = items.slice(0, 3).map((i) => i.id);import os, requests
HEADERS = {"Authorization": f"Bearer {os.environ['DROPFANS_API_KEY']}"}
res = requests.get("https://www.dropfans.io/api/external/vault?limit=10", headers=HEADERS)
ids = [i["id"] for i in res.json()["items"][:3]]Need to upload first? See Upload media.
2. Create the drop#
Price is USD dollars: 0 for free, otherwise $5–$750. At most 10 vault items per drop.
curl -X POST "https://www.dropfans.io/api/external/drops" \
-H "Authorization: Bearer $DROPFANS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Beach set", "price": 15, "vaultItemIds": ["VAULT_ITEM_A", "VAULT_ITEM_B"] }'const res = await fetch('https://www.dropfans.io/api/external/drops', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.DROPFANS_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'Beach set', price: 15, vaultItemIds: ids }),
});
const { productId, buyUrl } = await res.json();res = requests.post(
"https://www.dropfans.io/api/external/drops",
headers=HEADERS,
json={"name": "Beach set", "price": 15, "vaultItemIds": ids},
)
drop = res.json() # { "productId": …, "buyUrl": …, "mediaCount": 2 }POST /drops validates a description field for prohibited words but deliberately does not persist it. Do not build UI that expects it back.
A drop built from approved items is born APPROVED. Read it back any time with GET /drops/{id} — it returns the status, per-item moderation, salesCount and lastSaleAt:
curl "https://www.dropfans.io/api/external/drops/PRODUCT_ID" \
-H "Authorization: Bearer $DROPFANS_API_KEY"3. Optional: attach blurred previews#
If your product bakes its own teaser blur, attach a JPEG preview per media item (≤ 8 MB each) so the checkout page shows your blur instead of the default:
curl -X POST "https://www.dropfans.io/api/external/drops/PRODUCT_ID/previews" \
-H "Authorization: Bearer $DROPFANS_API_KEY" \
-F "previewBlob_VAULT_ITEM_A=@teaser_a.jpg;type=image/jpeg" \
-F "blurMeta_VAULT_ITEM_A=partial"The response is { "success": true, "updated": n } — compare updated with the number you sent; non-JPEG or oversized parts are skipped silently.
4. Share the link#
- Web: the
buyUrlfrom the create response —https://www.dropfans.io/buy/<productId>. - Telegram: take
telegram.buyTemplatefrom GET /links and substitute the product id — it opens the Dropfans Mini App straight on the checkout. Whentelegramisnullthe bot is not configured; fall back to the web URL. See Links & deep links.
5. Poll for the sale#
POST /drops/check-status takes up to 200 product ids and returns a map of the ones that sold:
curl -X POST "https://www.dropfans.io/api/external/drops/check-status" \
-H "Authorization: Bearer $DROPFANS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "productIds": ["PRODUCT_ID"] }'const res = await fetch('https://www.dropfans.io/api/external/drops/check-status', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.DROPFANS_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ productIds: [productId] }),
});
const { sales } = await res.json();
if (sales[productId]) console.log('sold for', sales[productId].saleAmountCents, 'cents');res = requests.post(
"https://www.dropfans.io/api/external/drops/check-status",
headers=HEADERS,
json={"productIds": [drop["productId"]]},
)
sales = res.json()["sales"] # unsold ids are simply absentCadence: check on demand (when the buyer says "paid") plus a periodic sweep every 1–5 minutes over your open drops, chunked at 200 ids. Unsold ids are omitted from the map — absence means "not sold yet", not an error.
A refunded sale still reports paid: true here. For money truth, reconcile against GET /earnings, which excludes refunds and chargebacks.
6. Read the money#
saleAmountCents is the gross charge in cents. Net (what the creator keeps), per-type totals and the transaction log live on GET /earnings — see Earnings & balance.
Next: Links & deep links or Earnings & balance.
Questions? [email protected]
