API Reference
Changelog
Every developer-facing change to the /api/v1 surface, newest first
Developer-facing changes to https://api.atribu.app/api/v1/**. See the deprecation policy for what a removal looks like before it happens.
Active deprecations
| Operation | Deprecated | Sunset | Successor |
|---|---|---|---|
POST /api/v1/goals | 2026-09-05 | 2026-12-04 | /api/v1/goals/definitions |
Changes
Each line links the issue that carries the full reasoning — what broke, what to change, and why the decision went the way it did.
2026-09-30
- Added · #1836 —
learning_stage_status(Meta's ad-set learning phase:LEARNING,SUCCESSorFAIL) andcampaign_objective(the parent campaign's objective) onGET /api/v1/ads/{id}?level=ad_set, read from Atribu's nightly-synced copy, never live from Meta. Null at every other level. - Fixed · #1831 —
api.atribu.appnow answers every operation that was served only onwww.atribu.app(the/api/v1/partner/*routes,POST /api/v1/events,/oauth/token, webhooks, calendar, email, Instagram and more) instead of404, by forwarding the request unchanged to the same handler.www.atribu.appremains the canonical base URL. - Fixed · #1828 —
outcome.recordedwebhooks are now enqueued in the replay's order within each delivered batch (they could be enqueued out of order inside a batch of up to 200). No stored data changed; the replay's order is unchanged. - Fixed · #1827 — Campaign conversions are paginated. The response used to stop at 50 conversions without saying so; it now takes
limit(1–200, default 50; anything else is a 400) andcursor, and returnspagination: { has_next, cursor }. Followcursorwhilehas_nextis true to read every conversion; a page can hold fewer thanlimitwhen paths are long. Without a cursor you get the same first page as before. - Added · #1827 — Each campaign conversion row carries
is_first_payment: whether the payment is the customer's first, the flag behind the first-payment figures. Null when the conversion is not a payment. Visible undercampaigns:read. - Breaking · #1825 — Campaign, ad set and ad counts now include only conversions CREDITED to the entity. Under the single-touch models (
last_touch,first_touch,last_non_direct) a conversion used to be counted under every campaign / ad set / ad its path crossed, and again in the unattributed remainder. Sooutcome_count,outcome_count_first_paymentandattributed_outcome_countgo DOWN for those models,cac/cac_first_paymentgo UP by the same ratio, andGET /api/v1/campaigns/unattributed's counts go down. Rows + remainder now add up to the attributed total. Values (outcome_value,roas) do not change. Underlinear,time_decayandatribua conversion split across two entities still counts under both. - Added · #1825 —
attributed_outcome_count(conversions of every kind credited to the entity, refunds excluded; not cash, never a ROAS/CAC denominator) andattributed_outcomes_by_type(the same count split by conversion type, e.g.{"dm_started": 89, "lead_created": 88}) onGET /api/v1/campaignsat everylevel,GET /api/v1/ads/{id}and theGET /api/v1/campaigns/unattributedremainder. A lead-gen or conversational account no longer reads a campaign row of zeros. - Added · #1824 — Each campaign conversion row now carries
outcome_event_id— the id Atribu returns at ingest — so a caller that stored it can join the drill back to its own record.conversion_idis a different id. Nullable.
2026-09-29
- Added · #1800 — Provider outcomes for partner apps. New webhook event
outcome.recorded(provideratribu, opt-in like every lifecycle event): each GoHighLevel and MercadoPago outcome recorded on a profile your app provisioned, with a stableid,data.provider,outcome_type,occurred_at,customer_key,value, andcontactonly when your app's grant carriescustomers:read. Never emitted for a healthcare-family profile. NewGET /api/v1/outcomes/recorded(conversions:readorcustomers:read) replays the same events in the same order from an opaque cursor (90-day window). - Changed · #1776 —
POST /api/v1/adsets/{id}/budgetrefuses an ad set that is ARCHIVED or DELETED on Meta, or whose campaign is, with the new409 adset_not_editable(status read live, Atribu's synced status only as a fallback). Nothing is written to Meta; the refused attempt is audited.
2026-09-28
- Breaking · #1800 — CRM stage mapping for partner keys. New
GET /api/v1/connections/{provider}/stages(gohighlevel;connections:readoranalytics:read;?locale=es|en): every pipeline stage with the suggested outcome, the mapping in force (current.source:suggested,userorengine_default), whether it can be written and, at the top,status,can_writeandnext_step. Write a stage withPATCH /api/v1/outcome-definitions/{id}{event_key}(goals:write, entitlement-gated, idempotent). Breaking (narrow): on apipeline_stagedefinition,event_keymust now be one of the read'soutcomes[].keyon both that PATCH andPUT /api/v1/profile/outcome-definitions; anything else is422 validation_error. - Changed · #1800 — CRM stages that are not outcomes stop producing outcome rows. A new GoHighLevel stage with no suggestion is stored as
unmapped(it used to becomelead_createdsilently). An opportunity in a stage mapped tounmappedorignored(including existing stages mapped toignored) records no outcome, so no conversion and no attribution, until the stage is mapped. Won / lost / abandoned deals are still recorded whatever their stage. No response shape changes; the visible effect is the customer journey timeline. Earlier rows are kept. - Added · #1800 — Reporting currency at provisioning.
POST /api/v1/profilesandPOST /api/v1/workspacesacceptreporting_currency(ISO-4217) for a new workspace; onPOST /api/v1/profilesit defaults to the request'scurrency. With neither, the workspace takes the currency of the first ad account connected to it instead of a silentUSD. Applied only when the call creates the workspace; change it later withPATCH /api/v1/workspaces/{workspaceId}/reporting-currency, which an ad-account connect never overrides. - Added · #1030 —
POST /api/v1/partner/ad-referrals: record the Meta ad a PAST click-to-message conversation came from, for a business moving to Atribu from another WhatsApp bot. Each referral becomes a touch atoccurred_at, so later money (closed_won,payment_received) is credited to the ad, only inside the profile's attribution windows. It creates no lead and never changes one. Nothing from the call is sent to Meta: the click id stays on the touch and is not stored on the customer. The ad must be one of the profile's own Meta ads (422 unknown_ad). Scoping and customer rules asPOST /api/v1/partner/declared-source; idempotent onidempotency_key.POST /api/v1/partner/ad-referrals/previewreports, read-only, how many existing conversions a batch of up to 500 would re-credit, per model.
2026-09-27
- Changed · #1800 — Connect hand-off hardening. One consent per hand-off (a double-click writes once, and the other tab normally reports the winner's outcome; confirm with the poll); a consent that stops partway on our side frees the link again after 3 minutes; a completed hand-off reports its real outcome on a second click; new
reasoninternal_error(not settled, retry the same link).POST /api/v1/connections/pending/{provider}/finalizeanswers409 account_connected_elsewhere(new error code) for an account live on another profile of the workspace, keeping the parked token.
2026-09-26
- Breaking · #1800 — Partner connect hand-off. A
connecthand-off now carriesstart_url, which goes straight to the provider's consent screen, and every ending returns your user to yourreturn_urlwithstatus(success,pending_selection,failed,cancelled), a machinereasonandhandoff_id. The samereasonlands onresult.reason. The/h/page shows your app's name and logo. Breaking: areturn_urlyour app is not registered for is now a400at the mint (it used to fail the hand-off later); a user who cancels on the provider's screen settles the hand-offcancelledwithuser_cancelled(wasfailedwithprovider_denied); a hand-off's bounce saysfailed/cancelledwhere it saiderror; andhandoff_unusableis nowhandoff_expiredorhandoff_used.
2026-09-25
- Changed · #1799 —
POST /api/v1/partner/declared-source:customeraccepts{ external_ref, email?, phone? }. An unknownexternal_refsent with an email and/or phone creates the customer from that PII and binds the ref (exactly asPOST /api/v1/eventsdoes foruser_traits.external_id), so answers can be backfilled for people Atribu has never seen; a ref alone still never creates one (422 unknown_customer_ref).wa_idis now accepted on healthcare-family profiles. A national id (RUT) is refused with400 invalid_parameter. - Added · #1782 —
privacy_mode(standard|platform_safe|hipaa) onPOST /api/v1/profilesand onPUT /api/v1/profiles/{profileId}/entitlements/atribu_attribution. It is written before the grant widens, so a healthcare profile's first attribution key is minted withoutcustomers:readandvisitors:read. Omitting it changes nothing. - Changed · #1782 — Policy S5 now covers keys that already exist. When a profile becomes healthcare-family (
privacy_modeplatform_safe/hipaa, or the workspace'sis_healthcare_agency), its live delegated keys carryingcustomers:readorvisitors:readare revoked. Mint a replacement withgrant_type=client_credentials; it comes back without both scopes. - Added · #1776 — Campaign pause and resume.
POST /api/v1/campaigns/{id}/pauseand/resumechange one campaign's status by its Meta id in ONE write — never a per-ad fan-out — withaffected_ads_count, the ledgeraction_idand a readyundocall in the answer (campaigns:apply,Idempotency-Keyrequired, audited). Both are idempotent on the end state (outcome: "no_change"). A resume after an Atribu pause is that pause's undo (rollback_of); ads paused on their own stay paused. New codescampaign_not_found,campaign_not_pausable,campaign_not_resumable.GET /api/v1/campaigns/{id}/statusreturns what a consent screen needs first — live status, the ads a pause would stop, daily budget, recent daily spend, each action's availability with areason, and aconfirm_tokento derive yourIdempotency-Keyfrom. Shared class codes can carryerror.reason. - Breaking · #1771 — Link tokens by your own customer key.
POST /api/v1/customers/link-tokensacceptscustomer_key(theuser_traits.external_idyou ingest) as a selector, and every result row now carriescustomer_key; a row that did not mint also carries areason(no_match,erased,no_selector,unknown_identifier_type,malformed_selector) and amessage. Newpartner_pagerecipe, and every recipe gainsrequires. Breaking for a key withoutcustomers:read: its rows no longer includecustomer_profile_id.identifier_typeis now one of five values (an unknown type makes the rowinvalidinstead ofnot_found), and a deployment without a token secret answers503 link_tokens_disabledinstead ofservice_unavailable. - Added · #1770 — Partner erasure by your own key.
POST /api/v1/customers/erasures(attribution:write) erases one customer of the key's profile named bycustomer_key,email,phoneor a pinned identifier, and answers a receipt:erasure_id,request_ref,status,customer_key,reason,requested_at,completed_at. Idempotent onrequest_ref(a replay answers200with the original receipt; the same ref for another customer is400). API keys only (a session gets403 api_key_required); never needs the attribution add-on. An unknown customer is404 customer_not_found, an unknown receipt404 erasure_not_found. Conversions stay in reports without a person; the key stops resolving and link tokens stop binding.GET /api/v1/customers/erasures/{erasure_id}reads the receipt. It never carriescustomer_profile_id. - Changed · #1768 — By-key journey misses get their own codes.
GET /api/v1/customers/journey?customer_key=answers404 customer_not_found(unknown, erased or another profile's customer, all the same) andGET /api/v1/conversions/{id}/journeyanswers404 conversion_not_found, instead ofnot_found, which also means a route the deployment does not serve. Same status; branch on the code. - Added · #1708 —
POST /api/v1/partner/declared-source: a partner that asks "how did you find us?" in its own WhatsApp conversation sends the interpreted answer, stored beside click attribution withcollected_via = partner. Name the customer by exactly one ofexternal_ref(your own customer id, resolved read-only — required on healthcare-family profiles) orwa_id. Idempotent onwamid, one answer per customer, first wins; an unknownanswer_keyis422 unknown_answer_key, an unknownexternal_refis422 unknown_customer_ref. The declared-source vocabulary gainswalk_in(walked or drove past the place).
2026-09-24
- Added · #1766 — Journey reads for keys without
customers:read.GET /api/v1/conversions/{id}/journeynow opens atconversions:read: a key withoutcustomers:readgets the key-only projection (touch, channel, campaign / ad set / ad by platform id, click-id kind, join method, credit,in_window) withmeta.conversion.customer_key— your own customer id — and no person, page, device, geo or session fields at all; a key with it gets the same response as before. New:GET /api/v1/customers/journey?customer_key=returns one person's whole path by theexternal_idyou ingest, withmeta.person(counts and the newest 50 conversions);?email=/?phone=needcustomers:read. Healthcare-family profiles' keys now also leave outvisitors:read(policy S5). - Added · #1696 — Apply Atribu's URL parameters to Meta ads.
POST /api/v1/quality/ad-ids/url-tags/applywrites them onto up to 25 of the adsGET /api/v1/quality/ad-idslists, merged with each ad's own, and returns one result per ad (applied, skipped with a reason, or Meta's refusal). Every call must carryacknowledge_learning_reset: true: the change replaces the ad's creative (same post), which Meta treats as a significant edit.…/undoputs one ad back on its previous creative;GET …/url-tagssays whether the profile can apply at all (ads_management+campaigns:apply) and lists recent changes. - Added · #1676 — Partner ad operations.
POST /api/v1/ads/{id}/pauseand/resumepause or resume one ad by its Meta id with no recommendation behind it, andPOST /api/v1/adsets/{id}/budgetchanges one ad set's daily budget (±50 % of the live budget per call,422 budget_change_out_of_boundsoutside it) — allcampaigns:apply,Idempotency-Keyrequired, audited and reversible. A connection whose Meta grant lacksads_managementanswers403 meta_write_permission_missingbefore anything is written.GET /api/v1/conversions/{id}/journeyreturns the visitor timeline behind one conversion, addressed by itsoutcome_event_id,conversions.idor your own event key. - Changed · #1676 — On a key minted by the app that provisioned the profile,
applied_bymay be omitted: the apply is attributed to the workspace's owner (the user provisioning created) while it is still the active owner. An explicitapplied_by, and every other key, behave as before.
2026-09-23
- Added · #1670 —
POST /api/v1/eventsaccepts an optionalaction_sourcein Meta's vocabulary (website,business_messaging,physical_store,system_generated,app,phone_call,chat,email,other). It reaches Meta verbatim on every Conversions API export of the event, in every privacy mode, and any value other thanwebsiteships with noevent_source_url— even when the buyer has fbc/fbp browser signals. Abusiness_messagingevent withclick_ids.ctwa_clidgoes to the dataset linked to the profile's WhatsApp Business Account; without that dataset or a click id it is sent aschat. Omitting the field keeps the previous inference. - Added · #1670 — Each deliveries ledger entry now carries
action_source: the Metaaction_sourcethe stored payload carried (the declared value when there was one, else Atribu's inference).nullfor a non-Meta delivery or a row skipped before a payload was built. - Changed · #1669 — Platform-Safe now sends to the profile's EXISTING Meta dataset by default. A new
platform_safe_target(existing|clean, read on the catalog settings and onGET /api/v1/exports/destinations) decides which dataset gets the scrubbed feed: underexistingthe ordinary Meta CAPI destinations ship and nothing is recreated; undercleanonly the Clean Dataset ships, as before. Profiles that already had a Clean Dataset were moved toclean, so their routing is unchanged. A successful provision sets the target toclean, and itschecksreport it. Deliveries held for a leftover Clean Dataset underexistingare skipped withplatform_safe_clean_dataset_inactive. - Added · #1669 —
POST /api/v1/conversion-sync/platform-safe/targetswitches a Platform-Safe profile between its existing dataset and a Clean Dataset.cleanprovisions the Clean Dataset and lists the ad sets to recreate (the target flips only when a Clean Dataset exists); going back toexistinganswers409 clean_dataset_has_live_ad_setswhile Clean Dataset ad sets still deliver, unlessconfirm_live_ad_sets: true. The wiring read'splatform_safeblock gainstarget,target_destination_ids,event_mapping(the event name each rule sends, with a 7-day double-count check against the dataset's own counts),categorization(the Events Manager checklist and the openplatform_safe_events_not_acceptedalert) and theswitch_to_clean_datasetremediation; underexistingit has no provision or recreate step. Clean Dataset ad sets now optimize on the dataset's standard event (optimize_on: standard_event), not on a custom conversion that Core Setup would silence. - Added · #1668 — A partner app's DPA is accepted once, at the app. An owner or admin of the workspace that administers the app accepts it in the console (Workspace settings → Compliance), and every profile the app provisioned — and every one it provisions later — reads
dpa_status: acceptedwith that version and acceptor, sosign_dpano longer appears for them. Withdrawing returns those inherited profiles towithdrawn; a profile its own owner accepted keeps its acceptance. An API key or MCP token still cannot accept. - Fixed · #1653 — An email send the mailbox's provider refuses is no longer a
502. A refusal about the mailbox answers its own code —403 account_suspended,403 mailbox_disabled,403 auth_revoked(withreconnect_required: true) or429 quota_exceeded— a refused recipient answers422 recipient_rejected, and every one carries the provider's own words inprovider,provider_status,provider_codeandprovider_message. The connection reportsstatus: "needs_action"(orreconnect_requiredfor a dead grant) with the provider's message instatus_reason, and achannel.health.updatedevent is pushed on provideremail; the first send the provider accepts clears it. Only a transient provider failure still answers502/503. - Fixed · #1653 — Inbound email
delivery_failurenow carries the report'sstatuson Gmail bounces — it was alwaysnullthere, because Gmail nests the per-recipient fields one part deeper than Atribu read, so a permanent5.x.xlooked like a soft bounce. It also gainsaction(failed,delayed,delivered,relayedorexpanded) anddiagnostic_code(the remote server's own words), bothnullwhen the report has none. - Fixed · #1653 — An email send from an Outlook mailbox now answers a real
provider_message_id— the Microsoft Graph immutable id of the sent message (its copy in Sent Items) — instead of"", for a new thread and for a reply alike.thread_idandrfc822_message_idare unchanged. Inbound Outlook events keep Graph's default-format id, so recognise your own send byrfc822_message_id, not by comparing ids. - Changed · #306 — The automated duplicate-and-swap is open to partners behind a consent contract. An API key or MCP user token that sends
confirm_recreate: trueorconfirm_duplicate_swap: truemust also sendconsent: { version, text_hash, accepted_by, accepted_at }for the current consent text (2026-09-23.duplicate-swap.v1, published on the Partners page); without it the call is409 consent_requiredand nothing reaches Meta. Signed-in sessions are unchanged. Both responses gainwarnings(new ad set ids, learning reset, double spend), and error envelopes may carryconsent. - Changed · #306 — Platform-Safe provisioning is ready for partners. An API key or MCP user token must switch the profile to
platform_safefirst, or gets409 platform_safe_required; no usable Meta Ads connection is409 meta_connection_required. A Clean Dataset that already covers every tracked outcome answersstatus: "already_provisioned"with no Meta call, and every 200 now carrieschecks(privacy mode, Meta connection,ads_management, mapped outcome types). With no Meta CAPI destination, the profile's single Meta Ads connection is used. - Added · #306 — The wiring read has a
platform_safeblock for Platform-Safe profiles:platform_safe_dataset: provisioned | missing(remediationprovision_platform_safe_dataset), only the Clean Dataset's rows inresults,recreate_ad_setinstead ofwire_optimization, and, on a live check, the ad sets to recreate with their target conversion and Ads Manager steps. It isnullin every other mode.
2026-09-22
- Added · #1653 — An email send can now START a thread, and the response says which thread it started.
POST /api/v1/messageswithchannel: "email"answers withthread_id(GmailthreadId/ OutlookconversationId— the same value the inboundmessage.receivedevent carries, so the thread you open is the thread the reply arrives on) andrfc822_message_id(theMessage-IDof what was sent). Sendto+ asubjectwith nothread_idand a NEW thread is opened; a reply returns the thread it replied into. The email content also takes an optionallist_unsubscribe: { url, one_click }, which writesList-Unsubscribe: <url>and — withone_click: true, which requires anhttps://url —List-Unsubscribe-Post: List-Unsubscribe=One-Click(RFC 8058). And the inbound email event gainsdelivery_failure:nullfor an ordinary message, or{ failed_recipient, status, original_rfc822_message_id }parsed out of the RFC 3464 delivery-status report a bounce carries, whereoriginal_rfc822_message_idequals therfc822_message_idyour send returned. Everything here is optional and additive — an existing consumer's request and response are unchanged.
2026-09-15
- Added · #1517 — The three message-send routes now honour an optional
Idempotency-Keyheader, so a send lost to a timeout or a 5xx can be retried without delivering twice. A repeat within 24 hours replays the first response verbatim and sends nothing; scope is your API key + the route + the key value. A repeat that arrives while the first send is still in flight answers409 idempotency_key_in_flight(a new error code — retry the same key with backoff) rather than risking a second delivery, and a repeat carrying a different body answers409 idempotency_key_conflict. Only a 2xx is stored, so a send that genuinely failed can be retried under the same key. Omit the header and nothing changes.
2026-09-14
- Added · #1420 —
GET /api/v1/partner/whatsapp/adslists a profile's click-to-WhatsApp ads with their destination (type, WhatsApp number, Page), call-to-action type, greeting andgreeting_status, paginated by keyset onad_id. Served from the nightly Meta sync, which now reads that configuration for every CTWA ad — so/partner/whatsapp/ads/{ad_id}/welcome-messageanswers for a synced ad without a Graph call, and still falls back to a live read for one launched since the last sync. No spend, no metrics, no PII.
2026-09-11
- Added · #1402 —
GET /api/v1/partner/whatsapp/ads/{ad_id}/welcome-messagereads the welcome message ("Saludo automático") a click-to-WhatsApp ad shows a customer BEFORE their first message — the greeting, the pre-filled text and the ice-breaker buttons. Meta never delivers it over the webhook.welcome: nullmeans the ad has none configured;404means Meta does not show us the ad,409that the profile has no usable Meta Ads connection.
2026-09-07
- Added · #452 — Per-consumer usage: request counts, the 4xx/5xx split and p50/p95 latency, per API key for a workspace and per registered app for Atribu ops. Every rate-limited response now also carries
X-RateLimit-Reset. - Changed · #452 — A registered consumer app now has a rate TIER (
standard300/min,elevated1200/min,internal6000/min) that sets its per-minute allowance on client-credential requests. Anatb_live_key keeps its ownrate_limit_per_minute— the two meter different principals and are not merged.
2026-09-06
- Added · #1209 —
GET /api/v1/openapi.jsonis served from the API host itself, so the spec and the API it describes cannot come from different deploys. - Added · #1190 —
DELETE /api/v1/workspaces/{workspaceId}archives a workspace, driven by membership. - Changed · #1185 — An OAuth app's empty
allowed_return_originsno longer means "no bounce": the effective allowlist derives from its ownredirect_uris.return_url_supportedonGET /api/v1/me's app-credential branch reports the effective set, and the admin routes returneffective_return_origins.
2026-09-05
- Breaking · #1159 — Two new scopes,
workspaces:writeandprofiles:write, both granted bymcp:write. An MCP user token minted with the defaultmcp:readgrant could create workspaces and profiles; it now gets403 insufficient_scopeand nothing is created on the way to that refusal. Sessions and app credentials are unchanged. - Breaking · #1154 — A webhook subscription URL must point at a public host; a private or non-routable target is refused at registration AND again at delivery time, and redirects are no longer followed.
- Added · #1109 — An agent can start a provider connect and hand the human a session-less URL. Stripe and MercadoPago join Meta Ads, Google Ads, Google Search Console and GoHighLevel on the shared
prepareConnectStartpath. - Added · #1099 — Every operation in the published spec carries structured
x-atribu-scopes,x-atribu-auth,x-atribu-idempotencyandx-atribu-scope-grainextensions, plus a response example per tag. - Added · #1089 — Every error envelope now carries
docs_url, pointing at that exact code's section of the generated errors reference. - Deprecated · #1086 —
POST /api/v1/goalsis deprecated in favour ofPOST /api/v1/goals/definitions, which can express what the older collection cannot (updates, an explicitconversion_key,lookback_window_days,is_default).
2026-09-03
- Added · #1000 —
GET /api/v1/workspaces/{workspaceId}/pii-access-log— who read this workspace's customer/visitor personal data, when, through which route and with what result. Durable for 365 days. - Changed · #502 —
DELETE /api/v1/connections/{id}now also revokes key generations older than the authorization that names them, when the authorization revoked is the app's last live one on that profile.revoked_keyscan exceed the number of keys you minted under it.
2026-09-02
- Added · #533 —
POST /api/v1/ads/{id}/creative-analysisqueues an on-demand creative analysis at the newcreatives:writescope. It never runs a model on the request; it answers202and theGETon the same path is the poll. - Added · #391 — Two commerce reads at the new
commerce:readscope: a connected store's catalogue (an ascending change feed you walk withupdated_since) and an order lookup by number, email or phone. The order row carries no customer identity.
2026-09-01
- Breaking · #898 —
GET /api/v1/overviewanswersspend: nullandroas: null— not"0"— when spend is unmeasurable for the requested filters. A measured zero is still"0". Guard the denominator; do not?? 0it. - Added · #675 — Eight classification writes at
goals:write. Every one of them changes the PAST: each queues a full-profile replay, so readreplay_queuedon the response —falsemeans the change is saved but existing sessions keep their old channels until a replay runs.