CanalYar API
With the same API the CanalYar WordPress plugin uses, you can publish, edit or delete posts in Telegram, Bale and Rubika channels from any site or software (Laravel, Django, Node, a mobile app …). Bots, proxies and messenger rate limits are our job.
Getting started and authentication
- Sign up in the CanalYar panel and get a plan.
- Copy the connection key from the panel. Treat it like a password: keep it on your server only, never in browser or app code.
- Add and verify your channel (in the panel, or with the channels API).
Base URL:
https://canalyarr.ir/bot-relay/v1
Send the key in the Authorization header:
Authorization: Bearer YOUR_KEY
The older method of putting the key in the URL (/v1/YOUR_KEY/publish) still works, but URLs end up in logs; the header is safer.
All requests are POST with a form body (multipart/form-data or application/x-www-form-urlencoded). Structured fields are JSON strings.
Responses and errors
Responses are always JSON with an ok key. Every failed response has these three keys:
{
"ok": false,
"error": "Error message (in Persian), ready to show to the user",
"description": "The same message (for compatibility with the Bot API format)",
"error_code": 402
}
| Status | Meaning |
|---|---|
400 | Invalid input: empty text, no channel selected, text too long, … |
402 | Subscription expired (code: "expired") or no plan bought yet (code: "inactive"). |
403 | Invalid or disabled key. |
404 | Unknown path or command. |
429 | More than 120 requests per minute. The Retry-After header says how many seconds to wait. |
502 | Sending failed for every channel; each channel’s reason is in results. |
503 | The service is temporarily unavailable; try again shortly. |
Account status
POST/api/account
Plan, expiry, monthly quota and the channel list. Channel IDs and the chat value you need for publishing come from here.
curl -X POST https://canalyarr.ir/bot-relay/v1/api/account \
-H "Authorization: Bearer YOUR_KEY"
{
"ok": true,
"plan": { "name": "Pro", "max_channels": 5, "branding": false, … },
"platforms": ["telegram", "bale", "rubika"],
"expires": 1767225600,
"active": true,
"quota": 1000,
"used": 42,
"channels": [
{ "id": 7, "platform": "telegram", "chat": "@myshop", "title": "My shop", "status": "verified", "verified": true }
],
…
}
A quota of zero means unlimited. expires is a Unix timestamp, and zero means it never expires.
Channels
You can only post in verified channels. Verifying proves the channel is yours: make the CanalYar bot (its name is in bots in the account response) an admin of the channel, add the channel so the bot posts a six-digit code in it, and send that code back.
Add
POST/api/channels/add
| Field | Notes |
|---|---|
platform | telegram, bale or rubika |
chat | Channel ID, e.g. @myshop |
curl -X POST https://canalyarr.ir/bot-relay/v1/api/channels/add \
-H "Authorization: Bearer YOUR_KEY" \
-d platform=telegram -d chat=@myshop
The response returns the channel with its id.
Verify
POST/api/channels/verify
Fields: id (channel ID) and code (the six-digit code posted in the channel).
Test message and removal
POST/api/channels/test
POST/api/channels/remove
Both take an id field.
Publish a post
POST/publish
Publishes one post to one or more channels at once. Each successful channel uses one post from the monthly quota.
| Field | Notes |
|---|---|
text | Post text, up to 4096 characters. *bold*, `code`, ~strikethrough~ and [text](https://link) (text link) are formatted on Telegram and turned into plain text on Bale and Rubika (links become “text: link”). |
channels | JSON: [{"platform":"telegram","chat":"@myshop"}, …] |
image0 … image9 | Optional; an image file (JPG, PNG, WEBP or GIF). Several images become an album on Telegram and Bale. |
image_mode | caption (default: text under the image), before (images, then text), after (text, then images) or none (no images). If the text is longer than the caption limit (1024 characters), it automatically becomes before. |
file | Optional, instead of images: one file up to 30 MB. MP3/M4A go with a music player and MP4 with a video player; everything else as a document. |
file_kind | Optional, document: always send the file as a document (e.g. a video without a player). |
rubika_extra | Optional, 1: Rubika has no albums, so by default only the first image goes. With this field the rest follow, each in its own post. |
curl -X POST https://canalyarr.ir/bot-relay/v1/publish \
-H "Authorization: Bearer YOUR_KEY" \
-F 'text=*Handmade leather bag*
Price: 1,200,000 Toman
https://myshop.ir/p/123' \
-F 'channels=[{"platform":"telegram","chat":"@myshop"},{"platform":"bale","chat":"@myshop"}]' \
-F image0=@bag-1.jpg \
-F image1=@bag-2.jpg
{
"ok": true,
"results": [
{ "platform": "telegram", "chat": "@myshop", "ok": true, "ids": ["812", "813"], "text_id": "812", "kind": "caption", "error": "", "note": "" },
{ "platform": "bale", "chat": "@myshop", "ok": false, "ids": [], "error": "این کانال برای این سایت تأیید نشده است.", "code": "" }
],
"usage": { "used": 43, "quota": 1000 }
}
If at least one channel succeeds, the response is 200; check each channel’s result in results. Keep ids, text_id and kind — you need them to edit or delete this post. A non-empty note says what was adjusted automatically (e.g. the text sent separately from the image).
Edit and delete posts
POST/edit
Uses no quota. The items field is a JSON array of up to 30 items per request:
[
{ "op": "edit", "platform": "telegram", "chat": "@myshop", "id": "812", "kind": "caption", "text": "New text" },
{ "op": "delete", "platform": "bale", "chat": "@myshop", "ids": ["55", "56"] }
]
- For editing,
idandkindare thetext_idandkindfrom the publish response. Only the text changes, not the images. - For deleting,
idsis theidsfrom the publish response (an album has several messages). - Telegram does not always delete messages older than 48 hours.
curl -X POST https://canalyarr.ir/bot-relay/v1/edit \
-H "Authorization: Bearer YOUR_KEY" \
--data-urlencode 'items=[{"op":"edit","platform":"telegram","chat":"@myshop","id":"812","kind":"caption","text":"*Out of stock*"}]'
The response is like publishing: ok and one results entry per item.
Send reports
POST/api/reports
A 30-day summary (summary), daily counts (daily) and log rows, 50 per page. All fields are optional: page, days (1 to 90, default 14), platform, result (ok or failed), from and to (Unix time).
{ "ok": true, "summary": { "total": 120, "failed": 3, … }, "daily": [ … ], "total": 120, "page": 1,
"rows": [ { "time": 1767225600, "platform": "managed", "method": "publish", "status": 200, "ok": true, "error": "" } ] }
Billing and invoices
POST/api/pay/start
Creates an order and returns the gateway URL. Fields: plan_id and months (from plans in the account response), return_url (after payment the user returns there with mkc_pay=ok|failed|canceled; it must be on your site’s address) and an optional coupon. The server calculates the amount. Response: url and amount.
POST/api/orders
All orders and payments for this site, newest first: id, number, plan, amount, discount, tax, paid and invoiced_at.
POST/api/invoice
POST/api/invoice/pay
POST/api/invoice/issue
invoicewithid: details of one order; if an official invoice has been issued, buyer and seller details are inofficial.invoice/paywithidandreturn_url: pay an order that is still open.invoice/issuewithid: issue an official invoice for a paid purchase. Tax details:buyer_type(personorcompany); for a personnational_code, for a companycompany_name,company_national_idandcompany_economic_code.
Account, key and referrals
POST/api/profile
POST/api/profile/update
Account holder details: first_name, last_name, email, business, site_url and the same tax fields. update changes only the fields you send. The mobile number is the login ID and cannot be changed through the API.
POST/api/key/rotate
Creates a new key and returns it in key. The current key stops working immediately; store the response right away. This month’s usage carries over to the new key.
POST/api/sites
POST/api/sites/add
Each site has its own key, channels and subscription. sites returns all sites on the account with plan, expiry and current (the site of this key), but not the other sites’ keys. sites/add creates a new site with an optional site_url and returns its key in key this one time only. A new site is inactive until a plan is bought. Up to 10 sites.
POST/api/referral
Referral link (link), stats (stats: invited, purchased, bonus days), the bonus per purchase (days) and the newcomer discount percentage (discount).
Support (tickets)
| Command | Fields |
|---|---|
/api/tickets | — list of tickets, with categories (categories) and priorities (priorities) |
/api/tickets/new | subject, category, body, priority (optional), file (optional; a JPG, PNG or WebP image) |
/api/tickets/get | id — the ticket with all its messages; new replies are marked as read |
/api/tickets/reply | id, body, file (optional). Replying to a closed ticket reopens it. |
/api/tickets/close | id |
/api/tickets/attachment | Message id — returns the image itself, not JSON |
This section and the account section only work for keys linked to a customer account in the panel.
Limits
- 120 requests per minute per key.
- Up to 10 images per post and 32 MB per request.
- 4096 characters for message text and 1024 for image captions.
- On plans with a signature, “Sent by CanalYar” is added under each post and counts toward the text length.
- Scheduling and queueing are up to you; the API publishes immediately.
Questions or problems? Ask through the tickets section of the panel.