Developers
Connect your product to your workspace with API keys, webhooks and developer guides.
Getting started
Three requests cover most integrations: add a contact, tag them, read a report.
Base URL and authentication
https://enoughstack-app-7r9ib.ondigitalocean.app/api/public/v1 Authorization: Bearer ms_live_… # create a key under API keys
Add a contact when someone signs up
curl -X POST https://enoughstack-app-7r9ib.ondigitalocean.app/api/public/v1/lists/lst_audience/contacts \
-H "Authorization: Bearer $ENOUGHSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email_address": "maya@northwind.studio",
"fields": {"FirstName": "Maya", "Plan": "Team"},
"tags": ["customer", "team"]}'Posting the same address again updates the contact instead of failing, so you can call this from a sign-up handler without checking first.
Read a campaign report
curl https://enoughstack-app-7r9ib.ondigitalocean.app/api/public/v1/campaigns/cmp_004/report \
-H "Authorization: Bearer $ENOUGHSTACK_API_KEY"
{
"campaign_id": "cmp_004",
"stats": { "recipients": 166, "delivered": 165, "opens_unique": 93, "open_rate": 0.5636, ... },
"links": [{ "url": "https://framecast.app", "unique_clicks": 27, "total_clicks": 35 }]
}Lists
- GET
/listsYour lists with subscriber countsread - GET
/lists/{list_id}One listread - GET
/lists/{list_id}/tagsTags on a listread - GET
/lists/{list_id}/segmentsSegments on a listread
Contacts
- GET
/lists/{list_id}/contactsPage through contacts; filter by status or tagread - POST
/lists/{list_id}/contactsCreate a contact, or update the one with that emailwrite - GET
/lists/{list_id}/contacts/{id_or_email}One contact, by id or email addressread - PATCH
/lists/{list_id}/contacts/{contact_id}Change fields, tags or statuswrite - DELETE
/lists/{list_id}/contacts/{contact_id}Remove a contactwrite - GET
/lists/{list_id}/segments/{segment_id}/contactsContacts matching a segmentread
Campaigns
- GET
/campaignsCampaigns, optionally by statusread - GET
/campaigns/{campaign_id}One campaign with its statsread - GET
/campaigns/{campaign_id}/reportDelivery stats and link clicksread
Automations
- GET
/automationsYour automationsread - POST
/automations/{automation_id}/enrollStart an automation for a contactwrite
Boards
- GET
/boardsYour open boards; add ?archived=true for closed onesread - GET
/boards/{board_id}One board with its lists and labelsread - GET
/boards/{board_id}/cardsOpen cards; filter by list_id, member_id or completedread
Cards
- POST
/boards/{board_id}/cardsCreate a card in a listwrite - GET
/cards/{card_id}One cardread - PATCH
/cards/{card_id}Change its name, dates, labels, members, status or completionwrite - POST
/cards/{card_id}/moveMove it to a list, top or bottomwrite
Boards, statuses and tags
- GET
/feedback/boardsYour feedback boards with post countsread - GET
/feedback/statusesThe workspace's post statusesread - GET
/feedback/tagsPost tagsread - GET
/feedback/changelog/labelsChangelog labelsread
Posts
- GET
/feedback/postsPage through posts; filter by board, status, tag, search or updated_sinceread - POST
/feedback/postsCreate a post, optionally for one of your userswrite - GET
/feedback/posts/{post_id}One postread - PATCH
/feedback/posts/{post_id}Change its title, details, status, tags, owner or ETAwrite - DELETE
/feedback/posts/{post_id}Delete a post (restorable from the dashboard)write - POST
/feedback/posts/{post_id}/votesAdd a vote for a userwrite - DELETE
/feedback/posts/{post_id}/votes/{vote_id}Remove a votewrite - GET
/feedback/posts/{post_id}/commentsComments; add ?internal=true for team notesread - POST
/feedback/posts/{post_id}/commentsComment as your team or as a userwrite
Portal users
- GET
/feedback/usersPeople who posted, voted or were identifiedread - GET
/feedback/users/{user_id}One user with their countersread - POST
/feedback/users/identifyCreate or update a user by external id or emailwrite
Changelog
- GET
/feedback/changelog/entriesPublished entries; add ?include_drafts=trueread - POST
/feedback/changelog/entriesCreate an entry; publish_at publishes or schedules itwrite - POST
/feedback/changelog/entries/{entry_id}/publishPublish a draft nowwrite - GET
/feedback/changelog/subscribersEmail subscribersread - POST
/feedback/changelog/subscribersAdd a confirmed subscriberwrite
Interactive docs
Every endpoint with request and response schemas, generated from the API itself. Use “Authorize” with one of your keys to try requests.
Open API docsConventions
- Errors
- Always
{"error": {"code", "message"}}. Codes includeunauthenticated,insufficient_scope,not_found,validation_error. - Pagination
- Lists return
paging.next; pass it back asstarting_after. Up to 100 items per page. - Webhooks
- Each delivery carries
X-EnoughStack-Signature: sha256=…, an HMAC of the raw body with the endpoint secret. Respond 2xx within 10 seconds. - Versioning
- The version is in the path (
/v1). Fields are only ever added.
Webhook payload
POST https://your-app.example/hooks
X-EnoughStack-Signature: sha256=…
{
"event": "contact.created",
"created_at": "2026-10-06T09:14:02Z",
"data": {
"id": "ct_0231",
"list_id": "lst_audience",
"email_address": "maya@northwind.studio",
"status": "subscribed",
"tags": ["customer", "team"]
}
}