SonarSend API Endpoints Templates

Templates

Parameters, sections, slots and blocks for dynamic content.

21 endpoints Base URL · https://api.sonarsend.com Auth · X-API-Key
The Template object 22 fields
content_htmlstring

Full row only.

content_mjmlstring · nullable

MJML source, present for format: mjml. Full row only.

content_textstring · nullable

Plain-text alternative. Full row only.

created_atdate-time
expects_schemaany[]

Contact-data expectations for Liquid iteration/access. Full row only.

formatenum

Which body the send pipeline renders: content_mjml for mjml, content_html for html.

One of: html, mjml
group_countinteger

Lean list row only — length of groups_schema.

groups_schemaany[]

Parameter groupings for the editor. Full row only; lean row carries group_count.

iduuid

Canonical template ID.

namestring
parameter_countinteger

Lean list row only — length of parameters_schema.

parameters_schemaany[]

Typed top-level parameters. Full row only; the lean row carries parameter_count instead.

section_countinteger

Lean list row only — length of sections_schema.

sections_schemaany[]

Repeatable sections. Full row only; lean row carries section_count.

slot_countinteger

Lean list row only — length of slots_schema.

slots_schemaany[]

Named slots blocks can fill. Full row only; lean row carries slot_count.

slugstring

Stable per-tenant key. Usable in place of the UUID in every template path, and as content.template_slug on a broadcast.

subjectstring · nullable

Default subject line. Full row only.

tagsstring[]
tenant_iduuid

Full row only — absent from the lean list row.

updated_atdate-time
usageobject

Present only with ?include_usage=true: { active_drafts, recent_broadcasts }.

The Block object 16 fields
activeboolean
content_htmlstring

Full row only.

content_mjmlstring · nullable

Full row only.

content_textstring · nullable

Full row only.

created_atdate-time
groups_schemaany[]

Full row only.

iduuid
namestring
parameters_schemaany[]

Present on both the lean and full row.

sections_schemaany[]

Present on both the lean and full row.

slot_keysstring[]

Which template slots this block is eligible to fill.

slugstring

Stable per-tenant key. Usable in a slot binding as block_id: <slug>.

tagsstring[]
tenant_iduuid

Full row only.

updated_atdate-time
usageobject

Present only with ?include_usage=true: { active_drafts, recent_broadcasts }.

List templates

GET /t/{slug}/api/templates/

Every template in the workspace. Returns a LEAN row by default — id, slug, name, format, tags, the four *_count fields and timestamps — dropping the bodies and full schema arrays so the response stays small. Pass ?full=true for the complete rows, and ?include_usage=true to add a usage object per row. Not paginated: the full list comes back with next_cursor: null.

include_usage string
full string

A page of Template objects, under data.

200

Every template. Lean rows unless ?full=true.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/templates" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "data": [
    {
      "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "slug": "acme",
      "name": "Acme onboarding",
      "subject": "Welcome to Acme",
      "format": "html"
    }
  ],
  "next_cursor": "eyJpZCI6IjljMjE4OTVkIn0",
  "has_more": true
}

Create a template

POST /t/{slug}/api/templates/

Creates a template. name and content_html are required; format defaults to html. Supply content_mjml and format: mjml for an MJML template. A slug is derived from name when omitted and is unique per workspace — collisions are de-duplicated. The parameter, slot, section and group schemas are validated against the body: an unresolvable reference is rejected.

content_htmlstringRequired
namestringRequired
content_mjmlstring
content_textstring
expects_schemaany[]
formatenum
One of: html, mjml
groups_schemaany[]
parameters_schemaany[]
sections_schemaany[]
slots_schemaany[]
slugstring
subjectstring
tagsstring[]

The Template object.

201

The template was created.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/templates" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme onboarding",
    "content_html": "string",
    "slug": "acme",
    "subject": "Welcome to Acme",
    "content_text": "string",
    "content_mjml": "string"
  }'
Response 201
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "slug": "acme",
  "name": "Acme onboarding",
  "subject": "Welcome to Acme",
  "format": "html"
}

Get a template

GET /t/{slug}/api/templates/{id}

The full template, addressed by UUID or per-tenant slug. Returns the complete row — bodies and schema arrays included, never the lean shape.

id string Required

The Template object.

200

The template.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "slug": "acme",
  "name": "Acme onboarding",
  "subject": "Welcome to Acme",
  "format": "html"
}

Update a template

PUT /t/{slug}/api/templates/{id}

Updates a template in place (JSON, not markdown). Only the fields you send change; the parameter/slot/section/group schemas are re-validated against the resulting body. Broadcasts already sent are unaffected — their content was frozen at send time.

id string Required
content_htmlstring
content_mjmlstring
content_textstring
expects_schemaany[]
formatenum
One of: html, mjml
groups_schemaany[]
namestring
parameters_schemaany[]
sections_schemaany[]
slots_schemaany[]
slugstring
subjectstring
tagsstring[]

The Template object.

200

The updated template.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X PUT "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme onboarding",
    "slug": "acme",
    "subject": "Welcome to Acme",
    "content_html": "string",
    "content_text": "string",
    "content_mjml": "string"
  }'
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "slug": "acme",
  "name": "Acme onboarding",
  "subject": "Welcome to Acme",
  "format": "html"
}

Delete a template

DELETE /t/{slug}/api/templates/{id}

Deletes a template, addressed by UUID or slug. Refused with 409 IN_USE while it is referenced by an active draft OR a recent broadcast — the counts come back in error.details. Pass ?force=true to delete anyway; that removes the definition in-flight drafts still point at, and there is nothing to restore from unless you exported the markdown first. Returns 204 on success.

id string Required
force string
204

The template was deleted.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X DELETE "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"

Get a template's schemas

GET /t/{slug}/api/templates/{id}/schema

Just the typed schemas of a template — parameters_schema, slots_schema, sections_schema, groups_schema, expects_schema — plus template_id, slug and name. The shape a form generator needs, without the bodies. Use binding-options instead when you also need the blocks eligible to fill each slot.

id string Required
expects_schemaany[]
groups_schemaany[]
namestring
parameters_schemaany[]
sections_schemaany[]
slots_schemaany[]
slugstring
template_iduuid
200

The template's identity plus its five typed schema arrays.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/schema" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "template_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "slug": "acme",
  "name": "Acme onboarding",
  "parameters_schema": [
    null
  ],
  "slots_schema": [
    null
  ],
  "sections_schema": [
    null
  ]
}

Get a template's binding options

GET /t/{slug}/api/templates/{id}/binding-options

Everything a client needs to build a binding for this template in one call: the parameter, section and group schemas, plus each slot with its eligible_blocks already resolved against the slot's tag filter. Each eligible block carries its slug, name, slot_keys and parameters_schema, so there is no second round trip to /api/blocks. The response is a discovery payload — its shape is documented in prose rather than fully enumerated.

id string Required
200

Discovery payload: template_id, slug, name, the parameter/section/group schemas, and the slots with their resolved eligible_blocks.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/binding-options" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{}

Render a template with a binding

POST /t/{slug}/api/templates/{id}/render

Dry-run render: resolves the template against a template_binding and returns the rendered subject, body_html, body_mjml, body_text, slots and template_snapshot. Creates no broadcast and sends nothing.

include_options: true also returns binding_options (the discovery payload) and missing (the validate-binding diagnostics), so an agent can render and inspect in one round trip. It implies lenient: true, which auto-fills missing required parameters/sections/slots with visible placeholders so a partial binding still renders — missing lists what was placeholder-filled.

A template_binding that is structurally invalid returns 400 INVALID_BINDING.

id string Required
template_bindingobjectRequired
include_optionsboolean
lenientboolean
200

The rendered content: subject, body_html, body_mjml (nullable), body_text (nullable), slots, template_snapshot. With include_options it also carries missing and binding_options. Modelled permissively — a render result is not a fixed shape.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/render" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template_binding": {},
    "include_options": true,
    "lenient": true
  }'
Response 200
{}

Validate a template binding

POST /t/{slug}/api/templates/{id}/validate-binding

Checks a template_binding against the template without rendering it, and returns structured diagnostics: { valid, issues[] }, where each issue carries a path, machine-readable code, message and fix hint. All problems are reported at once, for an agent iterating on a partial binding. Note it returns 200 with valid: false for an invalid binding — the shape is the same whether valid or not; it does not 400.

id string Required
template_bindingobjectRequired
issuesobject[]
validboolean
200

Diagnostics: valid (boolean) and issues (an array of { path, code, message, fix }).

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/validate-binding" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template_binding": {}
  }'
Response 200
{
  "valid": true,
  "issues": [
    {}
  ]
}

Import a template from markdown

POST /t/{slug}/api/templates/import

Creates a new template from a markdown file. Two equivalent body forms are accepted: raw markdown with Content-Type: text/markdown (or text/plain), or { "markdown": "…" } with Content-Type: application/json. Anything else is 400 INVALID_PAYLOAD.

The file carries front matter (kind: template, name, format) plus ## sections for the body and each schema. The body heading must match the format — ## MJML for mjml, ## HTML for html — which is the most common import failure. See the Template import/export guide.

The Template object.

201

The template was created.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/templates/import" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 201
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "slug": "acme",
  "name": "Acme onboarding",
  "subject": "Welcome to Acme",
  "format": "html"
}

Replace a template from markdown

PUT /t/{slug}/api/templates/{id}/import

Replaces an existing template's body AND schemas from a markdown file — the same two body forms as POST /api/templates/import. This is a REPLACE, not a merge: a parameter absent from the uploaded file is removed from the template. Broadcasts already sent are unaffected (their content is frozen); the risk is to drafts and sequences still pointing at it, so check /usage first.

id string Required

The Template object.

200

The template was replaced.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X PUT "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/import" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "slug": "acme",
  "name": "Acme onboarding",
  "subject": "Welcome to Acme",
  "format": "html"
}

Export a template as markdown

GET /t/{slug}/api/templates/{id}/export

Returns the template as a text/markdown file — the raw file, not JSON. The round-trip is stable (export twice → identical bytes; bodies stored verbatim), so a scheduled export is a reliable drift check. One wrinkle: if the source had no slug in front matter, the export comes back carrying the generated one.

id string Required
200

The template as a markdown file.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/export" \
  -H "X-API-Key: $SONARSEND_API_KEY"

Get a template's usage

GET /t/{slug}/api/templates/{id}/usage

How many active drafts and recent broadcasts reference this template — the counts a delete is gated on. Check it before replacing or deleting.

id string Required
active_draftsinteger

Draft broadcasts still pointing at this template.

recent_broadcastsinteger

Recently-sent broadcasts that used it.

200

Reference counts.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/usage" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "active_drafts": 0,
  "recent_broadcasts": 0
}

List content blocks

GET /t/{slug}/api/blocks/

Every content block in the workspace. Filter to blocks eligible for a slot with ?slot_keys= (comma-separated) and by ?active=. The default row is LEAN — it drops the bodies and groups_schema but KEEPS parameters_schema and sections_schema (the slot picker needs them). Pass ?full=true for the bodies, and ?include_usage=true for a usage object per row. Not paginated.

slot_keys string
active string
include_usage string
full string

A page of Block objects, under data.

200

Every content block. Lean rows unless ?full=true.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/blocks" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "data": [
    {
      "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "slug": "acme",
      "name": "Acme onboarding",
      "slot_keys": [
        "string"
      ],
      "tags": [
        "string"
      ]
    }
  ],
  "next_cursor": "eyJpZCI6IjljMjE4OTVkIn0",
  "has_more": true
}

Create a content block

POST /t/{slug}/api/blocks/

Creates a content block — reusable content that fills a template slot. name and content_html are required; slot_keys names the slots it is eligible for. A slug is derived from name when omitted, unique per workspace. Blocks share the templates:* scope.

content_htmlstringRequired
namestringRequired
activeboolean
content_mjmlstring
content_textstring
groups_schemaany[]
parameters_schemaany[]
sections_schemaany[]
slot_keysstring[]
slugstring
tagsstring[]

The Block object.

201

The block was created.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/blocks" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme onboarding",
    "content_html": "string",
    "slug": "acme",
    "slot_keys": [
      "string"
    ],
    "tags": [
      "string"
    ],
    "content_text": "string"
  }'
Response 201
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "slug": "acme",
  "name": "Acme onboarding",
  "slot_keys": [
    "string"
  ],
  "tags": [
    "string"
  ]
}

Import a content block from markdown

POST /t/{slug}/api/blocks/import

Creates a new content block from a markdown file with kind: block in its front matter. Same two body forms as the template import: raw text/markdown (or text/plain), or { "markdown": "…" } as application/json. There is no markdown REPLACE endpoint for a block — update an existing block through PUT /api/blocks/{id}.

The Block object.

201

The block was created.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/blocks/import" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 201
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "slug": "acme",
  "name": "Acme onboarding",
  "slot_keys": [
    "string"
  ],
  "tags": [
    "string"
  ]
}

Export a content block as markdown

GET /t/{slug}/api/blocks/{id}/export

Returns the block as a text/markdown file — the raw file, not JSON. Same round-trippable format as a template export, with kind: block.

id string Required
200

The block as a markdown file.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80/export" \
  -H "X-API-Key: $SONARSEND_API_KEY"

Get a content block

GET /t/{slug}/api/blocks/{id}

The full content block, addressed by UUID or per-tenant slug. Returns the complete row, bodies included.

id string Required

The Block object.

200

The block.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "slug": "acme",
  "name": "Acme onboarding",
  "slot_keys": [
    "string"
  ],
  "tags": [
    "string"
  ]
}

Update a content block

PUT /t/{slug}/api/blocks/{id}

Updates a block in place (JSON). Only the fields you send change. Broadcasts already sent are unaffected.

id string Required
activeboolean
content_htmlstring
content_mjmlstring
content_textstring
groups_schemaany[]
namestring
parameters_schemaany[]
sections_schemaany[]
slot_keysstring[]
slugstring
tagsstring[]

The Block object.

200

The updated block.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X PUT "https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme onboarding",
    "slug": "acme",
    "slot_keys": [
      "string"
    ],
    "tags": [
      "string"
    ],
    "content_html": "string",
    "content_text": "string"
  }'
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "slug": "acme",
  "name": "Acme onboarding",
  "slot_keys": [
    "string"
  ],
  "tags": [
    "string"
  ]
}

Delete a content block

DELETE /t/{slug}/api/blocks/{id}

Deletes a block, addressed by UUID or slug. Refused with 409 IN_USE while it is referenced by an active draft (the count is in error.details). Pass ?force=true to delete anyway. Returns 204 on success.

id string Required
force string
204

The block was deleted.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X DELETE "https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"

Get a content block's usage

GET /t/{slug}/api/blocks/{id}/usage

How many active drafts reference this block — the count a delete is gated on. Unlike a template, a block's gate is active drafts only, not broadcasts.

id string Required
active_draftsinteger

Draft broadcasts still referencing this block. This is the count the delete gate checks.

templates_with_fixed_referenceinteger

Templates that reference this block by a fixed (non-slot) reference.

200

Reference counts.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80/usage" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "active_drafts": 0,
  "templates_with_fixed_reference": 0
}