02 · CONCEPTS

How Layrcake thinks

Five mechanics govern every call: authentication, idempotency, human confirmations, async operations, and suppression. Understand these and the whole API is predictable.

1

Authentication & scopes

Bearer keys (cake_sk_…), stored only as hashes, revocable instantly from the console. Keys carry explicit scopes:

ScopeUnlocks
data:readfind_leads, enrich, verify_email, find_intent, state reads
llmmetered LLM passthrough
campaign:writecreating and editing draft campaigns, set_lead_status
campaign:launchlaunching (still confirmation-gated)
reply:sendreply_to_thread (confirmation-gated; not granted by default — request it explicitly when creating a key)
assets:purchasesetup_sending purchases (still confirmation-gated)
usage:readusage & billing reads

Default keys carry every scope — a human still has to approve each purchase and launch in the console, so scopes aren't the safety net; confirmations are. Trim a key's scopes when you want a narrower agent (say, data:read · usage:read for a research-only assistant).

2

Idempotency & retries

Send an Idempotency-Key header with any verb. A retry with the same key and the same parameters replays the original result without charging again (the response carries "replayed": true). Results are kept for 90 days. After that the result has expired: a replay says so instead of returning it, and GET /v1/requests/{id} still shows the request's status and billing; run the call again with a new Idempotency-Key. Same key with different parameters → 409 idempotency_conflict. A retry while the original is still running → 202 in_progress.

A failed call is different. If a data call — search, enrichment, verification, research, pictures or LLM — failed with provider_unavailable, rate_limited or insufficient_credits (once your balance covers it), a retry with the same key runs it once more as a new attempt with its own request_id, so it can take another route. It runs once the failure's cooldown has passed — 30 s, or its own longer retry_after_seconds; a retry before then gets the failure back with the seconds left. An insufficient_credits refusal needs no cooldown: it runs as soon as a top-up covers the call. A call whose provider may already have done the work (a timeout) is never run again under the same key, so a vendor is never paid twice for it. Concurrent retries share that one attempt, you are only charged for what it returns, and the failed attempt stays readable at GET /v1/requests/{id}. Every other stored failure, and every failure of a send-class verb, comes back unchanged with details.replayed: true and no retry hint; fix the cause and retry with a new key.

The header is required on the three send-class verbs — setup_sending (purchase step), create_campaign, launch_campaign — because retry storms must never duplicate real-world side effects. MCP tools expose this as the idempotency_key argument.

3

Human confirmations

Actions that spend money or contact real people are two-step by design:

  • Call the verb without a token → you get a quote/preview and a single-use confirmation_token (15-minute expiry).
  • A human opens the console and approves that exact action — the approval is bound to the precise parameters, so nothing can be swapped after the fact.
  • Call the verb again with the token → it executes.

This is server-enforced, not a UI convention: an agent alone cannot mint and redeem its own approval. New workspaces also pass a one-time review before their first external action.

4

Async operations

Purchases return an operation_id. Poll GET /v1/operations/:id: states run queued → running → waiting_external → succeeded | partially_succeeded | failed, with progress counts. Sending capacity typically provisions within hours and up to ~48h; assets activate automatically and the operation completes itself — no babysitting required.

5

Suppression

Where the 90-day contact check is enabled for your workspace, create_campaign, add_leads and launch_campaign leave out people emailed by another campaign in the last 90 days or being emailed now. The optional removed.contacted_recently count is included in removed.total; legacy suppression counts keep their meaning. get_me.guard.contacted_recently_window_days is 90 when enforced and absent otherwise. During a history outage, drafts still build with history_available: false and live-overlap checks; launch and additions to active campaigns return retryable contact_history_unavailable. Replies in existing threads are unchanged. This rollout is off by default.

Active person, company and area entries on a workspace’s do-not-contact list block campaign admission and replies. The optional removed.do_not_contact count is included once in removed.total. A person who also has a suppression fact is counted under that fact. List management requires an authorised person session. The separately gated email-upload API accepts campaign:write keys; it cannot add companies or areas, loosen entries, or remove them. MCP has no list-management tool. Entry creation is off by default, while existing entries remain enforced. Loosening takes 24 hours and never re-adds previously excluded people. Never contact again is permanent.

The final send fence serializes with list changes. If the list change wins, the answer is cancelled with recipient_on_list and nothing is sent. A send that already passed the fence is in flight and is recorded as “Sent moments before the list change.” Removal progress shows separate receipt, told and confirmed times; missing times remain unknown.

Lists, uploads and areas are off until released. In the console, an authorised person can add a person, company or area where enabled, choose an end date, and preview the affected people before adding a company or area. Area previews also show people with no location and drafts or waiting launches left empty. New people with no location are left out while an area rule is active; existing running recipients are removed only for a known match. The optional removed.no_location count is a subset of removed.do_not_contact, not an extra removal. Uploads accept a CSV of up to 5,000 emails and 2 MiB: Check reports valid, rejected and already-listed rows without writing; Import uses that checked digest. Upload history shows the latest 50 uploads and how many upload-owned entries remain listed. Removing an upload takes 24 hours; Keep cancels that pending removal. Entries strengthened independently and permanent Never contact again entries remain protected.

Adding an entry blocks new admissions immediately and queues removal from live campaigns. “Told” means the provider has received the stop request; “confirmed” means a read-back verified it. Provider delays can leave confirmation pending. Removing or expiring a list entry never clears an unsubscribe, hard bounce or other permanent suppression fact, and never automatically re-adds a recipient.

Replies, unsubscribes, and hard bounces suppress the address workspace-wide and permanently. A spam complaint, or a request to Layrcake to delete someone's data, blocks the address for every customer: no workspace can email it again. Suppression is enforced in three places: drafted leads are filtered at creation (with counts reported), the list is re-checked at launch, and inbound events remove live recipients from active campaigns. It is not configurable — that's the point. If a suppression removal can't be confirmed at launch, the launch aborts and the campaign stays a draft.

6

Trust & data

  • API keys are stored hashed; a key is displayed exactly once.
  • Enriched contacts are cached in your workspace only, for 90 days, then expire.
  • Saved request results are kept for 90 days. Campaign leads, replies and sent replies are kept for 12 months after the campaign ends, and webhook logs from the sending provider for 30 days after they are processed. Request records (the verb, the outcome and the charge) are kept for accounting, without results or personal data. The full schedule is in the Privacy Policy.
  • Suppression lists are permanent and honoured across every campaign you run.
  • Humans approve every purchase and every launch. Agents cannot spend alone.
  • Your data is never resold or shared across workspaces. The one exception is the block list: when someone reports an email as spam, or asks Layrcake to delete their data, their address is blocked for every customer.