RidingDesk Customer Data API v1

Customer-facing JSON API for voters, donations, volunteers, canvassing, lawn-signs, and campaign metadata. All keys are scoped per campaign.

Looking for the public Electoral Data API? That’s a different product — the free dataset of Canadian electoral boundaries, postal-code lookups, and election results (keys start with ced_). It lives at /docs/api/electoral under the path /api/v1/electoral/*. The Customer Data API documented here (paths /api/v1/voters, /donations, etc., keys rd_live_) is the paid CRM API for active campaigns. See also the APIs hub for a side-by-side comparison.
Machine-readable spec available:Download OpenAPI 3.1 spec (YAML)Covers every endpoint, auth, pagination, errors, idempotency, rate limits.

Use the spec with codegen

The OpenAPI spec at /openapi/v1.yaml can be fed directly into openapi-generator-cli (or any other OpenAPI tool) to produce a typed client in your language of choice. Examples:

TypeScript / JavaScript
npx @openapitools/openapi-generator-cli generate \
  -i https://ridingdesk.ca/openapi/v1.yaml \
  -g typescript-fetch \
  -o ./ridingdesk-client
Python
openapi-generator-cli generate \
  -i https://ridingdesk.ca/openapi/v1.yaml \
  -g python \
  -o ./ridingdesk-client
Go
openapi-generator-cli generate \
  -i https://ridingdesk.ca/openapi/v1.yaml \
  -g go \
  -o ./ridingdesk-client

Use it from Claude Desktop (MCP)

The official @ridingdesk/mcp-server npm package wraps every endpoint in this API as a Model Context Protocol tool, so Claude (or any MCP client) can read and write your campaign data in plain English. Add this to your claude_desktop_config.json and restart Claude:

claude_desktop_config.json
{
  "mcpServers": {
    "ridingdesk": {
      "command": "npx",
      "args": ["-y", "@ridingdesk/mcp-server"],
      "env": {
        "RIDINGDESK_API_KEY": "rd_live_paste_your_key_here"
      }
    }
  }
}

Config locations: macOS ~/Library/Application Support/Claude/claude_desktop_config.json, Windows %APPDATA%\Claude\claude_desktop_config.json, Linux ~/.config/Claude/claude_desktop_config.json. Full setup, tool reference, and troubleshooting in the package README.

Authentication

Pass your key in the Authorization header. Keys are issued from RidingDesk → Settings → API Keys.

curl
curl https://ridingdesk.ca/api/v1/campaign \
  -H "Authorization: Bearer rd_live_..."

Keys start with rd_live_. Treat them as secrets. The full key is shown once at creation time and is never retrievable again.

Tier gate: COMMUNITY-tier campaigns can’t mint live keys — upgrade to STARTER, PRO, or CAMPAIGN_HQ first. Calls from a key whose campaign downgrades to COMMUNITY return 403 forbidden.

Scopes

Every key explicitly enumerates the scopes it can use. There are no wildcards.

scope vocabulary
voters:read           voters:write
donations:read        donations:write
volunteers:read
canvassing:read       canvassing:write
lawn_signs:read       lawn_signs:write
campaign:read

Rate limits

Per-tier, per-key, per-minute:

STARTER       60 req/min
PRO          300 req/min
CAMPAIGN_HQ 1200 req/min

Every 429 Too Many Requests response includes:

Retry-After: 60
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0

Retry-After is in seconds. Excessive 429s are logged to your audit trail.

Response envelope

success — single resource
{ "data": { ... } }
success — list
{
  "data": [ ... ],
  "pagination": { "nextCursor": "eyJpZCI6...", "limit": 50 }
}
error
{ "error": "Human-readable message", "code": "validation_failed" }

Error codes

unauthorized            401  no/bad key
forbidden               403  key lacks the required scope, or COMMUNITY tier
not_found               404  resource doesn't exist (or isn't yours)
validation_failed       400  body or params failed validation
limit_exceeded          402  per-tier capacity exhausted (e.g. voter cap)
rate_limit_exceeded     429  per-key bucket exhausted
idempotency_key_reused  422  Idempotency-Key reused with a different body
internal                500  server error

402 Payment Required with limit_exceeded fires when a write would push the campaign past its plan’s per-tier capacity (today: voter count). Surface the message — it tells the user exactly which limit was hit and which tier is needed to lift it.

Idempotency

For POST/PATCH/DELETE, supply an Idempotency-Key header (any unique string — UUID is recommended). If the same key reaches the server a second time with the same body, the original response is replayed — no duplicate writes. Keys are remembered for 24 hours.

Idempotency-Key: 7b2e9c1c-3f0d-4a8a-9c1b-bc8c4f5b2e1a

Pagination

Cursor-based. Pass cursor from the previous response'spagination.nextCursor to fetch the next page. Defaultlimit is 50, max 200.

Voters

GET/api/v1/votersscope: voters:read

Optional filters: riding, support_level (1-5). Cursor pagination via cursor + limit.

response
{
  "data": [
    { "id": "vtr_...", "firstName": "Jane", "lastName": "Smith", "supportLevel": 5, ... }
  ],
  "pagination": { "nextCursor": null, "limit": 50 }
}
POST/api/v1/votersscope: voters:write
request body
{
  "firstName": "Jane",
  "lastName": "Smith",
  "address": "123 Elgin St",
  "city": "Ottawa",
  "province": "ON",
  "postalCode": "K2P 1L5",
  "riding": "Ottawa Centre"
}

Returns 402 limit_exceeded when adding this voter would push the campaign past its tier’s voter cap.

GET/api/v1/voters/{id}scope: voters:read
PATCH/api/v1/voters/{id}scope: voters:write

Donations

GET/api/v1/donationsscope: donations:read

Optional filters: q (donor-name/email/address text search, case-insensitive), receipt_status (PENDING / ISSUED / SPOILED), method (CREDIT_CARD / ETRANSFER / CHEQUE / CASH / OTHER / IN_KIND), date_from and date_to(inclusive YYYY-MM-DD bounds on the donation date).

POST/api/v1/donationsscope: donations:write
request body
{
  "donorName": "Jane Smith",
  "donorAddress": "123 Elgin St",
  "donorCity": "Ottawa",
  "donorProvince": "ON",
  "donorPostalCode": "K2P 1L5",
  "amount": 250,
  "date": "2026-05-02",
  "method": "ETRANSFER",
  "donorIsCanadianAttested": true
}

The federal individual contribution cap (Canada Elections Act s. 367) is enforced server-side. Requests that would push the donor over their annual eligible total return 422 validation_failed before any write occurs.

GET/api/v1/donations/{id}scope: donations:read
POST/api/v1/donations/{id}/receiptscope: donations:write

Issues an ITR 2000-compliant federal political contribution receipt. Six CRA gates apply (federal-only, monetary-only, agent-on-file, writ-window, EDA leader-authorization, donor-citizenship attestation). Failures return 422 validation_failed with an actionable message.

Volunteers

GET/api/v1/volunteersscope: volunteers:read

Read-only roster of campaign volunteers, newest-first, paginated. v1 does not expose volunteer write endpoints.

Canvassing

GET/api/v1/canvassing/interactionsscope: canvassing:read

Optional filter: voter_id to restrict to interactions with a single voter.

POST/api/v1/canvassing/interactionsscope: canvassing:write
request body
{
  "voterId": "vtr_...",
  "type": "DOOR",
  "disposition": "STRONG_SUPPORT",
  "notes": "Spoke at door, will put up a sign"
}
GET/api/v1/canvassing/walklistscope: canvassing:read

Returns at most 200 voters, ordered for door-knocking. Filters narrow byriding, poll (comma-separated codes),support_levels (comma-separated 1-5), andexclude_contacted (boolean — skip voters with a non-nulllastContact). Server-side pagination is intentionally a no-op; fetch the whole page each call.

Lawn signs

GET/api/v1/lawn-signsscope: lawn_signs:read

Optional filter: status (REQUESTED / INSTALLED / COLLECTED).

POST/api/v1/lawn-signsscope: lawn_signs:write
GET/api/v1/lawn-signs/{id}scope: lawn_signs:read
PATCH/api/v1/lawn-signs/{id}scope: lawn_signs:write

Setting status to INSTALLED or COLLECTED also stamps the matching timestamp server-side.

DELETE/api/v1/lawn-signs/{id}scope: lawn_signs:write

Campaign

GET/api/v1/campaignscope: campaign:read

Returns metadata for the campaign that owns the API key —candidateName, party, riding,electionLevel, electionDate,subscriptionTier, official-agent fields, and theleaderAuthorizationOnFile flag.

Compromised key?

Revoke immediately from RidingDesk → Settings → API Keys. Your audit log shows every action the key took between creation and revocation — review for unauthorized donations or voter changes and reverse them by hand.