Content

Aha! API Integration for Product Agents

Promoting an idea to a feature is a promise to whoever voted for it. How to wire the Aha! API to an agent, with reference numbers, two rate limits, and reviewable writes.

Chaitrali Kakde

DevRel Engineer

IntegrationsOct 23, 2026

Most API writes are private. You update a record, and the only people who notice are the people who go looking. Aha! is not like that. Promoting an idea to a feature and putting it on a release is a visible commitment, and if customers voted for that idea, some of them get told. Moving a feature's release date changes what your sales team promises next week.

So a product agent in Aha! is less about safety in the data-loss sense and more about not having a language model make commitments on your behalf. That shapes which writes you allow and which ones you gate.

Two things about the API that catch everyone

The base URL contains your company. There is no shared api.aha.io host. You call https://<yourcompany>.aha.io/api/v1/, using your own account subdomain. A model writing this from memory will reach for the generic host and get nothing useful back.

Records are addressed by reference number, not UUID. A feature is APP-123, so GET /api/v1/features/APP-123 is a real call. This is genuinely nice for agents: the identifier a human says out loud in standup is the identifier the API takes. Someone can type "summarise APP-412 and its comments" and the agent needs no lookup step at all.

Auth is a bearer token either way. OAuth2 is the preferred route for an app that multiple people will authorise; a personal API key from the Aha! UI is sent in the same Authorization: Bearer header and keeps working after a password change. HTTP Basic was supported historically and is no longer recommended, so do not copy an old example.

One note on API keys: they are tied to a user. If that person leaves and their account is deactivated, your agent stops working, and the error will not say so. For anything long-lived, use a service account or OAuth rather than someone's personal key.

Two rate limits, not one

Aha! enforces 300 requests per minute and 20 requests per second, per account. Both apply. Pass either one and you get a 429.

The per-second limit is the one agents hit, because an agent that decides to enrich 40 features will fire them as fast as the event loop allows, burn 20 requests in a second, and stall, all while sitting comfortably inside the per-minute budget. Then it retries and does it again.

Usefully, Aha! tells you where you stand: X-Ratelimit-Limit, X-Ratelimit-Remaining and X-Ratelimit-Reset come back on responses, and the reset value is a UTC unix timestamp telling you exactly when it is safe to try again. Respect it rather than guessing at a backoff interval.

Rate-limit errors are retried with exponential backoff automatically, which handles the recovery. The design fix is on your side: batch your reads. GET /api/v1/releases/{release_ref}/features takes page and per_page up to 200, so one call can replace forty.

Setting it up

setup.sh
npx swytchcode
swy login
swy init --mode=sandbox

swy get aha
swy auth connect aha            # paste the key, or run the OAuth flow
swy auth status

# find what you need, always scoped
swy discover "list ideas" --project aha
swy discover "create a feature" --project aha
swy discover "add a comment" --project aha
swy list methods aha

swy info <canonical_id>         # check inputs before allowing
swy add method <canonical_id>

Run swy get before swy info or info will report a real method as missing, and keep --project aha on every discover call, because semantic search spans the whole catalog and "create a feature" matches things in several other products.

The split we would ship for a product agent:

  • Allow freely: read features, read ideas, read releases, read comments. Reading your own roadmap is harmless and most of the value is here.
  • Allow with a policy: add a comment, set a tag or custom field, create a feature in a designated inbox release.
  • Do not add at all: delete anything, change a release date, change a feature's status to shipped. None of those should be reachable by a model, and the clean way to guarantee that is to leave them out of the allowlist entirely.

The policy that matters here

Promotion is the write worth gating. An idea sitting in the inbox is a suggestion; the same idea as a feature on next quarter's release is a roadmap commitment. Let the agent propose it and let a product manager confirm.

policies.json
{
  "id": "promotions-need-a-pm",
  "target": ["<aha create-feature canonical_id>"],
  "when": { "field": "release_id", "operator": "!=", "value": "<triage-inbox-release-id>" },
  "action": {
    "type": "REQUIRES_APPROVAL",
    "message": "Agent wants to add a feature to a real release, not the triage inbox"
  },
  "approval_timeout": "8h"
}

The shape is the useful bit: writes into a holding area flow freely, writes into anything customer-visible stop and ask. The eight hour window is deliberate, because product decisions are not urgent and a PM should get to it in a working day. If nobody approves, it fails closed and the feature is never created. Details in the human approval docs, schema in the policy reference, and a full walkthrough in how to set up policy guardrails.

Because the rule lives in policies.json in your repo, changing which releases are fair game is a pull request someone reviews. That matters more than it sounds in product orgs, where "who let the bot put that on the roadmap" is a question with political weight. More on that in policy-as-code for AI agents.

Idempotency, and the duplicate comment

Creating a feature or a comment is not idempotent. The familiar failure applies: the request lands, Aha! creates the record, the response is lost, the agent retries, and now there are two identical features in the triage inbox or the same comment twice on a customer-visible idea.

Writes are only auto-retried when an idempotency key is set, so an unprotected create fails visibly rather than silently duplicating. Derive the key from the source of the work, not the attempt: the support ticket ID that prompted the feature, or the idea reference plus the comment's purpose. A retry twenty minutes later then produces the same key. Background in API idempotency: the complete guide and the mechanics in the idempotency docs.

Cheap extra protection: before creating, search for a feature already referencing that ticket ID. Reads are generous here, writes are the thing you want to be careful with.

In Claude Code or Cursor

This is a strong MCP use case because of the reference numbers. You are in a pull request, you want to know what APP-318 actually asked for, and the agent can fetch it without you switching tabs. Connect through the MCP server and your allowlist and policies come with it. Per-client setup is in the MCP setup guide, the reference is in the MCP docs, and skills.md gives a coding agent the commands so it can wire this up without you narrating each step.

What you can build

  1. Idea triage that drafts, not decides. Read new ideas, cluster the duplicates, summarise each cluster with vote counts and the customers involved, and post the digest for a PM. The agent does the reading nobody has time for; the human makes the call.
  2. A roadmap digest for the rest of the company. Weekly, read the current release's features, note what changed status since last week, and post plain-language updates to Slack. Read-only. Removes a recurring meeting.
  3. Support signal into product signal. When a ticket in Intercom or FreshDesk matches an existing idea, add a comment with the customer context and bump the evidence. When it does not, create an idea in the triage inbox. This is where an agent genuinely beats a human, because nobody reliably does this by hand.
  4. Spec drafting. Take a promoted feature, pull its linked ideas and comments, and draft a one-pager into Notion for the PM to edit. Writes land in your doc tool rather than on the roadmap, which keeps the risky surface small.
  5. Hygiene reporting. Find features with no owner, ideas untouched for six months, releases with dates in the past, and anything missing a description. Produce a list; let someone fix it.

Things that will surprise you

Custom fields are where real product data lives, and their keys rarely match their display labels. The agent will be told "set the T-shirt size to Large" and needs to know that field's actual key. Resolve the field keys once, store the mapping, and do not make the model guess.

Reference numbers are stable and readable but also prefixed per product, so APP-123 and WEB-123 are different things. In a multi-product workspace, an agent told just "123" has no idea which one you meant. Require the full reference.

FAQ

Should the agent be allowed to promote an idea on its own?

We would not. Promotion is a prioritisation decision, and prioritisation is the job you are presumably paying a product manager for. Let the agent assemble the evidence and propose; the approval step takes a PM ten seconds and keeps the roadmap something humans own.

Can I use a personal API key to get started?

Yes, and it is the fastest path for a prototype. Just know that it carries that user's permissions and dies with their account, so move to OAuth or a service identity before anything depends on it.

How do I keep the agent out of other products in our workspace?

A policy on the reference prefix or product ID in write calls, so a correct method aimed at the wrong product gets held. Combine that with the allowlist and the agent is scoped by capability and by target. The general pattern is in API access control for AI agents.

Where do I see what it did?

swy audit network for what ran, swy audit policy for what was held or refused. In a product org the second one is the interesting log, because it shows you what the agent kept trying to do, which is useful signal about whether your rules match reality.

More content