TEASEDocs
ProductsLinkTelegram gate

Route different audiences with rules

Send non-premium fresh accounts to a channel, one country to a local manager, and everyone else to the DM — with named destinations and ordered rules.

The rules engine is the general form of the junk-traffic split: instead of one hardcoded DM/channel choice, you define up to 20 named destinations and up to 30 ordered rules. For every /start, rules are checked top to bottom and the first one that matches wins; nobody matching falls through to a default destination.

This is the current, "v2" configuration surface — it lives entirely under destinations, default_dest, and rules on the same gate row as everything else in this section.

Destinations

A destination is just a name and a URL:

{ "key": "dm", "title": "Manager's DM", "url": "https://t.me/your_manager" }
FieldRule
keySlug, [a-z0-9_-], 1–16 chars, unique in the list. Rules and default_dest reference destinations by this key — it doesn't change once other things point at it.
titleFree text, up to 64 chars. Display only.
urlMust start with https://, up to 1024 chars.

Save destinations with a full-replace PATCH (the list you send replaces the stored one):

curl -X PATCH https://app.tease.link/api/admin/tg/gate \
  -H "Authorization: Bearer $TEASE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "destinations": [
      { "key": "dm", "title": "Manager DM", "url": "https://t.me/your_manager" },
      { "key": "channel", "title": "Overflow channel", "url": "https://t.me/your_channel" },
      { "key": "br_manager", "title": "Brazil manager", "url": "https://t.me/br_manager" }
    ],
    "default_dest": "dm"
  }'

In the panel this is the Destinations card — add/rename/re-point inline; a destination that's in use by a rule or is the current default can't be deleted (the button is disabled with a tooltip explaining why) until you repoint that rule or the default first.

Rules

A rule is one condition set plus the destination it routes to when it matches:

{ "id": "r_br", "enabled": true, "conds": { "countries": ["BR"] }, "dest": "br_manager" }
FieldRule
idSlug, [a-z0-9_-], 1–24 chars, unique.
enabledBoolean — a disabled rule is skipped without deleting it.
destMust be a key from the destinations you're saving in the same patch (or already stored, if this patch doesn't touch destinations).
condsObject — see the table below. Every key is optional; all present conditions must match (AND). An empty {} matches everyone.

Available conditions, all optional:

ConditionTypeMatches when
premiumbooleanThe account's Telegram Premium flag equals this.
id_mininteger ≥ 0Telegram id ≥ this value (a newer account).
id_maxinteger ≥ 0Telegram id < this value (an older account).
langsup to 40 two-letter codesThe resolved greeting language is in the list.
countriesup to 40 two-letter ISO codesThe originating click's country is in the list — unknown country never matches.
has_usernamebooleanThe Telegram account has a public @username set.
organicbooleantrue = arrived with no ?start= click id; false = arrived tagged.

Save the whole ordered list (order is the priority — first match wins):

curl -X PATCH https://app.tease.link/api/admin/tg/gate \
  -H "Authorization: Bearer $TEASE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [
      { "id": "r_br", "enabled": true, "conds": { "countries": ["BR"] }, "dest": "br_manager" },
      { "id": "r_fresh", "enabled": true, "conds": { "premium": false, "id_min": 7000000000 }, "dest": "channel" }
    ]
  }'

In the panel this is the Split rules card: each row reads "IF [chips] → [destination]", with up/down reordering, a per-rule on/off switch, and a "caught in the period" counter next to each rule. The row at the bottom ("Everyone else →") sets default_dest.

default_dest, and every rule's dest, must resolve to a real destination key after the patch is applied. Changing destinations in the same request that still leaves a stored rule or the stored default pointing at a now-missing key is rejected with 422 — update them together.

Country conditions cost a lookup

A countries condition is the one thing that isn't free: only when at least one stored rule carries it does the webhook spend an extra indexed lookup (per /start) to resolve the originating click's country. No countries conditions anywhere → that lookup never runs.

How to know it worked

GET /api/admin/tg/gate (or just reloading the panel tab) echoes back the destinations, rules, and default exactly as saved — including the enabled flag on each rule. The stats block's by_dest and by_rule breakdowns for the selected period tell you which rule is actually firing and how often; the panel shows this inline as each rule's hit counter.

If it didn't work

  • 422 on a rule's dest — that key isn't in your destinations list (in this patch or already stored). Add the destination first, or fix the typo.
  • 422 on default_dest — same shape of problem: it must be a real key.
  • A rule you saved never seems to fire — see Troubleshooting: a saved rule silently never fires.

What's next

On this page