# API keys and the MCP endpoint

Canonical URL: https://support.optimonk.com/en/articles/api-keys-and-the-mcp-endpoint

The builder exposes an MCP (Model Context Protocol) server, so an AI agent —
Claude, Cursor, ChatGPT/Codex, or your own client — can drive it: start a build
from a reference image, iterate on the popup, and turn the result into a
campaign. This article covers where to mint a credential, what the endpoint
speaks, and where its limits are.

## Create an API key

Go to **Settings → API keys**. The panel opens with:

Connect the OptiMonk MCP server to AI agents like Claude or Cursor.

next to a **Documentation** link that points at the in-app article
`/docs/authentication`.

1. Type a **Key name** (placeholder: "e.g. Claude connection") — the name is
required; leaving it empty gives you "Give the key a name."
2. Press **New key**.
3. The **Key created** panel appears with the full secret and the warning "You
can only see the full key now — copy and store it safely. It can't be shown
again." Use **Copy**.

Afterwards the list shows only the key's 14-character prefix followed by dots,
its "last used" date and its creation date. Secrets are stored as a SHA-256
hash, never in plaintext, so there is no way to recover one — mint a new key
instead. **Revoke** kills a key immediately: "Any agent using this key loses
access immediately. This can't be undone."

Keys look like `omk_live_<43 base64url chars>`. The same page also lists
**Connected apps** — the OAuth equivalent, for clients that can run a browser
consent flow: "Apps you approved with Connect. They act on this account until
you disconnect them."

## The endpoint

```
POST https://<your-host>/mcp Authorization: Bearer omk_live_…
```

Transport is streamable HTTP and **stateless** — the server creates a fresh
session per request, so `GET /mcp` and `DELETE /mcp` both answer `405 method not allowed (stateless)`. The server identifies itself as `optimonk-popup-builder
`version `1.0.0`.

Auth details worth coding against:

- The bearer must start with `omk_`. An `omk_at_…` prefix is treated as an OAuth
access token, anything else as an API key.
- A missing, malformed, revoked or expired credential gets `401` with a
`WWW-Authenticate` header. When OAuth is enabled that challenge carries a
`resource_metadata` pointer (RFC 9728), which is what lets a capable client
start the consent flow itself instead of asking you for a key.
- Requests from a browser origin outside the allow-list get `403`.
- Request bodies are capped at 1 MB; larger ones get `413`.
- If both the API-key and OAuth doors are switched off server-side, `/mcp
`answers `404 mcp disabled`.

Both credential kinds carry the same four scopes: `build:read`, `build:write`,
`campaign:read`, `campaign:write`. A key created from the settings UI gets the
full set. Call `whoami` first — it returns the account id, the credential label,
the scopes and `canPublish`, which disambiguates "object not found" from
"credential pointed at the wrong account".

## What the tools can do

26 tools are exposed. Grouped by job:

- **Identity:**`whoami`.
- **Build:**`build_popup`, `get_build_status`, `watch_build`,
`upload_reference_image`, `get_popup_html`, `get_popup_preview`.
- **Iterate:**`edit_popup`, `patch_popup`, `restore_revision`.
- **Design conversation:**`start_design_thread`, `send_design_message`,
`get_design_thread`, `get_concept_board`, `select_variant`.
- **Campaign:**`list_campaigns`, `get_campaign`, `create_campaign`,
`set_campaign_name`, `get_campaign_settings`, `set_campaign_settings`,
`publish_campaign`.
- **Coupons:**`get_coupon`, `set_coupon`.
- **Jobs:**`get_job`, `watch_job`.

`build_popup` takes a publicly fetchable PNG/JPG/WEBP plus the target store
hostname and returns a `runId`. Builds are asynchronous: prefer `watch_build
`(it waits and emits progress notifications) over a polling loop, and use
`get_build_status` for a single read or from a client that cannot hold a request
open. **`interrupted`**** is not a terminal status** — the run resumes; treating it
as a failure is the most common mistake on this surface.

`create_campaign` makes a DRAFT campaign and needs a designer-session-backed
build: `start_design_thread` creates one, a standalone `build_popup` run does
not. `publish_campaign` publishes to OptiMonk and activates the campaign.

## What it is not for

- **It is not the analytics or subscriber API.** There is no tool for reports,
leads or submits — the tool list above is the whole surface.
- **It cannot configure ESP/CRM integrations.** No tool connects Klaviyo,
Mailchimp or a webhook; that is UI-only, in the campaign hub.
- **It cannot manage the account.** No user, domain, billing or API-key
administration; key and connected-app endpoints are cookie-authenticated on
purpose, so a credential can never enumerate or revoke its siblings.
- **Do not branch on HTTP status alone.** MCP tool refusals — including scope
errors and exhausted budgets — come back as `isError: true` with prose in
`content`, inside a JSON-RPC HTTP 200 response. They do not use the REST typed
error envelope or the rate-limit headers.
- **Do not retry a timed-out build blindly.** Pass `idempotencyKey` to
`build_popup` (the REST equivalent is an `Idempotency-Key` header, honoured
for 24 hours per account); a retry with the same key returns the first run
instead of paying for a second generation.

## Scripting instead of an agent?

The same credentials work against a REST surface described by an OpenAPI 3.1
document. Both `GET /.well-known/openapi.json` and `GET /llms.txt` are readable
without a credential. REST errors are typed (`type` + `code` + `request_id`;
branch on those, not on `message`), rate-limited responses carry
`X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`, and a
budget refusal is a `429` with `Retry-After`.
