TEASEDocs
ProductsClubHow-to

Create a smart link

Set up one /r/<slug> link with a paid/free-trial policy — no OnlyFans write, one link to hand out per domain.

Who this is for: a workspace owner or traffer.

A Route-Spine smart link is one URL — https://yourdomain.com/r/main — that holds both your paid offer and your free-trial offer and decides per visitor which to serve. Creating it is a pure database write: POST /api/admin/smartlinks never touches 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", "name": "Main link" }'

Body parameters

FieldTypeRequiredDescription
domainstringyesMust already be a registered domain, and in the caller's scope.
slugstringno1–32 chars from [a-z0-9_-]. paid, ftl, and qr are reserved. Left empty, a unique slug is derived from name (transliterated, lowercased, --joined).
namestringnoA label to recognize it by.
policyobjectnoThe routing policy. Omitted, it defaults to a 50/50 split with the trial arm gated to countries that have their own trial link.

Response

{
  "id": 42,
  "domain": "yourdomain.com",
  "slug": "main",
  "name": "Main link",
  "policy": { "v": 1, "arms": { "paid": { "enabled": true, "weight": 50, "label": "Платная" },
                                  "ftl": { "enabled": true, "weight": 50, "label": "Триал",
                                           "require_country_link": true } }, "geo": [] },
  "active": true,
  "url": "https://yourdomain.com/r/main",
  "created_at": 1751280000,
  "updated_at": 1751280000
}

url is the public address — hand it out as-is. A POST with a slug already taken on that domain answers 400 slug_taken.

/r/paid and /r/ftl are retired addresses

They used to be two separate fixed links. Today they're aliases that fall through to the domain's own smart-link policy — an old posted link keeps working, but you can't create new ones under those slugs, and /r/ftl no longer guarantees a trial.

The policy shape

The policy is what makes the link "smart":

{
  "v": 1,
  "arms": {
    "paid": { "enabled": true, "weight": 50, "label": "Платная" },
    "ftl":  { "enabled": true, "weight": 70, "label": "Триал", "require_country_link": true }
  },
  "geo": [
    { "countries": ["DE", "AT"], "arms": { "ftl": { "weight": 70 }, "paid": { "weight": 30 } } }
  ]
}
  • arms.weight — an integer 1–1000; a weighted, sticky-by-subnet split between paid and ftl. A returning visitor keeps the arm they were first given.
  • arms.ftl.require_country_link — defaults true. The trial arm only actually fires for a country that has its own trial destination in the inventory; every other country falls through to paid rather than getting a foreign trial by accident.
  • geo — up to 200 per-country override rules, each replacing the base arm weights for the listed ISO codes.

Change it any time with PATCH /api/admin/smartlinks/{id} — routing picks up the new policy within about a minute. Unknown fields anywhere in the policy are a validation error, not a silently-ignored typo — 400 bad_policy lists every problem found.

Pin a single arm

https://yourdomain.com/r/main/paid or /r/main/ftl always serves that one arm, bypassing the split — useful when a specific placement should never rotate. The plain /r/main link keeps splitting by policy.

Turning a live link off needs a confirm

PATCH { "active": false } on a smart link that published buttons still point at answers 409 deactivate_confirm_required instead of silently 404-ing fans. Repeat the call with "confirm": true once you've verified that's what you want.

A freshly created smart link has a policy but no destinations yet — every arm/country pair resolves to nothing until you fill them in.

What's next

Fill in where each arm points, then verify routing before you publish.

On this page