all posts
by Dima

why your feedback tool should have a real api

feedback tools gate the api behind enterprise, betting you'll never integrate. you will. what changes when the api is on every plan.

every feedback tool you'll evaluate has an api in the marketing copy. read the pricing page closely.

canny calls it out as "available on growth and above." growth starts at $359/mo. featurebase puts the api on its pro tier. userjot ships read-only api access on entry plans and write access higher up. the pattern is the same across the category: the api is treated as an upsell, not a feature.

we built spirby with the api on every plan, including the $19 starter. this post is about what changes when you make that decision, and why we think gating the api is one of those choices that quietly makes the whole product worse.

the workflows you'll want

before we get into the philosophy, let's be concrete. here are five things teams ask their feedback tool to do once they've used it for more than a quarter:

  1. when a post crosses 50 votes, create a linear issue and link it back.
  2. when a post ships, post in the team's slack #ship channel.
  3. when a customer files a support ticket about a feature on the roadmap, attach the ticket to the post so the count is honest.
  4. show the top five upcoming features inside the product, pulled live from the public roadmap.
  5. send a digest of new feedback to the customer success team every monday.

every one of these is a webhook listener or a polled api call. nothing exotic. nothing that requires a custom integration vendor. you write the script, you point it at the endpoint, you're done in an afternoon.

if the api is gated behind a higher plan, every one of these blocks at the same place: "we'd love to wire this up. let me check the budget." most of them die there.

the gate is the point

the case for gating the api is straightforward: enterprise teams have integration budgets, indie teams don't, so put the integration surface where the money is. that's the canny model. it's coherent on a spreadsheet.

the problem is that it inverts the audience. indie teams are the ones who will actually wire the api up. they don't have a procurement department. they have one engineer who can ship a node script in an afternoon. the enterprise team you've gated for has six people in a meeting about whether to use zapier or a custom go service.

so what you end up with is: the people who can use the api can't afford it, and the people who can afford it don't ship anything with it.

we'd rather optimize for the team that's going to actually plug it in. that's why the api ships on every plan, including the trial.

what "real api" means

a real api isn't a partial set of read endpoints behind a key. it's:

  • resource-oriented urls. /v1/boards/feedback/posts, not /api?action=getPosts. the spirby api is rest at api.spirby.com/v1.
  • standard methods and status codes. post to create. patch to update. delete to delete. 201, 404, 422, 429. nothing surprising.
  • cursor pagination, not page numbers. page numbers break when records shift. cursors don't.
  • idempotency keys. retries are inevitable. the api should make them safe.
  • outbound webhooks with hmac signatures. so you can trust what you receive.
  • a published openapi spec. at docs.spirby.com/api. machine-readable. generates clients.

each of these is table stakes for an api you're going to build a workflow on top of. several of the feedback tools in this category miss two or three of them, even on their paid tiers.

webhooks are the better half

read endpoints are useful. webhooks are how you actually keep two systems in sync without polling.

spirby fires events on post.created, post.status_changed, vote.created, comment.created, and changelog.published. the body is signed with hmac-sha256 in the x-spirby-signature header. you verify the signature with your webhook secret, and you can trust the payload is from us.

import crypto from 'node:crypto'

function verify(req, secret) {
  const signature = req.headers['x-spirby-signature']
  if (typeof signature !== 'string') return false
  const expected = `sha256=${crypto
    .createHmac('sha256', secret)
    .update(req.rawBody)
    .digest('hex')}`
  const sigBuf = Buffer.from(signature)
  const expBuf = Buffer.from(expected)
  if (sigBuf.length !== expBuf.length) return false
  return crypto.timingSafeEqual(sigBuf, expBuf)
}

twenty lines of code. failed deliveries retry on an exponential backoff: 30s, 2m, 10m, 1h across five attempts (about 72 minutes of total wall clock before the delivery is terminal). after fifty consecutive failures across deliveries the webhook is disabled and an admin gets an email. you can replay any failed delivery from the admin ui.

this is the pattern stripe uses. it's the pattern github uses. it's well understood and it's not interesting. that's the point.

what we don't ship

we don't ship pre-built integrations with slack, linear, or jira. that's on the post-launch roadmap, not in v1. for now, the api and webhooks are the integration surface, and we expect you to wire up the workflows you actually need.

that sounds like a downgrade compared to the integration-marketplace tools. we've been on both sides of this and we'd rather have a real api with a few example scripts than a directory of fifty pre-built integrations that mostly do half of what you wanted.

a pre-built slack integration tells you "a post was created." a webhook plus six lines of slack web api gives you "a post crossed 50 votes, here's the title, here's the link, here's who's voted on it, ping @dima." you can't get that out of an integration directory.

the bet

the bet we're making is that the people who pick spirby are going to wire something up in the first month. they'll write a small script. they'll connect it to the rest of their stack. when they do, they're not going to want to upgrade to a higher tier first.

that's why the api ships on the $19 starter. it's also why we're writing this post on day one. the api is the wedge. it's the reason to pick spirby over canny if you're an indie team that knows how to call an http endpoint. we want that to be obvious.

if you'd like to see what's possible, the openapi spec lives at docs.spirby.com/api. curl it. inspect the schema. mint a key. write the script you've been putting off.

that's the whole product story.