Building on Commons

Free, uncapped REST API. TypeScript + Python SDKs. Cursor pagination. Verified civic infrastructure.

Quick Start

1

Create an API key

Go to your organization dashboard and create an API key, or use the API:

bash
curl -X POST https://commons.email/api/v1/keys \
  -H "Content-Type: application/json" \
  -d '{"orgSlug": "your-org"}'
2

Make your first request

bash
curl https://commons.email/api/v1/supporters \
  -H "Authorization: Bearer ck_live_your_key_here"
3

Parse the response

json
{
  "data": [
    {
      "id": "sup_abc123",
      "email": "jane@example.com",
      "name": "Jane Doe",
      "verified": true,
      "tags": [{ "id": "tag_1", "name": "volunteer" }]
    }
  ],
  "meta": {
    "cursor": "sup_abc123",
    "hasMore": true,
    "total": 1450
  }
}

Installation

TypeScript
npm install @commons-platform/sdk
TypeScript
import { Commons } from '@commons-platform/sdk';

const commons = new Commons({ apiKey: 'ck_live_...' });

// List supporters
const supporters = await commons.supporters.list();

// Create a campaign
const campaign = await commons.campaigns.create({
  title: 'Clean Energy Act',
  type: 'LETTER'
});
Python
pip install commons-sdk
Python
from commons import Commons

client = Commons(api_key="ck_live_...")

# List supporters
supporters = client.supporters.list()

# Create a campaign
campaign = client.campaigns.create(
    title="Clean Energy Act",
    type="LETTER"
)

Authentication

All API requests require a Bearer token in the Authorization header. API keys use the prefix ck_live_ and are scoped to a single organization.

Scopes

  • read — List and retrieve resources (default)
  • write — Create, update, and delete resources (implies read)
bash
curl https://commons.email/api/v1/supporters \
  -H "Authorization: Bearer ck_live_abc123def456"

Resources

All resources are accessed under /api/v1/. Responses use the envelope format { data, meta?, error? }.

Organization GET

Returns the organization bound to the API key.

GET /api/v1/orgs
Response
{
  "data": {
    "id": "org_1",
    "name": "Climate Action Coalition",
    "slug": "climate-action",
    "description": "Grassroots climate advocacy",
    "counts": {
      "supporters": 12500,
      "campaigns": 8,
      "templates": 15
    }
  }
}
Supporters GET POST PATCH DELETE

Manage your organization's supporter CRM.

GET /api/v1/supporters

Query Parameters

ParamTypeDescription
cursorstringPagination cursor
limitintegerItems per page (max 50)
emailstringFilter by exact email
verifiedbooleanFilter by verification status
email_statusstringsubscribed, unsubscribed, bounced, complained
sourcestringcsv, recognized platform profile, organic, widget
tagstringFilter by tag ID
POST /api/v1/supporters write
Request Body
{
  "email": "jane@example.com",
  "name": "Jane Doe",
  "postalCode": "94105",
  "tags": ["tag_volunteer"]
}
PATCH /api/v1/supporters/{id} write
DELETE /api/v1/supporters/{id} write
Campaigns GET POST PATCH

Create and manage campaigns. Each campaign can have associated actions.

GET /api/v1/campaigns

Query Parameters

ParamTypeDescription
statusstringDRAFT, ACTIVE, PAUSED, COMPLETE
typestringLETTER, EVENT, FORM
GET /api/v1/campaigns/{id}
POST /api/v1/campaigns write
PATCH /api/v1/campaigns/{id} write
GET /api/v1/campaigns/{id}/actions

Query Parameters

ParamTypeDescription
verifiedbooleanFilter by verification status
Tags GET POST PATCH DELETE

Organize supporters with tags.

GET /api/v1/tags
POST /api/v1/tags write
Request Body
{ "name": "volunteer" }
PATCH /api/v1/tags/{id} write
DELETE /api/v1/tags/{id} write
Events GET

List and retrieve events with RSVP and attendance tracking.

GET /api/v1/events

Query Parameters

ParamTypeDescription
statusstringDRAFT, PUBLISHED, CANCELLED, COMPLETED
eventTypestringIN_PERSON, VIRTUAL, HYBRID
GET /api/v1/events/{id}
Response
{
  "data": {
    "id": "evt_1",
    "title": "Town Hall Q3",
    "eventType": "IN_PERSON",
    "status": "PUBLISHED",
    "startsAt": "2026-04-15T18:00:00Z",
    "endsAt": "2026-04-15T20:00:00Z",
    "venue": "City Hall, Room 201",
    "capacity": 200,
    "rsvpCount": 142,
    "attendeeCount": 0
  }
}
Donations GET

Retrieve donation records. 0% platform fee -- only Stripe's processing fee applies.

GET /api/v1/donations

Query Parameters

ParamTypeDescription
statusstringpending, completed, refunded
campaignIdstringFilter by campaign ID
GET /api/v1/donations/{id}
Workflows GET

Automation workflows with event-driven triggers and multi-step actions.

GET /api/v1/workflows

Query Parameters

ParamTypeDescription
enabledbooleanFilter by enabled status
GET /api/v1/workflows/{id}
SMS Blasts GET

Twilio-powered SMS blast campaigns.

GET /api/v1/sms

Query Parameters

ParamTypeDescription
statusstringdraft, sending, sent, failed
Patch-Through Calls GET

Patch-through calling with verified district matching.

GET /api/v1/calls

Query Parameters

ParamTypeDescription
statusstringinitiated, ringing, in-progress, completed, failed, no-answer, busy
campaignIdstringFilter by campaign ID
Representatives GET

International representative lookup with constituency filtering.

GET /api/v1/representatives

Query Parameters

ParamTypeDescription
countrystringISO country code
constituencystringConstituency ID
Usage GET

Current billing period usage for your organization.

GET /api/v1/usage
Response
{
  "data": {
    "verifiedActions": 847,
    "maxVerifiedActions": 5000,
    "emailsSent": 3200,
    "maxEmails": 10000
  }
}

Pagination

All list endpoints use cursor-based pagination. Pass the cursor from the previous response's meta object to get the next page.

bash
# First page
curl "https://commons.email/api/v1/supporters?limit=25"

# Next page (use cursor from previous response)
curl "https://commons.email/api/v1/supporters?limit=25&cursor=sup_abc123"
Response meta
{
  "meta": {
    "cursor": "sup_xyz789",
    "hasMore": true,
    "total": 1450
  }
}

The SDKs provide auto-pagination with async iterators:

TypeScript
// Auto-paginate through all supporters
for await (const supporter of commons.supporters.list()) {
  console.log(supporter.email);
}
Python
# Auto-paginate through all supporters
for supporter in client.supporters.list():
    print(supporter.email)

Errors

Error responses use the same envelope format with data: null and an error object:

Error response
{
  "data": null,
  "error": {
    "code": "NOT_FOUND",
    "message": "Resource not found"
  }
}
CodeHTTPDescription
BAD_REQUEST400Invalid input or malformed JSON
UNAUTHORIZED401Missing or invalid API key
FORBIDDEN403API key lacks required scope
NOT_FOUND404Resource does not exist
CONFLICT409Duplicate resource (e.g. email already exists)
RATE_LIMITED429Too many requests -- slow down
INTERNAL_ERROR500Unexpected server error

Rate Limits

Rate limits are applied per API key based on your organization's plan. Reads stay open while you're building -- a plan lifts the write ceiling and unlocks delivery. When rate limited, you'll receive a 429 response.

PlanRequests / min
No plan yetReads uncapped · writes 100
Starter300
Organization1,000
Coalition3,000