API Documentation
API Documentation
This guide shows the safe way to review generated campaign content for one prospect, edit individual messages, and then approve or discard the complete workflow run.
Approval is atomic per prospect workflow run. You may edit selected content items, but you cannot approve one message and leave the other generated messages unapproved. Approving continues the complete run; discarding stops it.
One item returned by the approval API represents one prospect in one campaign workflow run. It is not a campaign-wide batch and it is not one standalone message.
Campaign
└── Prospect workflow run
├── Initial DM editable content
├── Wait 1 day workflow timing
└── Follow-up DM 1 editable contentThe workflow run stays in waiting-approval while you review or edit it. A PATCH saves message changes without continuing the workflow. A later approval continues that same run; it does not create a second workflow run or a duplicate initial message.
| Step | Endpoint | Purpose |
|---|---|---|
| 1 | GET /campaigns/content-approvals | Find prospect runs waiting for review |
| 2 | GET /campaigns/{campaignId}/workflow-runs/{workflowInstanceId}/approval | Re-read one run before changing it |
| 3 | PATCH /campaigns/{campaignId}/workflow-runs/{workflowInstanceId}/messages | Save only the messages you changed |
| 4a | POST /campaigns/{campaignId}/workflow-runs/{workflowInstanceId}/approve |
Use API keys with campaigns:read for steps 1–2 and campaigns:write for steps 3–4.
curl "https://api.sonarly.io/v1/accounts/$ACCOUNT_GROUP_ID/campaigns/content-approvals?page=1&limit=25" \
-H "Authorization: Bearer $SONARLY_API_KEY" \
-H "Accept: application/json"Use campaignId when you only want approvals from one campaign:
curl "https://api.sonarly.io/v1/accounts/$ACCOUNT_GROUP_ID/campaigns/content-approvals?campaignId=$CAMPAIGN_ID&page=1&limit=100" \
-H "Authorization: Bearer $SONARLY_API_KEY" \
-H "Accept: application/json"This endpoint uses one-based page pagination. limit accepts 1–100. Read currentPage, totalPages, and totalItems from the response; do not assume that the first 25 items are the complete result.
Unknown query parameters are rejected with 422 VALIDATION_ERROR. This includes misspellings such as limti=100.
Each approval contains two related views:
workflowReview.nodes and workflowReview.edges describe the read-only workflow order and branches.pendingContent item has a nodeId and reviewContext that identify its position in that graph.{
"workflowInstanceId": "507f1f77bcf86cd799439071",
"campaignId": "aabbccddee1122334455ca11",
"leadId": "507f1f77bcf86cd799439072",
"firstName": "Avery",
"lastName": "Stone",
"pendingContent": [
{
"nodeId": "aabbccddee1122334455ca21",
"contentType": "direct_message",
"message":
Match content by nodeId, not by array position or label. Labels are for people; node IDs are the stable request keys for that approval payload.
Send only the content items you want to change. This request saves the edits and leaves the run waiting for approval.
curl -X PATCH \
"https://api.sonarly.io/v1/accounts/$ACCOUNT_GROUP_ID/campaigns/$CAMPAIGN_ID/workflow-runs/$WORKFLOW_INSTANCE_ID/messages" \
-H "Authorization: Bearer $SONARLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"contentItems": [
{
"nodeId": "aabbccddee1122334455ca23",
"message": "Would it be useful to compare how we keep approval workflows fast?"
}
]
}'The editable field depends on the content type:
| Content type | Text field | Additional field |
|---|---|---|
| Direct message | message | — |
| InMail | message | optional subject |
message | optional subject | |
| Connection request | requestMessage |
For multiline text, send normal JSON strings with \n line breaks.
Use variables and Spintax in writing-style samples, where Sonarly can render them for each prospect before approval:
{Hi|Hello|Hey} {{ First_name }}, I noticed your work at {{ Company_name }}.Supported profile variables are:
| Variable | Value |
|---|---|
{{ First_name }} | Prospect first name |
{{ Last_name }} | Prospect last name |
{{ Title }} | Prospect job title |
{{ Company_name }} | Prospect company name |
{{ Location }} | Prospect location |
{{ Industry }} |
Signal-aware samples also support {{ Signal }} and {{ Signal_anonym }}.
Spintax uses one pair of braces and pipe-separated alternatives, for example {Hi|Hello}. Template variables use double braces. Sonarly resolves both before the content reaches the approval lane.
Approval payloads must contain final rendered text. Do not send unresolved
variables such as {{ First_name }} in a workflow-run message update; the
approval service rejects unresolved placeholders instead of risking a bad
send. Always review the rendered pendingContent returned by the API.
If you want deterministic wording, use a strict writing-style template and keep the alternatives and variables in that template. The approval endpoint is for reviewing or correcting the final prospect-specific result, not for storing a reusable template.
If no message needs editing, skip the PATCH and approve the current saved content directly:
curl -X POST \
"https://api.sonarly.io/v1/accounts/$ACCOUNT_GROUP_ID/campaigns/$CAMPAIGN_ID/workflow-runs/$WORKFLOW_INSTANCE_ID/approve" \
-H "Authorization: Bearer $SONARLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{}'Approval applies to the complete pending content batch for this one prospect workflow run. It continues the existing run using its currently saved messages. Wait steps and branches still execute in their defined order; approval does not send every follow-up immediately.
To reject the complete run:
curl -X POST \
"https://api.sonarly.io/v1/accounts/$ACCOUNT_GROUP_ID/campaigns/$CAMPAIGN_ID/workflow-runs/$WORKFLOW_INSTANCE_ID/discard" \
-H "Authorization: Bearer $SONARLY_API_KEY" \
-H "Accept: application/json"Discard is terminal for that workflow run. It does not skip one content item and continue with the others.
workflowInstanceId as the approval unit./workflow-instances/.../approve requests with inline contentItems must include every pending item exactly once. Partial legacy batches are rejected without continuing the run.| Status | Meaning | What to do |
|---|---|---|
403 SCOPE_DENIED | The key lacks campaigns:read or campaigns:write | Update the API-key permissions |
404 NOT_FOUND | The campaign or workflow run is not available to this account group | Re-read the pending approval list |
409 INVALID_STATE | The run is no longer waiting for approval | Stop retrying and read its current state |
422 VALIDATION_ERROR |
For exact schemas, open the Campaigns endpoint reference.
| Approve the complete run and continue it |
| 4b | POST /campaigns/{campaignId}/workflow-runs/{workflowInstanceId}/discard | Stop the complete run instead |
message remains the generated content value |
| Comment | message | inspect postLink and postSummary before approval |
| Prospect company industry |
| Invalid query, missing fields, unknown fields, partial legacy batch, or unresolved variables |
| Correct the request; nothing was approved |
429 RATE_LIMIT_EXCEEDED | The API-key request limit was reached | Back off and retry after the reset window |