Skip to content

Inspect API Reference

All Inspect endpoints are under /v1/inspect and require data-plane authentication via X-WORKSPACE-ID + X-API-KEY headers.

Scopes:

  • inspect:read -- Read job status, findings, credits, and config
  • inspect:write -- Trigger runs, snooze/mark findings, create overrides, update config

Roles with Inspect access: Owner, Admin, Developer, Marketing.


POST /v1/inspect

Trigger a new inspect run against a template or raw HTML.

Scope: inspect:write

Request Body

FieldTypeRequiredDefaultDescription
channelstringNoemailInspect channel: email, push, in_app, sms, or whatsapp
template_idUUIDNo--Email template ID to inspect (channel=email)
template_slugstringNo--Email template slug (alternative to template_id)
body_htmlstringNo--Raw HTML to inspect (channel=email only; alternative to template lookup)
subjectstringNo--The subject the email will send with (channel=email). When supplied, suppresses the SJ001 (subject missing) finding. Ignored on other channels.
preheaderstringNo--The preheader/preview text the email will send with (channel=email). When supplied, suppresses the SJ007 (preheader missing) finding. Ignored on other channels.
push_template_idUUIDNo--Push template ID to inspect (channel=push)
push_template_slugstringNo--Push template slug (alternative to push_template_id)
in_app_message_idUUIDNo--In-app message ID to inspect (channel=in_app)
in_app_message_slugstringNo--In-app message slug (alternative to in_app_message_id)
sms_template_idUUIDNo--SMS template ID to inspect (channel=sms)
sms_template_slugstringNo--SMS template slug (alternative to sms_template_id)
whatsapp_template_idUUIDNo--WhatsApp template ID to inspect (channel=whatsapp)
whatsapp_template_slugstringNo--WhatsApp template slug (alternative to whatsapp_template_id)
quick_checkbooleanNofalseRun only Stages 1--3 for email (free, fast). For SMS / WhatsApp, quick_check makes the run free but does not change which checks run — those channels are single-stage.

Supply exactly the fields for the channel you name. Each channel requires its own identifier:

channelProvide one of
email (default)template_id, template_slug, or body_html
pushpush_template_id or push_template_slug
in_appin_app_message_id or in_app_message_slug
smssms_template_id or sms_template_slug
whatsappwhatsapp_template_id or whatsapp_template_slug

Mixing fields across channels is rejected with 422 Unprocessable Entity — for example, sending body_html with channel=sms, or sms_template_id with channel=email.

Note

Inspecting content-only templates. Synapse email templates are content-only — a template stores the body content, not a <title> or a hidden preheader element. A raw fragment (for example <p>Hello</p>) therefore has no subject or preheader for Inspect to read, and the subject/preheader checks would otherwise always fire. Pass the subject and preheader the email will actually send with so Inspect evaluates the real, complete email:

  • Supplying subject suppresses SJ001 (subject missing).
  • Supplying preheader suppresses SJ007 (preheader missing).

Omitting them preserves the prior behavior: Inspect reads any <title> / hidden preheader element in the HTML, and SJ001 / SJ007 fire when neither the request nor the body provides one. When both a request value and an in-body value are present, the request value takes precedence.

bash
# Inspect a content-only fragment with the subject and preheader it will send with
curl -X POST https://synapse-api.pyrx.tech/v1/inspect \
-H "X-WORKSPACE-ID: <workspace-uuid>" \
-H "X-API-KEY: psk_live_<hex32>" \
-H "Content-Type: application/json" \
-d '{
"channel": "email",
"body_html": "<p>Hello, here is your update.</p>",
"subject": "Your weekly update",
"preheader": "See what changed this week",
"quick_check": true
}'

Response (200)

The run completes synchronously — the response returns a completed job (there is no queued / polling step). The channel field echoes the channel that ran, and exactly one of the per-channel *_template_id / *_message_id fields is populated:

json
{
"id": "uuid",
"channel": "email",
"template_id": "uuid | null",
"push_template_id": null,
"in_app_message_id": null,
"sms_template_id": null,
"whatsapp_template_id": null,
"body_hash": "sha256-hex-string",
"status": "completed",
"stages_completed": 5,
"total_findings": 12,
"critical_count": 2,
"warning_count": 7,
"info_count": 3,
"started_at": "2026-05-06T10:00:00Z",
"completed_at": "2026-05-06T10:00:03Z",
"created_at": "2026-05-06T10:00:00Z"
}

For SMS and WhatsApp, stages_completed is 1 — those channels run a single analysis stage.


SMS and WhatsApp channels

Both SMS and WhatsApp run a single analysis stage against the stored template, rendered against a sample contact. The request, response, and findings all use the same endpoints as email — only the channel field and the template identifier differ.

Note

For the concepts behind each channel's checks, see SMS Quality Checks and WhatsApp Pre-Submission Inspect.

Inspect an SMS template

bash
curl -X POST https://synapse-api.pyrx.tech/v1/inspect \
-H "X-WORKSPACE-ID: <workspace-uuid>" \
-H "X-API-KEY: psk_live_<hex32>" \
-H "Content-Type: application/json" \
-d '{
"channel": "sms",
"sms_template_slug": "order-shipped-sms"
}'

Inspect a WhatsApp template

bash
curl -X POST https://synapse-api.pyrx.tech/v1/inspect \
-H "X-WORKSPACE-ID: <workspace-uuid>" \
-H "X-API-KEY: psk_live_<hex32>" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"whatsapp_template_slug": "order-shipped"
}'

A WhatsApp template is inspected regardless of its Meta approval status — a pending or rejected template is fully inspectable, since the point is to catch format issues before or after a Meta rejection.

Fetching SMS / WhatsApp findings

Fetch findings with the same GET /v1/inspect/{job_id}/findings endpoint. The finding shape is identical; category is sms or whatsapp, and channel-specific detail is carried in context:

json
{
"id": "uuid",
"job_id": "uuid",
"code": "SMS_MULTI_SEGMENT",
"severity": "warning",
"category": "sms",
"title": "SMS body spans multiple segments",
"description": "The rendered body exceeds a single SMS segment...",
"fix_text": "Shorten the body to fit one segment...",
"ast_node_id": null,
"client_specific": null,
"context": {
"segments_count": 2,
"encoding": "GSM-7",
"char_count": 214,
"estimated_cost": "0.0158",
"price_unit": "USD"
},
"status": "open",
"snoozed_until": null,
"created_at": "2026-05-06T10:00:01Z"
}

ast_node_id and client_specific are always null for SMS and WhatsApp — these channels have no template AST and no per-email-client rendering matrix.

Credits

SMS and WhatsApp runs consume 1 credit each, like email and push, unless quick_check: true is set (which makes the run free). Because both channels are single-stage, quick_check does not change which checks run — it only waives the credit.

Warning

WhatsApp Inspect does not gate sending. It is a pre-submission format check only — it does not submit your template to Meta and it is not wired into any send-time block. See WhatsApp Pre-Submission Inspect for the full boundary.


GET /v1/inspect/{job_id}

Get inspect job status and summary.

Scope: inspect:read

Response (200)

Same shape as the POST /v1/inspect response.


GET /v1/inspect/{job_id}/findings

List findings for an inspect job with optional filters.

Scope: inspect:read

Query Parameters

ParameterTypeDefaultDescription
severitystring--Filter by severity (critical, warning, info)
categorystring--Filter by category (html, css, images, links, spam, accessibility, rendering, dark_mode, subject, tracking)
statusstring--Filter by status (open, snoozed, fixed, false_positive)
pageinteger1Page number
per_pageinteger50Items per page (max 200)

Response (200)

json
{
"data": [
{
"id": "uuid",
"job_id": "uuid",
"code": "HTML_MISSING_DOCTYPE",
"severity": "critical",
"category": "html",
"title": "Missing DOCTYPE declaration",
"description": "Email has no <!DOCTYPE> declaration...",
"fix_text": "Add <!DOCTYPE html> at the very beginning...",
"ast_node_id": "block-abc123 | null",
"client_specific": "outlook_2019 | null",
"context": {},
"status": "open",
"snoozed_until": null,
"created_at": "2026-05-06T10:00:01Z"
}
],
"total": 12,
"page": 1,
"per_page": 50
}

POST /v1/inspect/findings/{finding_id}/snooze

Snooze a finding for a specified number of days.

Scope: inspect:write

Request Body

FieldTypeRequiredDefaultDescription
duration_daysintegerNo30Snooze duration (1--365 days)

Response (200)

Returns the updated finding with status: "snoozed" and snoozed_until set.


POST /v1/inspect/findings/{finding_id}/mark-fixed

Mark a finding as fixed.

Scope: inspect:write

Response (200)

Returns the updated finding with status: "fixed".


POST /v1/inspect/findings/{finding_id}/mark-false-positive

Mark a finding as a false positive.

Scope: inspect:write

Response (200)

Returns the updated finding with status: "false_positive".


GET /v1/inspect/credits

Get the current inspect credit balance and recent ledger entries.

Scope: inspect:read

Response (200)

json
{
"balance": 18,
"monthly_grant": 25,
"plan": "starter",
"recent_entries": [
{
"id": "uuid",
"job_id": "uuid | null",
"delta": -1,
"balance_after": 18,
"reason": "inspect_run",
"created_at": "2026-05-06T10:00:00Z"
}
]
}

POST /v1/inspect/override

Create a single-use override token for sending despite critical findings.

Scope: inspect:write

The token is valid for 5 minutes and bound to the specific inspect job. An audit log entry is created for the override.

Request Body

FieldTypeRequiredDescription
job_idUUIDYesInspect job ID to override
reasonstringYesFree-text reason (10--500 characters)

Response (200)

json
{
"token": "override-token-string",
"job_id": "uuid",
"expires_in_seconds": 300
}

POST /v1/inspect/pre-send-check

Check if a template can be sent based on inspect results.

Scope: inspect:read

Request Body

FieldTypeRequiredDefaultDescription
channelstringNoemailChannel to check: email, push, or sms. template_id is interpreted as that channel's template.
template_idUUIDYes--Template ID to check (email, push, or SMS template per channel)
override_tokenstringNo--Override token if sending despite critical findings
Note

The pre-send check accepts email, push, and sms only. WhatsApp is not a valid channel here — WhatsApp Inspect is a pre-submission format check and is not wired into any send-time gate. Passing channel=whatsapp returns 422.

Response (200)

json
{
"decision": "allow | allow_with_override | block_no_inspect | block_critical",
"job_id": "uuid | null",
"critical_finding_ids": ["uuid", "..."] ,
"reason": "Inspect passed with no critical findings",
"is_blocked": false
}

Decision Values

DecisionMeaning
allowTemplate passed inspection or blocking is disabled
allow_with_overrideCritical findings exist but a valid override token was provided
block_no_inspectNo completed inspect run found within the last 7 days for this template version
block_criticalOpen critical findings exist and no valid override was provided

GET /v1/inspect/config

Get workspace inspect configuration.

Scope: inspect:read

Response (200)

json
{
"workspace_id": "uuid",
"default_clients": ["gmail_web", "outlook_2019"],
"auto_run_on_save": false,
"block_send_on_critical": true,
"spam_provider": "internal | spamassassin | mail_tester",
"skip_domains": ["example.com"],
"snoozed_codes": ["HTML_MISSING_TITLE"],
"push_inspect_enabled": false,
"sms_inspect_enabled": false,
"whatsapp_inspect_enabled": false
}

PUT /v1/inspect/config

Update workspace inspect configuration. Only provided fields are updated.

Scope: inspect:write

Request Body

FieldTypeDescription
default_clientsstring[]Email clients to render in Stage 4
auto_run_on_savebooleanTrigger inspect automatically on template save
block_send_on_criticalbooleanBlock email sends when critical findings exist
spam_providerstringSpam scoring provider (internal, spamassassin, mail_tester)
skip_domainsstring[]Domains to skip in link checking
snoozed_codesstring[]Finding codes to auto-snooze
push_inspect_enabledbooleanOpt in to push pre-send blocking. Default false.
sms_inspect_enabledbooleanReserved SMS Inspect toggle. Default false. See the note below.
whatsapp_inspect_enabledbooleanReserved WhatsApp Inspect toggle. Default false. See the note below.
Note

sms_inspect_enabled and whatsapp_inspect_enabled are stored settings that do not yet gate any behavior. You can inspect an SMS or WhatsApp template with POST /v1/inspect regardless of these flags, and neither flag blocks a send today. They are surfaced now for consistency with push_inspect_enabled and for future dashboard gating. WhatsApp Inspect in particular is a standalone pre-submission analysis and is intentionally not part of any send-time gate.

Response (200)

Returns the full updated config.


Error Responses

All endpoints return standard error responses:

StatusCodeDescription
400bad_requestMissing required field or invalid input
402inspect_credits_exhaustedInspect credits exhausted for the current plan. The response body carries { "detail": ..., "code": "inspect_credits_exhausted" }.
404--Job, finding, or template not found. On a channel run, a missing template returns 404 with a plain message (e.g. SMS template not found, WhatsApp template not found).
422--Request validation failed — for example, a required channel identifier is missing, channel fields are mixed, or an unsupported channel value was passed to pre-send-check.