Telegram Integration
This guide walks through connecting an AI agent to Telegram via the Pai Provider system. There are two patterns:
- Interactive bot — a persistent
serviceagent that pollsgetUpdatesand responds to user messages in real time - Notification agent — a
taskagent (one-shot or scheduled) that sends reports to a chat viasendMessage
How it works
Telegram integration uses the Provider Proxy sidecar to intercept all calls to api.telegram.org and inject the real bot token transparently:
Agent Container
| TELEGRAM_BOT_TOKEN=pai-managed (dummy value)
| (DNS: api.telegram.org → 127.0.0.1)
v
Sidecar Proxy
|── rewrites /bot<dummy>/<method> → /bot<real-token>/<method>
|── logs API call for audit
v
api.telegram.org (real endpoint)
The agent never holds the real bot token. The sidecar rewrites the URL path on every request before forwarding to Telegram.
Step 1: Create a Telegram bot
- Open Telegram and search for @BotFather
- Send
/newbot - Choose a name for your bot (e.g., "My Pai Agent")
- Choose a username (must end in
bot, e.g.,my_pai_agent_bot) - BotFather will respond with your bot token — a string like
7123456789:AAH1bGc...
Save this token securely. You will need it in the next step.
Step 2: Store the bot token as a secret
pai add secret telegram-token --from-literal token=7123456789:AAH1bGcXYZ...
Step 3: Create the Provider
apiVersion: pai.io/v1
kind: Provider
metadata:
name: telegram-bot
spec:
type: telegram
auth:
type: token
secretRef: telegram-token
secretKey: token
policy:
allow: ["*"]
audit:
logRequests: true
Apply it:
pai apply -f telegram-provider.yaml
Key fields explained:
| Field | Purpose |
|---|---|
type: telegram | Activates the Telegram provider plugin in the sidecar |
auth.type: token | Single-token credential — the sidecar injects it by rewriting the URL path |
auth.secretRef | Kubernetes Secret holding the real bot token |
policy.allow: ["*"] | Allow all Telegram API methods |
The sidecar automatically injects TELEGRAM_BOT_TOKEN=pai-managed into the agent container. The agent uses this as a placeholder; the sidecar replaces it with the real token in the URL before forwarding.
Pattern A: Interactive bot (service agent)
A persistent agent that stays running, polls getUpdates, and responds to users in Telegram.
apiVersion: pai.io/v1
kind: Agent
metadata:
name: my-telegram-bot
spec:
type: service
image: ghcr.io/pai-platform/openclaw:latest
runAsUser: 1000
models:
- anthropic/claude-sonnet-4-6
providers:
- telegram-bot
inbound:
port: 3000
configFiles:
- path: /home/node/.openclaw/openclaw.json
content: |
{
"channels": ["telegram"],
"defaultModel": "claude-sonnet"
}
Deploy and verify:
pai apply -f my-telegram-bot.yaml
pai status my-telegram-bot
# Providers:
# - telegram-bot (telegram / api.telegram.org) — 12 requests today
Pattern B: Scheduled notification agent (task agent)
A task agent that runs on a schedule, researches or processes data, and posts a formatted message to a specific Telegram chat. No custom container image needed — uses the Pai harness.
Agent
apiVersion: pai.io/v1
kind: Agent
metadata:
name: daily-briefing
spec:
models:
- anthropic/claude-sonnet-4-6
providers:
- telegram-bot
tokens:
maxPerDay: 2000000
maxPerRequest: 16000
system: |
You are a daily briefing agent. Research today's highlights and send
a formatted report to Telegram.
Use web_search and web_fetch (max_bytes=15000 per page, at most 6 URLs)
to gather information, then send via bash:
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-H "Content-Type: application/json" \
-d "{\"chat_id\": \"${TELEGRAM_CHAT_ID}\", \"text\": \"...\", \"parse_mode\": \"Markdown\"}"
Telegram constraints:
- Max 4096 characters per message; use multiple sendMessage calls if needed
- Use Markdown parse_mode
tools:
- type: web_search
- type: web_fetch
- type: bash
env:
- name: TELEGRAM_CHAT_ID
value: "YOUR_CHAT_ID" # replace with your Telegram user or group ID
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"
Scheduled Agent
apiVersion: pai.io/v1
kind: Agent
metadata:
name: daily-briefing-job
spec:
type: task
agentDefinition: daily-briefing
schedule: "0 9 * * *" # Every day at 9:00am UTC
title: >
Research today's highlights and send a formatted report to Telegram.
Apply both:
pai apply -f template-agent.yaml
pai apply -f agent.yaml
kubectl get agent daily-briefing-job
# NAME TYPE PHASE SCHEDULE LAST RUN AGE
# daily-briefing-job task Scheduled 0 9 * * * Complete 1d
Finding your chat ID
To get your Telegram user ID or a group's chat ID:
- Send a message to your bot
- Open
https://api.telegram.org/bot<YOUR_TOKEN>/getUpdatesin a browser - Look for
"chat":{"id":123456789}— that number is your chat ID
Or use @userinfobot to get your own user ID instantly.
Triggering a run immediately
# Run once right now, outside the schedule
kubectl create job --from=cronjob/pai-cron-daily-briefing-job manual-$(date +%s) -n pai-system
Policy examples
Allow all Telegram methods (most common for notification agents):
policy:
allow: ["*"]
Restrict an interactive bot to safe read/send methods only:
policy:
allow:
- messages:send
- updates:read
- callback:answer
- chat:read
- bot:read
deny:
- chat:manage # block ban/unban/pin actions
Security notes
- The real bot token is stored in a Kubernetes Secret and mounted only in the sidecar container
- The agent container receives
TELEGRAM_BOT_TOKEN=pai-managed— the sidecar rewrites the URL on every outbound call before forwarding to Telegram - All Telegram API calls are logged in the audit trail with timestamps and method names
- Policy rules let you allow only specific Telegram methods per Provider
Troubleshooting
"Unauthorized" errors in agent logs
The token may have been rotated. Update the secret and restart:
pai delete secret telegram-token
pai add secret telegram-token --from-literal token=NEW_TOKEN_HERE
# Restart the agent (service) or re-run the task
Scheduled task not firing
Check that the CronJob exists and is not suspended:
kubectl get cronjob -n pai-system
kubectl describe cronjob pai-cron-daily-briefing-job -n pai-system
To resume a suspended CronJob:
kubectl patch cronjob pai-cron-daily-briefing-job -n pai-system -p '{"spec":{"suspend":false}}'