Error Handling
Error format
Section titled “Error format”All tool errors return the standard MCP error format:
{ "content": [{ "type": "text", "text": "Error message here" }], "isError": true}The text field contains a plain, human-readable message (not a JSON-encoded string). Errors are actionable — they tell you what went wrong and suggest which tool to call next.
Common errors
Section titled “Common errors”Insufficient permissions
Section titled “Insufficient permissions”{ "text": "Insufficient permissions: \"campaigns:write\" scope is required to call campaignstack_create_campaign. Use campaignstack_whoami to check your current scopes."}Fix: Create a new API key with the required scope, or add the scope to your existing key.
Not found
Section titled “Not found”{ "text": "Campaign not found (id: abc123). Use campaignstack_list_campaigns to find valid IDs."}Fix: Use the suggested list tool to find valid IDs.
Validation error
Section titled “Validation error”{ "text": "Invalid input: workspaceId is required"}Fix: Check the tool’s input schema and provide all required fields. All tools use .strict() validation — unknown fields are rejected.
Rate limit exceeded
Section titled “Rate limit exceeded”{ "text": "Rate limit exceeded for campaignstack_craft_message: llm tool limit of 10 requests/minute. Retry after 12s. These limits protect the API from abuse and runaway loops — slow down or batch your calls."}Each API key has per-minute limits, bucketed by tool category. Defaults (override with MCP_RATE_LIMIT_*_PER_MIN):
| Category | Limit/min | Examples |
|---|---|---|
read | 120 | list/get tools |
write | 30 | create/update/delete tools |
llm | 10 | AI craft tools (campaignstack_craft_*) |
overall | 240 | per-key ceiling across all categories |
Internal server-to-server callers are exempt. Current active limits are returned on GET /health.
Fix: Slow down or batch your calls; wait the number of seconds in the message before retrying.
workspaceId mismatch
Section titled “workspaceId mismatch”With a workspace key (cs_):
{ "text": "workspaceId mismatch: this API key is bound to a different workspace. Omit workspaceId (it defaults to your key's workspace) or use campaignstack_whoami to inspect your key."}With a user key (csu_):
{ "text": "workspaceId mismatch: you are not a member of this workspace. Use campaignstack_whoami to list your workspaces."}Fix: Workspace keys (cs_) are bound to one workspace — omit the workspaceId argument (it defaults to the key’s workspace), or use a key bound to the workspace you’re targeting. With a user key (csu_), pass a workspaceId of a workspace you’re a member of — call campaignstack_whoami to list them.
Authentication errors (HTTP only)
Section titled “Authentication errors (HTTP only)”| Status | Message | Fix |
|---|---|---|
| 401 | ”Authentication required. Provide a valid API key in the Authorization header: Bearer csu_… (user key) or cs_… (workspace key)“ | Add an Authorization: Bearer <api-key> header |
| 401 | ”Invalid or revoked API key. Create a new key in Settings > API Keys.” | Key was revoked or doesn’t exist — create a new one |
Cross-referencing pattern
Section titled “Cross-referencing pattern”CampaignStack errors consistently point to related tools. This helps AI agents self-correct:
- “Use campaignstack_list_campaigns to find valid IDs” — after a not-found error
- “Use campaignstack_whoami to check your current scopes” — after a permission error
- Tool descriptions reference related tools: “Use the returned campaignId values with campaignstack_get_campaign, campaignstack_list_workflows, or campaignstack_list_lead_lists.”
This pattern enables agents to recover from errors without human intervention.
Input validation
Section titled “Input validation”All tools enforce strict input validation via Zod schemas:
- Required fields — missing required fields return clear error messages
- Type checking — wrong types (string where number expected) are rejected
- Range validation — numeric limits (e.g.,
limitmust be 1-100) are enforced - Unknown fields — extra fields not in the schema are rejected (
.strict()mode)