Create a marketing template
Creates a template. It ALWAYS LANDS AS A DRAFT and this route can never put copy in front of a recipient: a draft is not sendable and publishing is a second, explicitly named call. That is what makes a bulk library import safe to run. channel is settable exactly once, here, because every body on the row is validated against it. status, current_version_id and current_version are refused by name. Names are unique per store, so a repeat is 409 name_taken rather than a second template nobody can tell apart. That is also what makes a migration safe to re-run with fresh idempotency keys.
Authorizations
A secret API key. Publishable keys cannot reach this API. A key may carry an expiry, and an expired key is refused exactly like an unknown one, with a 401 that names no reason; check the key's expires_at in the dashboard rather than inferring it from a response. When a merchant rolls a key's secret they choose a grace window of up to 3 days, and for its duration BOTH the new secret and the one it replaced authenticate, so an integration moves over on its own deploy schedule instead of at the instant the button is pressed. Move before the window closes: after it, the old secret is refused. Nothing else about this contract moves with a roll. The key keeps its id and its scopes, so the only thing an integration updates is the credential itself.
Headers
A unique key per logical write. Replaying a request with the same key returns the first response byte for byte instead of applying the write twice.
Body
A template ALWAYS lands as a draft and this route can never make copy sendable: publishing is a second, explicitly named call. status, current_version_id and current_version are refused by name for that reason. The three content fields are optional here and REQUIRED on the replace. Nothing is being overwritten by a create, so an omitted body destroys nothing; on a replace it would blank the merchant's stored copy.
What the merchant sees in their own dashboard, at most 200 bytes. Unique per store, so a repeat is 409 name_taken rather than a second template nobody can tell apart.
Which surface this copy is written for, and IMMUTABLE afterwards: every body on the row is validated against it, so the replace refuses the key by name. Only an email template may carry a subject or an html body.
email, sms, push The subject line, at most 500 bytes, and EMAIL ONLY. Sending one on an sms or push template is 400 subject_not_allowed rather than a silent drop, because a merchant who wrote a subject meant it to be delivered and a drop is discovered in the inbox. An email template needs one before it can be published.
The HTML part, at most 256 KB, and EMAIL ONLY: sms and push render plain text, so an html body there could never be delivered as written and is 400 html_not_allowed. Stored VERBATIM, with no trimming, so a read-modify-write round trip converges.
The plain-text part, at most 256 KB. It is the whole message on sms and push, and the text alternative on email. MERGE TAGS ARE A CLOSED VOCABULARY, checked in every field at save time. The legal names are first_name, last_name, email, phone, orders_count, total_spent, store_name, order_total, unsubscribe_url and preferences_url, each written as {{name}}. Anything else is 400 unknown_merge_tag naming the tag, and an unclosed brace is 400 malformed_merge_tag. Catching it here is what stops a foreign platform's tag shipping as a hole in a delivered message.
Response
Created
