Send Campaigns
22 endpoints from the published OpenAPI import.
List this workspace's campaigns
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | false | |
offset | query | integer | false | |
status | query | any | false | Filter to one lifecycle state. |
Try it
Examples
cURL
curl -X GET 'https://spideriq.ai/api/v1/dashboard/send/campaigns' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.get(
"https://spideriq.ai/api/v1/dashboard/send/campaigns",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns", {
method: "GET",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://spideriq.ai/api/v1/dashboard/send/campaigns", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Create a campaign DRAFT
A draft sends nothing, and nothing about creating one arms it.
A campaign only becomes eligible to mail anyone at POST /activate, which a PAT cannot call.
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/dashboard/send/campaigns' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"name": "string",
"folder": "string",
"brand_id": 0,
"timezone": "UTC",
"active_days": [
0
],
"starts_on": "2026-01-01",
"new_leads_per_day": 0,
"stop_condition": "reply",
"variant_strategy": "round_robin",
"company_pause": false,
"track_opens": false,
"track_clicks": true,
"plain_text": true,
"include_unsubscribe": true,
"include_postal_address": true
}'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/dashboard/send/campaigns",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"name": "string", "folder": "string", "brand_id": 0, "timezone": "UTC", "active_days": [0], "starts_on": "2026-01-01", "new_leads_per_day": 0, "stop_condition": "reply", "variant_strategy": "round_robin", "company_pause": false, "track_opens": false, "track_clicks": true, "plain_text": true, "include_unsubscribe": true, "include_postal_address": true},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns", {
method: "POST",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"name": "string", "folder": "string", "brand_id": 0, "timezone": "UTC", "active_days": [0], "starts_on": "2026-01-01", "new_leads_per_day": 0, "stop_condition": "reply", "variant_strategy": "round_robin", "company_pause": false, "track_opens": false, "track_clicks": true, "plain_text": true, "include_unsubscribe": true, "include_postal_address": true})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"name": "string", "folder": "string", "brand_id": 0, "timezone": "UTC", "active_days": [0], "starts_on": "2026-01-01", "new_leads_per_day": 0, "stop_condition": "reply", "variant_strategy": "round_robin", "company_pause": false, "track_opens": false, "track_clicks": true, "plain_text": true, "include_unsubscribe": true, "include_postal_address": true}`)
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/dashboard/send/campaigns", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
201 | Successful Response |
422 | Validation Error |
One campaign in full
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true |
Try it
Examples
cURL
curl -X GET 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.get(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}", {
method: "GET",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Patch a campaign's settings
True PATCH of the SHELL — schedule, stop condition, compliance toggles.
Allowed while active (scheduling changes do not re-aim a cursor); the sequence itself is not. status is deliberately not a patchable field: a lifecycle change is an act with preconditions, and admitting it here would let a PATCH route around the cookie-only arming gate.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true |
Try it
Examples
cURL
curl -X PATCH 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"name": "string",
"folder": "string",
"timezone": "string",
"active_days": [
0
],
"starts_on": "2026-01-01",
"new_leads_per_day": 0,
"stop_condition": "reply",
"variant_strategy": "round_robin",
"company_pause": true,
"track_opens": true,
"track_clicks": true,
"plain_text": true,
"include_unsubscribe": true,
"include_postal_address": true
}'Python
import httpx
resp = httpx.patch(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"name": "string", "folder": "string", "timezone": "string", "active_days": [0], "starts_on": "2026-01-01", "new_leads_per_day": 0, "stop_condition": "reply", "variant_strategy": "round_robin", "company_pause": true, "track_opens": true, "track_clicks": true, "plain_text": true, "include_unsubscribe": true, "include_postal_address": true},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}", {
method: "PATCH",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"name": "string", "folder": "string", "timezone": "string", "active_days": [0], "starts_on": "2026-01-01", "new_leads_per_day": 0, "stop_condition": "reply", "variant_strategy": "round_robin", "company_pause": true, "track_opens": true, "track_clicks": true, "plain_text": true, "include_unsubscribe": true, "include_postal_address": true})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"name": "string", "folder": "string", "timezone": "string", "active_days": [0], "starts_on": "2026-01-01", "new_leads_per_day": 0, "stop_condition": "reply", "variant_strategy": "round_robin", "company_pause": true, "track_opens": true, "track_clicks": true, "plain_text": true, "include_unsubscribe": true, "include_postal_address": true}`)
req, _ := http.NewRequest("PATCH", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Delete a DRAFT campaign
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true |
Try it
Examples
cURL
curl -X DELETE 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.delete(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}", {
method: "DELETE",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
204 | Successful Response |
422 | Validation Error |
Everything the analytics screen renders, in one read
The read half of the campaign surface (card 4.B1).
ONE endpoint rather than six because every number on the page is a slice of the same instant: a strip reading "5 sources sending" beside a chart built from a different second is a screen that contradicts itself while both halves are correct.
🔴 Two figures come back deliberately ABSENT rather than estimated, and a future change must not "fill them in":
live.capacityis INDICATIVE (indicativeis hard-True).send_claim_source()keeps onesent_todaycounter per mailbox and claims first-come-first-served — there is no per-campaign share to report. Planner ruling, BSV-131 2026-08-18: label it, do not grow a bound in the governor's hot path.forecast.predicted/shortfall/attributionareNone/ empty. Reconstructing a past day's intended volume needs that day's effective cap and nothing records one;send_source_capacityis today-only and is overwritten at each day roll.
Declared BEFORE PATCH /{campaign_id} in this file purely for reading order — the path is unambiguous either way, since analytics is a literal segment under a UUID param and cannot be read as a campaign id.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true |
Try it
Examples
cURL
curl -X GET 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/analytics' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.get(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/analytics",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/analytics", {
method: "GET",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/analytics", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Add a step to the sequence
wait_days is the gap BEFORE this step, so step 0 is normally 0.
Returns the whole campaign, not just the step: the cumulative day offsets of every LATER step change when one is inserted, and returning only the new row would leave a caller holding a schedule that is quietly wrong.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"step_index": 0,
"kind": "email",
"wait_days": 0,
"variant": {
"label": "A",
"subject_template": "",
"body_md": "",
"weight": 100
}
}'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"step_index": 0, "kind": "email", "wait_days": 0, "variant": {"label": "A", "subject_template": "", "body_md": "", "weight": 100}},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps", {
method: "POST",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"step_index": 0, "kind": "email", "wait_days": 0, "variant": {"label": "A", "subject_template": "", "body_md": "", "weight": 100}})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"step_index": 0, "kind": "email", "wait_days": 0, "variant": {"label": "A", "subject_template": "", "body_md": "", "weight": 100}}`)
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
201 | Successful Response |
422 | Validation Error |
Patch a step's wait or channel
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true | |
step_index | path | integer | true |
Try it
Examples
cURL
curl -X PATCH 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"kind": "email",
"wait_days": 0
}'Python
import httpx
resp = httpx.patch(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"kind": "email", "wait_days": 0},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}", {
method: "PATCH",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"kind": "email", "wait_days": 0})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"kind": "email", "wait_days": 0}`)
req, _ := http.NewRequest("PATCH", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Delete a step and its variants
Later steps keep their indices — the gap is deliberate, see the service.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true | |
step_index | path | integer | true |
Try it
Examples
cURL
curl -X DELETE 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.delete(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}", {
method: "DELETE",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Add an A/B arm to a step
🔴 subject_template="" MEANS CONTINUE THE THREAD — it is not blank.
An empty subject omits the Subject header and replies into the lead's existing thread (design §2.4). Do not default one in on a follow-up step.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true | |
step_index | path | integer | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"label": "A",
"subject_template": "",
"body_md": "",
"weight": 100
}'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"label": "A", "subject_template": "", "body_md": "", "weight": 100},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants", {
method: "POST",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"label": "A", "subject_template": "", "body_md": "", "weight": 100})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"label": "A", "subject_template": "", "body_md": "", "weight": 100}`)
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
201 | Successful Response |
422 | Validation Error |
Patch one arm's copy
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true | |
step_index | path | integer | true | |
label | path | string | true |
Try it
Examples
cURL
curl -X PATCH 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants/{label}' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"subject_template": "string",
"body_md": "string",
"weight": 0
}'Python
import httpx
resp = httpx.patch(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants/{label}",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"subject_template": "string", "body_md": "string", "weight": 0},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants/{label}", {
method: "PATCH",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"subject_template": "string", "body_md": "string", "weight": 0})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"subject_template": "string", "body_md": "string", "weight": 0}`)
req, _ := http.NewRequest("PATCH", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants/{label}", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Delete an arm (never the last one)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true | |
step_index | path | integer | true | |
label | path | string | true |
Try it
Examples
cURL
curl -X DELETE 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants/{label}' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.delete(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants/{label}",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants/{label}", {
method: "DELETE",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants/{label}", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Attach a sending mailbox to this campaign
Attaching does not send. It says which mailboxes MAY carry this campaign.
Allowed while active on purpose — adding capacity to a running sequence is the safe direction, and forcing a pause to add a mailbox is how an operator ends up over-sending on the one source they already had.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/sources' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"mailbox_id": 0
}'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/sources",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"mailbox_id": 0},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/sources", {
method: "POST",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"mailbox_id": 0})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"mailbox_id": 0}`)
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/sources", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
201 | Successful Response |
422 | Validation Error |
Detach a sending mailbox
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true | |
mailbox_id | path | integer | true |
Try it
Examples
cURL
curl -X DELETE 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/sources/{mailbox_id}' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.delete(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/sources/{mailbox_id}",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/sources/{mailbox_id}", {
method: "DELETE",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/sources/{mailbox_id}", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Author a branch (SubSequence) on this campaign
A branch watches the sequence and re-aims or ends ONE lead's run of it.
🔴 match is a validated AST, never a predicate string — the grammar is services.send.branch_ast and a malformed one is refused at 422 rather than stored. {} is refused too: it is the column default and means matches nothing, so a branch authored that way would be permanently inert with nothing to see. Write {"op": "always"} to fire on every occurrence.
Allowed while the campaign is ACTIVE. A branch re-aims no existing cursor — it states what happens next — so unlike a step edit it does not need the campaign paused.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"trigger": "classification",
"match": {},
"action": "stop",
"action_arg": {},
"enabled": true
}'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"trigger": "classification", "match": {}, "action": "stop", "action_arg": {}, "enabled": true},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches", {
method: "POST",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"trigger": "classification", "match": {}, "action": "stop", "action_arg": {}, "enabled": true})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"trigger": "classification", "match": {}, "action": "stop", "action_arg": {}, "enabled": true}`)
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
201 | Successful Response |
422 | Validation Error |
Patch a branch, or disable it
trigger+match move together, and so do action+action_arg.
A match names fields belonging to ONE trigger's grammar, so patching either half alone would store a match its trigger cannot read — accepted, stored, and dead. The schema refuses the half-pair; repeat the unchanged one.
enabled=false keeps the rule and its reasoning visible while stopping it firing, which is the state the design surfaces on purpose. It is not the same as deleting.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true | |
branch_id | path | integer | true |
Try it
Examples
cURL
curl -X PATCH 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches/{branch_id}' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"trigger": "classification",
"match": {},
"action": "stop",
"action_arg": {},
"enabled": true
}'Python
import httpx
resp = httpx.patch(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches/{branch_id}",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"trigger": "classification", "match": {}, "action": "stop", "action_arg": {}, "enabled": true},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches/{branch_id}", {
method: "PATCH",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"trigger": "classification", "match": {}, "action": "stop", "action_arg": {}, "enabled": true})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"trigger": "classification", "match": {}, "action": "stop", "action_arg": {}, "enabled": true}`)
req, _ := http.NewRequest("PATCH", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches/{branch_id}", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Delete a branch
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true | |
branch_id | path | integer | true |
Try it
Examples
cURL
curl -X DELETE 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches/{branch_id}' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.delete(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches/{branch_id}",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches/{branch_id}", {
method: "DELETE",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/branches/{branch_id}", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Who is enrolled, where each one is, and why they are or are not moving
member, like every other read on this router — it reports, it never acts.
metrics_available: false on the response means the suppression and outcome reads did NOT run. Treat a lead's blocked state as unknown in that case rather than as "not suppressed": the two are indistinguishable in the payload and only one of them is safe to act on.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true | |
limit | query | integer | false | |
offset | query | integer | false | |
filter | query | string | false | A VIEW, not a stored state: 'overdue' is the subset of in-sequence leads whose next step is past due, and 'replied' is the subset that left the sequence on an inbound. |
q | query | any | false | Substring match on the address. The send cluster stores no name. |
Try it
Examples
cURL
curl -X GET 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/leads' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.get(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/leads",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/leads", {
method: "GET",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/leads", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Render one step as a recipient will receive it
Server-side, through the SAME renderer the send path uses.
Two things an author can see nowhere else: the D-J compliance footer with this workspace's real postal address, and unresolved_merge_tags — the tags that will render EMPTY on a live send. That second one exists because the failure is silent: the placeholder is emitted, the provider accepts the message, and the only symptom is a greeting addressed to nobody.
continues_thread: true with subject: null is the deliberate state of a follow-up step, not a missing subject.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true | |
step_index | path | integer | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/preview' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"variant_label": "A",
"sample": {
"email": "string",
"first_name": "string",
"full_name": "string",
"current_company": "string"
}
}'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/preview",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"variant_label": "A", "sample": {"email": "string", "first_name": "string", "full_name": "string", "current_company": "string"}},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/preview", {
method: "POST",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"variant_label": "A", "sample": {"email": "string", "first_name": "string", "full_name": "string", "current_company": "string"}})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"variant_label": "A", "sample": {"email": "string", "first_name": "string", "full_name": "string", "current_company": "string"}}`)
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/preview", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
ARM this campaign — cookie-only, a human act
🔴 THIS IS THE ONE ROUTE IN THE FILE THAT A PAT CANNOT CALL.
Arming turns authored copy into mail sent to real people, unattended, for as long as leads keep enrolling. That is a decision a human takes in a session, not something a token does on their behalf — the same argument the paste-consent writes make, and the reason no send_activate_campaign MCP tool exists. An agent that has finished composing reports what is left to a human; the four preconditions below are refused by name so it can say exactly what.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/activate' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/activate",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/activate", {
method: "POST",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/activate", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Pause enrolment and advancement (reversible)
Deliberately PAT-reachable, unlike activate.
An agent may always stop mail and may never start it. A safety verb an agent cannot reach is a safety verb nobody uses at 3am.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/pause' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/pause",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/pause", {
method: "POST",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/pause", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Stop this campaign permanently
Terminal, and PAT-reachable for the same reason pause is.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/stop' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/stop",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/stop", {
method: "POST",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/dashboard/send/campaigns/{campaign_id}/stop", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |