Offer card, Neo-brutalist 1.0.0
| Template | Offer card, tpl_3b4631d0fc52 |
|---|---|
| Variant | Neo-brutalist |
| Version | 1.0.0, the newest: what a channel following this variant draws |
| Shapes | 9x16, 4x5, 1x1, 16x9, 2x3 |
| Length | 10 seconds |
Example video​
The 1x1 cut of this version, drawn from its own worked example. Open the video on its own.
What it accepts​
One offer: the product and its picture, the new price counting down from the old one as it is struck through, the saving, the unit price, how long it is valid and the legal note. metaData is the post's title, descriptions and hashtags and is never drawn.
| Field | Type | Required | Limit | What it is |
|---|---|---|---|---|
clipData | group | yes | Clip data: Everything drawn: a kicker, the product, its detail and picture, the prices, the unit price, the dates, the legal note and the look. | |
clipData.kicker | text | up to 20 characters | Kicker: A word above the product, such as THIS WEEK ONLY. | |
clipData.product | text | yes | up to 48 characters | Product: Shown from the first frame. |
clipData.detail | text | up to 60 characters | Detail: Pack size, variety, origin. | |
clipData.picture | image | yes | media:// file from the media library, alt text up to 120 characters | Product picture: Shown whole on a light panel, so a cut-out on a clear background works best. |
clipData.pictureFit | enum | fill, whole | Picture fit: Show the picture whole (the default) for a cut-out product; fill the frame for a photograph. | |
clipData.currency | enum | yes | EUR, USD, GBP, CHF, SEK, NOK, DKK, PLN, CZK, HUF, CAD, AUD, NZD, MXN, BRL, JPY, INR, ZAR | Currency |
clipData.price | number | yes | from 0, 2 decimals | Offer price |
clipData.oldPrice | number | from 0, 2 decimals | Previous price: Struck through, with the saving worked out from it and rounded down. Left out, the offer price stands alone. | |
clipData.unitPrice | group | yes | Unit price: The price per kilogram, litre or piece, written beside the offer price. | |
clipData.unitPrice.value | number | yes | from 0, 2 decimals | Price per unit |
clipData.unitPrice.per | text | yes | up to 12 characters | Per: The quantity it is the price of, such as 1 kg, 100 g or 1 l. |
clipData.validFrom | datetime | Valid from: Only the date is drawn, as it is written here. | ||
clipData.validUntil | datetime | yes | Valid until: Only the date is drawn, as it is written here. | |
clipData.footnote | text | yes | up to 160 characters | Legal note: What the previous price is, stock, where the offer applies, taxes. |
clipData.look | group | Look: How the clip looks and moves within its style. The same five switches in every format of the collections. Leave a switch out, or set it to the style's own, and the style decides. | ||
clipData.look.theme | enum | auto, brand, light, dark, bold | Theme: The style's own choice; your brand's own colours and background; a light or dark page tinted with your brand colour; or your brand colour as the page. | |
clipData.look.pace | enum | auto, calm, standard, punchy | Pace: How things arrive: the style's own rhythm, calm fades, a standard rhythm, or punchy slams and cuts. | |
clipData.look.corners | enum | auto, square, soft, round | Corners: The shape of cards, panels and marks. | |
clipData.look.align | enum | auto, start, centre | Text alignment: Where the text lines up. | |
clipData.look.logo | enum | auto, free | Logo: The style's own: the logo on the style's plate (a card, a disc, a badge) so it reads on any page. As it is: the logo whole, with no plate, frame or crop, for a logo made to stand on the page, such as one with a transparent background. As it is, make sure your logo reads on the page of the theme you choose. | |
metaData | group | Meta data: Everything about the post that is never drawn in the clip: its title, its description in four lengths, one per kind of platform, and its hashtags. Reelwire adds the brand's description, link and hashtags after it. Left out, the post text is generated from the clip data. | ||
metaData.title | text | up to 100 characters | Title: Used where the platform has a title of its own: Facebook Feed, LinkedIn, Pinterest and YouTube. Up to 100 characters, which every one of them takes. | |
metaData.captionTiny | text | up to 200 characters | Tiny description (Pinterest, Threads, X): Up to 200 characters. The brand's description, link and hashtags follow it within the platform's limit. | |
metaData.captionShort | text | up to 500 characters | Short description (Telegram, Threema, WeChat, WhatsApp): Up to 500 characters. The brand's description, link and hashtags follow it within the platform's limit. | |
metaData.captionMedium | text | up to 1500 characters | Medium description (Discord, Facebook Reels, Instagram, LinkedIn, Signal, Slack, TikTok): Up to 1500 characters. The brand's description, link and hashtags follow it within the platform's limit. | |
metaData.captionLong | text | up to 4000 characters | Long description (Facebook Feed, YouTube): Up to 4000 characters. The brand's description, link and hashtags follow it within the platform's limit. | |
metaData.hashtags | list | up to 15 | Hashtags: Written without the #. Up to 15, which every platform takes. | |
metaData.hashtags[] | text | up to 60 characters | Hashtag |
locales carries other languages: under each language code, the parts of clipData and metaData that differ, laid over the default, so a language with no copy still renders.
A picture is always a file from your media library: media:// followed by its id or its name.
Anything the table does not list is refused, and so is anything over its limit, with a 422 that
names the field.
The JSON Schema
{
"type": "object",
"properties": {
"clipData": {
"type": "object",
"properties": {
"kicker": {
"type": "string",
"maxLength": 20,
"title": "Kicker",
"description": "A word above the product, such as THIS WEEK ONLY."
},
"product": {
"type": "string",
"maxLength": 48,
"title": "Product",
"description": "Shown from the first frame."
},
"detail": {
"type": "string",
"maxLength": 60,
"title": "Detail",
"description": "Pack size, variety, origin."
},
"picture": {
"type": "object",
"properties": {
"url": {
"type": "string",
"maxLength": 2048,
"pattern": "^media:\\/\\/[^\\s/\\\\\\u0000-\\u001f][^/\\\\\\u0000-\\u001f]{0,254}$",
"title": "Picture",
"description": "A file from the workspace's media library, written media:// and the file's id or name. A web address is refused."
},
"alt": {
"type": "string",
"maxLength": 120,
"title": "Alt text",
"description": "What the picture shows, for anyone who cannot see it."
}
},
"required": [
"url"
],
"additionalProperties": false,
"title": "Product picture",
"description": "Shown whole on a light panel, so a cut-out on a clear background works best."
},
"pictureFit": {
"type": "string",
"enum": [
"fill",
"whole"
],
"title": "Picture fit",
"description": "Show the picture whole (the default) for a cut-out product; fill the frame for a photograph.",
"enumNames": [
"Fill the frame",
"Show it whole"
]
},
"currency": {
"type": "string",
"enum": [
"EUR",
"USD",
"GBP",
"CHF",
"SEK",
"NOK",
"DKK",
"PLN",
"CZK",
"HUF",
"CAD",
"AUD",
"NZD",
"MXN",
"BRL",
"JPY",
"INR",
"ZAR"
],
"title": "Currency",
"enumNames": [
"Euro",
"US dollar",
"Pound sterling",
"Swiss franc",
"Swedish krona",
"Norwegian krone",
"Danish krone",
"Polish zloty",
"Czech koruna",
"Hungarian forint",
"Canadian dollar",
"Australian dollar",
"New Zealand dollar",
"Mexican peso",
"Brazilian real",
"Japanese yen",
"Indian rupee",
"South African rand"
]
},
"price": {
"type": "number",
"minimum": 0,
"title": "Offer price"
},
"oldPrice": {
"type": "number",
"minimum": 0,
"title": "Previous price",
"description": "Struck through, with the saving worked out from it and rounded down. Left out, the offer price stands alone."
},
"unitPrice": {
"type": "object",
"properties": {
"value": {
"type": "number",
"minimum": 0,
"title": "Price per unit"
},
"per": {
"type": "string",
"maxLength": 12,
"title": "Per",
"description": "The quantity it is the price of, such as 1 kg, 100 g or 1 l."
}
},
"required": [
"value",
"per"
],
"additionalProperties": false,
"title": "Unit price",
"description": "The price per kilogram, litre or piece, written beside the offer price."
},
"validFrom": {
"type": "string",
"format": "date-time",
"title": "Valid from",
"description": "Only the date is drawn, as it is written here."
},
"validUntil": {
"type": "string",
"format": "date-time",
"title": "Valid until",
"description": "Only the date is drawn, as it is written here."
},
"footnote": {
"type": "string",
"maxLength": 160,
"title": "Legal note",
"description": "What the previous price is, stock, where the offer applies, taxes."
},
"look": {
"type": "object",
"properties": {
"theme": {
"type": "string",
"enum": [
"auto",
"brand",
"light",
"dark",
"bold"
],
"title": "Theme",
"description": "The style's own choice; your brand's own colours and background; a light or dark page tinted with your brand colour; or your brand colour as the page.",
"enumNames": [
"The style's own",
"Brand colours",
"Light",
"Dark",
"Bold brand colour"
]
},
"pace": {
"type": "string",
"enum": [
"auto",
"calm",
"standard",
"punchy"
],
"title": "Pace",
"description": "How things arrive: the style's own rhythm, calm fades, a standard rhythm, or punchy slams and cuts.",
"enumNames": [
"The style's own",
"Calm",
"Standard",
"Punchy"
]
},
"corners": {
"type": "string",
"enum": [
"auto",
"square",
"soft",
"round"
],
"title": "Corners",
"description": "The shape of cards, panels and marks.",
"enumNames": [
"The style's own",
"Square",
"Softly rounded",
"Round"
]
},
"align": {
"type": "string",
"enum": [
"auto",
"start",
"centre"
],
"title": "Text alignment",
"description": "Where the text lines up.",
"enumNames": [
"The style's own",
"At the start of the line",
"Centred"
]
},
"logo": {
"type": "string",
"enum": [
"auto",
"free"
],
"title": "Logo",
"description": "The style's own: the logo on the style's plate (a card, a disc, a badge) so it reads on any page. As it is: the logo whole, with no plate, frame or crop, for a logo made to stand on the page, such as one with a transparent background. As it is, make sure your logo reads on the page of the theme you choose.",
"enumNames": [
"The style's own",
"As it is, no plate or crop"
]
}
},
"additionalProperties": false,
"title": "Look",
"description": "How the clip looks and moves within its style. The same five switches in every format of the collections. Leave a switch out, or set it to the style's own, and the style decides."
}
},
"required": [
"product",
"picture",
"currency",
"price",
"unitPrice",
"validUntil",
"footnote"
],
"additionalProperties": false,
"title": "Clip data",
"description": "Everything drawn: a kicker, the product, its detail and picture, the prices, the unit price, the dates, the legal note and the look."
},
"metaData": {
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 100,
"title": "Title",
"description": "Used where the platform has a title of its own: Facebook Feed, LinkedIn, Pinterest and YouTube. Up to 100 characters, which every one of them takes."
},
"captionTiny": {
"type": "string",
"maxLength": 200,
"title": "Tiny description (Pinterest, Threads, X)",
"description": "Up to 200 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionShort": {
"type": "string",
"maxLength": 500,
"title": "Short description (Telegram, Threema, WeChat, WhatsApp)",
"description": "Up to 500 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionMedium": {
"type": "string",
"maxLength": 1500,
"title": "Medium description (Discord, Facebook Reels, Instagram, LinkedIn, Signal, Slack, TikTok)",
"description": "Up to 1500 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionLong": {
"type": "string",
"maxLength": 4000,
"title": "Long description (Facebook Feed, YouTube)",
"description": "Up to 4000 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"hashtags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 60,
"title": "Hashtag"
},
"maxItems": 15,
"title": "Hashtags",
"description": "Written without the #. Up to 15, which every platform takes."
}
},
"additionalProperties": false,
"title": "Meta data",
"description": "Everything about the post that is never drawn in the clip: its title, its description in four lengths, one per kind of platform, and its hashtags. Reelwire adds the brand's description, link and hashtags after it. Left out, the post text is generated from the clip data."
},
"locales": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"clipData": {
"type": "object",
"properties": {
"kicker": {
"type": "string",
"maxLength": 20,
"title": "Kicker",
"description": "A word above the product, such as THIS WEEK ONLY."
},
"product": {
"type": "string",
"maxLength": 48,
"title": "Product",
"description": "Shown from the first frame."
},
"detail": {
"type": "string",
"maxLength": 60,
"title": "Detail",
"description": "Pack size, variety, origin."
},
"picture": {
"type": "object",
"properties": {
"url": {
"type": "string",
"maxLength": 2048,
"pattern": "^media:\\/\\/[^\\s/\\\\\\u0000-\\u001f][^/\\\\\\u0000-\\u001f]{0,254}$",
"title": "Picture",
"description": "A file from the workspace's media library, written media:// and the file's id or name. A web address is refused."
},
"alt": {
"type": "string",
"maxLength": 120,
"title": "Alt text",
"description": "What the picture shows, for anyone who cannot see it."
}
},
"required": [
"url"
],
"additionalProperties": false,
"title": "Product picture",
"description": "Shown whole on a light panel, so a cut-out on a clear background works best."
},
"pictureFit": {
"type": "string",
"enum": [
"fill",
"whole"
],
"title": "Picture fit",
"description": "Show the picture whole (the default) for a cut-out product; fill the frame for a photograph.",
"enumNames": [
"Fill the frame",
"Show it whole"
]
},
"currency": {
"type": "string",
"enum": [
"EUR",
"USD",
"GBP",
"CHF",
"SEK",
"NOK",
"DKK",
"PLN",
"CZK",
"HUF",
"CAD",
"AUD",
"NZD",
"MXN",
"BRL",
"JPY",
"INR",
"ZAR"
],
"title": "Currency",
"enumNames": [
"Euro",
"US dollar",
"Pound sterling",
"Swiss franc",
"Swedish krona",
"Norwegian krone",
"Danish krone",
"Polish zloty",
"Czech koruna",
"Hungarian forint",
"Canadian dollar",
"Australian dollar",
"New Zealand dollar",
"Mexican peso",
"Brazilian real",
"Japanese yen",
"Indian rupee",
"South African rand"
]
},
"price": {
"type": "number",
"minimum": 0,
"title": "Offer price"
},
"oldPrice": {
"type": "number",
"minimum": 0,
"title": "Previous price",
"description": "Struck through, with the saving worked out from it and rounded down. Left out, the offer price stands alone."
},
"unitPrice": {
"type": "object",
"properties": {
"value": {
"type": "number",
"minimum": 0,
"title": "Price per unit"
},
"per": {
"type": "string",
"maxLength": 12,
"title": "Per",
"description": "The quantity it is the price of, such as 1 kg, 100 g or 1 l."
}
},
"additionalProperties": false,
"title": "Unit price",
"description": "The price per kilogram, litre or piece, written beside the offer price."
},
"validFrom": {
"type": "string",
"format": "date-time",
"title": "Valid from",
"description": "Only the date is drawn, as it is written here."
},
"validUntil": {
"type": "string",
"format": "date-time",
"title": "Valid until",
"description": "Only the date is drawn, as it is written here."
},
"footnote": {
"type": "string",
"maxLength": 160,
"title": "Legal note",
"description": "What the previous price is, stock, where the offer applies, taxes."
},
"look": {
"type": "object",
"properties": {
"theme": {
"type": "string",
"enum": [
"auto",
"brand",
"light",
"dark",
"bold"
],
"title": "Theme",
"description": "The style's own choice; your brand's own colours and background; a light or dark page tinted with your brand colour; or your brand colour as the page.",
"enumNames": [
"The style's own",
"Brand colours",
"Light",
"Dark",
"Bold brand colour"
]
},
"pace": {
"type": "string",
"enum": [
"auto",
"calm",
"standard",
"punchy"
],
"title": "Pace",
"description": "How things arrive: the style's own rhythm, calm fades, a standard rhythm, or punchy slams and cuts.",
"enumNames": [
"The style's own",
"Calm",
"Standard",
"Punchy"
]
},
"corners": {
"type": "string",
"enum": [
"auto",
"square",
"soft",
"round"
],
"title": "Corners",
"description": "The shape of cards, panels and marks.",
"enumNames": [
"The style's own",
"Square",
"Softly rounded",
"Round"
]
},
"align": {
"type": "string",
"enum": [
"auto",
"start",
"centre"
],
"title": "Text alignment",
"description": "Where the text lines up.",
"enumNames": [
"The style's own",
"At the start of the line",
"Centred"
]
},
"logo": {
"type": "string",
"enum": [
"auto",
"free"
],
"title": "Logo",
"description": "The style's own: the logo on the style's plate (a card, a disc, a badge) so it reads on any page. As it is: the logo whole, with no plate, frame or crop, for a logo made to stand on the page, such as one with a transparent background. As it is, make sure your logo reads on the page of the theme you choose.",
"enumNames": [
"The style's own",
"As it is, no plate or crop"
]
}
},
"additionalProperties": false,
"title": "Look",
"description": "How the clip looks and moves within its style. The same five switches in every format of the collections. Leave a switch out, or set it to the style's own, and the style decides."
}
},
"additionalProperties": false,
"title": "Clip data",
"description": "Everything drawn: a kicker, the product, its detail and picture, the prices, the unit price, the dates, the legal note and the look."
},
"metaData": {
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 100,
"title": "Title",
"description": "Used where the platform has a title of its own: Facebook Feed, LinkedIn, Pinterest and YouTube. Up to 100 characters, which every one of them takes."
},
"captionTiny": {
"type": "string",
"maxLength": 200,
"title": "Tiny description (Pinterest, Threads, X)",
"description": "Up to 200 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionShort": {
"type": "string",
"maxLength": 500,
"title": "Short description (Telegram, Threema, WeChat, WhatsApp)",
"description": "Up to 500 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionMedium": {
"type": "string",
"maxLength": 1500,
"title": "Medium description (Discord, Facebook Reels, Instagram, LinkedIn, Signal, Slack, TikTok)",
"description": "Up to 1500 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionLong": {
"type": "string",
"maxLength": 4000,
"title": "Long description (Facebook Feed, YouTube)",
"description": "Up to 4000 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"hashtags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 60,
"title": "Hashtag"
},
"maxItems": 15,
"title": "Hashtags",
"description": "Written without the #. Up to 15, which every platform takes."
}
},
"additionalProperties": false,
"title": "Meta data",
"description": "Everything about the post that is never drawn in the clip: its title, its description in four lengths, one per kind of platform, and its hashtags. Reelwire adds the brand's description, link and hashtags after it. Left out, the post text is generated from the clip data."
}
},
"additionalProperties": false
},
"propertyNames": {
"enum": [
"aa",
"ab",
"ae",
"af",
"ak",
"am",
"an",
"ar",
"as",
"av",
"ay",
"az",
"ba",
"be",
"bg",
"bi",
"bm",
"bn",
"bo",
"br",
"bs",
"ca",
"ce",
"ch",
"co",
"cr",
"cs",
"cu",
"cv",
"cy",
"da",
"de",
"dv",
"dz",
"ee",
"el",
"en",
"eo",
"es",
"et",
"eu",
"fa",
"ff",
"fi",
"fj",
"fo",
"fr",
"fy",
"ga",
"gd",
"gl",
"gn",
"gu",
"gv",
"ha",
"he",
"hi",
"ho",
"hr",
"ht",
"hu",
"hy",
"hz",
"ia",
"id",
"ie",
"ig",
"ii",
"ik",
"io",
"is",
"it",
"iu",
"ja",
"jv",
"ka",
"kg",
"ki",
"kj",
"kk",
"kl",
"km",
"kn",
"ko",
"kr",
"ks",
"ku",
"kv",
"kw",
"ky",
"la",
"lb",
"lg",
"li",
"ln",
"lo",
"lt",
"lu",
"lv",
"mg",
"mh",
"mi",
"mk",
"ml",
"mn",
"mr",
"ms",
"mt",
"my",
"na",
"nb",
"nd",
"ne",
"ng",
"nl",
"nn",
"no",
"nr",
"nv",
"ny",
"oc",
"oj",
"om",
"or",
"os",
"pa",
"pi",
"pl",
"ps",
"pt",
"qu",
"rm",
"rn",
"ro",
"ru",
"rw",
"sa",
"sc",
"sd",
"se",
"sg",
"si",
"sk",
"sl",
"sm",
"sn",
"so",
"sq",
"sr",
"ss",
"st",
"su",
"sv",
"sw",
"ta",
"te",
"tg",
"th",
"ti",
"tk",
"tl",
"tn",
"to",
"tr",
"ts",
"tt",
"tw",
"ty",
"ug",
"uk",
"ur",
"uz",
"ve",
"vi",
"vo",
"wa",
"wo",
"xh",
"yi",
"yo",
"za",
"zh",
"zu",
"zh-CN",
"zh-TW",
"pt-BR"
]
}
}
},
"required": [
"clipData"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
Ask Reelwire for the same document yourself, without signing in:
curl "http://localhost:4000/v1/formats/template/tpl_3b4631d0fc52?variant=Neo-brutalist&version=1.0.0"
Delivery JSON​
Your own system sends this to one of your streams, POST /v1/stream, and every channel listening to
that stream draws it with its template. data is the document described above; a stream with an
empty field map passes it to the template unchanged. Try it first on POST /v1/stream/validate,
which answers the same without publishing anything. Streams and feeds
explains the rest, including how to map a document of your own shape instead.
{
"streamId": "st_your_stream_id",
"eventId": "01M2SQMKK3G1NVFXV31DCBVB95",
"occurredAt": "2026-09-23T07:00:00Z",
"locale": "en",
"data": {
"clipData": {
"kicker": "This week only",
"product": "Silk dunes wallpaper, in four colourways",
"detail": "Non-woven, one roll of 10.05 x 0.53 m",
"picture": {
"alt": "Soft waves of blue, violet and orange",
"url": "media://example-image.jpg"
},
"pictureFit": "whole",
"currency": "EUR",
"price": 49,
"oldPrice": 69,
"unitPrice": {
"per": "1 m²",
"value": 9.2
},
"validFrom": "2026-10-05T07:00:00+02:00",
"validUntil": "2026-10-10T20:00:00+02:00",
"footnote": "Previous price: our lowest in the 30 days before the offer. While stocks last, in the shop and online. All prices include VAT.",
"look": {
"theme": "auto",
"pace": "auto",
"corners": "auto",
"align": "auto",
"logo": "auto"
}
},
"metaData": {
"title": "Arabica coffee beans for 6.99 euros instead of 9.49, this week at Hallam Market",
"captionTiny": "This week only at Hallam Market: dark roast Arabica coffee beans, 500 g, for 6.99 euros instead of 9.49. That is 13.98 euros per kilo. Valid 5 to 10 October.",
"captionShort": "This week only at Hallam Market: our dark roast Arabica coffee beans from the Huila highlands, 500 g whole beans, for 6.99 euros instead of 9.49, a saving of 26 percent. That is 13.98 euros per kilo. The offer is valid from Monday 5 to Saturday 10 October in all stores, while stocks last. The previous price is our lowest in the 30 days before the offer, and all prices include VAT.",
"captionMedium": "This week only at Hallam Market: our dark roast Arabica coffee beans from the Huila highlands, 500 g of whole beans, for 6.99 euros instead of 9.49, a saving of 26 percent. That is 13.98 euros per kilo. The beans are grown on small farms at 1,600 metres and roasted in our own roastery the week they reach the shelf, so they are at their best for a month after you open the bag. Grind them just before brewing for a full cup with notes of dark chocolate and red fruit, or ask in store and we will grind them for you, for a filter, a moka pot or an espresso machine. The offer is valid from Monday 5 to Saturday 10 October in all Hallam Market stores, while stocks last. The previous price is our lowest in the 30 days before the offer, and all prices include VAT.",
"captionLong": "This week only at Hallam Market: our dark roast Arabica coffee beans from the Huila highlands, 500 g of whole beans, for 6.99 euros instead of 9.49, a saving of 26 percent. That is 13.98 euros per kilo. The beans are grown on small farms at around 1,600 metres, picked by hand when the cherries are ripe, washed and dried in the sun, and roasted in our own roastery the week they reach the shelf. That is why the bag carries a roasting date rather than only a best-before date, and why they are at their best for a month after you open it. Grind them just before brewing for a full cup with notes of dark chocolate and red fruit and a low acidity that makes it easy to drink black. If you have no grinder, ask at the coffee counter and we will grind them for you while you wait, coarse for a French press, medium for a filter, fine for a moka pot or an espresso machine. Keep the beans in the closed bag or a tin, away from light and heat, and never in the fridge, where they take on moisture and other smells. The offer is valid from Monday 5 to Saturday 10 October in all Hallam Market stores, while stocks last, and not in the online shop. The previous price shown is our lowest price in the 30 days before the offer, and all prices include VAT. Prices and the store are illustrative.",
"hashtags": [
"offer",
"coffee",
"weeklyoffers",
"supermarket",
"deal"
]
}
}
}
The delivery names no version: which build draws it is the channel's choice, the newest of its
variant or the one it is pinned to. eventId is a ULID you make once per event: sending the same
one again changes nothing. occurredAt is when the thing happened, not when you sent it. locale
is the language of the text in data, and data.locales carries other languages beside it.