Listen to customers, plan what comes next and share what shipped.
Feedback API
Posts, votes, comments and changelog entries from your own code: open a post when a customer asks for something, vote on their behalf, and listen for post events with webhooks.
Getting started
Create posts and votes for the people using your product, and let webhooks tell you when something ships.
https://enoughstack-app-7r9ib.ondigitalocean.app/api/public/v1 Authorization: Bearer ms_live_… # create a key under API keys
curl -X POST https://enoughstack-app-7r9ib.ondigitalocean.app/api/public/v1/feedback/posts \
-H "Authorization: Bearer $ENOUGHSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"board_id": "features", "title": "Export to Final Cut",
"details": "Asked twice this week in support.",
"author": {"external_id": "cus_42", "email": "maya@northwind.studio", "name": "Maya"}}'The author becomes a portal user (or is found by external id or email), votes for the post and follows it, so they hear when its status changes. Posting the same user again never duplicates them.
curl -X POST https://enoughstack-app-7r9ib.ondigitalocean.app/api/public/v1/feedback/posts/post_0002/votes \
-H "Authorization: Bearer $ENOUGHSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"author": {"email": "jonas@fjordlabs.io"}, "note": "Blocking their upgrade"}'Subscribe a webhook to post.status_changed and changelog.published to close the loop in your own tools.
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
API keys
Every request carries a key with the read or write scope. Keys are created under Developers and shown once.
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"]
}
}