Skip to main content

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.

MethodPathPermission
GET/v1/brandsbrands:read
GET/v1/brands/{id}brands:read
GET/v1/brands/draftbrands:read
POST/v1/brandsbrands:write
PATCH/v1/brands/{id}brands:write
POST/v1/brands/palettebrands:write
POST/v1/brands/{id}/assets/{slot}brands:write
POST/v1/brands/{id}/assets/{slot}/linkbrands:write
POST/v1/brands/{id}/assets/{slot}/deletebrands:write
POST/v1/brands/{id}/deletebrands: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:

FieldRequiredWhat it is
brandyesThe one colour that makes a clip theirs, as a hex string.
stagenodark or light. Defaults to dark.
backgroundnoThe ground. Absent is a near-black or near-white carrying a trace of the brand's hue.
directionnogreen-up, and the others the enum offers. Defaults to green-up.
positive, negativenoOnly with direction: "custom".
secondnoThe second colour, where two things are compared.
overridesnoIndividual 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.

SlotTakesLimit
logoPNG, JPEG, WebP, SVG5 MB
backgroundPNG, JPEG, WebP, MP450 MB
badgePNG, JPEG, WebP, MP420 MB
soundMP3, WAV20 MB
intro-{ratio}, outro-{ratio}MP450 MB
font-primary, font-secondary, font-tertiary, font-frameWOFF2, WOFF, TTF, OTF10 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.