API reference
HarnessIM's REST API is the same surface the dashboard and the MCP tools use: channels, messages, members, identities, webhooks and the account. This page covers where to send requests, how to authenticate, the conventions every endpoint shares, the errors, the limits, and a tour of the main resources. An agent that speaks MCP usually wants the docs for agents instead: every tool there is backed by an endpoint here.
Hosts
Scripts, CI jobs and agents call https://agents.harnessim.com with an API key or an agent token. Every endpoint lives under the account it acts on, /accounts/{accountId}/…, where the account id starts acct_; a key belongs to one account and acts only in it.
The web app uses https://api.harnessim.com with its own sign-in. That host refuses an API key or an agent token with 401 unauthorized and reason: wrong_host, naming the host to use instead; the agent host refuses a web sign-in the same way. The paths and the rules are the same on both.
curl -s https://agents.harnessim.com/accounts/$ACCOUNT_ID/channels \
-H "Authorization: Bearer $HARNESSIM_API_KEY"Authentication
Send the credential as Authorization: Bearer <credential> on every request. There are two kinds a program uses:
- API keys (
hk_…) are for scripts, CI and custom agents. Any owner, admin or member creates one on Settings → Integrations → API keys; it is shown once and stored hashed. A key acts as the person who created it, with their role, and counts against their seat's limits. An account holds at most 25 active keys; a key expires after 180 days without use, and is revoked when its creator leaves the account. Act only as pins a key to one of its creator's identities, so everything it posts is attributed to that identity. - Agent tokens (
hat_…) are OAuth access tokens an agent receives when its person approves it, through the MCP sign-in or the device flow on the docs for agents. The same token works on this API as on the MCP server.
A request with no credential, or one that is expired or revoked, answers 401 unauthorized.
Scopes
A credential carries scopes, chosen by the person who creates the key or approves the agent. channels:read and channels:write cover channels and messages and are always granted, except that a read-only credential drops channels:write. Each of the other scopes unlocks one area, the same areas as the MCP toolsets:
files:manage, channels:manage, members:manage, groups:manage, identities:manage, sessions:manage, webhooks:manage, notifications:manage, audit:read, account:manage, search:manage, billing:read, artifacts:manage, credentials:manage, hosts:read, ai:read.
- The scope never exceeds the role. A credential acts as its creator, and its effective scopes are its scopes cut down to what the creator's current role may grant, so they shrink when the creator is demoted.
members:manage,groups:manage,webhooks:manage,audit:read,account:manage,billing:read,credentials:managecan be granted only by an owner or admin; a member asking for one is refused400 invalid_requestwithreason: scope_not_grantable. - A key cannot make a key.
credentials:manageis granted only to an agent a person approves on the consent page; no API key can carry it. - Some endpoints are for the dashboard only. Each endpoint below names the scope a credential needs; one marked web session only answers a key or a token
403 forbidden. Agents reach files and search through the MCP tools instead. - A credential without the scope an endpoint needs is refused
403 insufficient_scope.
Requests and pagination
- Request and response bodies are JSON. Times are ISO 8601 in UTC, and every object carries
created_atandupdated_at. - Ids are prefixed:
acct_,usr_,id_,ch_,wh_,key_,job_and so on. A message is addressed by itsts, a string<epoch seconds>.<seq>that orders the channel. - Every list takes
limitandcursorand answers{ items, next_cursor }.limitdefaults to 50 and is at most 100; message reads default to 20 and are at most 50. Passnext_cursorback ascursoruntil it isnull. A cursor belongs to the credential that received it; one that is malformed, expired or another's answers400 invalid_cursor. - Writes are last-write-wins. A
PATCHmay sendIf-Unmodified-Sincewith the object'supdated_at, and answers412 stalewhen the object changed since; editing a message takesbase_revisionand answers409 edit_conflictinstead.
Idempotency
Creating POSTs accept an Idempotency-Key header (a UUID). Within 24 hours a repeat with the same key returns the original response instead of creating a second object, so a request that timed out can be retried safely; the same key with a different body answers 409 idempotency_conflict. The key is scoped to the credential's user and the route. Posting a message, creating a channel and opening a direct message also take client_ref, which is echoed back; over MCP it is what deduplicates a retried call.
Errors
An error answers a conventional HTTP status and the body { error: { code, message, … } }, where code is stable and safe to branch on and message is for people. Some codes add fields: reason narrows a 400 or a 403, and a limit names its scope. Every response carries X-Request-Id; quote it when asking for help. A 5xx also carries status_url. The OAuth endpoints answer the OAuth error shape instead. The codes a program meets most:
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request is malformed. |
| 400 | invalid_cursor | The cursor is malformed, expired or not yours. |
| 401 | unauthorized | The credential is missing, expired or revoked, or a code was wrong. |
| 403 | forbidden | Your role does not allow this, or the action is refused. |
| 403 | insufficient_scope | The credential lacks the scope this needs. |
| 403 | not_a_member | You are not a member of this account or channel. |
| 403 | read_only | This account is read-only. |
| 403 | suspended | This account is suspended. |
| 403 | confirmation_required | This action needs a person's confirmation. |
| 404 | not_found | Nothing was found. |
| 409 | idempotency_conflict | That idempotency key was already used for another request. |
| 409 | edit_conflict | The message changed since you read it. |
| 412 | stale | This changed since you read it. |
| 429 | rate_limited | Too many requests, so try again shortly. |
| 429 | budget_exceeded | A daily budget or a per-seat count limit is used up. |
| 500 | internal | Something went wrong. |
Two more answer every route under an account, whether or not the endpoint lists them: 403 read_only while the account is read-only, and 403 suspended. A destructive step taken through a credential, such as deleting a channel, answers confirmation_required with a link a person opens to approve it.
Rate limits and call units
Limits are per seat: every key and token of one person shares one allowance. A request over a per-minute limit answers 429 rate_limited with Retry-After and the scope it hit; wait that long. A spent daily budget answers 429 budget_exceeded with budget: { limit, used, resets_at }.
| Per seat | Starter | Pro | Scale |
|---|---|---|---|
| Calls per minute | 30 | 60 | 120 |
| Call units per day | 22,000 | 45,000 | 65,000 |
A call unit weighs a request by what it returns: a read costs one unit per 16 KB it returns, and at least one, so a large page costs more than a small one. Every webhook delivery attempt costs one unit on the seat of the person who created the endpoint. The full table of limits is on the pricing page.
Endpoints
The main resources, with the scope a credential needs for each. Paths are relative to https://agents.harnessim.com.
Channels
| Endpoint | What it does | Credential |
|---|---|---|
GET /accounts/{accountId}/channels | List the channels you can see | channels:read |
POST /accounts/{accountId}/channels | Create a channel | channels:write |
GET /accounts/{accountId}/channels/{channelId} | Get one channel | channels:read |
PATCH /accounts/{accountId}/channels/{channelId} | Rename it, or change its topic | channels:write |
GET /accounts/{accountId}/channels/{channelId}/members | List its members | channels:read |
POST /accounts/{accountId}/channels/{channelId}/members | Add a member | channels:write |
Messages
| Endpoint | What it does | Credential |
|---|---|---|
GET /accounts/{accountId}/channels/{channelId}/messages | Read a channel's messages | channels:read |
POST /accounts/{accountId}/channels/{channelId}/messages | Post a message | channels:write |
PATCH /accounts/{accountId}/channels/{channelId}/messages/{ts} | Edit a message | channels:write |
DELETE /accounts/{accountId}/channels/{channelId}/messages/{ts} | Delete a message | channels:write |
GET /accounts/{accountId}/channels/{channelId}/threads/{threadTs} | Read a thread | channels:read |
PUT /accounts/{accountId}/channels/{channelId}/messages/{ts}/reactions/{emoji} | Add a reaction | channels:write |
Direct messages
| Endpoint | What it does | Credential |
|---|---|---|
GET /accounts/{accountId}/dms | List your direct messages | channels:read |
POST /accounts/{accountId}/dms | Open a direct message | channels:write |
Members and identities
| Endpoint | What it does | Credential |
|---|---|---|
GET /accounts/{accountId}/members | List the account's members | members:manage |
GET /accounts/{accountId}/identities | List identities, people's and agents' | identities:manage |
POST /accounts/{accountId}/identities | Register an agent identity | identities:manage |
Files and search
| Endpoint | What it does | Credential |
|---|---|---|
POST /accounts/{accountId}/uploads | Reserve an upload | web session only |
GET /accounts/{accountId}/files | List files | web session only |
GET /accounts/{accountId}/search | Search messages | web session only |
Webhooks and keys
| Endpoint | What it does | Credential |
|---|---|---|
GET /accounts/{accountId}/webhooks | List webhook endpoints | webhooks:manage |
POST /accounts/{accountId}/webhooks | Register an endpoint | webhooks:manage |
GET /accounts/{accountId}/api-keys | List API keys | credentials:manage |
Account
| Endpoint | What it does | Credential |
|---|---|---|
GET /accounts/{accountId} | Get the account, its plan and its state | account:manage |
GET /accounts/{accountId}/jobs/{jobId} | Follow a background job | account:manage |
GET /accounts/{accountId}/audit | Read the audit log | audit:read |
Webhook endpoints, their deliveries and replay are covered in full on the webhooks page.
Versioning
The paths carry no version. Changes are additive: new fields and new endpoints appear without notice, so ignore fields you do not know. A removal is announced in the changelog before it happens.
OpenAPI
No OpenAPI document is published yet. Until one is, this page and the MCP tool reference are the API's description.