Ionhour Docs
Integrations

Authentication

Create an API key, authenticate against /api/v1, and understand permission levels, resource scoping, role projection, rate limits, and workspace isolation.

The Integration API authenticates with API keys — the same ionh_ keys used by the MCP integration. Every key is bound to a single workspace, and every request is scoped to that workspace.

Create an API key

  1. In the Ionhour dashboard, open your avatar menu and choose API Keys — this lands on the Developer Console's Credentials page. Pick the target workspace with the switcher in the page header.
  2. Click Create token and give it a descriptive name.
  3. Choose a permission level (read-only or read/write) and, optionally, restrict it to specific resources (see below).
  4. Copy the key immediately — the full value is shown only once.

API keys have the format ionh_ followed by 32 hex characters. Ionhour stores only a SHA-256 hash plus a short display prefix; the plaintext is never retrievable after creation.

Treat API keys like passwords. Store them in your secret manager and never commit them to source control.

Use the key

Pass the key as a Bearer token on every /api/v1 request:

curl -H "Authorization: Bearer ionh_your_api_key_here" \
  https://api.ionhour.com/api/v1/whoami

The whoami endpoint is the recommended smoke test — it validates the key and echoes back the resolved context without touching any resource:

{
  "workspaceId": 42,
  "workspaceName": "Acme Production",
  "permissionLevel": "read_write",
  "projectedRole": "admin",
  "apiKeyPrefix": "ionh_1a2b"
}
whoami is resource-agnostic: it works even for a key that has been restricted to a narrow set of resources, so you can always confirm a key authenticates.

Permission levels

Each key carries one of two permission levels:

LevelValueAccess
Read-onlyread_onlyGET endpoints only. Any write (POST, PATCH, DELETE) returns 403 KEY_READ_ONLY.
Read/writeread_writeAll endpoints the key's role and resources allow.

Resource scoping

A key can optionally be restricted to an allowlist of resources. When set, the key may only call endpoints that map to one of the allowed resources; anything else returns 403 KEY_SCOPE_DENIED. Leaving the allowlist empty means the key can reach every resource.

The Integration API and MCP share the same resource identifiers, so a restriction you set in the dashboard applies identically over both surfaces.

Resource identifierCovers
mcp:incidentsIncident endpoints
mcp:status_pagesStatus-page and announcement endpoints
mcp:deploymentsDeployment endpoints
mcp:maintenance_windowsMaintenance-window endpoints
mcp:projectsProject endpoints
mcp:checksCheck endpoints and /v1/regions
mcp:jobsJob endpoints, and creating jobs from a slug ping
mcp:signalsSending slug pings
mcp:workspaceWorkspace introspection (whoami is exempt from the allowlist check)
The identifiers use underscores (mcp:status_pages), not hyphens. These are the exact strings the guard checks.

Role projection

A key never has more authority than its owner. The guard projects the owner's current workspace role through the key, capped at ADMIN, and enforces a minimum role per endpoint:

  • Reads (GET) require viewer.
  • Incident, deployment, and maintenance writes require member.
  • Project, check, and job creates, updates, pauses, and resumes require member.
  • Status-page announcement writes and project, check, and job deletes require admin.

If the owner is no longer an active member of the workspace, the key is rejected with 403 KEY_OWNER_NO_ACCESS. If the owner's role is below what an endpoint requires, the response is 403 INSUFFICIENT_ROLE.

Service keys

A service key belongs to the workspace instead of a person. Use one for CI pipelines, Terraform, and other automation that must keep working when people join or leave the team.

Personal keyService key
Owned byThe user who created itThe workspace
RoleThe owner's current role, capped at adminFixed when the key is created: member or admin
ExpiryOptionalRequired, at most 365 days ahead
Who can create itThe user, for themselvesMember-role keys: owners and admins. Admin-role keys: owners only.
When the creator leavesStops workingKeeps working

Service keys use the same ionh_ format, permission levels, and resource allowlist as personal keys. They are available on plans that include service keys.

A service key that cannot be used returns 401 SERVICE_KEY_INACTIVE on every request. This covers a disabled or expired key, a deleted or unsubscribed workspace, and a plan without service keys. The response never says which reason applies; check the key's state in the workspace. A service key whose fixed role is below an endpoint's minimum gets 403 INSUFFICIENT_ROLE, like any other key.

Service keys outlive their creator, so a leaked one stays valid until it expires or is revoked. Keep the expiry short, restrict the key to the resources your automation needs, and never put it in a URL.

Workspace scoping

Every query and mutation is scoped to the key's workspace. A resource id that belongs to another workspace is treated as if it does not exist — you get 404 NOT_FOUND, never a cross-tenant leak.

Rate limits

The Integration API has its own rate limits, separate from the dashboard API and from MCP. Every limit is a one-minute window:

LimitApplies toSize
Per keyEach API key, or each OAuth app installationSet by the workspace plan
Per workspaceAll keys in the workspace combinedTwice the per-key limit
Per IP addressEvery request, before authenticationA high backstop against abuse
Failed authentications per IP addressRequests rejected with 401100 per minute

When a limit is exceeded, the API returns 429 with a Retry-After header in seconds. The body names the limit that was hit in scope, which is principal, workspace, or ip:

{
  "statusCode": 429,
  "error": "Too Many Requests",
  "code": "RATE_LIMIT_EXCEEDED_KEY",
  "message": "Per-key rate limit exceeded (60 requests per minute on the FREE plan).",
  "limit": 60,
  "scope": "principal",
  "plan": "FREE"
}

plan is left out for OAuth apps and for IP limits. Wait for Retry-After before you retry. Requests sent while blocked are rejected and still count against the per-IP limit.

Successful responses carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset for your key's budget. Pace bulk work by these headers instead of retrying on 429.

Error format

Every /api/v1 error uses a stable envelope. Pattern-match on code, not on the human-readable message:

{
  "statusCode": 403,
  "error": "Forbidden",
  "code": "KEY_SCOPE_DENIED",
  "message": "This API key is not permitted to access \"mcp:incidents\"."
}

Error codes

HTTPcodeMeaning
401MISSING_BEARERNo Authorization: Bearer header.
401INVALID_KEYKey is malformed, unknown, or disabled.
401KEY_EXPIREDKey is past its expiry date (response also carries expiredAt). Mint a new key.
401SERVICE_KEY_INACTIVEA service key cannot be used. The reason is never returned.
401INVALID_PRINCIPALThe credential does not resolve to a valid user.
403INTEGRATION_API_DISABLEDThe Integration API is not enabled for this workspace's plan.
403KEY_READ_ONLYA read-only key attempted a write.
403KEY_SCOPE_DENIEDThe endpoint's resource is not in the key's allowlist (response carries allowedResources).
403OAUTH_SCOPE_DENIEDAn OAuth app token was not granted the endpoint's resource at the required read or write level.
403KEY_OWNER_NO_ACCESSThe key's owner is no longer an active member of the workspace.
403INSUFFICIENT_ROLEThe owner's projected role is below the endpoint's minimum (response carries requiredRole and effectiveRole).
403DEPENDENCY_PROVIDER_MANAGEDAn incident was declared against an Ionhour-managed dependency (see Incidents).
403PLAN_LIMIT_EXCEEDEDA plan limit was hit (response carries detail). See Checks & Jobs.
429RATE_LIMIT_EXCEEDED_KEYA rate limit was exceeded (response carries limit, scope, usually plan, and a Retry-After header).
400VALIDATION_FAILEDRequest body or query parameters failed validation.
422VALIDATION_FAILEDA well-formed value was rejected, such as an unserved probe region.
409SLUG_TAKEN, PROJECT_NOT_EMPTY, IDEMPOTENCY_KEY_IN_PROGRESSResource conflicts. See Checks & Jobs.
422CHECK_TYPE_NOT_SUPPORTED, IDEMPOTENCY_KEY_REUSEDSee Checks & Jobs.
404NOT_FOUNDResource not found, or it belongs to another workspace.
500INTERNAL_ERRORUnexpected server error. The detail is logged server-side, never returned.