Run a native OnlyFans promo or bundle
Create or stop an on-platform free-trial/discount promo, or edit the 3/6/12-month bundle discounts, and know what "connected"/"stale" means before you trust the numbers.
Who this is for: a workspace owner.
Native promos and bundles are OnlyFans' own on-platform offers, not a TEASE construct — this area only mirrors what the engine sees on your promo page and queues writes back to it.
Never blind-retry a create
Every write here — create/stop/delete a promo, create/update/delete a bundle — is
asynchronous: the endpoint answers {"status": "pending", "nonce": "..."} immediately and
the real OnlyFans write happens later, through an engine job. If a synchronous caller's
connection drops before that, a retry that assumes "it probably failed" can create a
second live promo on OnlyFans. Poll /promo/ops/{nonce} instead of
guessing.
Read the current promos and bundles
curl https://app.tease.link/api/admin/promo/campaigns \
-H "Authorization: Bearer $TEASE_API_KEY"{
"items": [ { "promo_id": "pr_9021", "audience": "new_subscribers", "kind": "free_trial",
"value": 7, "claim_limit": 100, "claims": 42, "expires_at": 1751880000,
"active": true, "page_owner_id": "..." } ],
"connected": true, "captured_at": 1751877000,
"of_enabled": true, "ops": { "create_promo": true, "stop_promo": true, "..." : "..." }
}connected is false in two different situations you should read differently: the engine has
never captured this page's promo screen yet ("connecting"), or it captured one over 3
hours ago (stale — treat the numbers as possibly out of date). captured_at: 0 distinguishes
the first case from the second. GET /api/admin/promo/bundles returns the same envelope shape
for the three fixed 3/6/12-month bundle slots on the current page only — bundles are not
summed across accounts the way promos can be with ?scope= (see below).
Pass ?scope=<page|creator|tenant>&id=... on the promo list to widen it beyond the current
page — each returned promo carries its own page_owner_id so a multi-account read stays
attributable.
Create a promo
curl -X POST https://app.tease.link/api/admin/promo/campaigns \
-H "Authorization: Bearer $TEASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "audience": "new_subscribers", "kind": "free_trial", "value": 7,
"limit": 100, "expiry_days": 14, "message": "" }'| Field | Type | Description |
|---|---|---|
audience | string | all_subscribers, new_subscribers, or expired_subscribers. |
kind | string | free_trial or first_month (a discount). |
value | integer | Days for free_trial (1–30). Percent for first_month (5–65, step 5). |
limit | integer | Claim cap; 0 = unlimited. |
expiry_days | integer | Days until the promo ends; 0 = no end date. |
message | string | Optional note. |
A value outside these ranges is rejected with 422 before it ever reaches the engine — OnlyFans'
own limits, checked locally so you find out immediately rather than a minute later.
curl -X POST https://app.tease.link/api/admin/promo/campaigns/pr_9021/stop \
-H "Authorization: Bearer $TEASE_API_KEY"Stopping means no new claims — it does not touch fans who already claimed.
Create or edit a bundle
Bundles are fixed 3/6/12-month slots — there's no list of arbitrary bundles to create, only these three to configure:
curl -X PUT https://app.tease.link/api/admin/promo/bundles/d3 \
-H "Authorization: Bearer $TEASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "duration": 3, "discount": 15 }'bundle_id in the path is whatever OnlyFans' own feed reported for that slot, or d<duration>
(d3 / d6 / d12) when it didn't — read it off the GET /promo/bundles list rather than
guessing it. duration must be 3, 6, or 12; discount is 0–50, step 5.
Poll a pending write
curl https://app.tease.link/api/admin/promo/ops/a1b2c3d4 \
-H "Authorization: Bearer $TEASE_API_KEY"{ "nonce": "a1b2c3d4", "status": "done", "attempts": 1, "error": "",
"requested_at": 1751877000, "settled_at": 1751877012 }status is one of queued, done, failed, or stale. stale means the engine hasn't
answered — it may have failed after it already reached OnlyFans — so treat it as "check the
next scan," not as "definitely didn't happen."
A pending write only stages if the gate is off
Until the write-ops gate is live for an account, these endpoints answer
{"enabled": false, "staged": true} and write nothing at all — an honest no-op rather than a
fake success.
What's next
Check what breaks before deleting a promo or bundle — deleting a promo is not the same as re-creating it later (the claim counter restarts at zero).
Reclaim a free-pool slot
Pull a link out of active routing and return it to the spare pool instead of retiring it.
Check what breaks before deleting
See which domains/countries would be left without a link, and read honest "we don't know" versus zero on the traffic counters — before you delete a trial, tracking link, promo, or bundle.