Changelog
Everything that changed in the API, the MCP server and the console, newest first. Price changes are announced here and by email; they apply to requests made after the change, never to settled ones.
ChangedPrivacy and opt-outs
- An opt-out on every first email. Layrcake adds a short P.S. to the first email of every sequence: P.S. Found you through public business info. Not for you? Just reply "no" and I'll leave you be. Your workspace can pick another wording, or write its own, but can't remove it. Don't put an opt-out line or a P.S. in create_campaign steps.
- Countries that need consent. Before launch, launch_campaign flags recipients in Germany, the Netherlands, Italy, Spain, Poland, Denmark, Austria and Belgium, where business email needs prior consent. Your agent asks you whether to send to them or leave them out, and passes your answer as consent_countries: send (the default) or leave_out. The choice is recorded and shown on the approval.
- Blocked for everyone. A reply asking to stop still suppresses the address for your workspace. A spam complaint, or a request to Layrcake to delete someone's data, now blocks the address for every customer. A person who asks Layrcake to delete their data is erased from every customer account that holds them, and from the sending provider.
- Retention. Saved request results and the contact cache are kept for 90 days. After that a request's result has expired; get_request keeps its status and billing. Leads, replies and sent replies are kept for 12 months after the campaign ends, and processed webhook logs for 30 days. Request records are kept for accounting, without results or personal data.
- Closing an account. Ask to close your account and it stays open, and works as normal, for 30 days, so you can change your mind. Then it closes, and its data is erased within 7 days of closing, apart from billing records (kept 6 years) and the opt-out list.
UnreleasedList, area and upload documentation
Documented person, company and area lists, area preview and missing-location counts, permanent Never contact again, checked CSV uploads, upload history, and the email-only upload API. List, area and upload creation stay off until released. Existing entries remain enforced; loosening takes 24 hours and never clears permanent suppression facts.
PricingNew prices for company search, research, checks and model calls
- find_companies: 11 credits per hiring company (was 6), 13 per lookalike (was 6), 16 per technology match (was 7). Firmographic (2) and local (3) are unchanged.
- enrich_company datasets 13 (was 8); research_company 9 per research (was 6); check_companies 3.5 per company answered (was 2).
- find_intent 11 per hiring company and 13 per paid check (were 10 and 12); verify_email 1.25 (was 1); a searched mobile 17 (was 15).
- find_leads 0.25 a lead (was 0.2), still with a 3-credit minimum when anyone is returned; the premium step 1.6 a lead (was 1).
- llm: Sonnet 5 0.9, Haiku 4.5 0.45, GPT-5 mini 0.2 credits per 1k tokens (were 0.5, 0.25, 0.1).
- Unchanged: enrich (5), briefs (10 / 40), prospect_picture's assembly fee (3), every free verb, and misses stay free. Calls already authorised keep the price they were quoted.
ChangedA filtered full read goes further on the same budget
- Realistic plan. With author_filter, read_all now plans on the read fee plus a small allowance for matches instead of assuming everyone matches, so the same budget_credits reads about twice as many people and full_read quotes about half. The charge is still never above the budget.
- Told, not cut short. Each post now reports not_read, and withheld for any matches past the budget (not charged), with limited_by: "budget". Raise budget_credits to get them.
Do-not-contact backend and plan version 3
Dry runs now return plan_version 3; plan fingerprints change. Optional removed.do_not_contact distinguishes active list entries from suppression facts without counting a person twice. Person-session list management remains disabled by default. The final send fence rechecks active entries, and in-flight replies retain an honest sent-before-list-change record. No schema migration.
Addedcheck_companies answers a seller's question from each company's own site
- One question, up to 10 companies. Ask what a company offers or does ("Do they connect to Zapier?"); each answer is yes, not_found or unclear.
- Every yes is quoted. A yes comes with a passage from the company's own site that states it on its own, word for word, and its page URL; pages_read lists every page checked.
- 2 credits per company answered yes or not_found. Unclear answers and unreadable sites are free.
Addedauthor_filter finds titles written in other words
- More of the right people. With author_filter, a title check also keeps people whose headline clearly means one of your terms (a Chief Commercial Officer for chief revenue officer). Each is marked matched_by: "judged"; filter_basis is "judged" when it added anyone. It only adds people and is charged like any person kept.
Addedfind_posts reads everyone who engaged with a post
- Everyone, in the background. engagers_of with read_all: true, budget_credits and an idempotency key reads every person who engaged, not a sample, about 2 minutes per 500 people. It returns an operation: get_operation returns the people 25 at a time with next_cursor. The same 0.5 credits a person kept (2 on Reddit); with author_filter, 0.4 credits per person read. Never above budget_credits.
- Price change: the filter fee. With author_filter, each person read now costs 0.4 credits, up from 0.25 on a sampled engagers_of call. It is the same 0.4 on a full read. The 0.5 credits per person kept (2 on Reddit) is unchanged, and calls without a filter cost the same as before.
- A quote first. When a sample missed people and the posts' counts are known, the answer's full_read gives the budget and the time to read everyone.
Pricingenrich_company datasets cost 8 credits, and a profile now carries real company facts
- Each structured dataset (profile, technologies, funding, relationships) costs 8 credits when it returns data, up from 7. Empty and unavailable datasets stay free; briefs stay 10 fast and 40 deep.
- A profile now returns headcount, industry, country and LinkedIn where they're known, and counts as a hit only when it holds one of headcount, industry, country or founded year. A name or description alone is empty and free.
- Calls already authorised keep the price they were quoted.
ChangedDry-run plan version 2
Every dry-run verb now returns plan_version 2, including all eight sending and research previews and data-verb previews. Plan fingerprints change across this deploy. The contact check remains off by default; shadow mode changes neither responses nor plan input_version. OpenAPI adds contact_history_unavailable and optional history_available fields. The 90-day window and contacted_recently count appear only when enforced; their absence means this rule was not evaluated in enforce mode.
Addedfind_posts finds the people behind the posts, and the reason to reach out
- Better sources. LinkedIn, X, Reddit and Facebook now answer keyword, author and company searches from one first source, with keyword backups. YouTube and TikTok stay keyword search. On Reddit and Facebook a returned post costs 2 credits.
- Leads. leads takes contacts as find_leads or enrich return them and gives each one's best recent reason to reach out (their own post, a comment, a reaction or their company's post), with the evidence and the person ready for enrich. 3 credits per lead with a signal; a lead with none is free. Up to 10 leads answer inline, and up to 1,000 run as an operation read through get_operation in pages of 25.
- Engagers. engagers_of returns the people who reacted to, commented on, replied to or reposted up to 5 posts: 0.5 credits a person (2 on Reddit). author_filter keeps people whose headline matches, at 0.25 credits per person read.
- Goals. audience orders posts by how worth a reply they are and adds why_now. intent adds hiring_for_it and competitor_complaint; competitor_complaint finds most, not all, complaint posts.
ChangedA person known only by name: enrich first
author: { type: "person" } is now refused before anything is charged, with the next call in details.next: call enrich with the person's name and company, then find_posts with the linkedin_url it returns. Instagram, Threads, Hacker News and Quora are no longer served.
AddedOptional fields for limits and refusal details
Monthly workspace spend enforcement is implemented but remains off. When enabled, only new paid work stops; replies, running campaigns, approved launches, reads, renewals and OAuth connections continue. Refusals preserve their workspace/key distinction and UTC reset on replay. Payer-only spend reporting includes in-flight commitments; spend notices remain dry-run. See Errors for the concurrent-settlement bound.
The contract now accepts daily ceiling, one-day extras, applying status, workspace spend and resume availability on get_me, plus campaign and waiting state on approvals. These fields remain absent until their rollout. Typed refusal and reset details, when supplied, survive storage, replay and request status. No new limit or sending behavior is enabled.
FixedA cancelled Google sign-in brings you back to the sign-in page
- Console: if you cancel on Google's account chooser, or Google sign-in fails, you now land back on the Layrcake sign-in page with a short note, and can try again or continue with email. It used to end on a bare “not found” page on Layrcake's API address.
- Console pages now live at app.layrcake.dev/w/<workspace>/…, for example /w/<workspace>/approvals. The pages themselves are unchanged.
- Older addresses keep working, in bookmarks and in emails sent before this change: /home, /approvals?id=… and the rest open the same page in the workspace the approval or key belongs to, or in yours.
- Links Layrcake gives you name the workspace too: approval_url in setup_sending, launch_campaign, add_leads and reply_to_thread answers, console_url in card errors, the links in approval, billing and welcome emails, and the return from Stripe.
- An Approvals page left open from before this change asks you to reload it before you approve or decline.
ChangedConsole pages name their workspace in the address
ChangedStopping sending, or cutting off an agent, ends the approvals it would have run
- For approvals Layrcake runs itself, which isn't switched on for any workspace yet: Stop sending in the console, a stop by Layrcake, revoking the key that asked, or disconnecting its connection now cancels, at that moment, every such approval that hasn't started, including one an agent is asking for at that very second. Clearing the stop brings none back; the agent asks again.
- The same approvals are expired once nobody has decided them in 7 days, and GET /v1/trail can list approval_held, approval_undone, approval_cancelled and approval_failed. approved and declined can also say where the decision was made.
- An identical launch_campaign or reply_to_thread call after a teammate joins or leaves returns the same approval, instead of asking again.
- A dry run where Layrcake runs approvals itself refuses supersedes and first_send_not_before rather than ignore them: send them on the call itself.
ChangedThe console's Approve and Decline check where they were pressed
- Approve and Decline on the Approvals page now refuse a request that didn't come from the console (403), as every other console change does, and decide in the approval's own workspace.
Fixedfind_leads searches a region or metro area as its cities
- A location such as "San Francisco Bay Area", "DFW", "Greater London" or "Randstad" is now searched as the principal cities it covers, so every source can take it. Before, most sources could not read a region name, and the search fell back to a narrower one or was turned down.
- The answer says so in result.interpreted.places: the location you sent, the region it was read as, and the cities searched. It appears only when a location named a known region; any other location is searched exactly as before.
- Known regions: the San Francisco Bay Area, Silicon Valley, Greater New York (the tri-state area), Greater Los Angeles, Southern California, Greater Boston, Dallas-Fort Worth, Greater Seattle, Greater Chicago, the Washington DC metro (the DMV), the Research Triangle, Greater Austin, Greater London, Greater Manchester, the Midlands (West and East), and the Randstad; plus NYC, SF and L.A. A region may carry its country or state ("Bay Area, CA").
- find_companies' firmographic mode reads the same regions in locations, when they lie inside country_codes, and lists them in result.interpreted_places.
Addedfind_companies can search by industry, place and size, or for local businesses
- Documented, and not yet switched on for any workspace: mode: "firmographic" finds companies by industries and country_codes, optionally narrowed by locations (matched against each company's headquarters) and company_headcount, at 2 credits a company; mode: "local" finds local businesses by categories in locations, each a city with its country, at 3 credits a business. Zero results are free.
- Their companies carry a firmographic or local signal and a city, only in a request that names that mode. Where a mode isn't available to your key, a request that names it is refused before anything is reserved. Requests without mode are unchanged.
Changedfind_leads, find_companies and enrich take a route
- Documented ahead of route choice, which isn't switched on for any key yet: route on the three verbs (balanced, the default, cheapest, broadest or fastest), a default route per key (data_route in the key policy, which only a workspace owner can set), and layrcake.route in the answer, which says which route was used and where it came from. The price doesn't change with the route.
- Until route choice is on for your key, route: "balanced" is accepted and any other route is refused with 400 invalid_request rather than ignored. A call without route answers exactly as before.
- Also only where route choice is on: find_leads refuses an industry outside LinkedIn's taxonomy with 400 invalid_request and up to five close values in details.candidates, instead of searching on it, and a company_headcount range whose min is above its max finds nobody, free, instead of searching every company size.
ChangedWho a campaign leaves out is decided in one place, and create_campaign reads only the addresses you send
- create_campaign now checks only the addresses in the request against your workspace's “no” list and the platform block list, instead of reading both lists whole. Who is left out, and every count in the answer, is unchanged; a large list gets there faster.
- Because of that, a create_campaign dry run's plan.input_version now changes only with the request's own addresses, not with the rest of your list. It will differ once from a dry run made before this release. An add_leads or launch_campaign dry run's input_version can also differ once, when the addresses it checks include two or more on the platform block list or your “no” list, or one address on both.
- Documented ahead of the 90-day contact rule, which isn't switched on for any workspace yet: removed.contacted_recently on campaign answers (absent until the rule applies), the removed reason contacted_recently in GET /v1/trail, and the approval ledger's category of the same name. Nothing is counted under them yet.
ChangedThe trail records every time a campaign stops sending, and when it finished
- GET /v1/trail gains the kind completed: the sending provider finished the campaign. It is recorded once per campaign, at the time the provider reports, and the kind filter accepts it.
- A paused entry is now recorded whenever a campaign stops sending, not only when sending is stopped: the provider reports it isn't sending (provider_reported), Layrcake pauses it because its daily sending limits aren't safe (sending_limits), add_leads pauses it to import new leads (add_leads), a launch recorded as failed went live anyway (failed_launch_live), or a launch is recorded as launched and paused (launched_paused). The reasons asset_health and backfill are new too.
- A campaign paused because its sending mailbox went missing is recorded with the reason asset_health; it read founder_kill before. That pause, and a pause for a risk review, now also record pause_requested straight away, as a suspension already did.
FixedA campaign the provider reports finished reads finished everywhere
- When the provider reports a campaign finished, the leads still in its sequence now read completed (for example in get_reply's lead.state), and its daily sending allowance is released straight away. Before, a report that arrived by webhook left them in_sequence and kept the allowance reserved.
Changedlaunch_campaign and reply_to_thread say what happens after a yes
- Both tools now say, in words that hold for today's approvals and for approvals Layrcake runs itself: repeating a call is always safe and never runs it twice, and calling again without a token while the same request still waits returns that approval, with no second email.
- Documented for workspaces where Layrcake runs approved launches and replies itself, which isn't switched on for any workspace yet: the result's approval block, first_send_not_before and supersedes on the request, the 202 a call with the token reads, the approval_superseded entry in GET /v1/trail, and the errors approval_declined, approval_expired, approval_superseded, approval_cancelled and approval_changed. Anywhere else, first_send_not_before and supersedes are refused rather than ignored.
FixedA launch that went live is recorded as launched, even when sending is stopped at that moment
- If sending was stopped (by Stop sending in the console, or by Layrcake) just as a campaign went live, launch_campaign now answers launched and get_operation shows it succeeded; the stop then pauses the campaign. It used to answer 403 forbidden and record the launch as failed, although the campaign had gone live.
- If the stop comes before the campaign goes live, nothing is started, as before.
FixedA launch or a reply that Layrcake takes over is answered and recorded once
- When a launch_campaign or reply_to_thread call stalls long enough for Layrcake to take over its outcome, the call now answers what Layrcake recorded: launched, sent, or 409 operation_unknown_state while it is still being checked. It no longer writes its own outcome over Layrcake's, which could leave the operation shown as being reconciled indefinitely, or answer 500 internal.
- A call that has stalled that long no longer starts the campaign or sends the reply itself; Layrcake finishes it, and get_request gives the outcome. A reply is also not sent if sending was stopped between its last check and the send.
- get_operation for a launch Layrcake finished no longer shows launch outcome is being reconciled as its error.
- If a refused reply can't be recorded because of a brief fault on our side, reply_to_thread answers 409 operation_unknown_state, and Layrcake then records it as not sent. The reply is not sent, and the thread can be answered again.
ChangedA launch whose outcome is uncertain is recorded from what the provider shows
- When Layrcake can't tell whether a launch went out, and the sending provider shows its campaign paused or blocked (neither a draft nor sending, for example because its sending accounts are unhealthy), the launch is now held while Layrcake checks what it sent. get_operation says it is being reconciled, and launch_campaign answers 409 operation_unknown_state. It used to be recorded as failed after 15 minutes. If it sent nothing, it is then recorded as failed and its campaign stays paused: build a new draft to launch again.
- A launch being checked that turns out to have gone live with daily sending limits other than the ones it reserved is now recorded as launched, and its campaign is paused. It used to be recorded as failed, although it had gone live.
- A campaign whose launch was recorded as failed, but that went live at the provider anyway, is now paused, and shows as paused instead of as a draft that could be launched again.
FixedA failed call reads the same everywhere, and a charge the card did not decline is no longer called a decline
- A call refused because billing is frozen, or because available credits are below its cost, now reads in get_request, and on a retry with the same Idempotency-Key, in exactly the words it was answered with. Before, those reads used slightly different words, for example “billing frozen …” for “billing is frozen …”, or “… the maximum cost of this call” where setup_sending answered “… the cost of this purchase”.
- The same now holds for add_leads (a campaign whose state takes no leads, an unapproved confirmation), for launch_campaign when the workspace is suspended during the launch, and for a prospect_picture left with nothing to picture by the key's daily intent cap. No answer's words changed.
- Every add_leads failure after its request is recorded now names it with request_id, as the other verbs' failures do.
- research_company, and the research part of a prospect_picture, no longer fail and release the reservation when the answer that writes the claims arrived but could not be recorded. The run is held as unknown with its reservation until it is reviewed, as it already was for its searches and page reads, so an answer that may have been paid for is never released unseen.
- Console: a card payment that failed shows the card's reason only when the card was declined (for example “insufficient funds”). A charge that failed for another reason reads “couldn’t be charged”, with no code, and the few declines that are never explained to a card holder read “generic decline”.
- Console, Sending: when an order's payment has failed for good, the note now says nothing was ordered and to order again, instead of promising that the order would be placed once the payment succeeds.
FixThe pricing page shows every price on a wide screen
- On any screen wider than a phone, the price table on Credits & pricing showed only its verb column: every price, and when it is charged, was cut off. The whole table now fits, and long prices wrap. No price changed.
- The same fault hid the last column of two more tables: what a key and the console may change, in Build your own dashboard, and what each tool maps to, in the MCP tools reference. Both now fit.
- A table still wider than its column, such as the one in Errors, now scrolls sideways instead of cutting off the end of each line.
Pricingfind_leads costs at least 3 credits when it returns anyone
- A search that returns at least one lead now costs at least 3 credits, so 1 to 15 leads cost 3 credits. From 15 leads up nothing changes: 0.2 credits a lead.
- A search that returns nobody is still free.
- The hold for a search is at least 3 credits too, so with less than 3 credits available a search is refused with insufficient_credits before it runs.
- Why: the data source bills for every result page that holds anyone, so a search of a few leads cost more to serve than 0.2 credits a lead.
- Searches made before the change keep the price they were quoted.
ChangedIn find_intent, a job post that fails your roles no longer brings its company's funding round
- A company's latest funding round comes with its hiring post. When the only post found for a company did not pass your roles (for example the quoted role "growth head" against the title “Head of Growth”), the round still came back, free, and funding counted as checked. That company is now treated like any company that is not hiring: with signals left out, funding is listed under skipped_signals and nothing is charged; with funding asked for, the usual funding check runs at its usual price (12 credits).
Fixedfind_posts finds the right person when a verified namesake is one step behind
- When you look up a person's posts by name, a profile with their exact name that names their company now wins over an exact-name verified namesake, and their posts are searched. A rounding error used to leave that match ambiguous, so the call was charged the 1-credit scoped base and searched nothing. A true tie still returns the candidates for you to choose from, at the scoped base as before, and every other match resolves as it did.
- Candidate confidences in author_resolutions are exact hundredths: a verified namesake shows 0.95, not 0.9500000000000001.
Fixedget_operation no longer shows the sending provider's order reference or status
- Once a setup_sending order was confirmed with the sending provider, get_operation, over the API and the MCP server, showed the provider's own order reference and order status in result (provider_order_id, provider_status). Both are gone; the rest of the result is unchanged.
FixedA failed setup_sending reads the same everywhere
- When the sending provider answers without placing the order, or the domains for your brand_hint were taken between the quote and the purchase, the purchase now answers 503 provider_unavailable or 400 invalid_request as intended. It could answer 500 internal before. A retry with the same Idempotency-Key, get_request and get_operation now give that answer's own text, as they now also do when a quote was not approved.
- A card-paid purchase whose payment fails for good now says so: the card payment failed and nothing was ordered. get_request, get_operation and a retry say the same. It used to say the order would be placed once the payment succeeded, which could not happen. While a declined charge waits for a retry or a new card, the answer is unchanged except that it no longer quotes the payment processor's own code; the Sending page in the console shows why the card was declined.
FixedA refused card-paid sending order is explained in our own words
- When you buy sending capacity with your saved card and the email provider refuses the order, get_request, get_operation and a retry with the same Idempotency-Key now give the purchase's own answer: the sending provider did not accept the order, and your card charge is refunded. They could show the provider's own error message before. The refund itself is unchanged.
- While such an order's outcome is being confirmed with the provider, get_operation says order outcome is being reconciled, as it does for orders paid with credits, instead of the provider's message.
FixedA failed operation reads as its request does
- For an operation whose request failed, get_operation's error is now the message get_request shows for that request. An operation that failed before v1.18 could still show a provider's own wording there; it now reads as the neutral message for its error code, as get_request already did. Notes on operations still in progress are unchanged.
FixedBilling errors no longer quote Stripe
- When Stripe refuses to start a credit checkout (POST /v1/billing/checkout and the console's Top up), a card setup or the card portal, the error message no longer quotes Stripe's own text. It is now just “could not start checkout”, “could not start card setup” or “could not open the card portal”; the error codes and statuses are unchanged.
FixedA replay and get_request show the error the call gave you
- Some find_posts and enrich_company failures were recorded in internal wording, such as "all requested social platforms unavailable" or "company datasets unavailable", while the call answered "social search is unavailable right now" or "company enrichment is unavailable right now". A retry with the same Idempotency-Key and GET /v1/requests/{id} showed the internal wording. Both now show the message the call answered. The answers themselves are unchanged.
- If the company data in a prospect_picture answered but Layrcake could not record the call, the company component read failed and the picture settled. The picture now shows unknown and its reservation is held until the call is reconciled, as its research and posts components already did: the source may have charged for an answer that was never recorded, so nothing is charged or refunded blind.
FixedA failure after the source answered is a free 503 that names its request
- If a data source answered but Layrcake could not record the call, a plain-language find_companies query and a find_posts search or intent classification answered 500 internal with no request_id, and the credits reserved for the call stayed on hold until it was reconciled by hand. They now answer 503 provider_unavailable naming the request, charge nothing and release the hold at once, as the other data verbs already did.
- Like a call that timed out, such a call never runs again under the same Idempotency-Key, so the source is never paid twice for it; retry with a new key.
FixThe pricing page lists every verb
- add_leads and reply_to_thread were missing from Credits & pricing. Both are listed now, and both are free, as they always were. No price changed.
- The page said research_company always runs fresh. It now says what has always happened: researching the same company at the same depth again within 7 days answers from your own workspace's cache at the research price (6 credits), with no page credits. The shared research library is used only by prospect_picture.
FixedData verbs no longer report a false pause, and every failure says why
- When a data source behind a route is out of credits or refuses its key, calls now fail with details.reason vendor_account, including the calls that skip that source until it is fixed. They used to say temporarily_paused.
- vendor_account now means only that: a source's account problem. When a source refuses one request, the reason is invalid_request if you can change the request (a find_leads filter the source's plan doesn't include, a find_companies query the model provider refuses) and upstream_error otherwise.
- A search that finds nobody, or a request a source rejects or refuses, no longer pauses the route for everyone else. Before, five in a row could make lead search answer temporarily_paused to every caller for five minutes.
- enrich, verify_email and find_companies failures now carry details.reason, and details.retry_after_seconds when known, as find_leads and find_intent already did.
- A source that cannot be priced right now is skipped without being called, and the next source answers. When none is left, the call is a free provider_unavailable with upstream_error.
FixedWhat you send is honoured or refused, never dropped
- find_companies: industries, country_codes and company_headcount sent with a plain-language query used to be replaced by the query's reading without a word. They now win over what the query says. company_headcount merges bound by bound, so a bound you leave out keeps the query's, unless that would invert the range, in which case yours is used alone. result.interpreted.caller_filters lists them with every reading they replaced or dropped.
- find_companies: a company_headcount max without a minimum still left out companies with fewer than 10 employees, so {"max": 5} always came back empty and {"max": 50} quietly searched 10–50. A max without a minimum now searches from 1 employee. The 10-employee floor applies only when you send no headcount bound at all, and with a query, result.interpreted.filters shows the minimum used.
- enrich: max_credits is now enforced. If the most the call can cost is above it, the call is refused with 400 invalid_request before anything is reserved or charged. want is still accepted but not yet applied, and the reference now says so.
- get_operation over MCP no longer asks for usage:read. A data:read key can poll research and pictures in its workspace, as it already could over REST; other operations still need usage:read.
ChangedAgents are pointed at the right tool
- The find_intent example no longer lists signals. Leaving it out checks hiring first, so a company that is not hiring costs nothing. The old example asked for funding explicitly, which pays a 12-credit check on every company that is not hiring.
- Goal-style asks about one company or person (angles, what's changed, how to approach, why now) are now pointed at research_company and prospect_picture in the find_intent, find_posts, research_company and prospect_picture tool descriptions.
- get_replies no longer lists labels that are never written. classification is out_of_office for an automatic reply and other for every other reply; record what a reply meant with set_lead_status.
FixedA card-paid mailbox purchase shows what it costs
- Its approval, in the console and in the approval email, shows the charge to your saved card now and the amount added each month, instead of “0 credits”. Credits are not used for these purchases.
FixedA sending suspension whose containment failed says so
- If a campaign could not be confirmed as paused at the provider, the Keys page says containment failed and the campaign may still be sending. It used to say containment had finished.
Changedverify_email never pays twice for one answer
- If the first stage of a check does not complete (the provider may have run it, but its answer never arrived), the second stage is not run. The call returns a refunded 503 provider_unavailable with details.reason upstream_error and details.retry_after_seconds. If the first stage could not be reached at all, the second stage answers as before.
- Until that wait is over (at most 5 minutes), a retry of the same address returns the same refunded 503 without running a new check.
Changed/v1/llm/chat has timeouts, and pauses a failing model
- A call is ended if a non-streaming answer takes more than 5 minutes, or if a stream does not start within 1 minute, stays silent for 2 minutes, or runs past 15 minutes.
- After repeated provider failures, a model is paused for 5 minutes. Calls to it answer provider_unavailable with details.reason temporarily_paused and retry_after_seconds.
- A request the model rejects as invalid (bad parameters, too large, flagged by its content policy) now answers 400 invalid_request instead of 503 provider_unavailable: fix it rather than retry. It never pauses a model.
- An error the model reports before producing any output costs nothing, including one that arrives inside a stream.
- When no answer reached you and nothing can settle the call, a non-streaming call returns operation_unknown_state, and a stream's final layrcake.billing event shows final: false with nothing charged. The credits stay reserved until the call is reviewed; they are never charged blind.
ChangedRetrying with the same Idempotency-Key runs a failed data call again
- Search, enrichment, verification, research, picture and LLM calls that failed with provider_unavailable, rate_limited or insufficient_credits (once your balance covers the call) now run once more when you retry with the same Idempotency-Key, instead of replaying the old failure. The retry is a new attempt with its own request_id; concurrent retries share that one attempt, and the failed attempt stays readable at GET /v1/requests/{id}.
- A failed call runs again only after its cooldown: 30 seconds, or its own longer retry_after_seconds. A retry before then gets the failure back with details.replayed: true and the seconds left, so an agent re-sending in a loop (for example to poll a picture or a research) starts at most one new attempt per cooldown. An insufficient_credits refusal runs as soon as a top-up covers the call.
- A call whose provider may already have done the work (it timed out) never runs again under the same key, so a vendor is never paid twice for it; it comes back with no retry hint.
- Send-class verbs (setup_sending, create_campaign, add_leads, launch_campaign, reply_to_thread, set_lead_status) still never run twice under one key. A stored failure now says so with details.replayed: true; retry it with a new key.
- Refusals, provider failures and replays of a metered call now name their request in request_id, including credit, spend-cap and billing-freeze refusals.
- Error messages no longer carry a provider's own wording, on the first answer or on a replay. Failures recorded before this change read as a neutral message for their code, in get_request and in replays.
ChangedEvery reply reaches you, including the ones after a first reply
- A reply that arrives after its lead has left a campaign is now recovered within minutes and appears in get_replies, in the right campaign and thread. Replies like this since 12 September have been recovered.
- Someone who replies, bounces or unsubscribes is now stopped at the sending provider instead of being removed, so their later replies arrive straight away. A lead that was never emailed is still removed.
- An out-of-office reply now stops the sequence too.
- reply_to_thread answers a recovered reply in the same thread. If the thread has nothing that can carry an answer, it refuses with reply_content_unavailable before anything is sent.
Changedfind_posts no longer keeps post judgements or resolved identities
- The pain / question judgement of a post and a person's resolved profile are worked out for each request and never stored for later requests. Asking again with the same Idempotency-Key still replays the original response at no cost.
- A new request over the same posts classifies them again, so its classification component is charged again (fractions of a credit).
- Cached company, search and research data keeps its published lifetimes and is now deleted once it expires.
ChangedYour key's MCP tool list shows only tools that can run for it
- A tool whose release switch is off, or whose provider is not configured, is left out of the list instead of being listed and answering "temporarily unavailable". A tool with several modes (find_companies, enrich_company, find_posts) stays listed while at least one mode can run.
- The route and the list use the same check, so they always agree. Reconnect your client to see a change.
- A plain-language find_companies query is refused straight away, before its interpretation, when neither hiring nor technology search is available to resolve it into.
ChangedEvery model call goes only to endpoints that keep no copy and never train
- /v1/llm/chat and the features that use a model for you (research_company, enrich_company briefs, plain-language find_companies, find_posts intent sorting) now route only to zero-data-retention endpoints that never collect prompts for training.
- A model with no such endpoint is refused with provider_unavailable and a plain reason, at no charge.
- In /v1/llm/chat, routing fields no longer reach the model router: models, route, plugins, preset and web_search_options are dropped, max_completion_tokens gives way to the capped max_tokens, and provider keeps only narrowing preferences. The call always runs on the allowlisted model it is priced as.
ChangedEvery purchase, launch and reply needs a person's approval, with no per-key exception
- The per-key “act without asking” setting is removed. It never let an action skip approval; now nothing suggests it could.
- autonomous_external_actions is deprecated. GET /v1/me and key policies always report false; PATCH /v1/keys/:id still accepts false and refuses true.
- Per-action limits (recipients per action, assets per purchase, replies per day) stay, and apply to every key.
FixedDry runs report approval and the key's own limits truthfully
- needs_confirmation is always true for setup_sending and launch_campaign, and for add_leads on a live campaign.
- would_exceed lists key_action_limits when the key's per-action limits would refuse the real call, counted the same way the real call counts: domains plus mailboxes for a purchase, leads left after suppression for add_leads.
Pricingprospect_picture reuses recent research from a shared library at 40% of the price
- Public facts from completed research (a dated launch, funding round, partnership, customer or expansion, each with its source link) are kept in a shared library that never records who asked.
- When anyone researched the same company at the same depth in the last 14 days, a picture's research component is served from the library at 40% of its fresh price: 2.4 credits instead of 6 for standard. The quote still shows the fresh price as the maximum; you are charged the lower amount.
- Library facts arrive in facts with their source link and date, never an excerpt; the component reports from_pool: true and why_now can include pooled_fact. research_company itself always runs fresh.
NewNetwork counts in prospect_picture
- The free network component adds anonymous totals for a company's email domain across all customers over 90 and 365 days: people emailed, replies, bounces, opt-outs and complaints.
- A window appears only when at least three customers and ten people contributed and no one customer makes up more than half. It never names people, messages or customers.
Fixedresearch_company keeps dated news it used to drop, and deep pages always count
- A news fact backed by its article's headline is now kept; titles of other results still are not evidence.
- deep gives the company's own pages their own claim slots, reads only the company's own site (no help centre or other subdomain), and a page you pay for is never swapped for a search snippet of the same URL.
- A site section's title, such as "Help Center", is no longer reported as a name for the company.
Pricingfind_intent checks hiring first, and paid checks are priced per check
- A company with a current hiring signal costs 10 credits, as before, and now also returns its latest funding round at no extra cost.
- Leave signals out and only hiring is checked: a company that is not hiring costs nothing and lists funding under skipped_signals. List funding to check every company.
- A paid check you ask for explicitly (funding on a company that is not hiring, news or job changes, technology) costs 12 credits, whatever it finds. It replaces the 0.5-credit scan fee. Failed checks stay free.
- Every company in result.domain_status now carries charge: what it cost, whether it was hiring, and how many paid checks it used.
FixedDiscovery errors, partial coverage and technology confidence
- find_intent now reports coverage per domain and signal family; incomplete companies are never billed as fully checked misses. Provider account and authorization failures are visible and carry zero provider cost.
- find_companies separates low-confidence technology candidates from chainable results, preserves high-confidence capacity when noise appears first, and validates the full 100-result + 25-candidate internal page safely.
- Temporary economics guard: find_intent.company_domains is capped at 25 per call and each key may check 25 domains per UTC day until Signals v2 can reuse observations. Requests above either limit are rejected before provider work.
NewRecurring plan billing on a saved card — additive contract, staged rollout
- Agents beyond your first two ($6.00 a month each, $3.00 from the 11th, $1.00 from the 101st), mailboxes ($7.50 a month) and domains ($15.00 one-off) are charged to a saved card in advance, on one consolidated workspace renewal per billing day. Usage stays on prepaid credits; the two never touch, and a declined plan charge can never spend credits.
- GET /v1/me gains optional upkeep (the calling agent's plan state: free, active, grace, suspended…) and billing (live agents, next renewal date and estimated total).
- setup_sending keeps every existing field and, when card billing applies to the workspace, adds card_quote and billing_period; its envelope credits_charged is "0" for card-funded assets. The semantic change from credit-funded to card-funded assets is switched on per workspace, never silently.
- New error codes: agent_suspended (403), card_required (402), payment_action_required (402), billing_unknown (409). See Errors.
- Console: Billing (plan, card, default-on auto-recharge with an off switch, itemised receipts) and Sending (mailboxes under domains, paid-through dates, cancel/undo). Paid API keys are created only after you confirm the first month's charge; the one-time secret can be revealed again for 15 minutes if the page is lost.
- Receipts are receipts, not tax invoices; any tax that applies appears on the Stripe receipt.
NewThe product is now Layrcake — hosts, keys, envelope and headers renamed
- Canonical hosts are api.layrcake.dev, mcp.layrcake.dev/mcp, app.layrcake.dev and docs.layrcake.dev. The former API and MCP hostnames keep working as direct aliases of the same service; they are never redirected, so POST bodies are safe.
- API keys now begin cake_sk_live_ / cake_sk_test_. Keys issued under the former prefix were revoked at the rename and are rejected everywhere, including on the former hostnames — create a new key in the console.
- The metering envelope on every response is layrcake (was the former product name) and the streaming identity headers are X-Layrcake-Request-Id and X-Layrcake-Credits-Reserved. The /v1 path and every verb contract are unchanged.
- MCP OAuth issuer moved to https://api.layrcake.dev. Connectors registered against the former issuer must be removed and added again; discovery on the new host advertises the new issuer.
- Sign-in email now comes from auth@layrcake.dev. Console sessions are host-bound, so sign in again on the new console.
Newreply_to_thread — answer a reply in its thread
- Your agent can now answer an inbound reply in the same email thread, from the mailbox that received it. A real email is sent, so it works like a launch: the first call returns a preview and a confirmation, a signed-in human approves it in the console, and the second call sends.
- It needs the new reply:send scope, which is never granted by default. Tick "Allow in-thread replies" when creating a key, or request the scope in an OAuth connection; the consent screen says exactly what it grants.
- One reply per inbound message. Suppressed contacts are refused, checked again immediately before sending. A workspace daily cap and a per-mailbox cap apply. Sender and subject are derived from the thread and cannot be chosen.
- If the provider's answer is lost mid-send you get operation_unknown_state; Layrcake reconciles against the provider's sent folder and settles once. Never retry a send yourself.
- get_reply lists your sent replies under our_replies.
Newget_reply and set_lead_status — read a reply in full, record what it meant
- set_lead_status records interested, meeting_booked, won, not_interested, out_of_office or neutral on the campaign lead. not_interested is enforced: the contact is suppressed workspace-wide and removed from active campaigns. The status is mirrored to the sending provider in the background and the mirror's state is reported.
- One free verb returns the complete plain-text and HTML body of an inbound reply, the mailbox of yours that received it, a thread id, and the campaign lead's state. get_replies stays the list; get_reply is the read.
- Bodies arrive with the reply and are served from Layrcake; the provider is consulted only when a body is missing, and the answer is cached either way. body_status says whether the body is available, gone, or temporarily unavailable.
- Replies and their bodies are kept for as long as the workspace exists, so an agent can always go back to what a prospect actually said.
- Every response marks the body as external, untrusted text (content_trust), so agent frameworks can keep prospect-written content out of their instruction channel.
- reply_to_thread shipped dark on the same day; see 14 September for its release.
FixCampaign timezone validation and UTC fidelity
- create_campaign.sending_window.timezone now validates against the supported IANA enum before metering or provider work.
- The default Etc/UTC window remains UTC at the sending engine instead of being silently substituted with a US timezone; Europe/Madrid is mapped to an equivalent civil-time zone.
- Signed-out human-approval links now preserve their complete destination through login, including the approval identifier.
- Suppression removal retries clear resolved diagnostics after a successful provider update, while durable retry state still records and recovers transient failures.
- Campaign completion and pause state are reconciled from the sending provider every two minutes as a backstop to webhooks; a locally paused campaign is never automatically resumed.
NewCompany discovery, company intelligence and social contracts
- find_companies, enrich_company and find_posts now share the REST, MCP and OpenAPI contract. Each provider mode/platform remains disabled until its published sample, economics and licensing gate passes.
- Company datasets report hit, empty or unavailable independently; briefs use bounded same-domain scraping and an internal extraction call.
- Social billing uses deterministic literal-v1 relevance. Known-author/company calls add a 1-credit resolution/retrieval base; unresolved names return candidates rather than a guess.
- Fix Provider-operation retries now claim execution atomically; timeouts and crashes stay visible as provisional COGS until reconciliation.
NewCanonical company domains and smarter retries
- find_leads accepts company_domains (1–100): only people at those companies are returned or charged, so "the VP Sales at each of these 40 accounts" is one call. Chains directly from company lists. Same 0.2 credits per lead.
- Every company_domain field now accepts a bare domain or a full URL (https://www.acme.com/about) and resolves it to the registrable domain. Subdomains, capitalisation, trailing dots and www. no longer create different identities; IP addresses, ports, credentials and reserved names are rejected with a clear message.
- List inputs (company_domains, titles, industries, locations, signals, want) are deduplicated and treated as sets, so a retry that reorders or repeats values replays the original result under the same Idempotency-Key instead of paying twice.
- Replayed responses now include credits_available, matching fresh responses. The success envelope { result, layrcake } is documented once in the OpenAPI spec for every verb.
- Fix Provider cost accounting is recorded before each provider call rather than after, so an interrupted request can never hide its cost. No customer-facing prices changed.
NewOAuth 2.1 for connector marketplaces
- The MCP server now supports OAuth 2.1 with PKCE and dynamic client registration, so Layrcake can be added to Claude, ChatGPT and other assistant directories with a sign-in flow instead of a pasted key. The token you approve is an API key in your workspace, scoped to what you approved, revocable from the console.
- Pricing find_intent is now two-part: 10 credits per signal-bearing company plus 0.5 credits per company scanned. Misses on every other verb stay free.
- Pricing LLM passthrough repriced to cost-plus: sonnet-5 0.5, haiku-4.5 0.25, gpt-5-mini 0.1 credits per 1k tokens.
- Pricing Sending capacity: domains 1,500 credits one-time (at cost), five mailboxes per domain, 750 credits per mailbox per month.
- Minimum top-up is now $20 (2,000 credits). Credits still never expire.
- Credits land within seconds of a successful payment; the console shows the new balance without a refresh.
- The console and the docs are fully usable on phones.
NewProduction, live billing, docs
- Layrcake is on production infrastructure with live card payments. Keys minted from today are cake_sk_live_.
- Documentation live at docs.layrcake.dev, including llms.txt for agents and the OpenAPI spec.
- Passwordless sign-in: a one-time link or 6-digit code by email, or Google. No passwords anywhere.
- Suppression events from the sending layer (replies, bounces, unsubscribes) now propagate to live campaigns within a minute.
- Fix Marketing site and docs render correctly at phone widths.
NewLayrcake API v1 and MCP server
- Nine verbs behind one key: find_leads, enrich, verify_email, find_intent, setup_sending, create_campaign, launch_campaign, get_replies and llm.
- Hosted MCP server at mcp.layrcake.dev exposing the same verbs as tools, with one-prompt setup for Claude Code, Cursor and Codex.
- Prepaid credits with the exact charge disclosed in every response envelope. Misses are free. 300 free credits on signup, no card.
- Human confirmation, enforced at the server, for every purchase of sending capacity and every campaign launch.
- Permanent, workspace-wide suppression applied at draft, re-checked at launch.
- Idempotency keys on every mutating verb: retries replay the original result without re-charging.