Stitchflow
Aha! logo

Aha! User Management API Guide

API workflow

How to automate user lifecycle operations through APIs with caveats that matter in production.

UpdatedFeb 25, 2026

Summary and recommendation

Aha! exposes a REST API at https://<yourcompany>.aha.io/api/v1 supporting API key (Bearer token) or OAuth 2.0 authentication. Core user operations - list, get, create (scoped to a product/workspace), and update including deactivation - are available.

There is no DELETE endpoint for users; setting enabled: false via PUT /api/v1/users/:id is the only supported offboarding method. No native SCIM endpoint exists; the API is the closest available programmatic alternative. Stitchflow connects to Aha!

through an MCP server with ~100 deep IT/identity integrations, enabling automated provisioning and deprovisioning workflows without building or maintaining custom API logic.

API quick reference

Has user APIYes
Auth methodAPI Key (Bearer token) or OAuth 2.0
Base URLOfficial docs
SCIM availableNo
SCIM plan requiredEnterprise

Authentication

Auth method: API Key (Bearer token) or OAuth 2.0

Setup steps

  1. Option A – API Key: Log in to Aha!, go to Settings → Personal → Developer, click 'Generate API key', name it, copy the key (shown only once). Pass as: Authorization: Bearer .
  2. Option B – OAuth 2.0: Register an OAuth2 application in Aha! (Settings → Personal → Developer → OAuth applications → Register OAuth application) to obtain client_id and client_secret.
  3. Redirect the user to https://.aha.io/oauth/authorize?client_id=...&redirect_uri=...&response_type=code (authorization code flow) or response_type=token (implicit flow).
  4. Exchange the returned code for an access token via POST https://.aha.io/oauth/token with client_id, client_secret, code, grant_type=authorization_code, redirect_uri.
  5. Use the returned access_token as: Authorization: Bearer on all subsequent API requests.
  6. Include a descriptive User-Agent header on every request, e.g. User-Agent: MyApp (admin@example.com).

User object / data model

Field Type Description On create On update Notes
id string (numeric 64-bit) Unique Aha! user identifier. system-generated immutable Used as path parameter for single-user endpoints.
email string User's email address; used as login identifier. required optional
first_name string User's first name. required optional
last_name string User's last name. required optional
enabled boolean Whether the user account is active. Set to false to deactivate. optional optional Default is false in update presets; must be explicitly set to true to enable.
administrator boolean Whether the user has account-level administrator privileges. optional optional Default is false.
role string (enum) Workspace-level role assigned to the user. Known values include 'viewer'. optional optional Scoped to a specific product/workspace when creating via /api/v1/products/:product_id/users.
created_at string (ISO 8601 datetime) Timestamp when the user record was created. system-generated immutable
updated_at string (ISO 8601 datetime) Timestamp of the last update to the user record. system-generated system-generated

Core endpoints

List all users

  • Method: GET
  • URL: https://company.aha.io/api/v1/users
  • Watch out for: Returns paginated results (default 30/page, max 200). Rate limit is per account, not per token.

Request example

curl -g "https://company.aha.io/api/v1/users" -X GET \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <token>"

Response example

{
  "pagination": {"total_records": 50, "total_pages": 2, "current_page": 1},
  "users": [{"id": "123", "email": "user@example.com", "first_name": "Sam"}]
}

Get a specific user

  • Method: GET
  • URL: https://company.aha.io/api/v1/users/:id
  • Watch out for: Returns 404 if the authenticated user does not have access to the requested record.

Request example

curl -g "https://company.aha.io/api/v1/users/1020675218" -X GET \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <token>"

Response example

{
  "user": {
    "id": "1020675218",
    "email": "sam@example.com",
    "first_name": "Sam",
    "last_name": "Doe",
    "enabled": true,
    "administrator": false
  }
}

Create a user (invite to workspace)

  • Method: POST
  • URL: https://company.aha.io/api/v1/products/:product_id/users
  • Watch out for: User creation triggers an email invitation. Scoped to a specific product/workspace. No global account-level user creation endpoint documented.

Request example

curl "https://company.aha.io/api/v1/products/PRJ1/users" \
  -d '{"user":{"email":"sam@example.com","first_name":"sam","last_name":"doe","role":"viewer"}}' \
  -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json"

Response example

{
  "user": {
    "id": "9876543210",
    "email": "sam@example.com",
    "first_name": "sam",
    "last_name": "doe",
    "role": "viewer"
  }
}

Update a user (including deactivate)

  • Method: PUT
  • URL: https://company.aha.io/api/v1/users/:id
  • Watch out for: Setting enabled: false deactivates the user. The numeric user id is required in the URL path.

Request example

curl "https://company.aha.io/api/v1/users/1020675218" \
  -d '{"user":{"first_name":"Sarah","enabled":false}}' \
  -X PUT -H "Authorization: Bearer <token>" -H "Content-Type: application/json"

Response example

{
  "user": {
    "id": "1020675218",
    "first_name": "Sarah",
    "enabled": false
  }
}

List portal users (Ideas portal)

  • Method: GET
  • URL: https://company.aha.io/api/v1/idea_portals/:idea_portal_id/portal_users
  • Watch out for: Portal users (Ideas portal) are separate from core Aha! workspace users and managed under a different resource path.

Request example

curl -g "https://company.aha.io/api/v1/idea_portals/1070474755/portal_users" -X GET \
  -H "Authorization: Bearer <token>" -H "Accept: application/json"

Response example

{
  "portal_users": [{"id": "646391926", "email": "user@example.com"}]
}

Create a portal user (Ideas portal)

  • Method: POST
  • URL: https://company.aha.io/api/v1/idea_portals/:idea_portal_id/portal_users
  • Watch out for: permission field controls portal access level (e.g., 'employee'). Separate from workspace roles.

Request example

curl "https://company.aha.io/api/v1/idea_portals/1070474755/portal_users" \
  -d '{"portal_user":{"email":"sam@example.com","first_name":"sam","last_name":"doe","permission":"employee"}}' \
  -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json"

Response example

{
  "portal_user": {
    "id": "999001",
    "email": "sam@example.com",
    "permission": "employee"
  }
}

Update a portal user (Ideas portal)

  • Method: PUT
  • URL: https://company.aha.io/api/v1/idea_portals/:idea_portal_id/portal_users/:id
  • Watch out for: Both the idea_portal_id and portal_user id are required in the path.

Request example

curl "https://company.aha.io/api/v1/idea_portals/1070474755/portal_users/646391926" \
  -d '{"portal_user":{"first_name":"Sarah"}}' \
  -X PUT -H "Authorization: Bearer <token>" -H "Content-Type: application/json"

Response example

{
  "portal_user": {
    "id": "646391926",
    "first_name": "Sarah"
  }
}

Validate webhook endpoint

  • Method: GET
  • URL: https://company.aha.io/api/v1/webhooks/:callback_token
  • Watch out for: Webhooks use callback tokens, not standard Bearer auth. Used to validate webhook URL connectivity during setup.

Request example

curl -g "https://company.aha.io/api/v1/webhooks/22b7893e7fa1c4c60847090f78fbf0ec" -X GET \
  -H "Authorization: Bearer <token>"

Response example

{ "status": "ok" }

Rate limits, pagination, and events

  • Rate limits: Rate limits are applied per account (all API users in the same account share the limit). Exceeding either limit returns HTTP 429.
  • Rate-limit headers: Yes
  • Retry-After header: No
  • Rate-limit notes: Response headers on rate-limited requests: X-Ratelimit-Limit, X-Ratelimit-Remaining, X-Ratelimit-Reset (UTC unix timestamp when limit resets). No Retry-After header; use X-Ratelimit-Reset to determine when to retry.
  • Pagination method: offset
  • Default page size: 30
  • Max page size: 200
  • Pagination pointer: page / per_page
Plan Limit Concurrent
All plans 300 requests/minute AND 20 requests/second 0
  • Webhooks available: Yes
  • Webhook notes: Aha! supports two outbound webhook types: (1) Activity webhooks - send workspace or account-level record change events to a third-party URL; configurable at account or workspace level via Settings → Integrations. Activity webhooks only send updated fields, not full records, and have a 5-minute delay. (2) Audit webhooks - provide a live stream of all events (create, update, destroy) occurring within Aha! for custom logic or compliance auditing. Inbound webhooks (from external tools like Salesforce, GitHub, etc.) are also supported via callback tokens.
  • Alternative event strategy: Poll GET /api/v1/users and related endpoints for user-state changes if webhook coverage for user events is insufficient.
  • Webhook events: audit (create), audit (update), audit (destroy), activity (record type changes - features, releases, epics, etc.), activity (field-level changes), activity (workspace-level changes)

SCIM API status

  • SCIM available: No
  • SCIM version: Not documented
  • Plan required: Enterprise
  • Endpoint: Not documented

Limitations:

  • No native SCIM support; feature has been requested by users.
  • JIT (Just-in-Time) provisioning via SAML SSO is supported but creates users with no default role or workspace access.
  • Administrators must manually assign roles and workspace access after JIT provisioning.
  • Custom SAML attributes (e.g., ProductPrefix/ProductRole) can be used for role mapping workarounds.
  • API-based user management (REST) is available as an alternative to SCIM for provisioning/deprovisioning.

Common scenarios

Three primary automation scenarios are supported by the API. First, deprovisioning a leaver: retrieve the user's numeric id via GET /api/v1/users (match on email), then call PUT /api/v1/users/:id with {"user":{"enabled":false}}; confirm enabled: false in the response.

Second, provisioning a new user: POST to /api/v1/products/:product_id/users with email, first_name, last_name, and role - this triggers an email invitation automatically, so batch imports should be sequenced carefully.

Third, roster sync: paginate GET /api/v1/users (default 30/page, max 200 via per_page) using pagination. total_pages, collect id, email, enabled, and administrator fields, diff against your directory, and issue PUT calls for discrepancies.

Workspace users and Ideas portal users are separate resources with distinct endpoints and permission models - do not conflate them.

Deprovision a leaver (disable user account)

  1. Identify the user's Aha! numeric id by calling GET /api/v1/users?page=1&per_page=200 and matching on email.
  2. Call PUT /api/v1/users/:id with body {"user":{"enabled":false}} using an admin-level API key.
  3. Verify the response returns enabled: false to confirm deactivation.

Watch out for: There is no DELETE endpoint for users - deactivation via enabled: false is the supported offboarding method. The user id (not email) is required in the URL.

Provision a new user to a workspace

  1. Obtain the target workspace's product_id (e.g., PRJ1) from the Aha! UI or GET /api/v1/products.
  2. Call POST /api/v1/products/:product_id/users with body {"user":{"email":"...","first_name":"...","last_name":"...","role":"viewer"}}.
  3. The user receives an email invitation; an admin must manually assign additional workspace roles or permissions if needed.
  4. Store the returned user id for future update or deactivation calls.

Watch out for: User creation sends an email invite automatically - suppress or batch carefully during bulk imports. No global account-level user creation endpoint exists; provisioning is always scoped to a product/workspace.

Sync user roster to an external directory

  1. Call GET /api/v1/users?page=1&per_page=200 and iterate through all pages using the pagination.total_pages field.
  2. For each page, collect id, email, first_name, last_name, enabled, administrator fields.
  3. Compare against your directory source of truth and identify discrepancies.
  4. For users to deactivate, call PUT /api/v1/users/:id with {"user":{"enabled":false}}.
  5. Respect the 300 req/min and 20 req/sec rate limits; back off on HTTP 429 using X-Ratelimit-Reset.

Watch out for: All API users in the same Aha! account share the rate limit pool. A sync job running alongside other integrations may exhaust the limit faster than expected.

Why building this yourself is a trap

Several non-obvious constraints affect API-based user management in Aha!. Rate limits are enforced per account, not per token - all integrations sharing an account pool the same 300 req/min and 20 req/sec ceiling; a sync job running alongside other integrations can exhaust the limit faster than expected.

HTTP 429 responses do not include a Retry-After header; use X-Ratelimit-Reset (UTC unix timestamp) to determine backoff timing. API keys are tied to a specific user, not a service account - all actions are attributed to that user, and key rotation requires manual reissuance.

Creating users via the API sends email invitations by default with no documented suppression flag, which is disruptive during bulk imports. Custom fields are omitted from list endpoint responses by default; use the fields= query parameter to include them.

Finally, there is no global account-level user creation endpoint - provisioning is always scoped to a specific product/workspace, meaning multi-workspace onboarding requires multiple POST calls.

Automate Aha! workflows without one-off scripts

Stitchflow builds and maintains identity workflows for your exact setup. We cover every app, including the ones without APIs, and run deterministic trigger-to-report workflows with human approvals where they matter.

Every app coverage, including apps without APIs
60+ deep API integrations plus browser automation where needed
Identity graph reconciliation across apps and your IdP
Less than a week to launch, maintained as APIs and admin consoles change
SOC 2 Type II. ~2 hours of your team's time

UpdatedFeb 25, 2026

* Details sourced from official product documentation and admin references.

Keep exploring

Related apps

ActiveCampaign logo

ActiveCampaign

API Only
AutomationAPI only
Last updatedFeb 2026

ActiveCampaign uses a group-based permission model: every user belongs to exactly one group, and all feature-area access (Contacts, Campaigns, Automations, Deals, Reports, Templates) is configured at the group level, not per individual. The default Adm

ADP logo

ADP

API Only
AutomationAPI only
Last updatedFeb 2026

ADP Workforce Now is a mid-market to enterprise HCM platform that serves as the HR source of record for employee data — payroll, benefits, time, and talent. User access is governed by a hybrid permission model: predefined security roles (Security Maste

Adyen logo

Adyen

API Only
AutomationAPI only
Last updatedFeb 2026

Adyen user management is handled entirely through the Customer Area (Settings > Users) using a predefined role-based access control model. There are no custom roles — all roles are defined by Adyen, and admins can only assign roles they themselves alre