Skip to main content

Channels

A channel is one account, on one 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 publishes is exactly the set of its subscriptions.

No credentials live on a channel itself. A channel is an abstract destination; where a customer chooses to store platform credentials with Reelwire they go under platform connections, encrypted, against the channel.

MethodPathPermission
GET/v1/channelschannels:read
GET/v1/channels/surfaceschannels:read
POST/v1/channelschannels:write
PATCH/v1/channels/{id}channels:write
POST/v1/channels/{id}/deletechannels: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/connectionschannels:read
PUT/v1/connections/modechannels:write
GET/v1/deliverieschannels:read
POST/v1/deliveries/{id}/redeliverchannels:write

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",
"ratio": "9x16",
"locale": "en",
"enabled": true,
"approval": "NONE",
"posting": {
"allowComments": true,
"brandedContent": false,
"allowDuetStitch": false,
"hashtagsAsFirstComment": false,
"silent": false
},
"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": "brk-test-feeds",
"formatSlug": "brk-feed-1",
"streamId": null,
"queueId": null,
"queueName": null,
"feedId": "fd_3e7b3bff3194",
"templateId": "tpl_a9c3fdb1b5c9",
"templateVariant": "Standard",
"templateVersion": null,
"enabled": true
}
]
}
]
}

posting only carries the switches the surface actually has, with its defaults filled in, so you never have to work out which apply.

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", "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"] }
]
}

"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.

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 "New channel N". Up to 80 characters.
surfacenoinstagram-reels
rationoThe surface's only ratio, or its first where it offers several.
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 for a reviewer.
postingnoThe surface's own defaults.

Two refusals worth knowing. Without a brand anywhere in the workspace: "Make a brand first: every channel wears one." And without a delivery endpoint: "This account has no delivery endpoint yet. Set one under Platform connections first." One endpoint serves the whole workspace, reused by every channel, so this is set once.

PATCH /v1/channels/{id} takes the same fields. Changing the surface, ratio, language or brand changes what the channel renders from now on, which is the reason it is allowed. Work already published is untouched: a post records the shape and language it went out in.

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".

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":"Standard","templateVersion":"latest"}'

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

{
"id": "01M2SQZVBYJJ5YF2QFYNEG5A78",
"kind": "STREAM",
"sourceSlug": null,
"formatSlug": null,
"streamId": "st_d84d89e39bfc",
"queueId": null,
"queueName": null,
"feedId": null,
"templateId": "tpl_a9c3fdb1b5c9",
"templateVariant": "Standard",
"templateVersion": null,
"enabled": true
}

"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.

Subscribing a channel twice to the same input is a 409. A shipped feed the account is not offered is a 403 naming the feed.

PATCH /v1/channels/{id}/subscriptions/{subId} changes the variant, the version or enabled. POST /v1/channels/{id}/subscriptions/{subId}/delete removes it and returns the channel as it now stands.

Connections and deliveries

GET /v1/connections says how this workspace's posts reach their platforms: the mode, the endpoint, and per channel which credential fields that platform needs and whether they are filled in.

{
"mode": "SELF_HOSTED",
"endpoint": {
"url": "http://connector-simulator:4300/v1/webhooks/publish/enterprise-2",
"keyId": "01M1VFRWE7P5V056GE36CXEK5D",
"enabled": true
},
"channels": [
{
"channelId": "01M1VFRWE7EWSY5D7QDQ0KNK1S",
"name": "Channel 1 en",
"platform": "instagram",
"surfaceLabel": "Instagram Reels",
"enabled": true,
"fields": [
{ "key": "igUserId", "label": "Instagram user id", "hint": "The professional account's id, from the linked page.", "secret": false, "value": null },
{ "key": "accessToken", "label": "Access token", "hint": "The long-lived token for that account.", "secret": true, "value": null }
],
"complete": false,
"updatedAt": null,
"unreadable": false
}
]
}

SELF_HOSTED means Reelwire signs a delivery and posts it to your endpoint, and your connector talks to the platform. MANAGED means Reelwire talks to the platform with the credentials you stored. PUT /v1/connections/mode switches between them; the next post goes the new way.

PUT /v1/channels/{id}/credentials stores the fields for one channel, secret: true ones encrypted. Reading them back, a secret comes back masked and an identifier comes back as typed, so re-saving what you read would store the mask. Send only the fields you are changing. value is null for a field nothing has been entered into, complete says whether every field this platform needs is filled in, and unreadable: true means something is stored that cannot be decrypted with the current key.

GET /v1/deliveries is the log of what Reelwire has tried to send.

curl "http://localhost:4000/v1/deliveries?limit=2" \
-H "Authorization: Bearer rw_your_key_here"
{
"deliveries": [
{
"id": "01M2SQPCF8DSFKXF22P648R59X",
"event": "post.publish",
"state": "DELIVERED",
"attempt": 1,
"nextAttemptAt": "2026-09-18T07:46:30.513Z",
"lastStatusCode": 200,
"lastError": null,
"deliveredAt": "2026-09-18T07:46:30.712Z",
"createdAt": "2026-09-18T07:46:30.514Z",
"postId": "01M2SQPANAT1W066PMYNXFTCNZ",
"channelName": "Channel 4 de",
"platform": "instagram"
}
]
}

limit defaults to 40 and is capped at 200. This list is not paged.

POST /v1/deliveries/{id}/redeliver sends a dead or retrying delivery again, byte for byte identical to the original, which means the same signature. See Webhooks.

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.

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 its history, and a deleted one does not.