Skip to main content

Channels

A channel is one account, in one format (a platform surface), speaking one language, wearing one brand. "Orchid Invest DE" on Instagram Reels is a channel; the same account posting to the Instagram feed is a different one, because the surface fixes the shape. What a channel makes is exactly the set of its subscriptions.

The surface shapes the clip, its caption and its hashtags. When a post is due it is handed over by email, by webhook or both, on the channel's switches deliverByEmail and deliverByWebhook, and whoever receives it posts it; where the platform has a platform connector, deliverByConnector has Reelwire post it there itself, with the credentials saved on the channel as connector. See How a post is handed over below.

MethodPathPermission
GET/v1/channelschannels:read
GET/v1/channels/surfaceschannels:read
POST/v1/channelschannels:write
PATCH/v1/channels/{id}channels:write
POST/v1/channels/connector/testchannels:write
POST/v1/channels/{id}/deletechannels:write
POST/v1/channels/{id}/duplicatechannels:write
POST/v1/channels/{id}/subscriptionschannels:write
PATCH/v1/channels/{id}/subscriptions/{subId}channels:write
POST/v1/channels/{id}/subscriptions/{subId}/deletechannels:write
PUT/v1/channels/{id}/credentialschannels:write
POST/v1/channels/{id}/credentials/deletechannels:write
GET/v1/connectionsconnections:read
PUT/v1/connections/modeconnections:write
GET/v1/deliveriesconnections:read
POST/v1/deliveries/{id}/redeliverconnections:write

A channel's platform connector credentials travel with the channel's own create and PATCH, but only a signed-in person with the Administration permission may set or remove them: a key holding channels:write reads them masked and tests them, and is refused if it sends them (see Platform connector credentials). The two retired credential routes, PUT /v1/channels/{id}/credentials and its delete, sit under /v1/channels too, so a key needs channels:write for them and a signed-in person the Administration permission, as for the webhook's address. How posts leave the workspace, and the log of what was delivered, are the Activity - What went out area, connections: handing somebody the delivery log does not hand them every channel.

List channels​

curl http://localhost:4000/v1/channels \
-H "Authorization: Bearer rw_your_key_here"
{
"channels": [
{
"id": "01M1VFRWE7EWSY5D7QDQ0KNK1S",
"name": "Channel 1 en",
"platform": "instagram",
"surface": "instagram-reels",
"surfaceLabel": "Instagram Reels",
"platformChoice": "instagram",
"ratio": "9x16",
"locale": "en",
"enabled": true,
"approval": "NONE",
"deliverByEmail": false,
"deliverByWebhook": true,
"deliverByConnector": false,
"connector": null,
"posting": {
"allowComments": true,
"brandedContent": false,
"allowDuetStitch": false,
"hashtagsAsFirstComment": false,
"silent": false,
"notifySubscribers": true,
"shareToFeed": true,
"protectContent": false,
"linksFacebook": false,
"linksInstagram": false,
"linksYoutube": true,
"postAs": "video"
},
"syndicates": false,
"syndicationToken": null,
"brand": { "id": "01M1VFRWE7VHP3H54ESRJQ0SAJ", "name": "Brand 1", "enabled": true },
"subscriptions": [
{
"id": "01M2985NZZ9NR9W21JJZ8VQQ4N",
"kind": "QUEUE",
"sourceSlug": null,
"formatSlug": null,
"streamId": null,
"queueId": "01M2985NYG2GJ450CQAF3DP5YK",
"queueName": "Enterprise 2 Post list 1",
"feedId": null,
"templateId": null,
"templateVariant": null,
"templateVersion": null,
"enabled": true
},
{
"id": "01M29DSF6S2JYYXQQM4XHJMGK7",
"kind": "BROADCAST",
"sourceSlug": "crypto-spot-and-dominance",
"formatSlug": "crypto-board",
"streamId": null,
"queueId": null,
"queueName": null,
"feedId": "fd_c16f5268064a",
"templateId": "tpl_06dc2554e5ac",
"templateVariant": "Data terminal",
"templateVersion": null,
"enabled": true
}
]
}
]
}

posting carries every switch, with its defaults filled in: the ones the surface does not have are always at their defaults there, and visibility appears only where the surface offers a choice and one was made. See Posting settings for which surface has which, and the Platform dropdown below for platformChoice.

Each channel also carries approvers, how many people approve one of its posts: 1, or 2 where an Enterprise workspace asks for two, and authorMayApprove, whether whoever wrote a post may be one of them (see Compliance). Beside channels, approval says what this workspace's plan does with a channel's approval: { "available": true, "dual": false }, where available is whether the setting is honoured (Small business and Enterprise) and dual whether two approvers may be asked (Enterprise). On a plan without it, a channel's approval is kept as it was set, and read as NONE.

The surface decides the shape​

GET /v1/channels/surfaces is the list to pick from, and it says which ratios each one offers.

{
"surfaces": [
{ "surface": "youtube-shorts", "platform": "youtube", "label": "YouTube Shorts", "ratios": ["9x16"] },
{ "surface": "youtube-video", "platform": "youtube", "label": "YouTube Long-form", "ratios": ["16x9"] },
{ "surface": "facebook-reels", "platform": "facebook", "label": "Facebook Reels", "ratios": ["9x16"] },
{ "surface": "facebook-feed", "platform": "facebook", "label": "Facebook Feed", "ratios": ["1x1"] },
{ "surface": "instagram-reels", "platform": "instagram", "label": "Instagram Reels", "ratios": ["9x16"] },
{ "surface": "instagram-feed", "platform": "instagram", "label": "Instagram Feed", "ratios": ["4x5", "1x1"] },
{ "surface": "tiktok", "platform": "tiktok", "label": "TikTok", "ratios": ["9x16"] },
{ "surface": "linkedin", "platform": "linkedin", "label": "LinkedIn", "ratios": ["1x1", "16x9", "9x16", "4x5"] },
{ "surface": "telegram", "platform": "telegram", "label": "Telegram", "ratios": ["9x16", "1x1", "16x9", "4x5"] },
{ "surface": "discord", "platform": "discord", "label": "Discord", "ratios": ["9x16", "4x5", "1x1", "16x9"] },
{ "surface": "slack", "platform": "slack", "label": "Slack", "ratios": ["9x16", "4x5", "1x1", "16x9"] },
{ "surface": "x", "platform": "x", "label": "X", "ratios": ["9x16", "4x5", "1x1", "16x9"] },
{ "surface": "threads", "platform": "threads", "label": "Threads", "ratios": ["9x16", "4x5", "1x1", "16x9"] },
{ "surface": "pinterest", "platform": "pinterest", "label": "Pinterest", "ratios": ["2x3", "9x16", "1x1", "4x5"] },
{ "surface": "download", "platform": "download", "label": "No specific platform", "ratios": ["9x16", "4x5", "1x1", "16x9"] }
],
"platforms": [
{
"id": "not-specified",
"label": "Not specified",
"shapes": [{ "ratio": "9x16", "surface": "download" }, { "ratio": "4x5", "surface": "download" }, { "ratio": "1x1", "surface": "download" }, { "ratio": "16x9", "surface": "download" }],
"connector": null,
"fields": []
},
{
"id": "facebook",
"label": "Facebook",
"group": "social",
"shapes": [{ "ratio": "9x16", "surface": "facebook-reels" }, { "ratio": "1x1", "surface": "facebook-feed" }],
"connector": "planned",
"fields": []
},
{
"id": "telegram",
"label": "Telegram",
"group": "communication",
"shapes": [{ "ratio": "9x16", "surface": "telegram" }, { "ratio": "1x1", "surface": "telegram" }, { "ratio": "16x9", "surface": "telegram" }, { "ratio": "4x5", "surface": "telegram" }],
"connector": "available",
"fields": [
{ "key": "botToken", "label": "Bot token", "hint": "From @BotFather in Telegram: send /newbot and copy the token it answers with.", "secret": true, "placeholder": "123456789:AAE..." },
{ "key": "chatId", "label": "Channel or group", "hint": "@name of a public channel, or the numeric id (-100...) of a private one or a group. Add the bot there as an administrator that may post messages.", "secret": false, "placeholder": "@yourchannel" }
]
}
]
}

platforms is the dashboard's Platform dropdown, in its order (the example shows three of its twelve): Not specified first, then the two sections the dropdown draws, group social (TikTok, Instagram, Facebook, YouTube, LinkedIn, X, Threads, Pinterest) and communication (Discord, Telegram, Slack), in that order, each platform with the shapes it takes, the surface each shape is saved as, whether Reelwire can post there itself (connector: available, planned, or null for Not specified), and what its connector asks for (fields, empty unless it is available). Three are available with fields to fill in: discord, slack and telegram. facebook, instagram, linkedin, pinterest, threads, tiktok, x and youtube are available with "connect": "service" and no fields: Reelwire posts there through Zernio, a posting service, once a person connects the account (see Accounts connected through Zernio); where the API has no key for Zernio they answer planned. A create or PATCH that names a surface no longer on offer is saved as download, No specific platform. LinkedIn is one entry since 2026-10-29, linkedin, a profile or a company Page chosen at LinkedIn's sign-in: every channel on linkedin-company moved to the linkedin surface, and a create or PATCH naming linkedin-company is saved as linkedin, so a webhook call for such a channel now says linkedin. A channel still stores its surface and ratio; platformChoice on a channel says which of these it belongs to, so a channel on the linkedin surface belongs to linkedin, one on youtube-shorts or youtube-video to youtube, and one on download to not-specified. YouTube was two entries until 2026-10-29, youtube-shorts and youtube; it is one now, its shape deciding (9x16 youtube-shorts, 16x9 youtube-video), so a channel on youtube-shorts answers youtube where it answered youtube-shorts.

"Facebook" is not an answer to the shape question: Reels want 9x16 and the page feed wants 1x1. A surface with one ratio fixes it, and ratio in the body is ignored. A surface with several asks, and leaving ratio out there is a 400 that names the choices.

No specific platform (download) is a surface of its own, and not a network: the format for a channel that does not name where its clips go. Every shape is offered because the person decides where it goes, and its caption is kept in four lengths, one for each kind of platform. The key is still download, the name it had when it was the one platform whose posts were emailed. Every new workspace starts with one, called "Default channel", handed over by email.

How a post is handed over​

FieldWhat it does
deliverByEmailWhen a post is due, the people who publish for the workspace (the owner and everybody holding Operations) get one email for the hand-over, saying which post is ready for which channels, with a button to the post kit: each channel's video to download or share, its text to copy, its posting settings, and Mark as published or Skip. See Posts handed over by email.
deliverByWebhookWhen a post is due, it is signed and sent to the workspace's webhook address. See Webhooks.
deliverByConnectorWhen a post is due, Reelwire posts it on the channel's platform itself through its platform connector, and reports it published with its link. Only where the platform's connector is available and credentials are saved; true otherwise is a 409 saying which.
connectorThe platform connector's saved credentials, masked (a secret as its last four characters), or null: what they reached when they last passed a test (account), when they were last tested (checkedAt), why that test failed (checkFailure, null when it passed or none was made), when they expire (expiresAt) and whether a reminder is emailed before (remindExpiry). unreadable is true where they are saved but can no longer be read, and need entering again. See Platform connector credentials.
syndicates, syndicationTokenDeprecated and without effect, answered until the next major release. A channel no longer has a page of its own: posts are handed on to participants by group, see Syndication.

Any of them may be true together: the post is then emailed, sent and posted, as switched on. All may be false: the post then goes to nobody of the workspace's own. It is handed over the moment it is due, appears on Posts with its requests, and still reaches every syndication participant whose group takes the channel, which is how a channel for syndication alone is made. Syndication participants never post through a channel's connector: they receive by the ways their group allows.

A post going to several channels is handed over once per batch, not once per channel. Its channels that need no approval go together, in one email and one signed webhook call carrying every one of them, once all of them are rendered and due; each channel that asks for approval follows on its own once it is approved. A platform connector posts each channel on its own. See One call per hand-over and Posts and jobs.

Platform connector credentials​

Only a signed-in person with the Administration permission sets or removes them, which is what the channel's Settings in the dashboard do through these same routes. An API key never can: a connector in its create or PATCH is a 403. An AI assistant's connection made by signing in may start POST /v1/channels/connector/connect for a person holding Administration, and keep on the channel the account it connected that way (connector: { "connection": "..." }); it never types or removes credentials. Anybody who may edit channels reads them masked and may test what is saved.

connector on a create or a PATCH sets them: { "fields": { ... }, "expiresAt": "...", "remindExpiry": true }, with the keys the platform's entry in platforms lists. A field left out or empty keeps what is saved for the same platform, and a field still missing after that is a 400 naming it; with credentials saved already, fields may be left out to change only the expiry or the reminder. expiresAt is when they stop working (ISO 8601, null for never), and remindExpiry emails the owner and everybody with Administration a week before and a day before. On a platform whose connector is not available it is a 409. Values that cannot be right, such as a Discord address on another host, are a 400. Saving asks the platform nothing: test them with the route below. New values whose test passed in the last half hour keep what it found; otherwise they show account: null until a test, or their first post, which tests them first and stops with the platform's reason if they do not work. "connector": null removes them and switches deliverByConnector off. Moving the channel to another platform removes them too, and switches it off.

{
"surface": "telegram",
"ratio": "9x16",
"connector": {
"fields": { "botToken": "123456789:AAE...", "chatId": "@companynews" },
"expiresAt": null,
"remindExpiry": false
},
"deliverByConnector": true
}
{
"channel": {
"id": "01M1VFRWE7EWSY5D7QDQ0KNK1S",
"deliverByConnector": true,
"connector": {
"platform": "telegram",
"account": "@companybot in Company News",
"checkedAt": "2026-09-30T09:12:44.120Z",
"checkFailure": null,
"expiresAt": null,
"remindExpiry": false,
"updatedAt": "2026-09-30T09:12:44.131Z",
"unreadable": false,
"fields": [
{ "key": "botToken", "value": "••••wxyz" },
{ "key": "chatId", "value": "@companynews" }
]
}
}
}

No response ever carries a saved secret in the clear. How to get each platform's values is in Posted by platform connector.

Accounts connected through Zernio​

On a platform whose entry has "connect": "service", nothing is typed: a connector with fields on a create or a PATCH is a 409, and "connector": null still removes the account. The account is connected by a person with Administration, in the channel's dialogue, on the Individual plan and above, before the channel is saved or after: POST /v1/channels/connector/connect with { "surface": "tiktok" } (the platform the dialogue has chosen) or { "channelId": "..." } answers the connect's id and the address of the platform's own sign-in through Zernio (url, good for half an hour), which the dashboard opens in a new tab. When the person has signed in, Zernio sends the browser back to GET /v1/connect-return, which keeps the account on the connect; GET /v1/channels/connector/connect/{id} answers waiting, connected with account such as "@companynews on TikTok", failed with a reason, or gone. The create or PATCH that follows sends "connector": { "connection": "<id>" } and keeps the account on the channel; a connected account waits an hour for it. These routes are a person's alone: no API key or AI assistant can connect an account, and on Free it is a 403. The saved account is tested like any other, with { "channelId": "..." } below; testing fields on such a platform is a 409.

Connected accounts. Every account connected this way counts against the workspace's allowance of connected accounts: what its plan includes (Individual 1, Small business 3, Enterprise 9 unless its offer says otherwise), plus the Additional connected accounts bought on the Plan screen and any Reelwire granted. An account counts once however many channels post to it. GET /v1/channels/surfaces answers both as connections, { "used": 1, "allowance": 3, "accounts": [...] }, accounts listing each connected account by platform, account in the platform's words and the channels posting to it (id and name). Another channel posts to one of them with "connector": { "sameAs": "<channelId>" } on its create or PATCH, naming a channel posting to it: nobody signs in, the account counts once, and it takes the Administration permission and a paid plan, as connecting does; a channel holding no account on that platform is a 409. With every one in use, the connect route, and a create or PATCH keeping a new account, is a 403 that says so; connecting the account a channel already holds, or another one in its place, is not. Where more are connected than the allowance (after a move down, or an extra given back), the accounts connected first post, and a post through one of the newest fails with a sentence saying why. On Free, which includes none, nothing posts through them, and 30 days after the move every one is disconnected: connector reads null on its channels and deliverByConnector false, with deliverByEmail switched on where the connector was the only way out.

Accounts connected for the workspace. POST /v1/channels/connector/connect with { "platform": "linkedin" }, a platform id with "connect": "service", names no channel: once the person has signed in, the account is kept for the workspace, connected until it is removed, whether or not a channel posts to it, and it counts against the allowance from then on. GET /v1/channels/connector/connect/{id} answers connected with kept: true then. Such an account is in connections.accounts with "kept": true, and with no channels until one posts to it; each account there carries a key, the first channel's id or, with no channel, the account's own, and "connector": { "sameAs": "<key>" } keeps any of them on a channel. GET /v1/channels/connector/accounts lists every connection with its channels and kept, and POST /v1/channels/connector/accounts/remove with { "key": "<key>" } takes one off every channel posting to it, keeps it for the workspace no longer, and disconnects it at Zernio. Both are the Administration permission's, as connecting is.

Testing a platform connector​

POST /v1/channels/connector/test asks the platform whether the credentials work, and posts nothing: it only reads what they reach, such as Telegram's bot and its membership of the chat, Slack's app and channel, the Discord webhook's own details or, through Zernio, the connected account. { "channelId": "..." } tests what is saved on that channel and keeps the outcome on it (checkedAt, account when it passed, checkFailure when not); any key or person with Channels: write may. Adding fields tests values typed and not saved, laid over what is saved for the same platform as a save would lay them, and surface names the platform when it is not the channel's own; for a channel not made yet, send fields and surface without channelId. Testing values that are not saved is for a person with Administration only, and a key sending fields is a 403. A test waits at most 20 seconds for the platform.

curl -X POST http://localhost:4000/v1/channels/connector/test \
-H "Authorization: Bearer rw_your_key_here" \
-H "Content-Type: application/json" \
-d '{"channelId":"01M1VFRWE7EWSY5D7QDQ0KNK1S"}'
{
"test": { "ok": true, "account": "@companybot in Company News", "checkedAt": "2026-10-02T09:12:44.120Z", "saved": true },
"channel": { "id": "01M1VFRWE7EWSY5D7QDQ0KNK1S", "connector": { "account": "@companybot in Company News", "checkFailure": null } }
}

A failed test is an answer too, a 200 with "ok": false and the platform's reason, such as a bot that may not post in the channel; a platform that could not be reached just now says so. saved is false for values not saved, which are not kept on the channel and come back without channel. A platform whose connector is not available, a channel with no credentials saved for its platform, or saved ones that can no longer be read are each a 409 saying which. Through MCP this is reelwire_test_channel_connector.

Posting settings​

posting is the channel's Posting settings, the last section of its dialogue: how its posts should go out on the platform. They go with every way out, whether or not Reelwire posts there itself: in the webhook call's posting, in words in the email's post kit, and applied by the channel's platform connector where its platform lets it, which each platform's guide says. brandedContent is applied by no connector.

FieldWhat it saysSurfaces that have itDefault
visibilityWho can see the post.YouTube Shorts and Long-form (public, unlisted, private), TikTok (public, private)absent, which is public
allowCommentsComments are open. On X, off means only the accounts the post mentions can reply.Instagram, TikTok, Xtrue
brandedContentDeclared to the platform as a paid partnership or the brand's own promotion.Instagram, TikTokfalse
allowDuetStitchOthers may duet or stitch it.TikTokfalse
hashtagsAsFirstCommentThe hashtags go in a first comment, which keeps the caption clean.Instagram Reels and Feedfalse
silentSent without sound: subscribers are notified without sound or vibration.Telegramfalse
notifySubscribersThe channel's subscribers are told about the new video.YouTube Shorts and Long-formtrue
shareToFeedThe Reel shows in the profile's feed grid as well as in Reels.Instagram Reelstrue
protectContentNobody can forward the post or save the video.Telegramfalse
linksFacebookThe Page has Meta One Advanced, Expert or Max, so its posts keep their links.Facebook Reels and Feed, No specific platformfalse
linksInstagramThe account has Meta One Advanced, Expert or Max, so the text keeps its links, to copy into Add links.Instagram Reels and Feed, No specific platformfalse
linksYoutubeThe channel has advanced features, so its posts keep their links.YouTube Shorts and Long-form, No specific platformtrue
postAsWhat every post is made of: video the clip, picture its keyframe, pictures a picture of every page the clip shows and the brand's outro end card last, slides the same pictures as one PDF document. No video is made for the three of pictures, and none is counted against the allowance.LinkedInvideo

Discord and Slack have none.

The three links switches change the text itself rather than how it goes out. Off, the post carries no linkUrl, and an address in its caption or disclaimer is written like example . com, by every way out alike. Links to Meta's own platforms stay. On No specific platform they split captions (Webhooks).

A value of the wrong type is a 400, and a key that is not one of these is dropped. posting is replaced whole, on a create and on a PATCH alike: a switch left out goes back to its default, so send every switch you want to keep. A switch the surface does not have is kept, reads as its default while the channel stays there, and applies once the channel moves to a surface that has it. A visibility the surface does not offer reads as public. Since release 0.428.0, YouTube, Facebook, LinkedIn, Threads and Pinterest no longer offer allowComments, YouTube and Facebook no longer offer brandedContent, and LinkedIn no longer offers visibility, since the posting service has no field for them: they read as their defaults there.

Create a channel​

A name is enough. Everything else has a defensible default, and a channel that exists can be corrected in one call.

curl http://localhost:4000/v1/channels \
-H "Authorization: Bearer rw_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name":"Orchid Invest DE","surface":"instagram-reels","locale":"de","brandId":"01M1VFRWE7VHP3H54ESRJQ0SAJ"}'
FieldRequiredDefault
namenoThe first free of "New channel", "New channel 2" and so on. Up to 80 characters.
surfacenoinstagram-reels
ratiowhere the surface offers severalThe surface's only ratio. Required where it offers more than one.
localenoen. An ISO 639-1 code, or one of zh-CN, zh-TW, pt-BR.
brandIdnoThe workspace's oldest enabled brand.
approvalnoNONE. PER_POST holds every post, and every update of a post that went out, for a reviewer; it comes with Small business and Enterprise, and is a 409 on another plan. Changing a channel from PER_POST back to NONE needs a key that also holds approvals:write (a person: the Approvals permission), or it is a 403. How many people approve, approvers on every channel (1 or 2), is set on Compliance (Enterprise).
deliverByEmailnotrue.
deliverByWebhooknofalse.
deliverByConnectornofalse. true needs connector in the same request, on a platform whose connector is available, so on a create only a signed-in person with Administration can switch it on.
connectornoNone saved. See Platform connector credentials.
postingnoEvery switch at its default. See Posting settings.

201, with the channel as { "channel": { ... } }. A name already in use is a 409.

Two refusals worth knowing. Without a brand anywhere in the workspace: "Make a brand first: every channel wears one." And without a webhook: "This account has no webhook yet. Set one under Webhook first." One webhook serves the whole workspace, reused by every channel, so this is set once. A workspace holds up to 200 channels unless Reelwire set it another number; one more is a 409 that names the number and says how to ask for more.

PATCH /v1/channels/{id} takes the same fields, and enabled to pause or resume it. Changing the surface, ratio, language or brand changes what the channel renders from now on, which is the reason it is allowed. Work already handed over is untouched: a post records the shape and language it went out in. A switch sent alone is laid over the others as they stand. A new surface sent without a ratio keeps the channel's ratio where the surface offers it; where it does not, a surface with one ratio takes that one, and a surface with several is a 400 naming the choices.

POST /v1/channels/{id}/delete removes a channel and answers { "deleted": "...", "cancelled": 2 }. Nothing more is rendered or handed over for it, every job still waiting is cancelled (cancelled counts them), and its stored credentials, its platform connector's included, are forgotten. What it already handed over stays where it was posted: removing a channel never asks anybody to take a post down. Its history stays too, under its name with " (removed)" on the end, so a new channel can take the old name.

POST /v1/channels/{id}/duplicate with { "name": "Instagram FR" } makes a second channel like this one and answers 201 with it, as create does. The copy has the channel's surface, ratio, locale, brand, approval, posting settings, deliverByEmail, deliverByWebhook, and every subscription with its variant, version and enabled. Three things differ, on purpose:

  • It starts paused (enabled false). Listening to the same sources, it would otherwise render every post a second time and hand it over the same ways. Change what differs with PATCH, then send enabled: true.
  • The platform connector is not copied, its credentials nor its switch: connector is null and deliverByConnector false. Credentials post as one account and are set by a person holding the Administration permission.
  • The name is required, and a name another channel has is a 409.

It is Duplicate on the Channels table, and reelwire_duplicate_channel for an AI assistant.

Subscriptions: what a channel listens to​

A subscription names exactly one input, and kind says which.

kindNames the input withTemplate comes from
BROADCASTsourceSlug and formatSlugThe feed's binding in the catalogue
STREAMstreamIdThe stream
QUEUEqueueIdEach post on the list

The source fixes the template; the channel picks the cut and the build. A producer writing to a stream was promised a shape, and a channel does not get to overrule it. What the channel chooses is templateVariant, and templateVersion, which is an exact major.minor.patch or "latest". Left out, the variant is the one the feed is bound to for a BROADCAST and the template's first for a STREAM, and the version is "latest".

A BROADCAST subscription may also carry language, the language the feed is delivered in on this channel: a code such as "es" or "pt-BR", or null for the channel's own language, which is what a subscription starts on. A feed whose multilingual is true in GET /v1/feeds takes any language and is translated by AI into the languages its channels chose. A feed whose multilingual is false comes in the languages its languages names, its default first, each written by the feed's own code and never by AI: one of those is accepted and any other is a 409, and with null it comes in the channel's own language where that is named, else in the feed's default. A language on a stream or a post list is a 409 too, since their events and posts carry their own languages. Each subscription answers its language, null where it follows the channel.

A BROADCAST or STREAM subscription may also carry look, how its clips look on this channel: the template's Look switches by key, such as { "theme": "dark", "pace": "calm", "hideHeader": true }. Which switches a template has, and the values each takes, are its schema's clipData.look (GET /v1/formats/template/{id}?variant= for the subscription's variant): Theme, Pace, Corners, Text alignment, Logo, Logo size, Logo form, Hide logo and title, Hide footer line, Background pattern, Brand background and, on a Background, Strength. They are laid into each clip's content as it is built, over what the content says, since the channel is choosing how its own clips look. A switch left out, "auto" or false is the style's own, and null is every switch the style's own, which is what a subscription starts on. A key or a value the build does not take is a 400 naming what it takes; a look on a QUEUE subscription is a 409, since each post on the list sets its own in its content. Each subscription answers its look, null for the style's own.

A BROADCAST subscription to a feed whose usable is false in GET /v1/feeds is a 403 naming the plans it needs: the feed is available on other plans only (plans). A channel that listened to it before the workspace changed plan keeps its subscription, and receives nothing from the feed until the plan includes it again.

curl http://localhost:4000/v1/channels/01M1VFRWE7EWSY5D7QDQ0KNK1S/subscriptions \
-H "Authorization: Bearer rw_your_key_here" \
-H "Content-Type: application/json" \
-d '{"kind":"STREAM","streamId":"st_d84d89e39bfc","templateVariant":"Corporate glass","templateVersion":"latest"}'

201, and the whole channel comes back, with the new subscription among its list:

{
"channel": {
"id": "01M1VFRWE7EWSY5D7QDQ0KNK1S",
"name": "Channel 1 en",
"subscriptions": [
{
"id": "01M2SQZVBYJJ5YF2QFYNEG5A78",
"kind": "STREAM",
"sourceSlug": null,
"formatSlug": null,
"streamId": "st_d84d89e39bfc",
"queueId": null,
"queueName": null,
"feedId": null,
"templateId": "tpl_dd7e6b264fb8",
"templateVariant": "Corporate glass",
"templateVersion": null,
"enabled": true
}
]
}
}

The channel's other fields are there too, exactly as GET /v1/channels shows them.

"latest" is stored as null, which is a real answer rather than an absence: it follows the newest build, resolved when something renders. Anything else must be an exact three-part version, because a pin that is almost a version is a pin to nothing, and a variant or version the template has no build for is refused rather than left to render nothing. A QUEUE subscription pins nothing: each post on the list chooses its own.

Subscribing a channel twice to the same input is a 409. A feed, stream or post list that does not exist is a 404, and a ready-made feed the account is not offered, by the industries it chose, is a 403 naming the feed. A channel listens to up to 100 sources unless Reelwire set the workspace another number; one more is a 409 that says so.

PATCH /v1/channels/{id}/subscriptions/{subId} changes the variant, the version, enabled, releaseSchedule, language or look, and returns the channel; a variant or version on a QUEUE subscription is a 409, since each post on the list chooses its own. A look sent replaces the one chosen and is checked against the build the subscription draws after the change; a new variant or version sent without one keeps the switches the new build still has.

releaseSchedule says when a ready-made feed's updates go out on this channel: null for as soon as they arrive, Send on arrival (every subscription starts so), or { "days": [1, 2, 3, 4, 5], "times": ["09:00"] }, ISO weekdays (1 Monday to 7 Sunday) and one to six times of day, HH:MM in UTC unless the schedule carries a timeZone, or monthly, { "monthDay": 1, "times": ["09:00"] }, one day of the month from 1 to 28, or delayed, { "delayMinutes": 30 }, each update that many minutes after it arrives, 1 to 1440 (24 hours). It is accepted on BROADCAST subscriptions only, on creation too; on a stream or a post list it is a 409. Each update then waits for the next release (or the end of its delay), and a newer one arriving first replaces it and goes out at that same moment, so only the newest is posted; a release with nothing waiting posts nothing. Each subscription in the channel answers releaseSchedule, releaseLabel (the schedule in words) and waiting: while an update waits, { "since", "goesAt", "skipped" }, the moment it arrived, its release, and how many older updates it replaced; otherwise null. Setting null while one waits sends it at once.

A days or monthly schedule may carry an optional timeZone, an IANA name such as Europe/Berlin: { "days": [1], "times": ["09:00"], "timeZone": "Europe/Berlin" }. The times are then that zone's clock and stay at 09:00 there when the clocks change between summer and winter time. Left out, the times are UTC, as before. releaseLabel writes the schedule in the zone of the signed-in person; a key, an assistant's included, gets UTC, so the words say "UTC" or name the schedule's own zone, such as "Mon to Fri at 09:00 Europe/Berlin". Instants on the wire, since and goesAt among them, are UTC ISO strings for everybody.

POST /v1/channels/{id}/subscriptions/{subId}/delete removes it and returns the channel as it now stands; one that already produced posts is kept hidden rather than deleted, so those posts still name where they came from. The input itself, the feed, stream or list, is untouched.

Stop everything​

Every channel of the workspace held at once, every plan, and planned silences, the same for a period set ahead, on Enterprise (the dashboard's Stop everything screen says what holding does). Read with Channels: read, changed with Channels: write; a signed-in person needs Operations or Approvals.

  • GET /v1/channels/stop answers stopped ({ since, by, reason }, or null), silentNow (why nothing goes out right now: { kind: "stopped" } or { kind: "silence", name, until }, or null), waiting ({ posts, deliveries } held back), silencesAvailable, the silences ({ id, name, startsAt, endsAt, state }, state planned, on or over) and the limits.
  • POST /v1/channels/stop with an optional { "reason": "..." } stops everything; POST /v1/channels/stop/resume resumes. Both answer the state as above.
  • POST /v1/channels/silences with { "name", "startsAt", "endsAt" } (ISO 8601 instants) plans a silence, PATCH /v1/channels/silences/{id} changes one, POST /v1/channels/silences/{id}/delete removes it and ends it at once if it is on. Below Enterprise planning is a 403; a silence lasts at most 366 days.
curl -X POST http://localhost:4000/v1/channels/stop \
-H "Authorization: Bearer rw_..." -H "Content-Type: application/json" \
-d '{"reason":"A wrong figure went out this morning"}'

While the workspace is stopped or silent, content that arrives makes no post, scheduled posts wait, and nothing in the outbox leaves except a request to delete; the delivery log shows those lines waiting. There is no MCP tool for it.

The retired connector, and the delivery log​

The routes that set up the first connector, from before every post was handed over, are retired. They have nothing to do with the platform connectors above, which are set on the channel itself. They still answer, each with a Deprecation: true header, until the next major release, and then go:

  • GET /v1/connections answers the workspace's mode, which is always SELF_HOSTED now, and the credential fields channels once stored.
  • PUT /v1/connections/mode takes { "mode": "SELF_HOSTED" } and refuses MANAGED with a 409: Reelwire no longer posts with stored credentials.
  • PUT /v1/channels/{id}/credentials and POST /v1/channels/{id}/credentials/delete still store and forget a channel's platform credentials, and nothing reads them.
  • GET /v1/connector/env, /v1/connector/script/{language} and /v1/connector/adapters/{platform} answer the old self-hosted connector, written for the first version of the webhook. Do not build on it: build a receiver for Webhooks instead.

How a channel's posts leave the workspace is its own deliverByEmail, deliverByWebhook and deliverByConnector: see How a post is handed over above.

GET /v1/deliveries is the log of every way every hand-over went out, one line for its email, one for its webhook call and one for each post a platform connector made (mechanism CONNECTOR), and GET /v1/deliveries/{id} one line whole, with what it carried and what came back. POST /v1/deliveries/{id}/redeliver sends one again, except a connector line that got through, which is a 409: the platform would take the post a second time. All three are on Webhooks, with examples.

Mistakes people make​

Treating platform as the destination. The surface is the destination. Instagram Reels and the Instagram feed are different channels with different shapes.

Choosing a ratio the surface does not offer. It is refused, or ignored where the surface has only one.

Expecting one channel to post in two languages. It cannot. A channel speaks one language, and two languages is two channels subscribed to the same input.

Sending only the switch you changed in posting. posting is replaced whole, so every switch left out goes back to its default. Send the whole object as GET /v1/channels answers it, with your change in it.

Setting templateId on a subscription. The input fixes it. Send templateVariant and templateVersion.

Pinning templateVersion and never revisiting it. A pin stops following new builds. Send "latest" unless you have a reason.

Deleting a channel to pause it. PATCH with enabled: false. A paused channel keeps its subscriptions and can be resumed; a removed one keeps only its history, and what was waiting for it is cancelled.