Templates
Parameters, sections, slots and blocks for dynamic content.
The Template object 22 fields
Full row only.
MJML source, present for format: mjml. Full row only.
Plain-text alternative. Full row only.
Contact-data expectations for Liquid iteration/access. Full row only.
Which body the send pipeline renders: content_mjml for mjml, content_html for html.
html, mjmlLean list row only — length of groups_schema.
Parameter groupings for the editor. Full row only; lean row carries group_count.
Canonical template ID.
Lean list row only — length of parameters_schema.
Typed top-level parameters. Full row only; the lean row carries parameter_count instead.
Lean list row only — length of sections_schema.
Repeatable sections. Full row only; lean row carries section_count.
Lean list row only — length of slots_schema.
Named slots blocks can fill. Full row only; lean row carries slot_count.
Stable per-tenant key. Usable in place of the UUID in every template path, and as content.template_slug on a broadcast.
Default subject line. Full row only.
Full row only — absent from the lean list row.
Present only with ?include_usage=true: { active_drafts, recent_broadcasts }.
The Block object 16 fields
Full row only.
Full row only.
Full row only.
Full row only.
Present on both the lean and full row.
Present on both the lean and full row.
Which template slots this block is eligible to fill.
Stable per-tenant key. Usable in a slot binding as block_id: <slug>.
Full row only.
Present only with ?include_usage=true: { active_drafts, recent_broadcasts }.
List 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.
A page of Template objects, under data.
Every template. Lean rows unless ?full=true.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/templates" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/templates', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"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
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.
html, mjmlThe Template object.
The template was created.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
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"
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/templates', {
method: 'POST',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Acme onboarding",
"content_html": "string",
"slug": "acme",
"subject": "Welcome to Acme",
"content_text": "string",
"content_mjml": "string"
}),
});
const data = await res.json();{
"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
The full template, addressed by UUID or per-tenant slug. Returns the complete row — bodies and schema arrays included, never the lean shape.
The Template object.
The template.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"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
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.
html, mjmlThe Template object.
The updated template.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
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"
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80', {
method: 'PUT',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Acme onboarding",
"slug": "acme",
"subject": "Welcome to Acme",
"content_html": "string",
"content_text": "string",
"content_mjml": "string"
}),
});
const data = await res.json();{
"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
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.
The template was deleted.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X DELETE "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80', {
method: 'DELETE',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();Get a template's schemas
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.
The template's identity plus its five typed schema arrays.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/schema" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/schema', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"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
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.
Discovery payload: template_id, slug, name, the parameter/section/group schemas, and the slots with their resolved eligible_blocks.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
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"
const res = await fetch('https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/binding-options', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{}Render a template with a binding
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.
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.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
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
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/render', {
method: 'POST',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"template_binding": {},
"include_options": true,
"lenient": true
}),
});
const data = await res.json();{}Validate a template 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.
Diagnostics: valid (boolean) and issues (an array of { path, code, message, fix }).
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
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": {}
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/validate-binding', {
method: 'POST',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"template_binding": {}
}),
});
const data = await res.json();{
"valid": true,
"issues": [
{}
]
}Import a template from markdown
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.
The template was created.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X POST "https://api.sonarsend.com/t/acme/api/templates/import" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/templates/import', {
method: 'POST',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"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
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.
The Template object.
The template was replaced.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X PUT "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/import" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/import', {
method: 'PUT',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"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
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.
The template as a markdown file.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/export" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/export', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();Get a template's usage
How many active drafts and recent broadcasts reference this template — the counts a delete is gated on. Check it before replacing or deleting.
Draft broadcasts still pointing at this template.
Recently-sent broadcasts that used it.
Reference counts.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/usage" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/templates/9c21895d-57f0-4a15-a1df-4dc6835d4f80/usage', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"active_drafts": 0,
"recent_broadcasts": 0
}List content 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.
A page of Block objects, under data.
Every content block. Lean rows unless ?full=true.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/blocks" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/blocks', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"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
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.
The Block object.
The block was created.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
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"
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/blocks', {
method: 'POST',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Acme onboarding",
"content_html": "string",
"slug": "acme",
"slot_keys": [
"string"
],
"tags": [
"string"
],
"content_text": "string"
}),
});
const data = await res.json();{
"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
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.
The block was created.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X POST "https://api.sonarsend.com/t/acme/api/blocks/import" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/blocks/import', {
method: 'POST',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"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
Returns the block as a text/markdown file — the raw file, not JSON. Same round-trippable format as a template export, with kind: block.
The block as a markdown file.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80/export" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80/export', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();Get a content block
The full content block, addressed by UUID or per-tenant slug. Returns the complete row, bodies included.
The Block object.
The block.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"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
Updates a block in place (JSON). Only the fields you send change. Broadcasts already sent are unaffected.
The Block object.
The updated block.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
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"
}'const res = await fetch('https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80', {
method: 'PUT',
headers: {
'X-API-Key': process.env.SONARSEND_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Acme onboarding",
"slug": "acme",
"slot_keys": [
"string"
],
"tags": [
"string"
],
"content_html": "string",
"content_text": "string"
}),
});
const data = await res.json();{
"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
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.
The block was deleted.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X DELETE "https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80', {
method: 'DELETE',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();Get a content block's 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.
Draft broadcasts still referencing this block. This is the count the delete gate checks.
Templates that reference this block by a fixed (non-slot) reference.
Reference counts.
The request failed validation, or a referenced record was rejected.
The tenant slug, or the record the path names, does not exist.
curl -X GET "https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80/usage" \ -H "X-API-Key: $SONARSEND_API_KEY"
const res = await fetch('https://api.sonarsend.com/t/acme/api/blocks/9c21895d-57f0-4a15-a1df-4dc6835d4f80/usage', {
method: 'GET',
headers: { 'X-API-Key': process.env.SONARSEND_API_KEY },
});
const data = await res.json();{
"active_drafts": 0,
"templates_with_fixed_reference": 0
}