API for agents

Everything in ExcuseMe can be driven without the UI: surveys, email check-ins, triggered sends, results. If you are a human, this page is for you. If you are an agent, fetch the raw reference instead:

curl https://excuseme.app/llms.txt

Or connect the MCP server: EXCUSEME_API_KEY=<key> npx excuseme-mcp. API keys are minted in the dashboard under Settings, then API keys.

The full reference

Rendered from /llms.txt, the same file agents read.

# ExcuseMe

> ExcuseMe is a drop-in feedback widget (emoji, star, or thumbs) you embed on any
> website with one script tag. It is built to be driven entirely by AI agents: an
> agent can create a survey, show a live preview, hand over the embed snippet, read
> the responses, and start an upgrade, with no human login. The owner confirms once
> by email before any real responses are stored.

There are two equally supported ways to use ExcuseMe. Pick whichever fits your agent.
Neither is a fallback for the other.

## Option A: the HTTP API (works with plain curl or fetch)

Base URL: `https://app.excuseme.app/api/v1/manage`. Auth: `Authorization: Bearer <apiKey>`.
Creating an account needs no key. Send `Idempotency-Key` on creates to make retries safe.

Quickstart:

```bash
# 1. Create an account and get an API key (no key needed). Save the apiKey.
curl -sX POST https://app.excuseme.app/api/v1/manage/accounts \
  -H 'content-type: application/json' \
  -d '{"email":"owner@example.com"}'
# -> {"accountId":"...","apiKey":"exm_live_...","defaultWebsiteId":"..."}

KEY=exm_live_...

# 2. Create a survey. Returns a preview URL and the embed snippet.
curl -sX POST https://app.excuseme.app/api/v1/manage/surveys \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"type":"emojis","question":"How do you like the new dashboard?"}'
# -> {"surveyId":"...","status":"pending_confirmation","previewUrl":"https://app.excuseme.app/preview/...","embedSnippet":"<script src=\"https://hey.excuseme.app/excuseme.js?id=...\" defer></script>"}

# 3. Read results (summarize them yourself).
curl -s https://app.excuseme.app/api/v1/manage/surveys/SURVEY_ID/summary -H "authorization: Bearer $KEY"
curl -s https://app.excuseme.app/api/v1/manage/surveys/SURVEY_ID/responses -H "authorization: Bearer $KEY"
```

Endpoints: `POST /accounts`, `GET|POST /websites`, `GET|POST /surveys`,
`GET|PATCH|DELETE /surveys/{id}`, `GET /surveys/{id}/responses`,
`GET /surveys/{id}/summary`, `POST /surveys/{id}/upgrade` (returns a Stripe checkout URL).
Survey types: `emojis`, `stars`, `thumbs`. Errors are `{"error":{"code","message"}}`.

### Email check-in endpoints

Check-ins are recurring email surveys. Each send is a **wave** that rotates through the
check-in's question list. Check-ins default to anonymous; anonymous waves lock aggregated
results until 3 responses arrive. `sendWeekday` is 0 (Sunday) to 6 (Saturday).

**POST /checkins** — create a check-in.

```json
// request
{
  "name": "Weekly team pulse",
  "anonymity": "anonymous",          // or "identified"
  "frequency": "weekly",             // "weekly" | "biweekly" | "monthly" | "manual"
  "sendWeekday": 1,                  // required unless frequency is "manual"
  "sendHour": 9,                     // 0-23; required unless frequency is "manual"
  "timezone": "America/New_York",    // IANA timezone; default "UTC"
  "questions": [
    { "type": "emojis", "question": "How did this week go?" }
  ],
  "recipients": [                    // optional; up to 500
    { "email": "alice@example.com", "name": "Alice" }
  ]
}

// response 201
{
  "checkinId": "uuid",
  "publicId": "short-id",
  "shareUrl": "https://app.excuseme.app/s/<publicId>",
  "nextSendAt": "2024-01-08T09:00:00.000Z",   // null when frequency is "manual"
  "recipients": { "added": 1, "skipped": 0 }   // null if none supplied at create
}
```

`400 recipient_limit` if the recipients list exceeds the plan cap (the check-in is still
created; `checkinId` is included in the error body). Send `Idempotency-Key` to make retries
safe.

**GET /checkins** — list all check-ins for the account. Returns an array of check-in objects.

**POST /checkins/{id}/recipients** — add recipients to an existing check-in.

```json
// request
{ "recipients": [{ "email": "bob@example.com", "name": "Bob" }] }  // 1-500

// response 200
{ "added": 1, "skipped": 0 }
```

`400 recipient_limit` if the total would exceed the plan limit.
`GET /checkins/{id}/recipients` lists current recipients (`id`, `email`, `name`, `status`,
`createdAt`). `DELETE /checkins/{id}/recipients` with body `{"recipientId":"<uuid>"}` removes
one.

**POST /checkins/{id}/send** — triggered send: email one address the current wave's question
right now. Use this for CSAT-style flows (order shipped, ticket closed, etc.).

```json
// request
{ "email": "alice@example.com", "name": "Alice" }  // name optional

// response 200
{ "sent": true, "waveId": "uuid", "recipientId": "uuid" }
```

Errors: `402 limit_reached` (check-in reached its free cap; upgrade first),
`409 recipient_blocked` (address bounced or unsubscribed; do not retry),
`409 already_responded` (recipient already answered in the current wave).

**GET /checkins/{id}/summary** — per-wave results, oldest wave first.

```json
{
  "checkinId": "uuid",
  "waves": [
    {
      "waveId": "uuid",
      "question": "How did this week go?",
      "type": "emojis",              // "emojis" | "stars" | "thumbs"
      "kind": "scheduled",           // or "triggered"
      "anonymity": "anonymous",
      "scheduledFor": "2024-01-08T09:00:00.000Z",
      "closedAt": null,
      "sentCount": 12,
      "respondedCount": 5,
      "reminded": false,
      "locked": false,               // true when anonymous and respondedCount < 3
      "lockedRemaining": 0,          // responses still needed to unlock
      "avg": 3.8,                    // null when locked
      "breakdown": { "1": 0, "2": 1, "3": 2, "4": 1, "5": 1 },  // null when locked
      "comments": [
        { "score": 4, "comment": "Good week overall", "createdAt": "2024-01-09T..." }
      ],                             // null when locked
      "sources": { "email": 4, "link": 1 }
    }
  ]
}
```

## Option B: the MCP server

`npx excuseme-mcp` exposes the same operations as MCP tools (`excuseme_create_survey`,
`excuseme_preview`, `excuseme_get_summary`, ...). Set `EXCUSEME_API_KEY` in its env.
See the package README for client config.

## How consent works

A new survey previews immediately but stores zero real responses until the owner clicks
the confirm link emailed at account or website creation. Show the human the `previewUrl`
in the same turn; they confirm once and collection starts.

## Pricing

Free: 1 active survey, 100 responses per survey. Paid: 1.99 USD or EUR per survey per
month for unlimited responses. Use `POST /surveys/{id}/upgrade` to get a checkout link to
hand to the human (an agent cannot pay on its own).