# Build on Luca: the developer docs

A real REST API with OpenAPI docs, scoped API keys, an MCP server for agents, and signed webhooks.

## REST API

- [Interactive API reference](https://api.setluca.com/docs): the full surface, rendered from the live OpenAPI spec.
- [openapi.json](https://api.setluca.com/openapi.json): raw spec for codegen.

### Get an API key

1. Sign in and open **Settings -> Developer API keys**.
2. Create a key and pick its scopes. Keys are workspace-scoped and shown once.
3. Send it as `x-api-key` (or `authorization: Bearer`):

```
curl https://api.setluca.com/api/leads \
  -H "x-api-key: luca_..."
```

## What the API can do

185 operations across these scopes, summarized from the live [capabilities endpoint](https://api.setluca.com/capabilities):

- `analytics:read`
- `bookings:read`
- `bookings:write`
- `broadcasts:read`
- `broadcasts:write`
- `cadences:write`
- `call_events:read`
- `call_events:write`
- `campaigns:read`
- `campaigns:write`
- `coach:read`
- `conversations:read`
- `conversations:write`
- `insights:write`
- `integrations:read`
- `integrations:write`
- `knowledge:read`
- `knowledge:write`
- `leads:read`
- `leads:write`
- `learning:read`
- `learning:write`
- `memory:read`
- `memory:write`
- `reports:read`
- `review_queue:read`
- `review_queue:write`
- `safety:read`
- `usage:read`
- `voice:read`
- `webhooks:read`
- `webhooks:write`

### Capability and sensitivity, two more dials

Every key also sets a capability tier and a sensitivity tier. Capability runs read, draft, queue ops (send, enroll, replay), then full, and each tier is a bundle of the scopes above. Sensitivity is separate: a redacted key sees metadata and summaries with no lead message text, and a full-content key sees the verbatim text.

A key you mint for an assistant can start at read and redacted, the narrowest corner of both dials. You widen either one later, from the same settings page, once the assistant earns more trust. A key used for the local MCP server carries the same two settings.

## Versioning and deprecation

This is version 1. Every response carries `Luca-API-Version: 1`, and you can send the same header on a request to pin the major version you built against -- a request naming another major version is refused with 400 `unsupported_api_version` rather than served by something that does not match.

Inside a major version we only add: new endpoints, new optional request fields, new response properties. Treat unknown response properties as forgiving. Anything that would break a correct client ships as a new major version on a new path prefix, and version 1 keeps serving.

A retiring endpoint says so on itself. `Deprecation` (RFC 9745) carries the date it became deprecated and still works; `Sunset` (RFC 8594) carries the date it stops answering. Both are HTTP dates, and the operation is marked `deprecated: true` in the spec. Alert on them.

## Rate limits

120 requests per workspace per minute, per endpoint, on a sliding window, shared by every key the workspace holds.

Pace off the response headers rather than counting locally:

- `RateLimit-Policy` -- the quota in effect, as `"default";q=120;w=60`. On every response under `/api`, including a 401.
- `RateLimit` -- what is left, as `"default";r=<remaining>;t=<seconds to reset>`.
- `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` -- the same numbers in the older spelling most SDKs read.
- `Retry-After` -- on a 429 only. Seconds to wait.

The first two follow the IETF RateLimit header fields draft. Reset and `Retry-After` count the seconds left in the current window, not a whole window.

## MCP server (for AI agents)

Luca ships an official MCP server: `@setluca/mcp` on npm, hosted at `https://mcp.setluca.com/mcp`, listed in the [MCP registry](https://registry.modelcontextprotocol.io) as `io.github.setluca/luca-mcp`. The guided setup lives at [/connect](/connect).

## Webhooks

Subscribe to workspace events with signed deliveries, replays, and a signature-verification guide. See the [webhooks section of the API reference](https://api.setluca.com/docs).

## What an integration can never do

The guardrails hold for the REST API, MCP, Zapier, and Make alike, because every surface maps onto the same policy-gated routes. No integration can:

- Send a DM straight out. A write creates a draft, exactly as the app does, and the draft waits in the review queue.
- Skip that queue, or switch auto-send on for a channel the coach didn't enable themselves.
- Launch a broadcast nobody approved. The approve step is enforced server-side, not in the client.
- Reach a workspace it wasn't scoped to. A key belongs to one workspace and dies the moment it's revoked.

## Zapier & Make

Full automation-platform apps built on the same public API and signed webhooks above. Both connect with a Luca API key from **Settings -> Developer API keys** -- the connection screen lists the exact scopes to grant.

### Zapier

- **Instant triggers for all 22 workspace events** -- REST hooks, not polling, so Zaps fire the moment a lead qualifies, a booking lands, or a broadcast finishes.
- **Actions across the whole surface** -- create and update leads, log lead notes, create bookings, enroll leads in campaigns, generate and simulate campaigns, run the full broadcast lifecycle (create -> preview -> approve -> launch, plus pause/retry/cancel), manage webhook subscriptions, and trigger CRM syncs.
- **Record pickers, not UUIDs** -- dropdowns for leads, campaigns, broadcasts, and bookings anywhere an ID is needed.
- **Find Lead + find-or-create** -- search by name, handle, or email and create the lead when nothing matches, for idempotent Zaps.
- **Safe retries** -- every action sends a deterministic idempotency key, so a flaky network never duplicates a lead or a broadcast launch.

### Make (formerly Integromat)

- **The same 22 events as instant triggers** -- dedicated webhooks registered automatically when a scenario activates, plus scheduled polling fallbacks for new leads and bookings.
- **Every action, fully typed** -- structured forms with date pickers and searchable dropdowns (leads, campaigns, broadcasts, bookings), organized into entity groups in the scenario editor.
- **Search modules** -- find leads and conversations with server-side search, limits, and pagination.
- **Make an API Call** -- the universal module reaches any endpoint the typed modules don't cover yet, using your connection.

One invariant on both platforms: no Zap or scenario can send a DM or skip the review queue -- every action maps onto the same policy-gated API routes documented above, and broadcast launches are rejected server-side unless the broadcast was approved first.

Both apps are finished and heading into platform review. Until the public listings land, the REST API and webhooks cover the same ground -- or email [hello@setluca.com](mailto:hello@setluca.com) for an early-access invite.

## FAQ

### How do I authenticate?

With a developer API key, sent as an x-api-key header. Keys are created in your Luca settings, scoped to your workspace, and shown once. Rotate or revoke one at any time and calls using it stop immediately.

### Can the API send a DM directly?

No, and that's deliberate. Every write goes through the same policy-gated routes the app uses. A draft still lands in the review queue, and auto-send still has to be on for that channel. No integration can route around the queue, because the queue is enforced server-side rather than in any client. That holds for the REST API, MCP, Zapier, and Make alike.

### How do I verify a webhook came from Luca?

Each delivery carries an HMAC-SHA256 signature with a v1= prefix. It's computed over the timestamp and the raw request body joined by a period, with a base64url digest. Verify against the raw bytes before parsing, and reject anything you can't match.

### What happens when my endpoint is down?

Deliveries retry on a backoff. Every attempt is listed in the delivery log, so you can inspect the response and replay it once you're back. A 410 Gone is taken as permanent: the delivery dead-letters and the subscription is disabled instead of retrying forever.

### Is there an OpenAPI spec I can generate a client from?

Yes. The spec is published and kept in sync with the routes themselves, so any generator you already use will produce a typed client. The MCP server is generated from the same source, which is why the two never drift.

### What can an agent do over MCP?

Everything the public API exposes, as tools your client can call, plus an operations resource and a planning prompt. It's the same trust boundary as the REST API: public routes only, scoped to the key you connect with. Connect the hosted server or run the npm package locally over stdio; either way the key's scopes, capability tier, and sensitivity tier decide what the agent can see and do.

### What does a scope control?

One verb on one resource. A key with leads:read can list leads and ask why one qualified; it takes leads:write to create or change them. A key gets exactly the scopes you tick, and a call outside them is refused outright instead of half-completing.

### Is there an official SDK?

No, on purpose. The OpenAPI spec is generated from the routes themselves, so a client you generate in your own language stays more current than an SDK we'd be hand-maintaining two releases behind.

### How will I know when the API changes?

This is version 1, and every response says so with a `Luca-API-Version: 1` header you can pin against. Inside a major version we only add. Anything that would break a correct client ships as a new major version, and version 1 keeps serving. A retiring endpoint announces itself with `Deprecation` and `Sunset` headers, so a running integration finds out without anyone reading a changelog. Diff the spec or the capabilities endpoint for everything else. Both are generated from the live routes.

### Do you have a sandbox or test workspace?

Use a webhook subscription's test delivery to see the exact payload shape before you write a handler. The event listing endpoint replays real events of a given type. For anything more involved, email hello@setluca.com and we'll set you up.

### Are there rate limits?

Yes. A workspace gets 120 requests a minute per endpoint on a sliding window, shared by every key it holds. Pace off the headers rather than counting locally: `RateLimit-Policy` states the quota, `RateLimit` states what's left, and a 429 carries `Retry-After`. That budget is sized for normal integration traffic, not bulk export. If you're planning something heavier, tell us what you're building and we'll make sure the limits fit.

The Luca API is operated by SetLuca, the company behind Luca.

Questions? Email [hello@setluca.com](mailto:hello@setluca.com).