Customer-facing JSON API for voters, donations, volunteers, canvassing, lawn-signs, and campaign metadata. All keys are scoped per campaign.
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.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:
npx @openapitools/openapi-generator-cli generate \ -i https://ridingdesk.ca/openapi/v1.yaml \ -g typescript-fetch \ -o ./ridingdesk-client
openapi-generator-cli generate \ -i https://ridingdesk.ca/openapi/v1.yaml \ -g python \ -o ./ridingdesk-client
openapi-generator-cli generate \ -i https://ridingdesk.ca/openapi/v1.yaml \ -g go \ -o ./ridingdesk-client
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:
{
"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.
Pass your key in the Authorization header. Keys are issued from RidingDesk → Settings → API Keys.
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.
Every key explicitly enumerates the scopes it can use. There are no wildcards.
voters:read voters:write donations:read donations:write volunteers:read canvassing:read canvassing:write lawn_signs:read lawn_signs:write campaign:read
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.
{ "data": { ... } }{
"data": [ ... ],
"pagination": { "nextCursor": "eyJpZCI6...", "limit": 50 }
}{ "error": "Human-readable message", "code": "validation_failed" }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.
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
Cursor-based. Pass cursor from the previous response'spagination.nextCursor to fetch the next page. Defaultlimit is 50, max 200.
/api/v1/votersscope: voters:readOptional filters: riding, support_level (1-5). Cursor pagination via cursor + limit.
{
"data": [
{ "id": "vtr_...", "firstName": "Jane", "lastName": "Smith", "supportLevel": 5, ... }
],
"pagination": { "nextCursor": null, "limit": 50 }
}/api/v1/votersscope: voters:write{
"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.
/api/v1/voters/{id}scope: voters:read/api/v1/voters/{id}scope: voters:write/api/v1/donationsscope: donations:readOptional 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).
/api/v1/donationsscope: donations:write{
"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.
/api/v1/donations/{id}scope: donations:read/api/v1/donations/{id}/receiptscope: donations:writeIssues 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.
/api/v1/volunteersscope: volunteers:readRead-only roster of campaign volunteers, newest-first, paginated. v1 does not expose volunteer write endpoints.
/api/v1/canvassing/interactionsscope: canvassing:readOptional filter: voter_id to restrict to interactions with a single voter.
/api/v1/canvassing/interactionsscope: canvassing:write{
"voterId": "vtr_...",
"type": "DOOR",
"disposition": "STRONG_SUPPORT",
"notes": "Spoke at door, will put up a sign"
}/api/v1/canvassing/walklistscope: canvassing:readReturns 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.
/api/v1/lawn-signsscope: lawn_signs:readOptional filter: status (REQUESTED / INSTALLED / COLLECTED).
/api/v1/lawn-signsscope: lawn_signs:write/api/v1/lawn-signs/{id}scope: lawn_signs:read/api/v1/lawn-signs/{id}scope: lawn_signs:writeSetting status to INSTALLED or COLLECTED also stamps the matching timestamp server-side.
/api/v1/lawn-signs/{id}scope: lawn_signs:write/api/v1/campaignscope: campaign:readReturns metadata for the campaign that owns the API key —candidateName, party, riding,electionLevel, electionDate,subscriptionTier, official-agent fields, and theleaderAuthorizationOnFile flag.
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.