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" },
"safeAreas": {
"16x9": { "top": 64, "right": 96, "bottom": 72, "left": 96 },
"9x16": { "top": 96, "right": 72, "bottom": 240, "left": 72 }
}
}
}
}
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.
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"}'
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 name
already in use is a 409.
PATCH /v1/brands/{id} takes name, enabled and style.
A style you send cannot carry file references. 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 by
uploading it to 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".
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",
"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": "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 }
]
}
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 | green-up, and the others the enum offers. 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.
| Slot | Takes | Limit |
|---|---|---|
logo | PNG, JPEG, WebP, SVG | 5 MB |
background | PNG, JPEG, WebP, MP4 | 50 MB |
badge | PNG, JPEG, WebP, MP4 | 20 MB |
sound | MP3, WAV | 20 MB |
intro-{ratio}, outro-{ratio} | MP4 | 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"
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 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.
POST /v1/brands/{id}/assets/{slot}/delete empties the slot.
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.
Mistakes people make
Sending file references in a style PATCH. They are ignored, and always were. Upload to the
slot.
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.