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.
| Method | Path | Permission |
|---|---|---|
GET | /v1/channels | channels:read |
GET | /v1/channels/surfaces | channels:read |
POST | /v1/channels | channels:write |
PATCH | /v1/channels/{id} | channels:write |
POST | /v1/channels/connector/test | channels:write |
POST | /v1/channels/{id}/delete | channels:write |
POST | /v1/channels/{id}/duplicate | channels:write |
POST | /v1/channels/{id}/subscriptions | channels:write |
PATCH | /v1/channels/{id}/subscriptions/{subId} | channels:write |
POST | /v1/channels/{id}/subscriptions/{subId}/delete | channels:write |
PUT | /v1/channels/{id}/credentials | channels:write |
POST | /v1/channels/{id}/credentials/delete | channels:write |
GET | /v1/connections | connections:read |
PUT | /v1/connections/mode | connections:write |
GET | /v1/deliveries | connections:read |
POST | /v1/deliveries/{id}/redeliver | connections: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
| Field | What it does |
|---|---|
deliverByEmail | When 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. |
deliverByWebhook | When a post is due, it is signed and sent to the workspace's webhook address. See Webhooks. |
deliverByConnector | When 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. |
connector | The 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, syndicationToken | Deprecated 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.
| Field | What it says | Surfaces that have it | Default |
|---|---|---|---|
visibility | Who can see the post. | YouTube Shorts and Long-form (public, unlisted, private), TikTok (public, private) | absent, which is public |
allowComments | Comments are open. On X, off means only the accounts the post mentions can reply. | Instagram, TikTok, X | true |
brandedContent | Declared to the platform as a paid partnership or the brand's own promotion. | Instagram, TikTok | false |
allowDuetStitch | Others may duet or stitch it. | TikTok | false |
hashtagsAsFirstComment | The hashtags go in a first comment, which keeps the caption clean. | Instagram Reels and Feed | false |
silent | Sent without sound: subscribers are notified without sound or vibration. | Telegram | false |
notifySubscribers | The channel's subscribers are told about the new video. | YouTube Shorts and Long-form | true |
shareToFeed | The Reel shows in the profile's feed grid as well as in Reels. | Instagram Reels | true |
protectContent | Nobody can forward the post or save the video. | Telegram | false |
linksFacebook | The Page has Meta One Advanced, Expert or Max, so its posts keep their links. | Facebook Reels and Feed, No specific platform | false |
linksInstagram | The 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 platform | false |
linksYoutube | The channel has advanced features, so its posts keep their links. | YouTube Shorts and Long-form, No specific platform | true |
postAs | What 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. | video |
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"}'
| Field | Required | Default |
|---|---|---|
name | no | The first free of "New channel", "New channel 2" and so on. Up to 80 characters. |
surface | no | instagram-reels |
ratio | where the surface offers several | The surface's only ratio. Required where it offers more than one. |
locale | no | en. An ISO 639-1 code, or one of zh-CN, zh-TW, pt-BR. |
brandId | no | The workspace's oldest enabled brand. |
approval | no | NONE. 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). |
deliverByEmail | no | true. |
deliverByWebhook | no | false. |
deliverByConnector | no | false. 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. |
connector | no | None saved. See Platform connector credentials. |
posting | no | Every 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 (
enabledfalse). Listening to the same sources, it would otherwise render every post a second time and hand it over the same ways. Change what differs withPATCH, then sendenabled: true. - The platform connector is not copied, its credentials nor its switch:
connectoris null anddeliverByConnectorfalse. 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.
kind | Names the input with | Template comes from |
|---|---|---|
BROADCAST | sourceSlug and formatSlug | The feed's binding in the catalogue |
STREAM | streamId | The stream |
QUEUE | queueId | Each 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/stopanswersstopped({ since, by, reason }, ornull),silentNow(why nothing goes out right now:{ kind: "stopped" }or{ kind: "silence", name, until }, ornull),waiting({ posts, deliveries }held back),silencesAvailable, thesilences({ id, name, startsAt, endsAt, state },stateplanned,onorover) and thelimits.POST /v1/channels/stopwith an optional{ "reason": "..." }stops everything;POST /v1/channels/stop/resumeresumes. Both answer the state as above.POST /v1/channels/silenceswith{ "name", "startsAt", "endsAt" }(ISO 8601 instants) plans a silence,PATCH /v1/channels/silences/{id}changes one,POST /v1/channels/silences/{id}/deleteremoves 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/connectionsanswers the workspace's mode, which is alwaysSELF_HOSTEDnow, and the credential fields channels once stored.PUT /v1/connections/modetakes{ "mode": "SELF_HOSTED" }and refusesMANAGEDwith a 409: Reelwire no longer posts with stored credentials.PUT /v1/channels/{id}/credentialsandPOST /v1/channels/{id}/credentials/deletestill 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.