Attach baked blur previews to a drop
Uploads pre-blurred ("baked") JPEG teasers for a drop's media, so the checkout page shows the exact same blur your app showed the buyer elsewhere.
/api/external/v1/drops/{id}/previewsalso served at /api/external/drops/{id}/previewsUploads pre-blurred ("baked") JPEG teasers for a drop's media, so the checkout page shows the exact same blur your app showed the buyer elsewhere.
The body is multipart/form-data with one part per media item, named by vault item id: previewBlob_<vaultItemId> (the baked JPEG) and optionally blurMeta_<vaultItemId> (a descriptor like "partial", "pixelated:59" or "full"). Without previews, paid media falls back to a generic gaussian blur.
Parts that are missing, not image/jpeg, over 8MB, or that hit a storage error are skipped without an error. The only signal is the updated count — compare it to how many parts you sent. Repeatable: re-sending overwrites the stored preview.
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}/drops/{id}/previews — an agency's own key for a creator or AI persona it manages (needs canManageDrops), 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.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Required | The product (drop) id. |
Request body
Content type: multipart/form-data — Field names embed the vault item id — one previewBlob per media item you want a preview on.
| Name | Type | Required | Description |
|---|---|---|---|
previewBlob_<vaultItemId> | file | Optional | The baked JPEG teaser for that media item. Must be image/jpeg, ≤8MB. |
blurMeta_<vaultItemId> | string | Optional | Optional blur descriptor recorded alongside: "full", "partial" or "pixelated:<intensity>". |
previewBlob_clxv1a2b30001item=@preview-1.jpg
blurMeta_clxv1a2b30001item=partial
previewBlob_clxv1a2b30002item=@preview-2.jpg
blurMeta_clxv1a2b30002item=pixelated:59Responses
| Name | Type | Description |
|---|---|---|
success | boolean | Always true. |
updated | integer | Previews stored on this call. |
{
"success": true,
"updated": 2
}Errors
| Status | Body | When |
|---|---|---|
| 401 | {"error":"Unauthorized","code":"unauthorized"} | Missing or invalid API key. |
| 403 | {"error":"You do not own this drop"} | The drop belongs to another creator. |
| 404 | {"error":"Drop not found"} | No such drop. |
| 500 | {"error":"Failed to attach previews"} | Unexpected failure — retry later. |
| 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":"The agency does not have permission for this on that account.","code":"permission_denied","permission":"canManageDrops"} | For a managed creator: the agency lacks `canManageDrops` on that account. |
| 403 | {"error":"This app does not have the drops API category enabled. Contact support to enable it.","code":"api_category_disabled","category":"drops"} | Your app has the Drops & checkout 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/drops/$PRODUCT_ID/previews" \
-H "Authorization: Bearer $DROPFANS_API_KEY" \
-F "previewBlob_clxv1a2b30001item=@preview-1.jpg;type=image/jpeg" \
-F "blurMeta_clxv1a2b30001item=partial" \
-F "previewBlob_clxv1a2b30002item=@preview-2.jpg;type=image/jpeg" \
-F "blurMeta_clxv1a2b30002item=pixelated:59"import { readFile } from 'node:fs/promises';
const form = new FormData();
form.append(
'previewBlob_clxv1a2b30001item',
new Blob([await readFile('preview-1.jpg')], { type: 'image/jpeg' }),
'preview-1.jpg',
);
form.append('blurMeta_clxv1a2b30001item', 'partial');
const res = await fetch(
`https://www.dropfans.io/api/external/v1/drops/${productId}/previews`,
{
method: 'POST',
headers: { Authorization: `Bearer ${process.env.DROPFANS_API_KEY}` },
body: form,
},
);
console.log(await res.json()); // { success: true, updated: 1 }import os, requests
res = requests.post(
f"https://www.dropfans.io/api/external/v1/drops/{product_id}/previews",
headers={"Authorization": f"Bearer {os.environ['DROPFANS_API_KEY']}"},
files={
"previewBlob_clxv1a2b30001item": ("preview-1.jpg", open("preview-1.jpg", "rb"), "image/jpeg"),
},
data={"blurMeta_clxv1a2b30001item": "partial"},
)
print(res.json()) # { "success": true, "updated": 1 }Questions? support@dropfans.io
