EnoughStack
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 docs

Conventions

Errors
Always {"error": {"code", "message"}}. Codes include unauthenticated, insufficient_scope, not_found, validation_error.
Pagination
Lists return paging.next; pass it back as starting_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"]
  }
}