SSpiderIQ
SSpiderIQ

Docs / api-reference/send-campaigns

Send Campaigns

22 endpoints from the published OpenAPI import.

GET/api/v1/dashboard/send/campaigns

List this workspace's campaigns

Parameters

NameInTypeRequiredDescription
limitqueryintegerfalse
offsetqueryintegerfalse
statusqueryanyfalseFilter to one lifecycle state.
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
POST/api/v1/dashboard/send/campaigns

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

StatusDescription
201Successful Response
422Validation Error
GET/api/v1/dashboard/send/campaigns/{campaign_id}

One campaign in full

Parameters

NameInTypeRequiredDescription
campaign_idpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
PATCH/api/v1/dashboard/send/campaigns/{campaign_id}

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

NameInTypeRequiredDescription
campaign_idpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
DELETE/api/v1/dashboard/send/campaigns/{campaign_id}

Delete a DRAFT campaign

Parameters

NameInTypeRequiredDescription
campaign_idpathstringtrue
Try it
Query

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

StatusDescription
204Successful Response
422Validation Error
GET/api/v1/dashboard/send/campaigns/{campaign_id}/analytics

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.capacity is INDICATIVE (indicative is hard-True). send_claim_source() keeps one sent_today counter 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 / attribution are None / empty. Reconstructing a past day's intended volume needs that day's effective cap and nothing records one; send_source_capacity is 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

NameInTypeRequiredDescription
campaign_idpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
POST/api/v1/dashboard/send/campaigns/{campaign_id}/steps

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

NameInTypeRequiredDescription
campaign_idpathstringtrue
Try it
Query

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

StatusDescription
201Successful Response
422Validation Error
PATCH/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}

Patch a step's wait or channel

Parameters

NameInTypeRequiredDescription
campaign_idpathstringtrue
step_indexpathintegertrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
DELETE/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}

Delete a step and its variants

Later steps keep their indices — the gap is deliberate, see the service.

Parameters

NameInTypeRequiredDescription
campaign_idpathstringtrue
step_indexpathintegertrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
POST/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants

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

NameInTypeRequiredDescription
campaign_idpathstringtrue
step_indexpathintegertrue
Try it
Query

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

StatusDescription
201Successful Response
422Validation Error
PATCH/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants/{label}

Patch one arm's copy

Parameters

NameInTypeRequiredDescription
campaign_idpathstringtrue
step_indexpathintegertrue
labelpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
DELETE/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/variants/{label}

Delete an arm (never the last one)

Parameters

NameInTypeRequiredDescription
campaign_idpathstringtrue
step_indexpathintegertrue
labelpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
POST/api/v1/dashboard/send/campaigns/{campaign_id}/sources

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

NameInTypeRequiredDescription
campaign_idpathstringtrue
Try it
Query

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

StatusDescription
201Successful Response
422Validation Error
DELETE/api/v1/dashboard/send/campaigns/{campaign_id}/sources/{mailbox_id}

Detach a sending mailbox

Parameters

NameInTypeRequiredDescription
campaign_idpathstringtrue
mailbox_idpathintegertrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
POST/api/v1/dashboard/send/campaigns/{campaign_id}/branches

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

NameInTypeRequiredDescription
campaign_idpathstringtrue
Try it
Query

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

StatusDescription
201Successful Response
422Validation Error
PATCH/api/v1/dashboard/send/campaigns/{campaign_id}/branches/{branch_id}

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

NameInTypeRequiredDescription
campaign_idpathstringtrue
branch_idpathintegertrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
DELETE/api/v1/dashboard/send/campaigns/{campaign_id}/branches/{branch_id}

Delete a branch

Parameters

NameInTypeRequiredDescription
campaign_idpathstringtrue
branch_idpathintegertrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
GET/api/v1/dashboard/send/campaigns/{campaign_id}/leads

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

NameInTypeRequiredDescription
campaign_idpathstringtrue
limitqueryintegerfalse
offsetqueryintegerfalse
filterquerystringfalseA 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.
qqueryanyfalseSubstring match on the address. The send cluster stores no name.
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
POST/api/v1/dashboard/send/campaigns/{campaign_id}/steps/{step_index}/preview

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

NameInTypeRequiredDescription
campaign_idpathstringtrue
step_indexpathintegertrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
POST/api/v1/dashboard/send/campaigns/{campaign_id}/activate

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

NameInTypeRequiredDescription
campaign_idpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
POST/api/v1/dashboard/send/campaigns/{campaign_id}/pause

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

NameInTypeRequiredDescription
campaign_idpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
POST/api/v1/dashboard/send/campaigns/{campaign_id}/stop

Stop this campaign permanently

Terminal, and PAT-reachable for the same reason pause is.

Parameters

NameInTypeRequiredDescription
campaign_idpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error