# Open API — POST /telegram/v1

Auth is in the JSON body (no admin JWT). App must be enabled. Secret = App Backend secret.

```json
{
  "tenantId": "your-tenant-id",
  "appId": 2,
  "timestamp": 1737000000000,
  "sign": "<md5(secretKey + tenantId + appId + timestamp) lowercase hex>",
  "data": "<Base64 UTF-8 payload>"
}
```

Timestamp: max ~5 minutes old, ~1 minute future skew.

## Endpoints (all POST)

| Path | `data` before Base64 |
|------|----------------------|
| `/telegram/v1/bots` | Plain text: `botUsername\|botToken` (not JSON) |
| `/telegram/v1/bots/list` | Optional `{"pageNum":1,"pageSize":20}` |
| `/telegram/v1/groups` | `botUsernames` + `groupUsername` or `groupId` |
| `/telegram/v1/groups/list` | Pagination + optional `botUsername` |
| `/telegram/v1/channels` | `botUsernames` + `channelAddress` or `channelId` |
| `/telegram/v1/channels/list` | Pagination |
| `/telegram/v1/templates` | One Managed template object |
| `/telegram/v1/templates/list` | Pagination + optional `title` |
| `/telegram/v1/routes` | One event route object |
| `/telegram/v1/routes/list` | Pagination + `triggerType` / `triggerKey` |
| `/telegram/v1/outbound/send` | Immediate send. `botId` required; optional `groupIds`/`channelIds`; `templateId` or `content`/`buttons`. Omit group/channel ids to DM all users of that bot. |
| `/telegram/v1/messages/send` | **Queue** (SnailJob `telegramMessageSendJob`). See below. Not immediate; no `templateId` / inline template / Passthrough JSON. |
| `/telegram/v1/bots/update` | `{ "id": 16, "botToken": "..." }` — token only; username cannot change. Aliases: `botId`, `token` |
| `/telegram/v1/bots/delete` | `{ "id": 16 }` |
| `/telegram/v1/groups/update` | `{ "id": 19, "botUsernames": ["my_bot"] }` |
| `/telegram/v1/groups/delete` | `{ "id": 19 }` |
| `/telegram/v1/channels/update` | `{ "id": 18, "botUsernames": ["my_bot"] }` |
| `/telegram/v1/channels/delete` | `{ "id": 18 }` |
| `/telegram/v1/templates/update` | `{ "id": 16, ...same fields as create }` |
| `/telegram/v1/templates/delete` | `{ "id": 16 }` |
| `/telegram/v1/routes/update` | `{ "id": 4, ...same fields as create }` (template **titles**) |
| `/telegram/v1/routes/delete` | `{ "id": 4 }` |

Use numeric **row `id`** from list responses. For groups/channels that is **not** the Telegram chat id.

If `groupIds` and `channelIds` are both omitted on **outbound/send**, the platform DMs all users of that bot.

## Scheduled messages (`POST /telegram/v1/messages/send`)

Same MD5 envelope as other Open API calls. Enqueues one `telegram_message` row and a SnailJob per target. Groups/channels must be **JOINED**; channels need post permission.

Requires **`content` and/or `attachment`**, plus a target:

- `chatId` — also send `botId` **or** `botUsername`
- `groupIds` / `channelIds` — list **row ids** from `/groups/list` and `/channels/list` (not Telegram chat ids). **`botId` is required.**

Optional:

| Field | Meaning |
|-------|---------|
| `scheduleType` | `0` send now (still queued), `1` one-shot at `scheduledTime`, `2` recurring `cronExpression` |
| `scheduledTime` | epoch millis, `yyyy-MM-dd HH:mm:ss`, or ISO-8601 |
| `cronExpression` | Quartz, e.g. `0 0 9 * * ?` |
| `buttonRows` | inline keyboard JSON |

Unlike `/outbound/send`, this does **not** send in the HTTP request and does **not** support `templateId`, inline `template`, or Passthrough JSON.

```json
{
  "chatId": "-1001",
  "botUsername": "mybot",
  "content": "hello",
  "scheduleType": 0
}
```

```json
{
  "botId": 5,
  "groupIds": [1],
  "channelIds": [2],
  "attachment": "https://cdn.example.com/notice.png",
  "scheduleType": 2,
  "cronExpression": "0 0 9 * * ?"
}
```

Response includes `summary` (`total`, `ok`, `failed`) and `results[]` with `targetType`, `targetId`, `chatId`, `messageId`, `snailJobId`, `scheduleType`, `success`, `error`.

## Template object (create)

See `managed-templates.json`. Fields: `title`, `content` (HTML + `{{placeholders}}`), `messageType` (1 text, 2 photo), `templateType` (`TEXT`|`PHOTO`|`TOAST`), `image`, `buttons`, `status`.

Callback button `url`: `triggerKey` or `triggerKey|name=value` (max 64 bytes).

## Event route object (create)

See `managed-routes.json`. Fields: `triggerType` (`COMMAND`|`CALLBACK`|`TEXT`), `triggerKey`, `matchMode` (`EXACT`|`PREFIX`), `executionMode` (`LOCAL`|`REMOTE`), `httpMethod`, `backendPath`, `responseMode`, `localTemplateTitle` / `remoteDefaultTemplateTitle`, `paramMapping`, `status`.

Admin import is **one JSON object at a time**. Open API create is also one object per call.
