API reference

Everything is a JSON envelope.

Success is { ok: true, data, warnings }. Failure is { ok: false, error: { code, message, hint } }. Error codes are a closed set: BAD_INPUT, UNAUTHORIZED, NOT_FOUND, CONFLICT, NOT_CONNECTED, RATE_LIMITED, PLAN_LIMIT, TELEGRAM_ERROR, INTERNAL.

Two kinds of key. rg_admin_… is the platform key: it creates tenants and reaches everything. rg_live_… is tenant-scoped: it can only ever touch its own tenant's data, and that is enforced on every route below.

Authentication

Two kinds of key. rg_admin_… is the platform key and can reach every tenant. rg_live_… is tenant-scoped and can only ever touch its own tenant. Send it as Authorization: Bearer <key>.

curl http://localhost:8787/v1/tenants \
  -H "Authorization: Bearer $RELAY_ADMIN_KEY"

Endpoints

MethodPathWhoWhat it does
POST/v1/tenantsadminCreate a tenant, returns its tenant key
GET/v1/tenantsadminList tenants
GET/v1/tenants/:idadmin, tenantOne tenant
POST/v1/tenants/:id/connect-linksadmin, tenantMint a connect link
POST/v1/connect/:token/completepublicCustomer picks a bot name
POST/v1/messagestenantSend a message
GET/v1/messagestenantMessage log
GET/v1/tenants/:id/messagesadmin, tenantMessage log for a tenant
POST/v1/webhookstenantRegister an event webhook
GET/v1/webhookstenantList webhooks
DELETE/v1/webhooks/:idtenantRemove a webhook
GET/v1/usagetenantMessages used this period
POST/v1/telegram/webhookTelegramUpdates from Telegram
GET/healthzpublicLiveness

Create a tenant

curl -X POST http://localhost:8787/v1/tenants \
  -H "Authorization: Bearer $RELAY_ADMIN_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"Sara'"'"'s Phone Shop"}'

The response contains the tenant's rg_live_… key. It is shown once and stored only as a hash.

Send a message

curl -X POST http://localhost:8787/v1/messages \
  -H "Authorization: Bearer $TENANT_KEY" \
  -H "content-type: application/json" \
  -d '{
    "chat_id": 15551234567,
    "text": "Your order #412 ships tomorrow.",
    "parse_mode": "HTML"
  }'

chat_id is a numeric user id or a @username. Rate limits are enforced for you: about 1 message/second per chat, 20/minute per group, 30/second overall.

Webhooks

Register a URL and we POST every event to it. The body is signed: x-relay-signature: sha256=…, computed with the secret we returned at registration. Verify it with an HMAC-SHA256 over the raw body.

curl -X POST http://localhost:8787/v1/webhooks \
  -H "Authorization: Bearer $TENANT_KEY" \
  -H "content-type: application/json" \
  -d '{
    "url": "https://example.com/hooks/telegram",
    "events": ["message.sent", "message.received"]
  }'

MCP tools

The same operations are exposed to AI agents over MCP. Start it with node dist/mcp.js and point your client at it.

ToolWhat it does
relay_list_tenantsList tenants and their connection state
relay_create_tenantCreate a tenant and its key
relay_create_connect_linkMint a connect link for a tenant
relay_send_messageSend a message as a tenant's bot
relay_list_messagesRead a tenant's message log
relay_register_webhookRegister an event webhook
relay_get_usageMessages used this period

Configuration

VariableDefaultMeaning
RELAY_DATA_DIR~/.relay-gatewaySQLite file and dev key
RELAY_HOST127.0.0.1HTTP bind address
RELAY_PORT8787HTTP port
RELAY_PUBLIC_URLhttp://host:portBase URL for connect links
RELAY_ADMIN_KEYgeneratedThe platform key
RELAY_MANAGER_BOT_TOKEN—The manager bot that holds managed-bot tokens
RELAY_MANAGER_BOT_USERNAME—Its @username, for the deep link
RELAY_TELEGRAM_WEBHOOK_SECRETgeneratedHeader Telegram sends us
RELAY_ENCRYPTION_KEYderived32 bytes, hex or base64
RELAY_DEMO0Use the fake Telegram client
RELAY_PLANselfhostselfhost or hosted