Brands
A brand is the look a clip wears: the colours, the typefaces, the logo, the badge, the opening and closing clips, and the lines of text that go out beside a post. Every channel wears exactly one brand, and a brand can be worn by many channels. Changing a brand changes every clip drawn from now on, and touches nothing already published.
| Method | Path | Permission |
|---|---|---|
GET | /v1/brands | brands:read |
GET | /v1/brands/{id} | brands:read |
GET | /v1/brands/draft | brands:read |
POST | /v1/brands | brands:write |
PATCH | /v1/brands/{id} | brands:write |
POST | /v1/brands/palette | brands:write |
POST | /v1/brands/{id}/assets/{slot} | brands:write |
POST | /v1/brands/{id}/assets/{slot}/link | brands:write |
POST | /v1/brands/{id}/assets/{slot}/delete | brands:write |
POST | /v1/brands/{id}/delete | brands:write |
The style document
GET /v1/brands/{id} returns the brand with its whole style document.
curl http://localhost:4000/v1/brands/01M1VFRWE7VHP3H54ESRJQ0SAJ \
-H "Authorization: Bearer rw_your_key_here"
{
"brand": {
"id": "01M1VFRWE7VHP3H54ESRJQ0SAJ",
"name": "Brand 1",
"enabled": true,
"style": {
"identity": {
"name": "Brand 1",
"titlePrimary": "Enterprise 2 Brand 1",
"titleSecondary": "orchid.example",
"footerPrimary": "Enterprise 2 Brand 1",
"footerSecondary": "Orchid Invest. Educational only. Not advice."
},
"design": { "first": "#120A16", "second": "#1E1226", "third": "#2e2732", "fourth": "#77EE58" },
"accent": { "positive": "#77EE58", "negative": "#E8527F" },
"text": {
"primary": { "font": "open-sans", "size": 1, "color": "#F6EEFA" },
"secondary": { "font": "open-sans", "size": 1, "color": "#A990B5" }
},
"logo": {
"assetId": "01M2S2CPNV2YKF8VRV9396BYRD",
"fileName": "logo.png",
"mimeType": "image/png",
"url": "http://localhost:4000/v1/assets/01M2S2CPNV2YKF8VRV9396BYRD/download?exp=1789722057&sig=2af0b0a171347dad96f961ed99f44862050dfa2c678f0566f12a7f56392319c5"
},
"badge": {
"kind": "image",
"enabled": true,
"primaryLine": "Alex Fischer",
"secondaryLine": "Head of markets",
"asset": { "assetId": "01M2S2CP6YVZQX9BPE68FXSP5Y", "fileName": "badge.png", "mimeType": "image/png", "url": "..." }
},
"background": { "kind": "color" },
"sound": {
"mode": "clip-outro",
"asset": { "assetId": "01M2S2CPB4XJ7K0N2QH3RZ5TWE", "fileName": "jingle.mp3", "mimeType": "audio/mpeg", "url": "..." }
},
"posting": {
"description": "Orchid Invest shares short market updates. Educational only, not advice.",
"hashtags": ["orchidinvest"],
"link": { "url": "https://orchid.example", "label": "Visit our website" }
},
"safeAreas": {
"16x9": { "top": 64, "right": 96, "bottom": 72, "left": 96 },
"9x16": { "top": 96, "right": 72, "bottom": 240, "left": 72 }
}
}
}
}
The parts of the document:
| Part | What it is |
|---|---|
identity | The brand's name, and the four lines a template draws: titlePrimary and titleSecondary across the top, footerPrimary and footerSecondary across the bottom. |
palette, design, accent | The colours. Where palette is set (the same fields /v1/brands/palette takes), the four design colours and the two accent colours are worked out from it on every save. |
text | primary (headlines) and secondary (body), each a font, a size from 0.5 to 3.5 and a color; tertiary, whose colour is used for the smallest lines; and frame, the face and size of the top and bottom lines. A role can carry a font file of the brand's own. |
logo, badge, background, intro, outro | The files, filled through the slots below. The badge adds two lines of text, primaryLine and secondaryLine, drawn as a name card. |
sound | A soundtrack laid over the finished clip, with mode: none, clip, clip-intro, clip-outro or clip-intro-outro. |
posting | What goes out beside every post, never on the clip: a description (sent as the post's disclaimer), hashtags added to the post's own, and a link. Together they may take at most 300 characters, so every post keeps room of its own. |
locales | The same brand in another language, by language code: only what that language says differently, such as its own lines, posting text or files. Everything it leaves out is the default's. |
GET /v1/brands returns every brand with a flattened summary rather than the whole document, plus
the channels wearing it. Read the list to find a brand, read one brand to edit it. File addresses
in a brand are signed previews, good for an hour.
See a new brand before you make one
GET /v1/brands/draft composes exactly what create would have composed and stores nothing. It is
how the New brand editor shows a real document while the workspace still has no such brand.
curl http://localhost:4000/v1/brands/draft \
-H "Authorization: Bearer rw_your_key_here"
{
"name": "New brand",
"style": {
"identity": {},
"design": { "first": "#120A16", "second": "#1E1226", "third": "#2e2732", "fourth": "#E0A52E" },
"accent": { "positive": "#E0A52E", "negative": "#E8527F" },
"text": {
"primary": { "font": "open-sans", "size": 1, "color": "#F6EEFA" },
"secondary": { "font": "open-sans", "size": 1, "color": "#A990B5" }
},
"safeAreas": {
"16x9": { "top": 64, "right": 96, "bottom": 72, "left": 96 },
"9x16": { "top": 96, "right": 72, "bottom": 240, "left": 72 }
}
}
}
name is the first free name in the workspace. identity comes back empty on purpose: a form with
"New brand 6" already typed into it is a form whose lazy path publishes a machine's placeholder onto
a clip.
Create one
curl http://localhost:4000/v1/brands \
-H "Authorization: Bearer rw_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name":"Orchid Invest DE"}'
201, with the brand as { "brand": { ... } }. name and style are both optional. Without a
name, Reelwire takes the first free one. Without a style, it composes the same starting document
/v1/brands/draft shows, with the identity filled in from the name rather than left empty, because a
headless create has nobody to type it. A new brand takes the colours and type of the newest brand,
never its files, its identity or its posting text. A name already in use is a 409.
PATCH /v1/brands/{id} takes name, enabled and style. A style is checked whole against the
contract, and a refusal is a 400 with a pointer under /style for each problem.
The files in a style you send are mostly ignored. The logo, the badge picture, the background
file and the intro and outro clips are read from what is stored, not from your body. A file is
attached through a slot, so a document from a stale client cannot introduce a reference the store
does not hold, and cannot lose one by leaving it out. What your body does control is the text beside
those files: the badge's two lines and its kind, the intro and outro enabled switches, and a
background of kind: "color".
Three parts are taken as you send them: the font files under text, the sound, and the
locales. Send them back as GET gave them to you, or the save removes them.
Whatever a document names, only files Reelwire holds are drawn: a file uploaded to the brand or one from the media library. A reference to anything else draws as nothing.
The name drawn on the clip and the name in the list follow each other: setting style.identity.name
renames the record too, unless another brand already has that name, in which case the record keeps
its old one and the save still succeeds.
Derive a palette
POST /v1/brands/palette works out a whole palette from one brand colour, and checks its contrast.
It stores nothing, so you can call it while somebody drags a colour picker.
curl http://localhost:4000/v1/brands/palette \
-H "Authorization: Bearer rw_your_key_here" \
-H "Content-Type: application/json" \
-d '{"stage":"dark","brand":"#77EE58","direction":"green-up"}'
{
"palette": {
"stage": "#0E100D",
"surface": "#1C1F1B",
"surfaceRaised": "#2A2C28",
"line": "#383C36",
"lineStrong": "#525750",
"textStrong": "#F7F9F6",
"textBody": "#E3E5E2",
"textMuted": "#9D9F9C",
"onBrand": "#0B0B0C",
"onPositive": "#0B0B0C",
"onNegative": "#0B0B0C",
"brand": "#77EE58",
"brandMuted": "#21301D",
"brandSecond": "#B2CFFE",
"positive": "#3FB950",
"negative": "#E5534B",
"neutral": "#848882",
"series": ["#77EE58", "#B2CFFE", "#29B2EE", "#787AD8", "#D383D1", "#CC5E6B"]
},
"checks": [
{ "label": "Headline on the stage", "ratio": 18.1, "target": 7, "ok": true },
{ "label": "Headline on a card", "ratio": 15.7, "target": 7, "ok": true },
{ "label": "Body text on a card", "ratio": 13.1, "target": 7, "ok": true },
{ "label": "Labels on a card", "ratio": 6.2, "target": 4.5, "ok": true },
{ "label": "Brand colour on the stage", "ratio": 12.9, "target": 3, "ok": true },
{ "label": "Rise on the stage", "ratio": 7.5, "target": 3, "ok": true },
{ "label": "Fall on the stage", "ratio": 5.2, "target": 3, "ok": true },
{ "label": "Text on a brand fill", "ratio": 13.3, "target": 7, "ok": true }
]
}
The body:
| Field | Required | What it is |
|---|---|---|
brand | yes | The one colour that makes a clip theirs, as a hex string. |
stage | no | dark or light. Defaults to dark. |
background | no | The ground. Absent is a near-black or near-white carrying a trace of the brand's hue. |
direction | no | Which colours mean up and down: green-up, red-up, blue-up or custom. Defaults to green-up. |
positive, negative | no | Only with direction: "custom". |
second | no | The second colour, where two things are compared. |
overrides | no | Individual colours pinned by hand. |
brand is required, and a body without it is a 400 naming /palette/brand.
Brand files
A brand's files go in named slots, one file per slot, uploaded as multipart/form-data exactly as
the media library takes one. The previous file in the slot is deleted: a slot
holds one thing. A brand's files count against the workspace's storage like any other.
| Slot | Takes | Limit |
|---|---|---|
logo | PNG, JPEG, WebP, SVG | 5 MB |
background | PNG, JPEG, WebP, MP4, WebM | 50 MB |
badge | PNG, JPEG, WebP, MP4, WebM | 20 MB |
sound | MP3, WAV | 20 MB |
intro-{ratio}, outro-{ratio} | MP4, WebM | 50 MB |
font-primary, font-secondary, font-tertiary, font-frame | WOFF2, WOFF, TTF, OTF | 10 MB |
curl http://localhost:4000/v1/brands/01M1VFRWE7VHP3H54ESRJQ0SAJ/assets/logo \
-H "Authorization: Bearer rw_your_key_here" \
-F "file=@logo.png;type=image/png"
{
"slot": "logo",
"asset": {
"assetId": "01M2SRB6Q0Y4ZT5N8X2KDM7HCE",
"url": "http://localhost:4000/v1/assets/01M2SRB6Q0Y4ZT5N8X2KDM7HCE/download?exp=...&sig=...",
"mimeType": "image/png",
"fileName": "logo.png"
}
}
201. The first clip put in an intro or outro slot switches that side on, and the first sound file switches the soundtrack on over the clip.
Intro and outro are addressed per ratio: intro-9x16, outro-16x9. One clip per shape rather
than one stretched to five, because a 16x9 opener letterboxed into a 9x16 frame is worse than no
opener at all. A ratio nobody uploaded has none at all.
Fonts and sound are not per language: a brand is set in its typeface and sounds the same whichever
language it speaks. The logo, the background, the badge and the clips can be, with ?locale=de, and
the file then lands in that language's overlay rather than in the brand's default.
The reference is written into the stored document at upload time rather than left for a later save, so there is never a file the store holds and nothing points at.
POST /v1/brands/{id}/assets/{slot}/link points a slot at a file already in the media library,
with { "url": "media://{id}" }, instead of uploading a second copy. Only a library file of this
workspace, of a type the slot takes, is accepted: a brand never points at a file on somebody else's
server. It answers with the slot and the reference, as an upload does.
POST /v1/brands/{id}/assets/{slot}/delete empties the slot and answers
{ "slot": "...", "asset": null }.
Deleting a brand
Every channel wears a brand, so POST /v1/brands/{id}/delete on a brand in use is a 409 whose
message names the count: "Brand 1 is used by 5 channels. Choose the brand they use instead."
Send { "moveTo": "{another brand id}" } and the channels are moved to that brand in the same step.
The channels array on GET /v1/brands tells you in advance how many there are. The answer is
{ "deleted": "...", "moved": 5 }. A removed brand leaves the list but not the history: clips it
already drew keep its look, and its name gets " (removed)" on the end, so a new brand can take it.
Mistakes people make
Sending file references in a style PATCH. The logo, badge, background and clips are ignored,
and always were. Upload to the slot, or link a library file to it.
Leaving text, sound or locales out of a style PATCH. Those are taken as sent, so a
document missing them saves a brand without its font files, its soundtrack or its languages.
Uploading one intro clip and expecting it everywhere. The slot is per ratio. A 9x16 channel with
only intro-16x9 uploaded has no opener.
Editing a brand to change one channel. Every channel wearing it changes. Make a second brand.
Calling /v1/brands/palette a write. It saves nothing, but it is a POST and therefore needs
brands:write. A read-only key gets a 403.
Deleting a brand without moveTo. A 409 that names the number of channels, every time.