Skip to main content

Templates and formats

A template is how a clip is drawn. It comes in variants, which are cuts of the same idea, in versions, which are immutable builds, and in ratios, which are the shapes it can be drawn at. What matters to a developer is that a template publishes the exact document it accepts, so you can generate a client from it rather than guess.

MethodPathPermission
GET/v1/templatestemplates:read
GET/v1/templates/{templateId}/referencetemplates:read
GET/v1/templates/{templateId}/reference/{ratio}templates:read
GET/v1/formats/templatestemplates:read
GET/v1/formats/template/{templateId}templates:read
GET/v1/formats/feed/{feedId}templates:read
GET/v1/formats/feed/{source}/{format}templates:read
GET/v1/cataloguetemplates:read
POST/v1/templates/synctemplates:write

/v1/catalogue, /v1/formats/templates, /v1/formats/template/{id} and /v1/formats/feed/... are served without authentication as well. What shape a template accepts is a product description, not customer data, and a producer needs it before it has a reason to authenticate. Nothing in them says who uses a template or what has been sent to it.

Ask a template what it accepts

This is the call to build a producer around.

curl http://localhost:4000/v1/formats/template/tpl_a9c3fdb1b5c9

The answer carries four things: keys, a human-readable description of every input; jsonSchema, the same thing as JSON Schema, for generating a client; example, a worked payload that satisfies it; and ratios, the shapes this build comes in.

{
"templateId": "tpl_a9c3fdb1b5c9",
"templateName": "Template 1",
"variant": "Standard",
"version": "1.0.0",
"ratios": ["9x16", "4x5", "1x1", "16x9", "2x3"],
"keys": {
"format": "reelwire.keys/1",
"summary": "One story in two parts: clipData, which is drawn in the clip (a headline, an optional second line, up to six figures, up to five points, up to three images and a source), and metaData, which is the post's title, captions and hashtags and is never drawn.",
"keys": [
{
"key": "clipData",
"type": "group",
"label": "Clip data",
"required": true,
"keys": [
{ "key": "headline", "type": "text", "label": "Headline", "required": true, "maxLength": 80, "example": "This is the headline of the clip" },
{ "key": "subline", "type": "text", "label": "Second line", "required": false, "maxLength": 120 },
{
"key": "figures",
"type": "list",
"label": "Figures",
"required": false,
"maxItems": 6,
"item": {
"type": "group",
"keys": [
{ "key": "label", "type": "text", "required": true, "maxLength": 24 },
{ "key": "value", "type": "number", "required": true, "decimals": 4 },
{ "key": "unit", "type": "text", "required": false, "maxLength": 8 },
{ "key": "direction", "type": "enum", "required": false }
]
}
}
]
}
]
},
"jsonSchema": { "type": "object", "required": ["clipData"], "properties": {} },
"example": {
"clipData": {
"headline": "This is the headline of the clip",
"subline": "This is a second line that adds a little more detail",
"figures": [{ "label": "Example figure", "value": 1250, "unit": "", "direction": "up" }],
"bullets": ["This is a short supporting point"],
"images": [{ "alt": "Example image, PNG", "url": "media://example-image.png" }],
"source": "Example source"
},
"metaData": {
"title": "This is the title of the post",
"captionShort": "This is the short caption, for Telegram.",
"captionMedium": "This is the medium caption, for Instagram, TikTok, Facebook Reels and LinkedIn.",
"captionLong": "This is the long caption, for YouTube and Facebook Feed.",
"hashtags": ["example", "reelwire"]
},
"locales": {
"de": { "clipData": { "headline": "Das ist die Überschrift des Clips" }, "metaData": { "title": "Das ist der Titel des Beitrags" } }
}
}
}

Two halves, always. clipData is drawn in the clip. metaData is the post's title, captions and hashtags, and is never drawn on the video.

locales inside the content is how one event carries several languages. Each channel takes its own language's copy laid over the default, so a language with no copy still renders, with the default. The captions come in three lengths because the platforms do: short for Telegram, medium for Instagram, TikTok, Facebook Reels and LinkedIn, long for YouTube and the Facebook feed.

Query it for a particular cut or build with ?variant=Standard&version=1.0.0. Without them you get the first variant and the newest build.

Ask by feed instead

A producer usually knows it sends central bank decisions, not which template draws those. Which template a feed uses is Reelwire's decision and changes without the producer being told, so ask by the feed and follow.

curl http://localhost:4000/v1/formats/feed/fd_3e7b3bff3194

The answer is the same document, resolved through the feed's current binding. GET /v1/formats/feed/{source}/{format} is the same answer addressed by the two slugs.

What is on the shelf

GET /v1/formats/templates is every template that can describe itself: one entry per template holding a project with a key mapping, which is exactly the set a producer could usefully be pointed at.

{
"templates": [
{ "templateId": "tpl_a9c3fdb1b5c9", "templateName": "Template 1", "variant": "Standard", "version": "1.0.0", "keys": 2 },
{ "templateId": "tpl_1cbd9f79150f", "templateName": "Template 2", "variant": "Standard", "version": "1.0.0", "keys": 2 }
]
}

A template with no build yet is a name on a shelf with nothing to describe, so it is not listed rather than listed and unusable.

GET /v1/templates is the workspace's own view: what this account is offered, joined with the templates it already meets through a feed, a stream or a post list, each with its inputSchema, duration, ratios and variants.

{
"catalogue": [
{ "id": "tpl_a9c3fdb1b5c9", "name": "Template 1", "family": "", "description": "Template description 1", "variants": ["Standard"] }
],
"templates": [
{
"templateId": "tpl_0083ab80ce80",
"offered": true,
"version": "1.0.0",
"name": "Template 7",
"contentStyle": "motion-graphics",
"family": "Template projects",
"milestone": 1,
"ratios": ["9x16", "4x5", "1x1", "16x9", "2x3"],
"duration": { "mode": "fixed", "frames": 300 },
"subvariants": [],
"overridableStyle": ["*"],
"inputSchema": { "type": "object", "required": ["clipData"] }
}
]
}

GET /v1/catalogue is the shipped feed catalogue rather than the template shelf: what Reelwire carries, with each format's own payload schema. It is served with an etag and cache-control: public, max-age=30, so a client that has seen the version can skip the parse.

Which builds exist

curl http://localhost:4000/v1/templates/tpl_a9c3fdb1b5c9/reference \
-H "Authorization: Bearer rw_your_key_here"
{
"templateId": "tpl_a9c3fdb1b5c9",
"name": "Template 1",
"variants": [
{ "name": "Standard", "versions": [{ "version": "1.0.0", "ratios": ["9x16", "4x5", "1x1", "16x9", "2x3"] }] }
]
}

GET /v1/templates/{templateId}/reference/{ratio} returns the reference clip itself, so you can see what a template looks like before you commit to it. It is a video, not JSON:

HTTP/1.1 200 OK
content-type: video/mp4
etag: "5f3346d28eb21a9d24768c82d387c7a66708cf6561511a9f3837c8455f385fa2-9x16"
cache-control: private, no-cache
x-template-version: 1.0.0

Narrow it with ?variant=Standard&version=1.0.0. A ratio that is not NxM is a 400 that says "No such shape".

Versions and variants

A version is an immutable build: 1.0.0 today draws the same thing next year. "latest" is a real answer rather than an absence, and it is the default: it follows the newest build, resolved at the moment something renders.

A variant is a cut of the same template. Which variant and which version are the subscribing channel's choice, or the post's on a post list; they are never the producer's. See Channels.

POST /v1/templates/sync mirrors the template registry into the workspace. It is an administrative action, not part of an integration: nothing a producer or a publisher does needs it.

Mistakes people make

Writing the payload by hand from an example. Take jsonSchema and generate a type. The example is there to check yourself against, not to copy.

Putting drawn content in metaData. It is never drawn. Headlines and figures go in clipData.

Sending locales beside the content. It goes inside the content, where the template validates it.

Pinning a version because it is there. A pin stops following improvements. Pin when you have a reason to, not by default.

Assuming every template comes in every shape. ratios is per build. A channel on a surface the template does not draw has nothing to render.

Addressing a feed by its slugs in stored configuration. Slugs are words and words get edited. Use the fd_ id.