Free, uncapped REST API. TypeScript + Python SDKs. Cursor pagination. Verified civic infrastructure.
Go to your organization dashboard and create an API key, or use the API:
curl -X POST https://commons.email/api/v1/keys \
-H "Content-Type: application/json" \
-d '{"orgSlug": "your-org"}'curl https://commons.email/api/v1/supporters \
-H "Authorization: Bearer ck_live_your_key_here"{
"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
}
}npm install @commons-platform/sdkimport { 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'
});pip install commons-sdkfrom 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"
)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.
read — List and retrieve resources (default)write — Create, update, and delete resources (implies read)curl https://commons.email/api/v1/supporters \
-H "Authorization: Bearer ck_live_abc123def456"All resources are accessed under /api/v1/. Responses use the envelope format { data, meta?, error? }.
Returns the organization bound to the API key.
/api/v1/orgs{
"data": {
"id": "org_1",
"name": "Climate Action Coalition",
"slug": "climate-action",
"description": "Grassroots climate advocacy",
"counts": {
"supporters": 12500,
"campaigns": 8,
"templates": 15
}
}
}Manage your organization's supporter CRM.
/api/v1/supporters| Param | Type | Description |
|---|---|---|
cursor | string | Pagination cursor |
limit | integer | Items per page (max 50) |
email | string | Filter by exact email |
verified | boolean | Filter by verification status |
email_status | string | subscribed, unsubscribed, bounced, complained |
source | string | csv, recognized platform profile, organic, widget |
tag | string | Filter by tag ID |
/api/v1/supporters write{
"email": "jane@example.com",
"name": "Jane Doe",
"postalCode": "94105",
"tags": ["tag_volunteer"]
}/api/v1/supporters/{id} write/api/v1/supporters/{id} writeCreate and manage campaigns. Each campaign can have associated actions.
/api/v1/campaigns| Param | Type | Description |
|---|---|---|
status | string | DRAFT, ACTIVE, PAUSED, COMPLETE |
type | string | LETTER, EVENT, FORM |
/api/v1/campaigns/{id}/api/v1/campaigns write/api/v1/campaigns/{id} write/api/v1/campaigns/{id}/actions| Param | Type | Description |
|---|---|---|
verified | boolean | Filter by verification status |
Organize supporters with tags.
/api/v1/tags/api/v1/tags write{ "name": "volunteer" }/api/v1/tags/{id} write/api/v1/tags/{id} writeList and retrieve events with RSVP and attendance tracking.
/api/v1/events| Param | Type | Description |
|---|---|---|
status | string | DRAFT, PUBLISHED, CANCELLED, COMPLETED |
eventType | string | IN_PERSON, VIRTUAL, HYBRID |
/api/v1/events/{id}{
"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
}
}Retrieve donation records. 0% platform fee -- only Stripe's processing fee applies.
/api/v1/donations| Param | Type | Description |
|---|---|---|
status | string | pending, completed, refunded |
campaignId | string | Filter by campaign ID |
/api/v1/donations/{id}Automation workflows with event-driven triggers and multi-step actions.
/api/v1/workflows| Param | Type | Description |
|---|---|---|
enabled | boolean | Filter by enabled status |
/api/v1/workflows/{id}Twilio-powered SMS blast campaigns.
/api/v1/sms| Param | Type | Description |
|---|---|---|
status | string | draft, sending, sent, failed |
Patch-through calling with verified district matching.
/api/v1/calls| Param | Type | Description |
|---|---|---|
status | string | initiated, ringing, in-progress, completed, failed, no-answer, busy |
campaignId | string | Filter by campaign ID |
International representative lookup with constituency filtering.
/api/v1/representatives| Param | Type | Description |
|---|---|---|
country | string | ISO country code |
constituency | string | Constituency ID |
Current billing period usage for your organization.
/api/v1/usage{
"data": {
"verifiedActions": 847,
"maxVerifiedActions": 5000,
"emailsSent": 3200,
"maxEmails": 10000
}
}All list endpoints use cursor-based pagination. Pass the cursor from the previous
response's meta object to get the next page.
# 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"{
"meta": {
"cursor": "sup_xyz789",
"hasMore": true,
"total": 1450
}
}The SDKs provide auto-pagination with async iterators:
// Auto-paginate through all supporters
for await (const supporter of commons.supporters.list()) {
console.log(supporter.email);
}# Auto-paginate through all supporters
for supporter in client.supporters.list():
print(supporter.email)Error responses use the same envelope format with data: null and an error object:
{
"data": null,
"error": {
"code": "NOT_FOUND",
"message": "Resource not found"
}
}| Code | HTTP | Description |
|---|---|---|
BAD_REQUEST | 400 | Invalid input or malformed JSON |
UNAUTHORIZED | 401 | Missing or invalid API key |
FORBIDDEN | 403 | API key lacks required scope |
NOT_FOUND | 404 | Resource does not exist |
CONFLICT | 409 | Duplicate resource (e.g. email already exists) |
RATE_LIMITED | 429 | Too many requests -- slow down |
INTERNAL_ERROR | 500 | Unexpected server error |
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.
| Plan | Requests / min |
|---|---|
| No plan yet | Reads uncapped · writes 100 |
| Starter | 300 |
| Organization | 1,000 |
| Coalition | 3,000 |