Publish a post to the For You feed
API reference

Publish a post to the For You feed

Rate limit: 5 posts per creator per rolling 24 hours. Bursts are fine — only the daily volume is capped. Exceeding it returns 429 with a Retry-After header.

View as MarkdownUpdated Aug 19, 2026
POST/api/external/posts

Rate limit: 5 posts per creator per rolling 24 hours. Bursts are fine — only the daily volume is capped. Exceeding it returns 429 with a Retry-After header.

Three shapes: • TEXT — caption only. • MEDIA — a gallery of 1-10 images from your vault, each free or paid. • DROP — attach an existing approved drop by productId.

For MEDIA, list your vault first (GET /api/external/vault) and use the id of an item whose moderationStatus is APPROVED to publish without waiting.

Everything posted here goes through exactly the same moderation as a post written in the web composer: captions run the prohibited-word filter, and media posts stay PENDING until every image clears the NSFW pipeline. You cannot use this API to bypass review.

Authentication

Send the creator's API key as a bearer token: Authorization: Bearer dpfn_…. See Authentication & API keys.

Only CREATOR and AGENCY accounts can call this endpoint.

Request body

Content type: application/json

NameTypeRequiredDescription
captionstringOptionalPost text. Required for kind=TEXT. Silently truncated to 2000 characters.
Max length: 2000
kindstringOptionalPost shape. DROP is not a request value — send productId instead.
One of: TEXT, MEDIA, SUBSCRIPTION, COMMUNITY
Default: "TEXT"
productIdstringOptionalAttach an existing approved drop. Forces kind=DROP. Must belong to you.
mediaobject[]OptionalRequired when kind=MEDIA.
Max items: 10
Show child attributes
vaultItemIdstringA vault item you own (GET /api/external/vault).
isPaidbooleanPaid items are shown blurred and mint a PPV unlock. Note: posting via API bakes no custom blur preview — paid items fall back to a gaussian blur.
Default: false
pricenumberUSD. Required when isPaid is true. Minimum $5.
Min: 5
orderintegerDisplay order. Defaults to array index.
scheduledAtstringOptionalISO-8601. At least 1 minute ahead, at most 30 days. Omit to publish now.
Format: date-time
Text post
{
  "caption": "im 5 min away, wyd?"
}
Free image from the vault
{
  "kind": "MEDIA",
  "caption": "rate my fit 1-10",
  "media": [
    {
      "vaultItemId": "clxv1a2b30001item",
      "isPaid": false,
      "order": 0
    }
  ]
}
Free teaser + paid unlock, scheduled
{
  "kind": "MEDIA",
  "caption": "can i show you??",
  "scheduledAt": "2026-08-26T19:00:00Z",
  "media": [
    {
      "vaultItemId": "clxv1a2b30001item",
      "isPaid": false,
      "order": 0
    },
    {
      "vaultItemId": "clxv1a2b30002item",
      "isPaid": true,
      "price": 10,
      "order": 1
    }
  ]
}

Responses

201Post created. Check `status`: APPROVED means it is already live, PENDING means it is still in review and will go live by itself once it clears.
NameTypeDescription
idstringThe new post id — poll GET /api/external/posts/{id} with it.
statusstringPENDING is normal, not an error: media posts wait on the NSFW pipeline and a flagged caption waits on a human.
One of: PENDING, APPROVED
pendingbooleanConvenience mirror of status === "PENDING".
scheduledAtstring | nullEchoed schedule time, or null for immediate posts.
Format: date-time
urlstring | nullThe creator’s profile URL (where the post appears once live). Null when the creator has no username yet.
Format: uri
Example response
{
  "id": "clxp0st000001feed",
  "status": "PENDING",
  "pending": true,
  "scheduledAt": null,
  "url": "https://www.dropfans.io/u/valeria"
}

Errors

StatusBodyWhen
400{"error":"Invalid JSON body"}The body is not valid JSON.
400{"error":"media must be a non-empty array for kind=MEDIA"}kind=MEDIA without media.
400{"error":"A post can carry at most 10 media items"}More than 10 media entries.
400{"error":"media[0].vaultItemId is required"}A media entry lacks vaultItemId (index varies).
400{"error":"media[0].price must be at least $5 for a paid item"}isPaid without a valid price (index varies).
400{"error":"Invalid schedule time"}scheduledAt is not a parseable date.
400{"error":"Schedule time must be at least a minute in the future"}scheduledAt is in the past or under a minute ahead.
400{"error":"Posts can be scheduled at most 30 days ahead"}scheduledAt beyond 30 days.
400{"error":"Write something first"}kind=TEXT with an empty caption.
400{"error":"Drop is not approved yet"}productId points at a drop still in moderation — check GET /api/external/drops/{id}.
401{"error":"Unauthorized","code":"unauthorized"}Missing or invalid API key.
403{"error":"Only creators can post"}The key belongs to a CONSUMER account.
404{"error":"Drop not found"}productId does not exist on this account.
422{"error":"Your post contains a word that isn’t allowed.","matchedWord":"…"}Caption tripped the prohibited-word filter — rewrite it and retry.
429{"error":"You’ve reached the posting limit (5 per day). Try again in …"}Daily posting cap (5 per rolling 24h) reached. Retry-After header = seconds to wait. This is separate from the API rate limit (whose body carries code:"rate_limited").

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/posts" \
  -H "Authorization: Bearer $DROPFANS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "im 5 min away, wyd?"
}'

Notes

The 429 here (posting cap, prose body with Retry-After) is a different limit from the gateway rate limit (429 with code:"rate_limited") — handle both. SUBSCRIPTION and COMMUNITY kinds additionally require an active subscription setup / VIP Telegram channel on the account and 400 otherwise.

Questions? [email protected]