Create a link for a drop, a tip, a subscription or the profile
Creates one link id. The link is `https://www.dropfans.io/buy/{id}` (web) and `https://t.me/<bot>/app?startapp={id}` (Telegram), with no parameters. The drop, the tip amount and your `externalRef` are stored with the id.
/api/external/v1/linksalso served at /api/external/linksNewCreates one link id. The link is https://www.dropfans.io/buy/{id} (web) and https://t.me/<bot>/app?startapp={id} (Telegram), with no parameters. The drop, the tip amount and your externalRef are stored with the id.
buy: a drop (productIdfrom create-drop) opens its checkout. A subscriber-only drop opens the subscribe sheet instead.tip: opens the tip sheet, prefilled withamountUsdwhen you set one ($5–$750), or the fan picks.subscribe: opens the subscribe sheet.profileopens the profile.
externalRef (1–32 of A-Z a-z 0-9 _ -) comes back on the sale: the drop.paid / tip.paid webhooks and GET /orders and /tips, together with the link's linkId. One link per fan per drop is the usual pattern.
The same request always returns the same link, so it's safe to call again. With a key that belongs to an app, sales through the link earn your app owner commission for life, and the call makes your app the account's connected CRM.
Authentication
Send the creator's API key as a bearer token: Authorization: Bearer dpfn_…. See Authentication & API keys.
For a creator you manage or onboarded
POST /api/external/v1/creators/{creatorId}/links — an agency's own key for a creator or AI persona it manages, or your app owner's own key for an account your app onboarded. The X-Dropfans-Account header still works but is deprecated. See Acting for a creator.
Request body
Content type: application/json
| Name | Type | Required | Description |
|---|---|---|---|
type | string | Required | What the link opens. One of: buy, tip, subscribe, profile |
productId | string | Optional | The drop, for buy (required there, refused otherwise). Must belong to the account. |
amountUsd | number | Optional | Fixed tip amount for tip ($5–$750). Omit to let the fan pick. |
externalRef | string | Optional | Your per-fan reference, returned on the sale. Max length: 32 Pattern: ^[A-Za-z0-9_-]{1,32}$ |
{
"type": "buy",
"productId": "cmv5tmpcm0015h4oucxkgc4ma",
"externalRef": "tg_48213377"
}{
"type": "tip",
"amountUsd": 20,
"externalRef": "tg_48213377"
}Responses
| Name | Type | Description |
|---|---|---|
id | string | The link id (10 of a-z 0-9). Comes back as linkId on sales through the link. |
type | string | What the link opens. One of: buy, tip, subscribe, profile |
url | string | The web link — /buy/{id}, no parameters.Format: uri |
telegram | string | null | The Telegram Mini App link (startapp={id}). Null when the shared Dropfans bot is not configured.Format: uri |
productId | string | null | The drop of a buy link. |
amountUsd | number | null | Fixed amount of a tip link; null = the fan picks. |
externalRef | string | null | Your per-fan reference, returned on the sale. |
createdAt | string | When the link was created. Format: date-time |
{
"id": "q7m2c8zk4w",
"type": "buy",
"url": "https://www.dropfans.io/buy/q7m2c8zk4w",
"telegram": "https://t.me/DropfansBot/app?startapp=q7m2c8zk4w",
"productId": "cmv5tmpcm0015h4oucxkgc4ma",
"amountUsd": null,
"externalRef": "tg_48213377",
"createdAt": "2026-10-29T12:00:00.000Z"
}Errors
| Status | Body | When |
|---|---|---|
| 400 | {"error":"type must be one of profile, tip, subscribe, buy","code":"invalid_type"} | Unknown `type`. Also `invalid_external_ref`, `invalid_amount` (out of range, or not a tip) and `invalid_product` (productId on a non-buy link). |
| 404 | {"error":"Drop not found","code":"product_not_found"} | `productId` is missing, unknown or another account’s. |
| 409 | {"error":"Set a username first","code":"username_required"} | The creator has no username yet. |
| 400 | {"error":"X-Dropfans-Account must be an account id (from GET /api/external/agency/creators), not a username.","code":"invalid_account_header"} | `X-Dropfans-Account` is not an account id. |
| 400 | {"error":"X-Dropfans-Account names a different account than the path — send one or the other.","code":"account_mismatch"} | Called under /v1/creators/{creatorId}/ with an `X-Dropfans-Account` naming another account. |
| 403 | {"error":"This key cannot act for that account.","code":"account_not_managed"} | The creator in the path (or `X-Dropfans-Account`) is one this key cannot act for: no ACCEPTED management by your agency and not an account your app onboarded (or it no longer holds a key bound to your app), a key minted for one creator, or someone is suspended, banned or deleted. An unknown id gets the same answer. |
| 403 | {"error":"This app does not have the links API category enabled. Contact support to enable it.","code":"api_category_disabled","category":"links"} | Your app has the Links [API category](/developers/concepts/api-categories) switched off. |
Rate limiting
Every response carries the X-RateLimit-Tier header and, on limited tiers, the per-minute and per-day trios — read X-RateLimit-Remaining and X-RateLimit-Reset instead of hardcoding limits. Details in Rate limits.
Code samples
curl -X POST "https://www.dropfans.io/api/external/v1/links" \
-H "Authorization: Bearer $DROPFANS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "buy",
"productId": "cmv5tmpcm0015h4oucxkgc4ma",
"externalRef": "tg_48213377"
}'const res = await fetch(`https://www.dropfans.io/api/external/v1/links`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DROPFANS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"type": "buy",
"productId": "cmv5tmpcm0015h4oucxkgc4ma",
"externalRef": "tg_48213377"
}),
});
console.log(await res.json());import os
import requests
res = requests.post(
"https://www.dropfans.io/api/external/v1/links",
headers={"Authorization": f"Bearer {os.environ['DROPFANS_API_KEY']}"},
json={
"type": "buy",
"productId": "cmv5tmpcm0015h4oucxkgc4ma",
"externalRef": "tg_48213377",
},
)
print(res.json())Questions? support@dropfans.io
