# REST API


# REST API

imatic products ship REST APIs you can automate from your own code. The conventions on this page — how you authenticate, how API keys work, how webhooks are signed, and how pagination, rate limits, and errors behave — are the same across products. What differs is the URL shape and the **resources** each product exposes: Calendar uses versioned `/v1` paths, while Survey uses verb-style `POST` endpoints. Each product's developer page documents its exact surface.

:::note[Examples are illustrative]

The request and response snippets below show the *shape* of calls — headers, auth, error envelopes — not an exact field-by-field schema. For the precise endpoints, parameters, and field names of a product, follow the links to that product's developer page.

:::

## Authentication

imatic uses two kinds of credentials, for two different callers:

- **JWT (session token)** — what the web app uses after you sign in with email and password. It represents *you* in the browser and is short-lived. You don't manage this directly; the app does.
- **Scoped API key** — what *your code* uses to call the `v1` REST API. You create it yourself, it carries explicit scopes, and it doesn't expire with a browser session.

For server-to-server calls, always use an API key — never your password or a copied session token.

### Sending an API key

Pass the key as a Bearer token in the `Authorization` header:

```bash
curl https://<product-host>/v1/event-types \
  -H "Authorization: Bearer cal_abc123.s3cr3t_keymaterial" \
  -H "Content-Type: application/json"
```

## How API keys work

You create and manage API keys in each product's developer area — for example Calendar's **Developers → API keys** and Survey's **Developers → API keys**. A key has three properties worth understanding:

- **`prefix.secret` format** — a key has a public **prefix** and a secret part, joined by a dot (for example `cal_abc123.s3cr3t_keymaterial` — Calendar keys use a short `cal_` prefix). The prefix lets imatic identify the key in lists and logs; the secret is what proves it's yours.
- **Shown once** — the full key value is displayed only at the moment you create it. After that, only the prefix is visible.
- **Scopes** — each key is limited to the actions you grant it. Calendar's scopes are `slots:read`, `bookings:read`, `bookings:write`, and `mcp` (the `mcp` scope is what an AI agent's key needs — see [MCP for AI agents](./mcp)). A request that exceeds a key's scope is rejected.

:::danger[Treat API keys like passwords]

A key's secret is shown only once, at creation. Copy it then and store it in a secret manager or environment variable — never in source control, a screenshot, or a shared doc. If a key is exposed, **revoke it** in the developer area and create a replacement. Revocation takes effect immediately.

:::

## Webhooks

Rather than polling for changes, register a webhook URL and imatic will `POST` an event to it when something happens — for example a booking is created in Calendar or a response is submitted in Survey. You manage webhook endpoints in each product's **Developers → Webhooks** area, where you can also send a test delivery.

A delivery looks roughly like this:

```http
POST /your/webhook/endpoint HTTP/1.1
Content-Type: application/json
X-Imatic-Signature: t=1718900000,v1=9f86d081884c7d659a2feaa0c55ad015...

{
  "event": "booking.created",
  "data": { "...": "event-specific payload" }
}
```

### Verifying the signature

Each delivery is signed with **HMAC-SHA256** using your endpoint's signing secret, in a Stripe-style `X-Imatic-Signature` header. The header carries two fields: `t`, the unix-epoch **seconds** at send time, and `v1`, the hex HMAC of the string `${t}.${body}` (the timestamp, a literal dot, then the exact raw request body). To verify, split the header, recompute `v1` over `t + "." + rawBody` with your signing secret, and compare in constant time. Reject if they don't match — and optionally reject when `t` is older than a few minutes to guard against replay.

```js
import crypto from "node:crypto";

function isValid(rawBody, signatureHeader, signingSecret) {
  // Header shape: "t=<epoch-seconds>,v1=<hex hmac>"
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((kv) => kv.split("=")),
  );
  const { t, v1 } = parts;
  if (!t || !v1) return false;

  // Sign the timestamped body: `${t}.${rawBody}` — NOT the raw body alone.
  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  // Constant-time comparison to avoid timing attacks.
  const a = Buffer.from(v1);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

:::note[Exact scheme per product]

The `t=…,v1=…` scheme above is Calendar's. Confirm header name, fields, and the signed string on each product's developer page before you ship a verifier.

:::

:::tip[Respond fast, process later]

Return a `2xx` quickly to acknowledge receipt, then do any slow work asynchronously. If your endpoint is slow or errors, deliveries may be retried.

:::

## Pagination

List endpoints return results in pages so a single call never has to load everything. Pass paging parameters on the request and read the paging metadata on the response to fetch the next page.

```bash
curl "https://<product-host>/v1/bookings?limit=25" \
  -H "Authorization: Bearer cal_abc123.s3cr3t_keymaterial"
```

```json
{
  "success": true,
  "data": [ { "...": "one item per result" } ],
  "pagination": { "page": 1, "limit": 20, "total": 0, "pages": 0 }
}
```

Keep requesting the next page until there are no more results.

## Rate limiting

API keys are rate limited to keep the platform fair and stable. If you exceed the limit, the API responds with HTTP `429 Too Many Requests`. Back off and retry — ideally with exponential backoff and jitter — rather than hammering the endpoint.

```json
{
  "success": false,
  "error": "rate_limited",
  "message": "Too many requests. Please retry after a short delay."
}
```

## Error handling

Errors use standard HTTP status codes with a JSON body that describes what went wrong. Codes you'll see most often:

- **`400`** — the request was malformed or failed validation.
- **`401`** — missing or invalid API key.
- **`403`** — the key is valid but lacks the scope (or org access) for this action.
- **`404`** — the resource doesn't exist or isn't visible to this key.
- **`409`** — a conflict, such as trying to book a slot that was just taken.
- **`429`** — rate limited; back off and retry.
- **`5xx`** — a server-side error; retry transient failures.

A typical error envelope:

```json
{
  "success": false,
  "error": "validation_failed",
  "message": "A human-readable explanation of the problem."
}
```

## Resources by product

The conventions above are shared, but each product exposes its own endpoints. Start from the product's developer page for exact resources, parameters, and field names:

- **Calendar** — event types, availability **slots**, bookings (create, reschedule, cancel), calendars, webhooks, API keys, and event links. Webhook events are `booking.created`, `booking.cancelled`, `booking.rescheduled`, and `booking.no_show`. See [Calendar for developers](/calendar/developers).
- **Survey** — forms, responses, response aggregation, AI form generation, and webhooks (`response.created`, `response.flagged`, `response.updated`, `form.published`, `form.closed`). See [Survey for developers](/survey/developers).
- **Voice Portal** — agents, campaigns, and contacts, plus tools and MCP server registration. See [Tools & MCP for agents](/voice/agents/tools-mcp).

## Related

- [Platform overview](./overview) — accounts, organizations, and shared concepts.
- [MCP for AI agents](./mcp) — let AI assistants call these resources for you.
- [Calendar for developers](/calendar/developers) — Calendar's REST API and webhooks.
- [Survey for developers](/survey/developers) — Survey's API, webhooks, and MCP tools.
