API Keys Guide

Use team API keys to call Last Chance from scripts, automation, or other machine clients without a browser session.

Overview

API keys authenticate as your team, not as an individual user. Send the key on every request:

Authorization: Bearer YOUR_API_KEY

Keys are created in Account Settings → API Keys. The full secret is shown once at creation — store it securely (password manager, secrets vault, or environment variable). You can revoke a key at any time; revoked keys stop working immediately and remain visible for audit.

Who can manage keys: Team admins only.

Rate limits (API keys only): Dashboard/session traffic is unaffected.

BucketLimitApplies to
Auth attempts60 / minute per IPAny lc_live_… Bearer (valid or invalid)
Global30 requests / minute per keyDual-auth API routes (including tool-backed MCP calls); not the /mcp entry handshake itself
MCP entry60 / minute per keyRemote MCP endpoint (/mcp) only — separate from the global bucket
Analytics summary20 / minute per key/api/dashboard/stats, /api/analytics/unsubscribe-rates
Analytics attribution6 / minute per key/api/analytics/revenue-impact

AI endpoints keep their existing tighter limits. Exceeding a limit returns 429.

MCP throughput: Each MCP request counts against MCP entry and against the per-IP auth-attempt bucket at /mcp. Each tools/call then counts against global (plus analytics caps when those tools run) — in-process tool handlers do not re-charge auth-attempt. The practical ceiling is about 30 tool calls per minute per key (the global bucket), even though MCP entry allows 60.


Create a key

  1. Open Account Settings → API Keys
  2. Click Create key
  3. Choose a name (for example CI scripts or Billing sync)
  4. Pick scopes (defaults are read-only: pages:read and team:read)
  5. Choose expiry (defaults to 90 days; you can pick 30 / 90 / 365 days or never)
  6. Copy the secret immediately — it will not be shown again

Limits:

  • Up to 20 active keys per team
  • Create rate limit: 10 keys per hour per team

Connect with MCP (Claude / Cursor)

When Connected MCP apps in Account Settings shows that OAuth connections are available, OAuth is the primary way to connect interactive Claude and Cursor clients to Last Chance’s hosted, read-only MCP server. No API key is placed in the client configuration. If that section says new OAuth connections are temporarily unavailable, use the API-key fallback below; existing API-key connections continue working.

Prerequisites:

  • A Last Chance account with an explicit membership in the team you want to connect
  • A Claude or Cursor client that supports remote MCP servers and browser-based OAuth
  • Permission to open a browser, sign in, choose a team, and approve read-only access

Primary app host only. Use https://www.yourlastchance.app/mcp. Branded unsubscribe hosts do not serve MCP or OAuth.

  1. Open Account Settings → API Keys.
  2. In Connected MCP apps, select Add to Cursor for one-click installation. The documentation renderer does not launch cursor: links directly, so the authenticated Account Settings button is the supported one-click entry point.
  3. For another supported client, select Copy config and add the URL-only configuration to the client’s remote MCP settings:
{
  "mcpServers": {
    "last-chance": {
      "url": "https://www.yourlastchance.app/mcp"
    }
  }
}
  1. Start the connection in Claude or Cursor. Your browser opens Last Chance for sign-in, team selection, and consent.
  2. Review the client name, verified host when available, team, and read-only data categories before approving.

What the connector can access

The OAuth grant is tied to the selected team and provides read-only access:

  • get_team_context — team tier, limits, and Klaviyo/Shopify connection flags
  • list_email_integrations / list_klaviyo_lists — connected provider and list metadata
  • list_unsubscribe_pages / get_unsubscribe_page / list_page_versions — page content and version discovery
  • list_page_templates — safe template IDs, names, and descriptions
  • get_unsubscribe_page_urls — operator preview URL for the published version
  • get_dashboard_stats / get_unsubscribe_rates — aggregated performance metrics
  • get_revenue_impact — aggregated attribution and revenue-impact reporting for a date range

The directory-launch OAuth connector exposes these 11 read-only tools and cannot create, edit, publish, archive, or delete pages. It does not expose event-level email addresses.

Last Chance also has post-launch MCP mutation tools behind a server-side global gate and an exact private-client allowlist. They are never available to DCR/CIMD directory clients and are not advertised in public OAuth metadata. An allowlisted pre-registered client must explicitly request pages:write and/or pages:publish; only explicit team ADMIN or SUPERADMIN members can approve those scopes. API-key callers receive mutation tools only when the global gate is enabled and their key has a write or publish scope. Publishing and permanent deletion also require an explicit confirm: true tool argument.

Example prompts

  • “List our unsubscribe pages and tell me which one is currently primary.”
  • “Compare our unsubscribe rate before and after Last Chance activation.”
  • “Show revenue impact for July and explain the major funnel drop-offs.”
  • “Which Klaviyo lists are connected, and what published URL should I send to a stakeholder?”

Your AI client decides when to send a tool call. Last Chance receives the selected tool name and its arguments and returns the requested result to that client. Last Chance does not receive the surrounding conversation unless the client includes some of it in a tool argument. See Privacy and Security guidance.

Disconnect or revoke

Open Account Settings → API Keys → Connected MCP apps, choose the connection, and select Revoke. The approving member can revoke their own connection; team admins can revoke any connection for the team. You can also remove the server from Claude or Cursor. Revoking in Last Chance immediately invalidates that grant’s active credentials.

Troubleshooting OAuth

ProblemWhat to check
Browser sign-in does not openConfirm the client supports remote MCP OAuth and that the server URL is exactly https://www.yourlastchance.app/mcp
Redirect or registration errorUpdate Claude/Cursor and retry; do not substitute a branded hostname or add a callback manually
Wrong team appearsConfirm your user is an explicit member of the intended team, then reconnect
401 after a previous connectionThe grant may be revoked or expired; remove the connection and complete OAuth again
A tool reports insufficient accessOAuth is read-only; use the dashboard for changes

If the problem continues, contact [email protected] and include the request/correlation ID shown by your client. Never send access tokens or API keys.

API-key fallback for scripts and CI

Team API keys remain supported for scripts, CI, automation, and clients that require a static Authorization header. Admins can create a key and copy the API-key configuration from this page. Prefer an environment variable or secrets vault; never commit the key.

{
  "mcpServers": {
    "last-chance": {
      "url": "https://www.yourlastchance.app/mcp",
      "headers": {
        "Authorization": "Bearer ${env:LAST_CHANCE_API_KEY}"
      }
    }
  }
}

Set LAST_CHANCE_API_KEY in your environment to your lc_live_… secret (or paste the Bearer value directly — prefer the env var so the key is not committed).

Agency portfolio keys

If your organization uses the Partner (agency) dashboard, partner admins can create portfolio API keys under Partner → Settings. These use the same lc_live_… format and the same hosted MCP URL, but they are stamped to the partner account (not a merchant team).

Team key (Account → API Keys)Partner key (Partner → Settings)
Who createsTeam adminPartner admin
ScopesPages, team, analyticspartner:read, partner:revenue:read, clients:read
MCP toolsTeam pages / analytics toolsPortfolio tools + client_* tools (teamId required; need clients:read to run)
Sees other clients’ MRRNoYes (your agency portfolio only)

Merchant team keys cannot access partner portfolio data. Use a partner key when you want Cursor or Claude to reason about managed teams and Shopify-billed portfolio MRR. With clients:read, the same partner key can also read one attached client at a time (pages, team context, analytics) by passing that client’s team id — you do not need a separate team key per client.

Available tools (read-only)

  • get_team_context — team tier, limits, Klaviyo/Shopify flags
  • list_email_integrations / list_klaviyo_lists — integrations and lists
  • list_unsubscribe_pages / get_unsubscribe_page / list_page_versions — page discovery
  • get_unsubscribe_page_urls — published-page preview URL only (machine callers cannot mint draft/share links)
  • get_dashboard_stats / get_unsubscribe_rates — summary analytics (analytics:summary:read)
  • get_revenue_impact — attribution analytics (analytics:attribution:read; requires startDate + endDate)

Partner portfolio tools (partner key only):

  • get_partner_context — partner account + current-period portfolio rollup (partner:read)
  • list_partner_teams — managed client teams + current MRR (partner:read)
  • get_partner_revenue — historical monthly MRR series (partner:revenue:read)

Partner client tools (listed for partner keys; each requires teamId from list_partner_teams and clients:read at execution):

  • get_client_team_context / list_client_email_integrations / list_client_klaviyo_lists
  • list_client_unsubscribe_pages / get_client_unsubscribe_page / get_client_unsubscribe_page_urls / list_client_page_versions
  • get_client_dashboard_stats / get_client_unsubscribe_rates / get_client_revenue_impact

For raw REST (same partner key), send X-LC-Client-Team-Id: <clientTeamId> on team GET routes. Optional ?teamId= works as a fallback when the header is omitted; if both are sent they must match. Prefer the header. The team must be attached to your partner account. Writes are not granted by clients:read.

Example (partner key + attached client):

curl -sS "https://www.yourlastchance.app/api/team" \
  -H "Authorization: Bearer YOUR_PARTNER_API_KEY" \
  -H "X-LC-Client-Team-Id: CLIENT_TEAM_ID"

Revoke team keys in Account Settings → API Keys, or partner keys in Partner → Settings, to cut off access immediately.


Authentication

Every machine request needs your API key in the Authorization header:

-H "Authorization: Bearer YOUR_API_KEY"
-H "Content-Type: application/json"

Replace YOUR_API_KEY with the full secret from Account Settings → API Keys. All examples below use https://www.yourlastchance.app.

Cookie/session auth still works for dashboard requests that do not present an Authorization header. Any presented Authorization header is authoritative: unsupported schemes, malformed values, and Bearer tokens that are not valid Last Chance credentials fail closed and never fall back to cookies.

Machine page reads are always bounded: page lists and details use metadata-only projections, and version lists use metadata-only pagination capped at 50 results. Query parameters cannot opt an API key or OAuth caller into dashboard-sized page/version payloads.


Scopes

ScopeAllows
pages:readList and get unsubscribe pages and versions
pages:writeCreate pages, save drafts, archive/primary, attach/detach email lists, toggle list webhooks, delete pages (with confirm)
pages:publishPublish a draft version
pages:aiAI page chat and generate-from-website
team:readTeam context/limits, email integrations status, Klaviyo list discovery
analytics:summary:readDashboard stats and unsubscribe rate trends (aggregated counts only — no email PII)
analytics:attribution:readRevenue impact / churn recovery attribution for a date range
partner:readPartner portfolio context, managed teams, and current-period MRR (partner keys only)
partner:revenue:readHistorical partner portfolio MRR series (partner keys only)
clients:readRead attached client teams via team GET APIs / MCP client_* tools when a client team id is specified (partner keys only)

Grant the minimum scopes your client needs. A read-only team integration usually only needs pages:read and team:read. Add analytics:summary:read / analytics:attribution:read when the client should pull metrics. Partner scopes are available only on keys created under Partner → Settings. clients:read implies team read scopes for attached clients only (no page writes/publish/AI).


Example requests

Get team context

Requires team:read.

curl -sS "https://www.yourlastchance.app/api/team" \
  -H "Authorization: Bearer YOUR_API_KEY"

Returns team id/name, tier, Klaviyo/Shopify connection flags, subscription/access booleans, and page/AI/analytics limits. API-key responses omit billing provider IDs, shop domain, and other dashboard-only fields.

List email integrations

Requires team:read.

curl -sS "https://www.yourlastchance.app/api/email-integrations" \
  -H "Authorization: Bearer YOUR_API_KEY"

List Klaviyo lists

Requires team:read. Team keys use the team bound to the key. Partner keys with clients:read must target an attached client via X-LC-Client-Team-Id (same as other team GET routes).

curl -sS "https://www.yourlastchance.app/api/email-integrations/klaviyo/lists" \
  -H "Authorization: Bearer YOUR_API_KEY"

List unsubscribe pages

Requires pages:read.

curl -sS "https://www.yourlastchance.app/api/unsubscribe-pages" \
  -H "Authorization: Bearer YOUR_API_KEY"

Get one page

Requires pages:read. Machine callers get a metadata summary by default.

curl -sS "https://www.yourlastchance.app/api/unsubscribe-pages/PAGE_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

List versions and read page content

Requires pages:read. Machine callers get metadata-only versions, capped at 50 per request. Each version lists its componentIds (bounded to 20, with componentIdsTruncated when there are more).

curl -sS "https://www.yourlastchance.app/api/unsubscribe-pages/PAGE_ID/versions?page=1&limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"

Read one component at a time using a versionNumber from that list and one of its componentIds:

curl -sS "https://www.yourlastchance.app/api/unsubscribe-pages/PAGE_ID?mcpProjection=component&versionNumber=2&componentId=initial" \
  -H "Authorization: Bearer YOUR_API_KEY"

A component larger than 400 KiB returns 413 with code: "CONTENT_COMPONENT_TOO_LARGE"; open that version in the dashboard editor instead.

List page templates

Requires pages:read. Returns only bounded template IDs, names, and descriptions. Use an ID from this catalog with the create endpoint; template HTML is resolved server-side.

curl -sS "https://www.yourlastchance.app/api/unsubscribe-pages/templates" \
  -H "Authorization: Bearer YOUR_API_KEY"

Get the published preview URL

Requires pages:read. API-key and OAuth callers receive only previewUrl for the currently published version (using your branded public host when configured). The URL always targets the requested unsubscribe page, including when it is not the team's primary page.

Machine credentials cannot mint draft or public share links. Create those links from the authenticated dashboard when stakeholder review is needed. Do not send previewUrl as a public review link because it is an operator preview flow.

curl -sS "https://www.yourlastchance.app/api/unsubscribe-pages/PAGE_ID/urls" \
  -H "Authorization: Bearer YOUR_API_KEY"

Create a page from a template

Requires pages:write. Unpaid teams are limited to 3 pages total; paid teams are unlimited.

curl -sS -X POST "https://www.yourlastchance.app/api/unsubscribe-pages" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stay offer page",
    "templateId": "TEMPLATE_ID"
  }'

Save a draft version

Requires pages:write.

curl -sS -X POST "https://www.yourlastchance.app/api/unsubscribe-pages/PAGE_ID/versions" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Draft from API",
    "expectedLatestVersionNumber": 1,
    "globalStyles": {},
    "pages": [
      {
        "id": "initial",
        "enabled": true,
        "html": "<div data-action=\"stay\">Stay subscribed</div>"
      }
    ]
  }'

Required fields for API-key clients: name, expectedLatestVersionNumber, globalStyles, and pages (an array of page objects). The server assigns the next version number. Fetch the current version first and send its number as expectedLatestVersionNumber; a concurrent edit returns 409 with code: "VERSION_CONFLICT" and the actual latest number. Use the same JSON shape the dashboard editor saves (HTML/CSS, theme, offers). Omitted optional draft fields preserve their latest values; send null to clear nullable description, CSS, theme, or discount fields. Unchanged content may return the existing version instead of creating a new one.

Publish a version

Requires pages:publish. Publishing requires an active subscription.

curl -sS -X POST "https://www.yourlastchance.app/api/unsubscribe-pages/PAGE_ID/versions/VERSION_ID/publish" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"confirm": true, "expectedLatestVersionNumber": 7}'

expectedLatestVersionNumber is optional and guards against publishing from a stale view: send the version number you believe is the newest draft, and the publish is refused with 409 and code: "VERSION_CONFLICT" (plus the actual latest number) if someone saved past you. Omit it only when you mean to roll the live page back to an older version.

The page must have its own active Klaviyo integration, and the list ID must be accessible through that integration. Last Chance verifies the ID with Klaviyo and stores Klaviyo's canonical list name.

Attach a Klaviyo list to a page

Requires pages:write.

curl -sS -X POST "https://www.yourlastchance.app/api/unsubscribe-pages/PAGE_ID/email-lists" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "listId": "KLAVIYO_LIST_ID",
    "listName": "Newsletter",
    "provider": "klaviyo"
  }'

Delete a page (API key only)

Requires pages:write and confirm=true (query or JSON body). Session deletes from the dashboard do not need this flag.

curl -sS -X DELETE "https://www.yourlastchance.app/api/unsubscribe-pages/PAGE_ID?confirm=true" \
  -H "Authorization: Bearer YOUR_API_KEY"

Or:

curl -sS -X DELETE "https://www.yourlastchance.app/api/unsubscribe-pages/PAGE_ID" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"confirm": true}'

Dashboard stats (summary)

Requires analytics:summary:read. Returns aggregated stay/unsubscribe counts, visitor retention, and attributed order revenue for the team (no per-email data).

curl -sS "https://www.yourlastchance.app/api/dashboard/stats" \
  -H "Authorization: Bearer YOUR_API_KEY"

Unsubscribe rates (summary)

Requires analytics:summary:read. Compares Klaviyo unsubscribe rates before/after Last Chance activation. Optional startDate / endDate (ISO). Requires an active Klaviyo integration.

curl -sS "https://www.yourlastchance.app/api/analytics/unsubscribe-rates" \
  -H "Authorization: Bearer YOUR_API_KEY"

Revenue impact (attribution)

Requires analytics:attribution:read. Full-funnel churn recovery and revenue metrics. startDate and endDate are required (ISO date strings). Optional: enforceActivationDate, variantId, attributionWindowMode. Requires an active Klaviyo integration.

curl -sS "https://www.yourlastchance.app/api/analytics/revenue-impact?startDate=2026-01-01&endDate=2026-03-31" \
  -H "Authorization: Bearer YOUR_API_KEY"

Event-level analytics that include email addresses are not available via API keys.


Common errors

StatusCode / messageMeaning
401UnauthorizedMissing/invalid key, revoked key, or expired key
403insufficient_scopeKey lacks a required scope (partner client reads need clients:read)
403partner_key_requires_client_teamPartner key on a team endpoint without X-LC-Client-Team-Id (or ?teamId=)
403partner_client_not_attachedPartner key targeted a team that is not attached to the partner account
403partner_client_read_onlyPartner key attempted a write/mutation on a client team route
400client_team_id_mismatchX-LC-Client-Team-Id and query teamId disagree
403partner_key_not_allowedInvalid partner/team key stamp (should not happen for keys created in the app)
403team_key_not_allowedMerchant team key used on a partner portfolio endpoint
403Active subscription requiredPublish (and some paid features) need a paid team
400CONFIRMATION_REQUIREDAPI-key page delete missing confirm=true
403API_KEY_LIMIT_REACHEDTeam or partner already has 20 active keys
429Rate limitedToo many API-key requests, AI calls, or key creates
503rate_limit_unavailableRate limiter temporarily unavailable (retry shortly; key create blocked)

Responses never include Klaviyo OAuth secrets (accessToken, refreshToken, etc.).


Security best practices

  • Prefer short-lived keys (90 days) and rotate before expiry
  • Use the smallest scope set that works
  • Store secrets in env vars or a vault — never commit them
  • Revoke keys you no longer need; check Last used on the API Keys page
  • Do not share one key across unrelated systems if you can avoid it