Base URL

All endpoints live under https://crowdstack.org/api/v1/. The version is part of the path; breaking changes ship as a new version and the old one keeps working. Requests and responses are JSON, and responses are never cached.

Authentication

Create a key under Settings → General → API keys. Keys belong to the workspace, not to a person, and are shown once at creation. Send one as a bearer token on every request:

Authorization: Bearer cs_live_…
Content-Type: application/json

Writes made with a key are attributed to the member who created it, so the audit trail always names a person. Revoking a key takes effect immediately. Keep keys in your integration's secret store; if one leaks, revoke it and create another.

Connect an AI agent

CrowdStack runs a Model Context Protocol server. Point any MCP client at it and every operation in this reference becomes a tool the model can call: read the inbox, reply to tickets, move deals, enrol contacts in campaigns, write help articles, and so on. The server tells the model to call get_workspace first so it uses your statuses, stages and fields, and it marks deleting tools as destructive so a careful client asks before using them.

ClientHow to connect
Claude Code, Cursor, and other clients that send headersURL https://crowdstack.org/api/mcp with header Authorization: Bearer cs_live_…. For example: claude mcp add --transport http crowdstack https://crowdstack.org/api/mcp --header "Authorization: Bearer cs_live_…"
claude.ai and Claude Desktop connectors, ChatGPT connectorsThese take a URL and nothing else, so put the key in the URL: https://crowdstack.org/api/mcp/cs_live_…. Treat that URL as the secret it is: make a dedicated key for the connector so you can revoke it on its own.
Custom GPTs, LangChain, and anything that reads OpenAPIImport https://crowdstack.org/api/v1/openapi.json. It is generated from the same definitions as this page, with bearer auth declared and every operation marked consequential or not.

The MCP server is stateless: each request is complete in itself, there is no session to keep alive, and it answers with plain JSON. The endpoint accepts POST only; GET returns 405 by design.

A few tools act outward. A public reply on a ticket can email the customer, activating a campaign starts sending, and the delete tools are permanent. The tool descriptions say so, and it is worth telling your agent which of those it may do without asking.

Errors

Every error has the same shape. code is stable and meant for programs; message is for people.

{
  "error": {
    "code": "validation_failed",
    "message": "The request body did not validate.",
    "details": [
      { "path": "fields.demo_url", "message": "Unknown field. Defined fields: plan_interest." }
    ]
  }
}
StatusCodeWhen
400invalid_requestThe body is not valid JSON.
401unauthorizedThe key is missing, revoked, or unknown.
404not_foundThe record is not in this workspace.
422validation_failedThe body failed validation. `details` lists each problem.
429rate_limitedThe key is over its per-minute limit. Retry after a short wait.
500internalSomething failed on our side. Safe to retry.

Rate limits

300 requests per minute per key, whether the calls come over REST or MCP. Over the limit you get a 429 with code rate_limited; wait a moment and retry. Limits apply per key, so give each integration its own key and one runaway script cannot starve the others.

Conventions

Resources