Docs
API reference
Base address https://shipnoted.com/api/v1. Every request needs a key; each endpoint lists the scope it needs.
Project#
/api/v1/projectsList projects
Scope: read
The projects this key can use. A quick way to find the slug.
curl "https://shipnoted.com/api/v1/projects" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: Projects.
{
"ok": true,
"projects": [
{
"slug": "acme",
"name": "Acme",
"url": "https://acme.shipnoted.com",
"live": true
}
]
}/api/v1/{slug}/projectGet the project
Scope: read
Names, addresses and counts. The first call a new tool makes, to check its key and slug.
curl "https://shipnoted.com/api/v1/your-slug/project" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: Project.
/api/v1/{slug}/themesRequests grouped into themes
Scope: read
The same wish in different words, grouped, ranked by revenue then votes.
curl "https://shipnoted.com/api/v1/your-slug/themes" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: Themes.
/api/v1/{slug}/segmentsCustomer groups that have voted
Scope: read
The values ?segment= accepts, with votes and revenue.
curl "https://shipnoted.com/api/v1/your-slug/segments" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: Segments.
/api/v1/{slug}/loopThe loop in numbers
Scope: read
Requests received and shipped, median days to ship, people told, and pull requests waiting for you.
curl "https://shipnoted.com/api/v1/your-slug/loop" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: Stats and suggestions.
Releases#
- get/{slug}/releases
- post/{slug}/releases
- get/{slug}/releases/{id}
- patch/{slug}/releases/{id}
- delete/{slug}/releases/{id}
- post/{slug}/releases/{id}/publish
- post/{slug}/draft
/api/v1/{slug}/releasesList releases
Scope: read
Query
statusdraft, scheduled or publishedlimit1 to 100, default 50offsetSkip this many
curl "https://shipnoted.com/api/v1/your-slug/releases" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: Releases, newest first, with hasMore.
/api/v1/{slug}/releasesCreate a release
Scope: releases:write
Add publish: true to publish at once (and email: true to email confirmed subscribers). Without it you get a draft.
Body (JSON)
titlerequiredstringbodystring · Markdownversionstringtags"new" | "improved" | "fixed" | "breaking"[]coverstring · An image addressaudiencestring · Aim it at a group: "plan:pro", "beta:true". Empty is everyoneannounceboolean · Pop it up in the widgetlinkedstring[]
curl -X POST "https://shipnoted.com/api/v1/your-slug/releases" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY" \
-H "content-type: application/json" \
-d '{"title":"Dark mode","body":"- Dark mode for the dashboard","version":"v1.4.0","tags":["new"],"publish":true,"email":true}'Returns 201: The release.
{
"ok": true,
"release": {
"id": "r_abc",
"title": "Dark mode",
"status": "published",
"url": "https://acme.shipnoted.com/p/acme/dark-mode"
},
"emailed": 212
}/api/v1/{slug}/releases/{id}Get a release
Scope: read
Includes the requests it answers.
curl "https://shipnoted.com/api/v1/your-slug/releases/r_abc" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: The release with linked requests.
/api/v1/{slug}/releases/{id}Update a release
Scope: releases:write
Any of the create fields. A published release changes in place.
Body (JSON)
titlestringbodystring · Markdownversionstringtags"new" | "improved" | "fixed" | "breaking"[]coverstring · An image addressaudiencestring · Aim it at a group: "plan:pro", "beta:true". Empty is everyoneannounceboolean · Pop it up in the widgetlinkedstring[]
curl -X PATCH "https://shipnoted.com/api/v1/your-slug/releases/r_abc" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: The release.
/api/v1/{slug}/releases/{id}Delete a release
Scope: releases:write
curl -X DELETE "https://shipnoted.com/api/v1/your-slug/releases/r_abc" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: ok.
/api/v1/{slug}/releases/{id}/publishPublish a draft
Scope: releases:write
Linked requests become Shipped and the people who voted for them are emailed, exactly as from the dashboard.
Body (JSON)
emailboolean
curl -X POST "https://shipnoted.com/api/v1/your-slug/releases/r_abc/publish" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: The public url and how many were told.
{
"ok": true,
"url": "https://acme.shipnoted.com/p/acme/dark-mode",
"emailed": 212,
"votersNotified": 14,
"requestsShipped": 3
}/api/v1/{slug}/draftDraft release notes from changes
Scope: releases:write
Send pull request titles or commit subjects; Claude (or the built-in writer) groups them into New, Improved and Fixed. The draft is never published for you.
Body (JSON)
itemsrequiredobject[]versionstring · Set as the version, such as a tag nametitlestring
curl -X POST "https://shipnoted.com/api/v1/your-slug/draft" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY" \
-H "content-type: application/json" \
-d '{"version":"v1.4.0","items":[{"title":"feat: dark mode (#212)"},{"title":"fix: login loop (#215)"}]}'Returns 201: The draft's id and who wrote it (claude or builtin).
{
"ok": true,
"releaseId": "r_abc",
"source": "claude"
}Requests#
- get/{slug}/feedback
- post/{slug}/feedback
- get/{slug}/feedback/{id}
- patch/{slug}/feedback/{id}
- delete/{slug}/feedback/{id}
- post/{slug}/feedback/{id}/comments
/api/v1/{slug}/feedbackList requests
Scope: read
Query
statusopen, planned, progress, shipped or declinedqSearch words, or a reference like SN-42sorttop, revenue or newplanOnly requests a customer on this plan voted forsegmentA group: "beta:true", "company:Acme"limit1 to 100offsetSkip this many
curl "https://shipnoted.com/api/v1/your-slug/feedback" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: Requests, with total and hasMore.
/api/v1/{slug}/feedbackFile a request, or move one
Scope: feedback:write
With a title it files a new request (send idempotencyKey so a retry files it once). With an id and a status it moves an existing request.
Body (JSON)
titlestringbodystringnamestringemailstringsourcestring · Where it came from, such as IntercomidempotencyKeystringidstring · Instead of a title: the request to movestatus"open" | "planned" | "progress" | "shipped" | "declined"etastring
curl -X POST "https://shipnoted.com/api/v1/your-slug/feedback" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY" \
-H "content-type: application/json" \
-d '{"title":"Export to CSV","body":"We need it for invoices","email":"ada@acme.com","source":"Support","idempotencyKey":"ticket-8841"}'Returns 201: The new request's id and reference, or ok.
{
"ok": true,
"id": "f_x",
"ref": "SN-42",
"duplicate": false
}/api/v1/{slug}/feedback/{id}Get a request
Scope: read
The id may be its reference, like SN-42. Includes comments and linked GitHub issues.
curl "https://shipnoted.com/api/v1/your-slug/feedback/SN-42" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: The request.
/api/v1/{slug}/feedback/{id}Change a request
Scope: feedback:write
Status and timing, who it is assigned to, and whether it is archived. An archived request leaves the inbox and the public board but keeps its votes.
Body (JSON)
status"open" | "planned" | "progress" | "shipped" | "declined"etastringassigneestring · A teammate's email, or an empty string to unassignarchivedboolean
curl -X PATCH "https://shipnoted.com/api/v1/your-slug/feedback/SN-42" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: The request.
/api/v1/{slug}/feedback/{id}Delete a request
Scope: feedback:write
curl -X DELETE "https://shipnoted.com/api/v1/your-slug/feedback/SN-42" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: ok.
/api/v1/{slug}/feedback/{id}/commentsReply as the team
Scope: feedback:write
Set internal: true for a note only your team sees.
Body (JSON)
bodyrequiredstringnamestringinternalboolean
curl -X POST "https://shipnoted.com/api/v1/your-slug/feedback/SN-42/comments" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY" \
-H "content-type: application/json" \
-d '{"body":"…"}'Returns 201: The comment.
Subscribers#
/api/v1/{slug}/subscribersList subscribers
Scope: subscribers:read
Email addresses are personal data, so this has its own scope.
Query
statusactive, pending or unsubscribedlimit1 to 500, default 100offsetSkip this many
curl "https://shipnoted.com/api/v1/your-slug/subscribers" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: Subscribers and total.
Webhook endpoints#
- get/{slug}/webhooks
- post/{slug}/webhooks
- patch/{slug}/webhooks/{id}
- delete/{slug}/webhooks/{id}
- post/{slug}/webhooks/{id}/test
/api/v1/{slug}/webhooksList webhook endpoints
Scope: webhooks:write
curl "https://shipnoted.com/api/v1/your-slug/webhooks" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: Endpoints and the event catalogue.
/api/v1/{slug}/webhooksCreate a webhook endpoint
Scope: webhooks:write
Events: feedback.created, feedback.status_changed, comment.created, release.published, feedback.voted, subscriber.confirmed. All of them when events is left out. The signing secret is in this response and never shown again.
Body (JSON)
urlrequiredstring · An https addresseventsstring[]
curl -X POST "https://shipnoted.com/api/v1/your-slug/webhooks" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY" \
-H "content-type: application/json" \
-d '{"url":"https://example.com/hooks/shipnoted","events":["feedback.created","release.published"]}'Returns 201: The endpoint and its secret.
{
"ok": true,
"endpoint": {
"id": "we_abc",
"url": "https://example.com/hooks/shipnoted",
"active": true
},
"secret": "whsec_…"
}/api/v1/{slug}/webhooks/{id}Pause, resume or change an endpoint
Scope: webhooks:write
Body (JSON)
activebooleaneventsstring[]
curl -X PATCH "https://shipnoted.com/api/v1/your-slug/webhooks/we_abc" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: The endpoint.
/api/v1/{slug}/webhooks/{id}Delete an endpoint
Scope: webhooks:write
curl -X DELETE "https://shipnoted.com/api/v1/your-slug/webhooks/we_abc" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: ok.
/api/v1/{slug}/webhooks/{id}/testSend a test ping
Scope: webhooks:write
A signed ping event, sent now. Reports what your server answered.
curl -X POST "https://shipnoted.com/api/v1/your-slug/webhooks/we_abc/test" \
-H "Authorization: Bearer $SHIPNOTED_API_KEY"Returns 200: ok and a note.
Errors#
Every error is { "ok": false, "error": "…" } with one of these statuses:
400A field is missing or not valid. The error says which.401No key, a wrong key, or an expired key.402The workspace is not on a plan with API access.403The key is valid but lacks the scope this endpoint needs. The response names it.404No such project, release, request or endpoint, or the key is limited to other projects.409Already done, for example publishing a release that is published.429More than 120 requests a minute. Wait the Retry-After seconds.