---
title: The developer API
description: API keys, the REST surface, webhooks and how they differ from the MCP connector
order: 2
---

For code that talks to SquadBear directly - a script, a backend job, a webhook listener.
MCP is the other surface ([setup](/docs/getting-started/connect-an-agent/)): a connected
agent acts as the person who connected it, on scopes consented per connection and separate
from the API-key scopes below.

## API keys

Create one at **Developer → API keys**
([app.squadbear.com/workspace/api-keys](https://app.squadbear.com/workspace/api-keys)),
admin and owner only. The token starts with `sbk_` and is shown once; only its display
prefix is stored, so a lost token is replaced, not recovered.

A key carries the scopes checked at creation - `people:read`, `teams:read`, `leave:read`,
`leave:write`, `balances:read`, `reports:read`, `time:read`, `time:write`,
`attendance:read`, `events:read`, `webhooks:write`, `admin:write`. An endpoint whose scope the
key lacks returns 403 naming it, not a partial result.

A key belongs to the **workspace**, not a user account: a tenant-scoped service actor
reading the whole tenant, audited to the employee who minted it. Scopes bound its reach,
not a role.

## The REST API

Base path is `/api/v1`, authenticated with `Authorization: Bearer <token>` - not a custom
header; a missing or malformed one returns 401 with `WWW-Authenticate: Bearer`.
Interactive docs are public at
[app.squadbear.com/api/v1/docs](https://app.squadbear.com/api/v1/docs), the same document
as JSON at `/api/v1/openapi.json`.

Attendance is read-only: `GET /attendance/shifts` and `GET /attendance/presence`. No bearer
key writes a punch - an agent punches over MCP (`punch`, `attendance:write`), a paired
kiosk posts to `/api/kiosk` with its device token.

## Webhooks

Manage subscriptions at **Developer → Webhooks**
([app.squadbear.com/workspace/webhooks](https://app.squadbear.com/workspace/webhooks));
its "Recent events" panel lists deliveries. Subscribe to every event, or pass an
`eventTypes` list at creation over API or MCP. Event names include `request.created`,
`request.approved`, `employee.created` and `process.run_completed`; unknown ones are
rejected. Webhooks are Team and Enterprise only - creating one on Free returns 402
`upgrade_required`, and a downgraded workspace's subscriptions go dormant, not deleted.

Every delivery is a signed POST: `X-Squadbear-Signature: sha256=<hex-hmac>`, HMAC-SHA256 of
the raw body keyed by the secret shown once at creation - verify it before trusting a
payload. Failed deliveries retry on the five-minute cron, 3 attempts per event; the third
failure is terminal. Delivery is at-least-once - backfill from `GET /api/v1/events` with
the last `seq` you processed as `after`, within the plan's retention window - 30 days on
Free, 365 on Team and Enterprise. Anything older is pruned nightly and cannot be replayed.

## UI names vs. wire names

Wire names are the stable contract; UI labels change. Where they differ:

| UI name | MCP tools | API route | Scope |
|---|---|---|---|
| People → Company documents (Files) | `list_files`, `get_file_link`, `delete_file` | `/api/files` | `files:read` / `files:write` |
| People → Company documents (Campaigns) | `list_policies`, `create_policy_rollout`, `acknowledge_document` | `/api/documents/policies` | `docs:ack` |
| Inbox → Needs action | `list_my_work`, `complete_process_action` | `/api/my-work`, `/api/my-tasks/summary` | `tasks:read` / `tasks:write` |
| Work items (project tasks) | `list_work_items`, `create_work_item` | `/api/projects/:id/tasks`, `/api/tasks/:id` | `time:read` / `time:write` |
| Leave → My leave | `request_time_off`, `list_my_requests` | `/api/v1/leave-requests` (public) · `/api/requests` (internal) | `leave:read` / `leave:write` |

The `tasks:*` scopes and `/api/tasks/:id` predate the task→work-item rename, so MCP says
`work_item` while the parameter noun stays `task` - `log_time` takes a `taskId`,
`get_time_report` groups by `task`. `files` is the wire umbrella; **Company documents** is
its `scope=tenant` slice.

## Worked example

Bartek, Northlake's ops engineer, mirrors new leave requests into a team chat: a key
scoped to `webhooks:write`, a subscription
`{"url": "https://hooks.example/northlake", "eventTypes": ["request.created"]}`, and a
receiver checking `X-Squadbear-Signature` against the one-time secret.

> [!AGENT]
> Create a webhook for https://hooks.example/northlake subscribed only to request.created events, and tell me the signing secret.

## Related

- [Connect Claude to SquadBear](/docs/getting-started/connect-an-agent/)
- [Prompts to run through your connected agent](/docs/agents/recipes/)
- [The people model in SquadBear](/docs/admin/people/)
