DEVELOPERS
Account
The Account object
Account settings, setup progress and sending schedule.
ATTRIBUTES
- object enum
- account
- id string
- name string
- billing_source enum
- stripe
- api
- sandbox boolean
- default_locale enum
- en
- de
- fr
- it
- pt
- plan enum
NoChurn's own plan: details in GET /v1/billing/plan.
- free
- paid
- custom
- external_id_metadata_key string
Stripe customer metadata key copied into external_id. Default: user_id.
Show 8 more attributesHide 8 attributes
- require_two_factor boolean
Every member must use two-factor authentication. Owners only, from a 2FA session.
- timezone string
IANA time zone: "this month" and "today" in the dashboard.
- audience enum | null
Who your customers are: sets the default send window (GET /v1/account/send_schedule).
- b2b
- b2c
- reporting_currency string | null
Currency the dashboard converts every amount into (ECB rates). null: the connected Stripe account's default currency, else the one most customers pay in.
- onboarding object
Show child attributesHide child attributes
- disputes_settings object
Show child attributesHide child attributes
- auto_submit boolean
Off by default: a human approves and sends.
- auto_refund_fraud_warnings boolean
Off by default.
- auto_refund_max_cents integer | null
- notify_email boolean
- notify_slack boolean
Post to the Slack channel of Integrations (if connected).
- contest_cost_cents integer | null
What contesting costs you (provider fee and time). Unset: no suggestion to accept.
- contest_cost_currency string | null
Currency of contest_cost_cents; null = the reporting currency.
-
Automatic submission only from this chance of winning (70 by default).
-
Schema: Account
{
"object": "account",
"id": "acc_pApr4brLkk4KGSjf",
"name": "Account 1",
"billing_source": "stripe",
"sandbox": true,
"default_locale": "en",
"plan": "free",
"external_id_metadata_key": "user_id",
"require_two_factor": false,
"timezone": "Europe/Paris",
"audience": null,
"reporting_currency": null,
"onboarding": {
"step": "billing",
"skipped": [],
"completed_at": null
},
"disputes_settings": {
"auto_submit": false,
"auto_submit_hours_before": 24,
"auto_refund_fraud_warnings": false,
"auto_refund_max_cents": null,
"cancel_subscription_on_fraud_refund": true,
"notify_email": true,
"notify_slack": false,
"contest_cost_cents": null,
"contest_cost_currency": null,
"auto_submit_min_score": 70
},
"created_at": "2026-11-04T10:00:00.000Z",
"updated_at": "2026-11-04T10:00:00.000Z"
} The account of the API key (or of the dashboard session)
- Secret key (
nc_test_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
RETURNS
-
200account — the Account object
ERRORS
-
401unauthorized missing key
curl https://api.nochurn.sh/v1/account \
-H "Authorization: Bearer nc_test_…"const res = await fetch("https://api.nochurn.sh/v1/account", {
headers: {
Authorization: "Bearer nc_test_…",
},
});
const account = await res.json();import requests
res = requests.get(
"https://api.nochurn.sh/v1/account",
headers={
"Authorization": "Bearer nc_test_…",
},
)
account = res.json()require "net/http"
require "json"
uri = URI("https://api.nochurn.sh/v1/account")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer nc_test_…"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
account = JSON.parse(res.body) {
"object": "account",
"id": "acc_pApr4brLkk4KGSjf",
"name": "Account 1",
"billing_source": "stripe",
"sandbox": true,
"default_locale": "en",
"plan": "free",
"external_id_metadata_key": "user_id",
"require_two_factor": false,
"timezone": "Europe/Paris",
"audience": null,
"reporting_currency": null,
"onboarding": {
"step": "billing",
"skipped": [],
"completed_at": null
},
"disputes_settings": {
"auto_submit": false,
"auto_submit_hours_before": 24,
"auto_refund_fraud_warnings": false,
"auto_refund_max_cents": null,
"cancel_subscription_on_fraud_refund": true,
"notify_email": true,
"notify_slack": false,
"contest_cost_cents": null,
"contest_cost_currency": null,
"auto_submit_min_score": 70
},
"created_at": "2026-11-04T10:00:00.000Z",
"updated_at": "2026-11-04T10:00:00.000Z"
} Update account settings
- Secret key (
nc_test_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth): not available.
BODY PARAMETERS
- name string
- default_locale enum
- en
- de
- fr
- it
- pt
- billing_source enum
- stripe
- api
- external_id_metadata_key string
- require_two_factor boolean
Owners only, from a dashboard session with 2FA on.
- timezone string
IANA time zone.
- reporting_currency string | null
Currency every amount is converted into (one with ECB rates). null: Stripe's default currency.
- onboarding object
Where the onboarding resumes. completed_at is set when step becomes done.
- disputes_settings object
Chargebacks › Settings (owner / admin). Only the keys sent change.
Show child parametersHide child parameters
- auto_submit boolean
- auto_refund_fraud_warnings boolean
- auto_refund_max_cents integer | null
- notify_email boolean
- notify_slack boolean
- contest_cost_cents integer | null
- contest_cost_currency string | null
-
RETURNS
-
200amounts reported in another currency — the Account object
ERRORS
-
400invalid_request a step that cannot be skipped -
422invalid_request unknown time zone
curl https://api.nochurn.sh/v1/account \
-X PATCH \
-H "Authorization: Bearer nc_test_…" \
-H "Content-Type: application/json" \
-d '{
"name": "Renamed",
"external_id_metadata_key": "app_user_id"
}'const res = await fetch("https://api.nochurn.sh/v1/account", {
method: "PATCH",
headers: {
Authorization: "Bearer nc_test_…",
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Renamed",
"external_id_metadata_key": "app_user_id"
}),
});
const account = await res.json();import requests
res = requests.patch(
"https://api.nochurn.sh/v1/account",
headers={
"Authorization": "Bearer nc_test_…",
},
json={
"name": "Renamed",
"external_id_metadata_key": "app_user_id"
},
)
account = res.json()require "net/http"
require "json"
uri = URI("https://api.nochurn.sh/v1/account")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
"name" => "Renamed",
"external_id_metadata_key" => "app_user_id"
}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
account = JSON.parse(res.body) {
"object": "account",
"id": "acc_hM7usMgmWPctiKMY",
"name": "Renamed",
"billing_source": "stripe",
"sandbox": true,
"default_locale": "en",
"plan": "free",
"external_id_metadata_key": "app_user_id",
"require_two_factor": false,
"timezone": "Europe/Paris",
"audience": null,
"reporting_currency": null,
"onboarding": {
"step": "billing",
"skipped": [],
"completed_at": null
},
"disputes_settings": {
"auto_submit": false,
"auto_submit_hours_before": 24,
"auto_refund_fraud_warnings": false,
"auto_refund_max_cents": null,
"cancel_subscription_on_fraud_refund": true,
"notify_email": true,
"notify_slack": false,
"contest_cost_cents": null,
"contest_cost_currency": null,
"auto_submit_min_score": 70
},
"created_at": "2026-11-04T10:00:00.000Z",
"updated_at": "2026-11-04T10:00:00.000Z"
} Versions of NoChurn's client code calling the API
Versions of NoChurn's own client code (the widget js.nochurn.sh, the MCP server, the SDKs) seen in the last 90 days in this mode, from the NoChurn-Client header (<name>/<semver>), per client with its latest and minimum supported version. Row status: current, update_available (below latest), unsupported (below minimum), unknown (a client NoChurn does not publish). The widget's origin_host is the host of the page that loaded it; api_key is the key used, oauth_client the AI app (MCP through OAuth). Rows are written at most once every 5 minutes: last_seen_at is within 5 minutes and calls_count is a lower bound. Resource account (read), every role. js.nochurn.sh/v1 updates itself within a major version: update_available for the widget means a pinned /v1.x.y.js URL, a self-hosted copy or an older major.
- Secret key (
nc_test_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
RETURNS
-
200the clients and their versions — a ClientVersions
ERRORS
-
401unauthorized no credentials
curl https://api.nochurn.sh/v1/account/clients \
-H "Authorization: Bearer nc_test_…"const res = await fetch("https://api.nochurn.sh/v1/account/clients", {
headers: {
Authorization: "Bearer nc_test_…",
},
});
const clientVersions = await res.json();import requests
res = requests.get(
"https://api.nochurn.sh/v1/account/clients",
headers={
"Authorization": "Bearer nc_test_…",
},
)
client_versions = res.json()require "net/http"
require "json"
uri = URI("https://api.nochurn.sh/v1/account/clients")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer nc_test_…"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
client_versions = JSON.parse(res.body) {
"object": "client_versions",
"livemode": false,
"window_days": 90,
"data": [
{
"object": "client",
"client": "widget",
"name": "Cancel widget (js.nochurn.sh)",
"latest": "1.0.0",
"minimum": "1.0.0",
"status": "unsupported",
"versions": [
{
"id": "clv_fv7a8FvU8CUJZJ8F",
"version": "1.0.0",
"status": "current",
"origin_host": "app.acme.com",
"api_key": {
"id": "key_ytLQLbZVZ2GdVyUF",
"name": "Key 1",
"kind": "publishable",
"mode": "test",
"last4": "ntgP"
},
"oauth_client": null,
"first_seen_at": "2026-11-04T09:00:00.000Z",
"last_seen_at": "2026-11-04T09:00:00.000Z",
"calls_count": 3
},
{
"id": "clv_nRfcm9YjNjXUcMqC",
"version": "0.9.0",
"status": "unsupported",
"origin_host": "app.acme.com",
"api_key": {
"id": "key_ytLQLbZVZ2GdVyUF",
"name": "Key 1",
"kind": "publishable",
"mode": "test",
"last4": "ntgP"
},
"oauth_client": null,
"first_seen_at": "2026-11-02T10:00:00.000Z",
"last_seen_at": "2026-11-02T10:00:00.000Z",
"calls_count": 3
}
]
},
{
"object": "client",
"client": "mcp",
"name": "NoChurn MCP server",
"latest": "0.1.0",
"minimum": "0.1.0",
"status": "current",
"versions": [
{
"id": "clv_E1d1eodQeZi9yoUJ",
"version": "0.1.0",
"status": "current",
"origin_host": null,
"api_key": null,
"oauth_client": {
"id": "mcpc_EioDePP8sJxkMntr",
"name": "Claude Code"
},
"first_seen_at": "2026-11-04T09:00:00.000Z",
"last_seen_at": "2026-11-04T09:00:00.000Z",
"calls_count": 3
}
]
},
{
"object": "client",
"client": "node",
"name": null,
"latest": null,
"minimum": null,
"status": "unknown",
"versions": [
{
"id": "clv_L5QU6Xo2vi6b7TLV",
"version": "1.2.0",
"status": "unknown",
"origin_host": null,
"api_key": {
"id": "key_HCwE3oWH5fUAkZ91",
"name": "Server",
"kind": "secret",
"mode": "test",
"last4": "51K6"
},
"oauth_client": null,
"first_seen_at": "2026-11-04T09:00:00.000Z",
"last_seen_at": "2026-11-04T09:00:00.000Z",
"calls_count": 3
}
]
}
]
} Flows that set their own send window options
In the current mode: the options the published version of each flow sets itself (instead of following Settings › Send schedule), those its draft sets, and the segments (THEN) and A/B variants of the flow that set them (no mode: listed in both; the variants of a running test with applies false). Only flows with at least one.
- Secret key (
nc_test_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
QUERY PARAMETERS
- keys[] array of enums
Only these options (all by default).
- audience
- respect_customer_timezone
- skip_weekends
- skip_public_holidays
- windows
- quiet_hours
- days_off
RETURNS
-
200a flow that follows the account, but one of its segments and A/B variants do not — a list of SendScheduleFlowOverride (SendScheduleFlowOverrideList)
curl https://api.nochurn.sh/v1/account/send_schedule/flow_overrides \
-H "Authorization: Bearer nc_test_…"const res = await fetch("https://api.nochurn.sh/v1/account/send_schedule/flow_overrides", {
headers: {
Authorization: "Bearer nc_test_…",
},
});
const page = await res.json();import requests
res = requests.get(
"https://api.nochurn.sh/v1/account/send_schedule/flow_overrides",
headers={
"Authorization": "Bearer nc_test_…",
},
)
page = res.json()require "net/http"
require "json"
uri = URI("https://api.nochurn.sh/v1/account/send_schedule/flow_overrides")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer nc_test_…"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
page = JSON.parse(res.body) {
"object": "list",
"data": [
{
"object": "send_schedule_flow_override",
"flow": {
"id": "flw_3yDzGfxYA3qXrkwX",
"kind": "recovery",
"name": "Recovery flow"
},
"version": {
"id": "flv_dS81MgAgS7sx1js4",
"number": 1,
"published_to": "live"
},
"published": {
"quiet_hours": {
"to": "08:00",
"from": "21:00",
"enabled": true
}
},
"draft": {
"quiet_hours": {
"to": "08:00",
"from": "21:00",
"enabled": true
}
},
"segments": [],
"variants": []
}
],
"has_more": false,
"next_cursor": null
} Make flows follow Settings › Send schedule
Sets the options to null (follow the account) in the flows that set them: the published version of the current mode is copied into a new version with only those options changed (other draft changes are never published), and the draft loses them too. The segments (THEN) and A/B variants of those flows lose them in place (removed, so they follow their flow; they have no mode and no versions, so this changes both modes). The variants of a running test are not changed. Owners and admins. Nothing to change: nothing happens.
- Secret key (
nc_test_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth): not available.
BODY PARAMETERS
- keys array of enums
All options by default.
- audience
- respect_customer_timezone
- skip_weekends
- skip_public_holidays
- windows
- quiet_hours
- days_off
- flow_ids array of strings
All flows that override by default. A flow's segments and A/B variants are applied with it.
RETURNS
-
200a segment and a draft test's variant follow their flow again; a running test is left alone — a SendScheduleFlowsApplied
ERRORS
-
400invalid_request unknown option -
404not_found a flow of another account
curl https://api.nochurn.sh/v1/account/send_schedule/apply_to_flows \
-H "Authorization: Bearer nc_test_…" \
-H "Content-Type: application/json" \
-d '{
"keys": ["quiet_hours", "respect_customer_timezone"],
"flow_ids": ["flw_cRisxrrykqgKPHdX"]
}'const res = await fetch("https://api.nochurn.sh/v1/account/send_schedule/apply_to_flows", {
method: "POST",
headers: {
Authorization: "Bearer nc_test_…",
"Content-Type": "application/json",
},
body: JSON.stringify({
"keys": ["quiet_hours", "respect_customer_timezone"],
"flow_ids": ["flw_cRisxrrykqgKPHdX"]
}),
});
const sendScheduleFlowsApplied = await res.json();import requests
res = requests.post(
"https://api.nochurn.sh/v1/account/send_schedule/apply_to_flows",
headers={
"Authorization": "Bearer nc_test_…",
},
json={
"keys": ["quiet_hours", "respect_customer_timezone"],
"flow_ids": ["flw_cRisxrrykqgKPHdX"]
},
)
send_schedule_flows_applied = res.json()require "net/http"
require "json"
uri = URI("https://api.nochurn.sh/v1/account/send_schedule/apply_to_flows")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
"keys" => ["quiet_hours", "respect_customer_timezone"],
"flow_ids" => ["flw_cRisxrrykqgKPHdX"]
}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
send_schedule_flows_applied = JSON.parse(res.body) {
"object": "send_schedule_flows_applied",
"livemode": true,
"applied": 1,
"counts": {
"flows": 1,
"segments": 0,
"variants": 0
},
"data": [
{
"object": "send_schedule_flow_applied",
"flow": {
"id": "flw_cRisxrrykqgKPHdX",
"kind": "recovery",
"name": "Recovery flow"
},
"version": {
"id": "flv_taVfx8DNTK3tKcwE",
"number": 2,
"published_to": "live"
},
"published": {
"quiet_hours": {
"to": "08:00",
"from": "21:00",
"enabled": true
},
"respect_customer_timezone": true
},
"draft": {
"quiet_hours": {
"to": "08:00",
"from": "21:00",
"enabled": true
},
"respect_customer_timezone": true
},
"segments": [],
"variants": []
}
]
} When NoChurn may contact your customers
- Secret key (
nc_test_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
RETURNS
-
200B2B preset by default — a SendSchedule
curl https://api.nochurn.sh/v1/account/send_schedule \
-H "Authorization: Bearer nc_test_…"const res = await fetch("https://api.nochurn.sh/v1/account/send_schedule", {
headers: {
Authorization: "Bearer nc_test_…",
},
});
const sendSchedule = await res.json();import requests
res = requests.get(
"https://api.nochurn.sh/v1/account/send_schedule",
headers={
"Authorization": "Bearer nc_test_…",
},
)
send_schedule = res.json()require "net/http"
require "json"
uri = URI("https://api.nochurn.sh/v1/account/send_schedule")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer nc_test_…"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
send_schedule = JSON.parse(res.body) {
"object": "send_schedule",
"audience": null,
"overrides": {},
"effective": {
"audience": "b2b",
"respect_customer_timezone": true,
"quiet_hours": {
"enabled": true,
"from": "21:00",
"to": "08:00"
},
"days_off": [],
"skip_weekends": true,
"skip_public_holidays": true,
"windows": {
"weekday": [
["09:00", "17:00"]
],
"weekend": []
}
},
"presets": {
"b2b": {
"respect_customer_timezone": true,
"quiet_hours": {
"enabled": true,
"from": "21:00",
"to": "08:00"
},
"days_off": [],
"skip_weekends": true,
"skip_public_holidays": true,
"windows": {
"weekday": [
["09:00", "17:00"]
],
"weekend": []
}
},
"b2c": {
"respect_customer_timezone": true,
"quiet_hours": {
"enabled": true,
"from": "21:00",
"to": "08:00"
},
"days_off": [],
"skip_weekends": false,
"skip_public_holidays": false,
"windows": {
"weekday": [
["11:00", "13:00"],
["18:00", "21:00"]
],
"weekend": [
["10:00", "20:00"]
]
}
}
},
"suggested_audience": null
} Change the audience or any option
Each option can be switched on or off; null removes an override (back to the preset).
- Secret key (
nc_test_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth): not available.
BODY PARAMETERS
- audience enum
- b2b
- b2c
- skip_weekends boolean | null
- skip_public_holidays boolean | null
- respect_customer_timezone boolean | null
- windows object | null
- quiet_hours object | null
- days_off array of strings | null
RETURNS
-
200B2C, with Christmas Eve off — a SendSchedule
ERRORS
-
400invalid_request unknown option
curl https://api.nochurn.sh/v1/account/send_schedule \
-X PATCH \
-H "Authorization: Bearer nc_test_…" \
-H "Content-Type: application/json" \
-d '{
"audience": "b2c",
"days_off": ["2026-12-24"]
}'const res = await fetch("https://api.nochurn.sh/v1/account/send_schedule", {
method: "PATCH",
headers: {
Authorization: "Bearer nc_test_…",
"Content-Type": "application/json",
},
body: JSON.stringify({
"audience": "b2c",
"days_off": ["2026-12-24"]
}),
});
const sendSchedule = await res.json();import requests
res = requests.patch(
"https://api.nochurn.sh/v1/account/send_schedule",
headers={
"Authorization": "Bearer nc_test_…",
},
json={
"audience": "b2c",
"days_off": ["2026-12-24"]
},
)
send_schedule = res.json()require "net/http"
require "json"
uri = URI("https://api.nochurn.sh/v1/account/send_schedule")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
"audience" => "b2c",
"days_off" => ["2026-12-24"]
}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
send_schedule = JSON.parse(res.body) {
"object": "send_schedule",
"audience": "b2c",
"overrides": {
"days_off": ["2026-12-24"]
},
"effective": {
"audience": "b2c",
"respect_customer_timezone": true,
"quiet_hours": {
"enabled": true,
"from": "21:00",
"to": "08:00"
},
"days_off": ["2026-12-24"],
"skip_weekends": false,
"skip_public_holidays": false,
"windows": {
"weekday": [
["11:00", "13:00"],
["18:00", "21:00"]
],
"weekend": [
["10:00", "20:00"]
]
}
},
"presets": {
"b2b": {
"respect_customer_timezone": true,
"quiet_hours": {
"enabled": true,
"from": "21:00",
"to": "08:00"
},
"days_off": [],
"skip_weekends": true,
"skip_public_holidays": true,
"windows": {
"weekday": [
["09:00", "17:00"]
],
"weekend": []
}
},
"b2c": {
"respect_customer_timezone": true,
"quiet_hours": {
"enabled": true,
"from": "21:00",
"to": "08:00"
},
"days_off": [],
"skip_weekends": false,
"skip_public_holidays": false,
"windows": {
"weekday": [
["11:00", "13:00"],
["18:00", "21:00"]
],
"weekend": [
["10:00", "20:00"]
]
}
}
},
"suggested_audience": null
} Setup checklist: what is installed and what is missing
- Secret key (
nc_test_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
RETURNS
-
200billing by API: no Stripe import step; an active support tool counts — a Setup
curl https://api.nochurn.sh/v1/account/setup \
-H "Authorization: Bearer nc_test_…"const res = await fetch("https://api.nochurn.sh/v1/account/setup", {
headers: {
Authorization: "Bearer nc_test_…",
},
});
const setup = await res.json();import requests
res = requests.get(
"https://api.nochurn.sh/v1/account/setup",
headers={
"Authorization": "Bearer nc_test_…",
},
)
setup = res.json()require "net/http"
require "json"
uri = URI("https://api.nochurn.sh/v1/account/setup")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer nc_test_…"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
setup = JSON.parse(res.body) {
"object": "setup",
"livemode": false,
"billing_source": "stripe",
"api_base_url": "https://api.nochurn.sh",
"complete": false,
"steps": [
{
"key": "billing_connected",
"done": true,
"fix": null,
"skipped": false
},
{
"key": "billing_synced",
"done": true,
"fix": null,
"skipped": false
},
{
"key": "history_imported",
"done": false,
"fix": "Wait for the 12-month history import to finish, or re-run the import from Integrations.",
"skipped": false
},
{
"key": "webhook_received",
"done": false,
"fix": "Trigger an event in your billing tool (e.g. update a customer) and check it arrives.",
"skipped": false
},
{
"key": "api_key_created",
"done": true,
"fix": null,
"skipped": false
},
{
"key": "first_api_call",
"done": true,
"fix": null,
"skipped": false
},
{
"key": "support_connected",
"done": false,
"fix": "Connect your support tool (Intercom, Zendesk, Crisp, HubSpot, Help Scout or Gorgias) from Integrations.",
"skipped": false
},
{
"key": "cancel_modal_detected",
"done": false,
"fix": "Install the cancel modal on your cancel button: NoChurn detects its first load.",
"skipped": false
},
{
"key": "email_domain_verified",
"done": false,
"fix": "Add your sending domain and its DNS records, then check them (Settings › Email domain).",
"skipped": false
},
{
"key": "cancel_flow_published",
"done": false,
"fix": "Publish your cancel flow (test mode first) from Cancel flow › Settings.",
"skipped": false
},
{
"key": "recovery_retries_configured",
"done": true,
"fix": null,
"skipped": false
},
{
"key": "prevention_signals_enabled",
"done": true,
"fix": null,
"skipped": false
}
],
"onboarding": {
"step": "billing",
"skipped": [],
"completed_at": null
},
"counters": {
"customers_imported": 0,
"active_customers": 0,
"mrr": [],
"failed_payments_to_recover": {
"count": 0,
"amounts": []
},
"high_risk_customers": 0,
"scored_customers": 0,
"first_api_call_at": "2026-11-04T10:00:00.000Z",
"customers_with_multiple_subscriptions": 0,
"sessions_without_subscription_30d": 0
},
"history_import": {
"status": "pending",
"since": null,
"progress": {},
"error": null,
"started_at": null,
"finished_at": null
}
}