Smart Links
One link — /r/<slug> — that holds both your paid subscription and your free trial and decides per visitor which to show: a weighted split with per-country rules. Plus click, subscriber, and revenue performance.
A smart link is one trackable URL — https://yourdomain.com/r/main — that you drop into a
bio, an ad, or a message. It holds both your paid subscription and your free-trial offer at
once and decides per visitor which one to show (a weighted A/B split, overridable per country).
TEASE records the click and later ties any subscription, rebill, tip, or PPV back to that link — so
you can see exactly what each source earns.
One link — the rotation lives inside it
You don't set up a separate "paid" link and a separate "trial" link and hand out two. You create one smart link with a policy and tune the paid/trial split inside it. The individual per-arm OnlyFans links are just a pool the smart link draws its destinations from, filled by provisioning — see Where destinations come from: the link pool at the end.
Create a smart link
You create one /r/<slug> link (usually main) with a routing policy — the paid/trial arm
weights. The /api/admin/smartlinks endpoint does not touch OnlyFans.
curl -X POST https://app.tease.link/api/admin/smartlinks \
-H "Authorization: Bearer $TEASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "domain": "yourdomain.com", "slug": "main",
"policy": { "arms": { "paid": { "weight": 50 }, "ftl": { "weight": 50 } } } }'const res = await fetch('https://app.tease.link/api/admin/smartlinks', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TEASE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
domain: 'yourdomain.com',
slug: 'main',
policy: { arms: { paid: { weight: 50 }, ftl: { weight: 50 } } },
}),
});
const link = await res.json();res = requests.post(
"https://app.tease.link/api/admin/smartlinks",
headers={"Authorization": f"Bearer {os.environ['TEASE_API_KEY']}"},
json={
"domain": "yourdomain.com",
"slug": "main",
"policy": {"arms": {"paid": {"weight": 50}, "ftl": {"weight": 50}}},
},
)
link = res.json()Parameters
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | yes | Your host (must already be registered). |
slug | string | yes | 1–32 chars [a-z0-9_-]. paid and ftl are reserved. Usually main. |
name | string | no | A label to recognize it by. |
policy | object | no | The routing policy (arm weights + per-country rules). Defaults to 50/50, with the trial arm served only where a country has its own permanent trial link. |
The public address is https://yourdomain.com/r/main. Share it as-is — TEASE takes care of
routing the fan to the right offer and capturing the click server-side.
You can pin a single arm with https://yourdomain.com/r/main/paid or /r/main/ftl — for when a
specific placement should always lead to a specific offer (the plain /r/main link keeps
splitting traffic by the policy).
The old /r/paid and /r/ftl addresses are retired. They used to be two separate links —
one always paid, one always trial. The offer is the smartlink policy's call now, not the URL's.
An old link you already posted keeps working: your domain's smartlink serves it exactly like
/r/main. But you cannot create new ones, and /r/ftl no longer guarantees a trial.
How it decides: the policy
The policy is what turns one link into a smart one:
- Weighted split. Each arm gets a weight (
50/50,30/70). The choice is sticky — a returning visitor always gets the same arm. - Per-country rules. Override the split for specific countries (
DE/AT: mostly trial,RU: paid only). By default the trial arm is served only to countries that have their own permanent trial link — everyone else lands on the paid page automatically. - Policy shape.
{"arms": {"paid": {"weight": 50}, "ftl": {"weight": 50}}, "geo": [{"countries": ["DE"], "arms": {"ftl": {"weight": 70}, "paid": {"weight": 30}}}]}. Full shape in the API reference.
Change the split any time with PATCH /api/admin/smartlinks/{id} and a new policy — routing picks
the change up within about a minute.
Destinations — where each arm points
A policy smart link routes to destination links — one per (arm, country) pair. You can view and edit them directly.
List the applied destinations (active and switched off):
curl https://app.tease.link/api/admin/smartlinks/42/inventory \
-H "Authorization: Bearer $TEASE_API_KEY"Add a destination for an arm and country. Leave country empty to make it the arm-wide
default — the one every other country falls back to:
curl -X POST https://app.tease.link/api/admin/smartlinks/42/inventory \
-H "Authorization: Bearer $TEASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"arm": "ftl", "country": "DE", "url": "https://onlyfans.com/yourpage/trial",
"name": "German trial"}'Edit one destination — swap its URL, relabel it, or toggle it in or out of routing:
curl -X PATCH https://app.tease.link/api/admin/smartlinks/42/inventory/1088 \
-H "Authorization: Bearer $TEASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"active": false}'Verify before launch
Before you publish, read-only endpoints show exactly what your traffic will get:
- Used in — the buttons that point at this link, and the audiences (geo-segments) that route each country here.
- Geo map — for every country your
audience covers, the arm and the concrete destination a visitor lands on. Pass
?countries=DE,FR,USto probe specific ones. - Validate — a health check: it runs
production routing across all bound countries and finds dead-end routes (a fan would land on
nothing), enabled arms with no destination, or a link no button uses yet. Fix any
block-severity issue before you go live.
curl https://app.tease.link/api/admin/smartlinks/42/validate \
-H "Authorization: Bearer $TEASE_API_KEY"{
"ok": false,
"blocking": 1,
"warnings": 0,
"issues": [
{
"severity": "block",
"code": "dead_route",
"message": "Audience \"German\" routes here, but country DE has no working destination — a fan would hit a dead end.",
"fix": { "type": "country", "country": "DE" }
}
]
}Read performance
Drill into a single link to see the fans it converted and revenue broken down by conversion type. All amounts are integer net cents:
curl https://app.tease.link/api/admin/smart-links/lnk_8fq2 \
-H "Authorization: Bearer $TEASE_API_KEY"{
"provider_id": "lnk_8fq2",
"name": "instagram-bio",
"fans": [
{ "fan_id": "f_3081", "total_net_cents": 4200, "purchases": 3, "last_at": "2026-07-01" }
],
"by_type": [
{ "conversion_type": "subscription", "count": 12, "revenue_net_cents": 24000 },
{ "conversion_type": "tip", "count": 5, "revenue_net_cents": 6300 }
]
}| Field | Description |
|---|---|
fans | Per-fan rollup: net cents earned, purchase count, and last activity. |
by_type | Count and net revenue per conversion type (subscription, tip, PPV, rebill). |
For the aggregate click → subscriber → revenue funnel across all your links, read the list endpoint — each row carries its own performance snapshot and conversion rollup.
Attach a conversion pixel
You can forward conversions to Meta or TikTok server-side so your ad platform optimizes on real revenue. See Webhooks & CAPI and Attribution.
Where destinations come from: the link pool
The /r/main smart link doesn't store OnlyFans links itself — it draws them from your
domain's destination pool. You fill the pool in one step by provisioning (paid + trial,
per country):
curl -X POST "https://app.tease.link/api/admin/domains/yourdomain.com/provision-links" \
-H "Authorization: Bearer $TEASE_API_KEY"The smart link then attaches those links to arms and countries via dress_country or the
destinations editor above. You never create
per-arm links by hand — that's what the pool is. Hand fans only /r/main. More:
Provision domain links.
Next steps
- Attribution — revenue by source and country.
- Webhooks & CAPI — server-side conversion events for ads.
- API Reference — every smart-link endpoint.
Cloaking
Per-domain protection that keeps your content buttons invisible to bots, scrapers, and in-app link scanners — so a real fan sees the funnel and a moderation crawler doesn't.
Scanning & data freshness
How TEASE reads your OnlyFans account — what it scans, how fresh the data is, how you trigger a scan on demand, and the ban-safe discipline that makes it all safe.