API pipeline: Submit your Telegram groups, find groups by keyword, place or rubric, vet them and attach them to a campaign

Index: API pipelines — index

Goal: choose the Telegram groups a campaign works in: submit your own groups by username, find more in the open index by keyword, place or rubric, vet them, attach groups or presets of groups to a campaign, and exclude groups from it.

Model

A. Submit your own groups

1. Submit usernames. One call creates a new preset and files every username.

POST /v0/my/telegram_groups
Idempotency-Key: groups-phuket-1
{"title": "Phuket expats", "project": "<project nonce>",
 "telegram_group_handles": ["@example_expats", "https://t.me/example_market", "t.me/+AbCdEf"]}
→ {"workspace": {"nonce": "470350000000000001", "card_type": "workspace", "visibility": "Private", …},
   "handles": [
     {"handle": "t.me/+AbCdEf", "state": "rejected"},
     {"handle": "example_expats", "state": "contained", "nonce": "470350000000000002"},
     {"handle": "example_market", "state": "proposed", "nonce": "470350000000000003"}],
   "proposals_workspace": "470350000000000005"}

2. Behind: checking (asynchronous). Cabi.Chat checks each proposed username: it must be a public group (not a broadcast channel), not marked scam. Groups that pass are added to the index and to your preset; the others are not added.

3. See the result.

GET /v0/my/workspaces/470350000000000001?contained_limit=200
→ {"contained": [{"card_type": "telegram_group", "nonce": "…", "title": "Example market", …}], "contained_next_offset": null}

One username: GET /v0/graph/resolve/@tgg.example_market answers once the group is in the index. Groups that pass the check show up in your preset.

Add a known group to any of your presets: POST /v0/my/workspaces/{ws}/{group nonce} → 204; remove it: DELETE on the same path.

B. Find groups in the index

Keyword:

POST /v0/search/graph
{"query": "phuket", "card_type": "telegram_group", "limit": 20, "page": 1}
→ {"hits": [{"card_type": "telegram_group", "nonce": "…", "handle": "tgg.example_phuket",
            "title": "…", "item_count": 13934, "location": {…}, …}],
   "total_hits": 115, "page": 1, "total_pages": 6}

/v0/search/graph combines keyword and meaning; /keyword and /vector run one side only. query is cut at 200 characters.

Place. Filters take place nonces (rubric cards of kind place), not geonameids or ISO codes:

  1. GET /v0/search/geo/cities?q=phuket → {"geonameid": 1151254, "name": "Phuket", "country_iso": "TH", …}
  2. GET /v0/graph/resolve/@rub.geo-1151254 → {"card_type": "rubric", "nonce": "<place nonce>", "kind": "place"}. A country is @rub.geo-<iso lowercase>, e.g. @rub.geo-th.
  3. {"query": "", "card_type": "telegram_group", "filters": {"locations": ["<place nonce>"], "exclude_terms": ["rent"]}}

locations and places_in match the place and everything inside it. Place nonces go only there; inside rubrics they match nothing.

Rubric: "filters": {"rubrics": [["<rubric nonce>"]]}; inner arrays OR, outer AND; rubrics_not excludes. Find a rubric: {"query": "dating", "card_type": "rubric", "filters": {"kind": "tag"}}.

Inside a preset: add "workspace": "<preset nonce>".

Allowed filter keys: rubrics, rubrics_not, rubrics_in, locations, places_in, tags_in, project, preference_countries, preference_city {nonce, lat, lon}, radius_km, roots_only, kind, card_type, exclude_terms; any other → 400 unknown filter: <key>.

Member count and ratings are on each group's overview (below). Recently added groups are found best by keyword.

Vet a group:

GET /v0/graph/telegram_groups/@tgg.example_phuket/overview
→ {"tile": {…}, "username": "example_phuket", "members_count": 13934,
   "stats": {"spam": 10.4, "density": 13.8, "openness": 60.9, "moderation": 17.0,
             "audience": 58.1, "pace": 34.0, "msgs_per_day": 221.5, "sample": 200, "rated_at": "…"},
   "mine": {"totals": {"sent": 0, "deleted": 0, "replied": 0, "joined": 0, …}, "timeline": […], "campaigns": […], "replies": […]}}

Ratings are 0–100; stats is null until the group is rated. mine is your own record in that group. The group page without stats: GET /v0/graph/telegram_groups/{nonce | @tgg.<username>}.

Ready presets (GET /v0/graph/workspaces/{ident}?contained_limit=&contained_offset=): @ws.tgg.geo-<geonameid>.2026-09-12 (e.g. @ws.tgg.geo-1151254.2026-09-12, Phuket) and @ws.tgg.iab-<topic>.2026-09-12 (e.g. @ws.tgg.iab-travel.2026-09-12): snapshots of 2026-09-12.

Lists that keep themselves up to date. Some public group lists are maintained by Cabi.Chat automatically and change on their own, for example groups heavy with spam or groups where many messages get deleted. They carry the rubric «Auto-maintained» (find it with {"query": "Auto-maintained", "card_type": "rubric"}, then search workspaces with that rubric). Use them like presets, for example in not_tg_groups to keep a campaign out of such groups.

C. Put groups into a campaign

PATCH /v0/my/campaigns/<campaign nonce>
{"tg_groups": ["<preset nonce>", "<group nonce>"]}

Exclude groups. not_tg_groups lists groups the campaign skips; it takes the same entries as tg_groups. Useful to drop a few groups from a ready preset:

PATCH /v0/my/campaigns/<campaign nonce>
{"tg_groups": ["<preset nonce>"], "not_tg_groups": ["<group nonce>", "<other preset nonce>"]}

API surface

Endpoint Purpose FE AI / oracle
POST /v0/my/telegram_groups Submit usernames as a new preset yes (Submit Telegram groups) yes, the way to bring own groups
GET /v0/my/workspaces/{ws} Read a preset; see which groups arrived yes yes
POST / DELETE /v0/my/workspaces/{ws}/{group} Add / remove a known group yes yes
GET /v0/graph/resolve/{@handle} @tgg.<username> → group; @rub.geo-<id> → place yes yes
POST /v0/search/graph (/keyword, /vector) Find groups by keyword, place, rubric, inside a preset yes yes, main discovery
GET /v0/search/geo/cities, /countries City or country lookup yes yes, then resolve
GET /v0/graph/telegram_groups/{ident}, /overview Group page; members, ratings, your record yes yes, vetting
GET /v0/graph/workspaces/{ident} Ready presets yes yes
POST / PATCH /v0/my/campaigns… tg_groups, not_tg_groups Attach or exclude groups or presets yes yes

Mentioned