> ## Documentation Index
> Fetch the complete documentation index at: https://docs.haloagents.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# REST API & Webhooks

> OAuth-authenticated endpoints under /api/v1 for integrations: run Halo tools, read recent records, fill pickers, and subscribe URLs to workspace events.

The endpoints under `https://app.haloagents.ai/api/v1` are for integrations that act on behalf of a workspace: the [Zapier app](https://docs.haloagents.ai/integrations/zapier.md) is built on them. They differ from the [SDK endpoints](https://docs.haloagents.ai/api-reference/overview.md) in one way: they take an **OAuth 2.0 access token**, never the publishable widget key.

> [!NOTE]
>
> The REST API is the **API access** capability (Pro and up). A token for a workspace on another plan gets `403 upgrade_required`.

## Authentication

Halo's authorization server is the one the [MCP connector](https://docs.haloagents.ai/mcp/overview.md) uses. Discovery lives at `/.well-known/oauth-authorization-server`; the flow is authorization code with PKCE, refresh tokens rotate, and the consent screen lets the person pick the scopes they grant.

To get a token that works here, bind the grant to this API with the RFC 8707 `resource` parameter on both the authorize and the token request:

```
resource=https://app.haloagents.ai/api/v1
```

Then send it on every call:

```
Authorization: Bearer <access_token>
```

Register a client with `POST /api/mcp/oauth/register` (dynamic client registration; use `token_endpoint_auth_method: client_secret_post` for a confidential client).

### Scopes

| Scope | Grants |
| - | - |
| `data:read` | Tickets, contacts, companies, events and feedback: records, lookups, and webhook events about them |
| `data:write` | The contact and company write tools |
| `inbox:write` | The ticket tools (create, reply, note, status, assign, move) |
| `cs:read` / `cs:write` | Deals, issues, pipelines and the tasks tools |
| `workflows:read` / `workflows:write` | Workflow listing and enrollment |

A call the token's scopes do not cover returns `403 insufficient_scope`. Every call is also limited to what the person who granted the token can do in the dashboard.

### Errors

Errors are `{ "error": "<code>", "error_description": "<message>" }`. `401 invalid_token` carries a `WWW-Authenticate` challenge; `429 rate_limited` carries `Retry-After` (300 requests per minute per token).

## Endpoints

### `GET /api/v1/me`

The workspace, the person who granted the token and the scopes it holds.

```json theme={null}
{
  "org": { "id": "…", "name": "Acme Inc" },
  "user": { "id": "…", "name": "Jane Doe", "email": "jane@acme.com" },
  "client_id": "mcp_…",
  "scopes": ["data:read", "inbox:write"],
  "resource": "https://app.haloagents.ai/api/v1",
  "expires_at": "2026-11-02T12:00:00.000Z"
}
```

### `GET /api/v1/tools` and `POST /api/v1/tools/{name}`

The tools the token may call, with their JSON Schema, and a way to run one: the body is the tool's arguments, the response is `{ "tool": "<name>", "result": … }`. These are the same tools the MCP connector exposes (`inbox_create_ticket`, `inbox_reply_ticket_email`, `data_create_user`, `data_update_company`, `cs_create_deal`, `cs_move_deal_stage`, `create_task`, `cs_create_issue`, `workflow_enroll_records`, …), run through the same validation, permission checks and audit log.

```bash theme={null}
curl -X POST https://app.haloagents.ai/api/v1/tools/inbox_create_ticket \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{ "query": "Export to CSV is missing columns", "end_user_id": "…" }'
```

A tool that ran but failed at the business level (not found, validation, not allowed) is a 4xx with the tool's message.

### `GET /api/v1/records/{type}`

The newest records of one kind, in the same shape webhook deliveries carry them, with simple filters. `limit` is 1–50 (default 3).

| Type | Filters |
| - | - |
| `tickets` | `status`, `q` (message contains) |
| `contacts` | `email` (exact), `external_id`, `contact_type` (`user` / `lead`), `q` (name or email contains) |
| `companies` | `domain` (exact), `external_id`, `q` (name or domain contains) |
| `deals` | `pipeline_id`, `status` (`open` / `won` / `lost`), `q` (name contains) |
| `issues` | `status`, `q` (title contains) |
| `events` | `event_name` |
| `feedback` | `rating` (`up` / `down`) |

Response: `{ "records": [ … ] }`.

### `GET /api/v1/lookups/{kind}`

Short `{ id, name }` lists for pickers: `members`, `agents`, `inboxes`, `pipelines`, `stages` (`?pipeline_id=` narrows), `workflows`, `ticket_statuses`, `issue_priorities`. Response: `{ "items": [ … ] }`.

## Webhooks

A webhook subscription asks Halo to POST events to a URL. Subscriptions are per workspace and per token's OAuth client: deliveries stop while that client holds no live token (the person disconnected it).

### `GET /api/v1/webhooks/events`

The catalog. Each event needs its object's read scope to subscribe.

| Event | Object | Scope | Filters |
| - | - | - | - |
| `ticket.created` | ticket | `data:read` | |
| `ticket.escalated` | ticket | `data:read` | |
| `ticket.status_changed` | ticket | `data:read` | `status` |
| `contact.created` | contact | `data:read` | |
| `lead.captured` | contact | `data:read` | |
| `company.created` | company | `data:read` | |
| `deal.created` | deal | `cs:read` | |
| `deal.stage_changed` | deal | `cs:read` | |
| `deal.won` | deal | `cs:read` | |
| `issue.created` | issue | `cs:read` | |
| `feedback.negative` | feedback | `data:read` | |
| `health.score_dropped` | company | `data:read` | |
| `renewal.at_risk` | company | `cs:read` | |
| `event.tracked` | event | `data:read` | `event_name` |

### `POST /api/v1/webhooks`

```json theme={null}
{
  "url": "https://hooks.example.com/halo",
  "events": ["ticket.escalated", "ticket.status_changed"],
  "filters": { "status": "resolved" },
  "description": "Pager"
}
```

Returns `201` with `{ "webhook": { … }, "secret": "whsec_…" }`. **The secret is shown once.** `url` must be https on a public host. A workspace may hold 100 active subscriptions.

### `GET /api/v1/webhooks`, `GET /api/v1/webhooks/{id}`, `DELETE /api/v1/webhooks/{id}`

List, read, and remove the subscriptions this token's OAuth client registered; another client's are never visible. `DELETE` answers `204`, or `404` when it was already gone.

### Deliveries

Each delivery is a `POST` with the envelope:

```json theme={null}
{
  "id": "4c7f…",
  "event": "ticket.status_changed",
  "object": "ticket",
  "object_id": "9f8e…",
  "org_id": "…",
  "occurred_at": "2026-11-02T10:15:00.000Z",
  "actor": { "user_id": "…" },
  "data": {
    "ticket_id": "9f8e…",
    "ticket": { "id": "9f8e…", "status": "resolved", "subject": "…", "contact": { … }, "company": { … }, "assignee": { … }, "url": "…" },
    "old_status": "open",
    "new_status": "resolved",
    "status": "resolved"
  }
}
```

`id` is stable across retries of the same event: deduplicate on it. Headers:

| Header | Value |
| - | - |
| `X-Halo-Event` | The event id |
| `X-Halo-Delivery` | The envelope `id` |
| `X-Halo-Timestamp` | Unix seconds |
| `X-Halo-Signature` | `t=<timestamp>,v1=<hex HMAC-SHA256(secret, "<timestamp>.<raw body>")>` |

Verify by recomputing the HMAC over `<timestamp>.<raw body>` with the subscription's secret and comparing in constant time; reject timestamps older than a few minutes.

Answer `2xx` within 10 seconds. A `5xx`, `408`, `429` or timeout is retried with backoff; any other `4xx` is final for that event. After 10 consecutive failed events the subscription is switched off (`disabled_reason: too_many_failures`); a `410 Gone` switches it off at once (`receiver_gone`).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.