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.
| Bucket | Limit | Applies to |
|---|---|---|
| Auth attempts | 60 / minute per IP | Any lc_live_… Bearer (valid or invalid) |
| Global | 30 requests / minute per key | Dual-auth API routes (including tool-backed MCP calls); not the /mcp entry handshake itself |
| MCP entry | 60 / minute per key | Remote MCP endpoint (/mcp) only — separate from the global bucket |
| Analytics summary | 20 / minute per key | /api/dashboard/stats, /api/analytics/unsubscribe-rates |
| Analytics attribution | 6 / 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 1× against MCP entry and 1× against the per-IP auth-attempt bucket at /mcp. Each tools/call then counts 1× 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
- Open Account Settings → API Keys
- Click Create key
- Choose a name (for example
CI scriptsorBilling sync) - Pick scopes (defaults are read-only:
pages:readandteam:read) - Choose expiry (defaults to 90 days; you can pick 30 / 90 / 365 days or never)
- 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.
- Open Account Settings → API Keys.
- 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. - 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"
}
}
}
- Start the connection in Claude or Cursor. Your browser opens Last Chance for sign-in, team selection, and consent.
- 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 flagslist_email_integrations/list_klaviyo_lists— connected provider and list metadatalist_unsubscribe_pages/get_unsubscribe_page/list_page_versions— page content and version discoverylist_page_templates— safe template IDs, names, and descriptionsget_unsubscribe_page_urls— operator preview URL for the published versionget_dashboard_stats/get_unsubscribe_rates— aggregated performance metricsget_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
| Problem | What to check |
|---|---|
| Browser sign-in does not open | Confirm the client supports remote MCP OAuth and that the server URL is exactly https://www.yourlastchance.app/mcp |
| Redirect or registration error | Update Claude/Cursor and retry; do not substitute a branded hostname or add a callback manually |
| Wrong team appears | Confirm your user is an explicit member of the intended team, then reconnect |
401 after a previous connection | The grant may be revoked or expired; remove the connection and complete OAuth again |
| A tool reports insufficient access | OAuth 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 creates | Team admin | Partner admin |
| Scopes | Pages, team, analytics | partner:read, partner:revenue:read, clients:read |
| MCP tools | Team pages / analytics tools | Portfolio tools + client_* tools (teamId required; need clients:read to run) |
| Sees other clients’ MRR | No | Yes (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 flagslist_email_integrations/list_klaviyo_lists— integrations and listslist_unsubscribe_pages/get_unsubscribe_page/list_page_versions— page discoveryget_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; requiresstartDate+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_listslist_client_unsubscribe_pages/get_client_unsubscribe_page/get_client_unsubscribe_page_urls/list_client_page_versionsget_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
| Scope | Allows |
|---|---|
pages:read | List and get unsubscribe pages and versions |
pages:write | Create pages, save drafts, archive/primary, attach/detach email lists, toggle list webhooks, delete pages (with confirm) |
pages:publish | Publish a draft version |
pages:ai | AI page chat and generate-from-website |
team:read | Team context/limits, email integrations status, Klaviyo list discovery |
analytics:summary:read | Dashboard stats and unsubscribe rate trends (aggregated counts only — no email PII) |
analytics:attribution:read | Revenue impact / churn recovery attribution for a date range |
partner:read | Partner portfolio context, managed teams, and current-period MRR (partner keys only) |
partner:revenue:read | Historical partner portfolio MRR series (partner keys only) |
clients:read | Read 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
| Status | Code / message | Meaning |
|---|---|---|
401 | Unauthorized | Missing/invalid key, revoked key, or expired key |
403 | insufficient_scope | Key lacks a required scope (partner client reads need clients:read) |
403 | partner_key_requires_client_team | Partner key on a team endpoint without X-LC-Client-Team-Id (or ?teamId=) |
403 | partner_client_not_attached | Partner key targeted a team that is not attached to the partner account |
403 | partner_client_read_only | Partner key attempted a write/mutation on a client team route |
400 | client_team_id_mismatch | X-LC-Client-Team-Id and query teamId disagree |
403 | partner_key_not_allowed | Invalid partner/team key stamp (should not happen for keys created in the app) |
403 | team_key_not_allowed | Merchant team key used on a partner portfolio endpoint |
403 | Active subscription required | Publish (and some paid features) need a paid team |
400 | CONFIRMATION_REQUIRED | API-key page delete missing confirm=true |
403 | API_KEY_LIMIT_REACHED | Team or partner already has 20 active keys |
429 | Rate limited | Too many API-key requests, AI calls, or key creates |
503 | rate_limit_unavailable | Rate 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
Related
- Settings — Other account configuration
- Getting Started — Create and publish your first page in the UI
- Manage keys in-app: Account Settings → API Keys