API REFERENCE v1

DEVELOPERS

Sandbox

The Sandbox customer object

Test-mode scenarios: create a customer, fail a payment, remove a card, run a cancel flow.

ATTRIBUTES

  • object enum
    • sandbox_customer
  • customer object
    Show child attributesHide child attributes
    • object enum
      • customer
    • id string

      NoChurn id, always present.

    • stripe_customer_id string | null

      Stripe customer id, when billed by Stripe.

    • external_id string | null

      The id of this customer in your app.

    • stripe_account string | null
    • livemode boolean
    • email string | null
    • company_domain string | null

      Domain of the email when it is a company's (not a free, disposable or placeholder address): the customer's logo is GET /v1/logos/{company_domain}.

    • name string | null
    • locale string | null
    • country string | null
    • region string | null

      State or province of the address.

    • timezone string | null

      IANA time zone used to reach the customer.

    • timezone_source enum | null

      api (sent by you, never overwritten), attribute, address, country_guess (main zone of a multi-zone country), ip_country, card_country, account (fallback).

      • api
      • attribute
      • address
      • country_guess
      • ip_country
      • card_country
      • account
    • attributes object

      Custom attributes (M4/M6).

    • churn_score integer | null
    • churn_level enum | null
      • low
      • medium
      • high
    • flag object | null

      FLAGGED: a prevention signal before the renewal. Cleared when a new card arrives.

      Show child attributesHide child attributes
    • state enum

      From billing: in_recovery = an open invoice with a failed attempt; churned = no live subscription.

      • none
      • trialing
      • active
      • in_recovery
      • paused
      • churned
    • plan string | null

      Plan of the main live subscription.

    • mrr_cents integer

      Monthly recurring revenue, in the minor unit of currency.

    • Paid invoices seen by NoChurn (12 months of Stripe history, then all).

    • currency string | null

      Billing currency. Amounts are never converted.

    • payment_method object | null

      Default payment method (null when none).

      Show child attributesHide child attributes
    • last_seen_at string | null date-time

      From the last_active_at attribute your app sends.

    • churned_at string | null date-time
    • flows_paused_at string | null date-time

      Set by Pause all flows: no flow acts on this customer.

    • signup_at string | null date-time
    • created_at string date-time
    • updated_at string date-time
    • identifiers array of objects

      Every identifier a source gave for this customer, oldest first: how Stripe, your other tools, your app and support all reach this one customer (docs: customer matching).

      Show child attributesHide child attributes
      • kind enum

        stripe (cus_), provider (a billing, store or support tool's id), external (your id, current or previous), email, nochurn (the cst_ id of a customer merged into this one, still accepted).

        • stripe
        • external
        • email
        • provider
        • nochurn
      • provider string | null

        The tool for stripe and provider ids (stripe, chargebee, app_store, intercom…).

      • connection string | null

        The billing connection (scn_ / bcn_) or support integration (itg_) the id belongs to.

      • value string
      • source enum

        Who gave it: api, import, stripe, stripe_metadata, provider, support, manual, merge (moved by a merge).

        • api
        • stripe_metadata
        • stripe
        • manual
        • provider
        • support
        • import
        • merge
      • created_at string date-time
    • risk object

      With include[]=risk (list only): when the customer renews and why the score is what it is. Early signals (card removed…) are in flag.

      Show child attributesHide child attributes
      • next_renewal_at string | null date-time

        Soonest end of the current period of a paying subscription (active or past_due) still to come.

      • cancel_at_period_end boolean | null

        That subscription stops then instead of renewing. null without a renewal.

      • score_factors array of objects 5 items at most

        Factors that raised the last churn score, largest first (starting point and lowering factors left out).

        Show child attributesHide child attributes
      • score_computed_at string | null date-time
  • subscription object
    Show child attributesHide child attributes
  • Metadata only. NoChurn never stores card numbers.

    Show child attributesHide child attributes
  • next string

Schema: SandboxCustomer

THE SANDBOX CUSTOMER OBJECT
{
  "object": "sandbox_customer",
  "customer": {
    "object": "customer",
    "id": "cst_bunoEakFaTiPtdny",
    "stripe_customer_id": null,
    "external_id": "sandbox_204d2c04",
    "stripe_account": null,
    "livemode": false,
    "email": "sandbox+204d2c04@example.com",
    "company_domain": null,
    "name": "Sandbox customer",
    "locale": null,
    "country": null,
    "region": null,
    "timezone": "Europe/Paris",
    "timezone_source": "account",
    "attributes": {
      "signup_source": "ads"
    },
    "churn_score": null,
    "churn_level": null,
    "flag": null,
    "state": "active",
    "plan": "Team",
    "mrr_cents": 4900,
    "lifetime_value_cents": 0,
    "currency": "usd",
    "payment_method": {
      "id": "npm_wv5NxAxFfWoidwEv",
      "type": "card",
      "brand": "visa",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2029,
      "funding": "credit",
      "wallet": null,
      "risk_category": null,
      "label": "Visa •••• 4242"
    },
    "last_seen_at": null,
    "churned_at": null,
    "flows_paused_at": null,
    "signup_at": "2026-11-04T10:00:00.000Z",
    "created_at": "2026-11-04T10:00:00.000Z",
    "updated_at": "2026-11-04T10:00:00.000Z",
    "identifiers": [
      {
        "kind": "email",
        "provider": null,
        "connection": null,
        "value": "sandbox+204d2c04@example.com",
        "source": "api",
        "created_at": "2026-11-04T10:00:00.000Z"
      },
      {
        "kind": "external",
        "provider": null,
        "connection": null,
        "value": "sandbox_204d2c04",
        "source": "api",
        "created_at": "2026-11-04T10:00:00.000Z"
      }
    ]
  },
  "subscription": {
    "object": "subscription",
    "id": "nsub_ee6FgdZrth7eEGKc",
    "customer": "cst_bunoEakFaTiPtdny",
    "stripe_subscription_id": null,
    "external_id": "sandbox_sub_204d2c04",
    "livemode": false,
    "status": "active",
    "plan": "Team",
    "amount_cents": 4900,
    "currency": "usd",
    "interval": "month",
    "interval_count": 1,
    "mrr_cents": 4900,
    "current_period_end": "2026-11-09T10:00:00.000Z",
    "cancel_at_period_end": false,
    "canceled_at": null,
    "paused_until": null,
    "trial_end": null,
    "created_at": "2026-11-04T10:00:00.000Z"
  },
  "payment_method": {
    "object": "payment_method",
    "id": "npm_wv5NxAxFfWoidwEv",
    "customer": "cst_bunoEakFaTiPtdny",
    "stripe_payment_method_id": null,
    "livemode": false,
    "type": "card",
    "brand": "visa",
    "last4": "4242",
    "exp_month": 12,
    "exp_year": 2029,
    "funding": "credit",
    "country": null,
    "wallet": null,
    "is_default": true,
    "detached_at": null,
    "created_at": "2026-11-04T10:00:00.000Z"
  },
  "next": "Play a scenario: POST /v1/sandbox/failed_payment, /card_removed or /cancel_flow_run with customer: sandbox_204d2c04."
}

Sandbox: create a test customer

Test mode only. A customer with an active subscription billed by API (no Stripe call) renewing in renews_in_days (5), and a default test card (visa 4242). Writes sandbox.customer_created.

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): sandbox scope, test mode only.

BODY PARAMETERS

RETURNS

ERRORS

POST/v1/sandbox/customers
curl https://api.nochurn.sh/v1/sandbox/customers \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "plan": "Team",
    "attributes": {
      "signup_source": "ads"
    }
  }'
const res = await fetch("https://api.nochurn.sh/v1/sandbox/customers", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "plan": "Team",
    "attributes": {
      "signup_source": "ads"
    }
  }),
});
const sandboxCustomer = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/sandbox/customers",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "plan": "Team",
      "attributes": {
        "signup_source": "ads"
      }
    },
)
sandbox_customer = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/sandbox/customers")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "plan" => "Team",
  "attributes" => {
    "signup_source" => "ads"
  }
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
sandbox_customer = JSON.parse(res.body)
RESPONSE 201
{
  "object": "sandbox_customer",
  "customer": {
    "object": "customer",
    "id": "cst_bunoEakFaTiPtdny",
    "stripe_customer_id": null,
    "external_id": "sandbox_204d2c04",
    "stripe_account": null,
    "livemode": false,
    "email": "sandbox+204d2c04@example.com",
    "company_domain": null,
    "name": "Sandbox customer",
    "locale": null,
    "country": null,
    "region": null,
    "timezone": "Europe/Paris",
    "timezone_source": "account",
    "attributes": {
      "signup_source": "ads"
    },
    "churn_score": null,
    "churn_level": null,
    "flag": null,
    "state": "active",
    "plan": "Team",
    "mrr_cents": 4900,
    "lifetime_value_cents": 0,
    "currency": "usd",
    "payment_method": {
      "id": "npm_wv5NxAxFfWoidwEv",
      "type": "card",
      "brand": "visa",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2029,
      "funding": "credit",
      "wallet": null,
      "risk_category": null,
      "label": "Visa •••• 4242"
    },
    "last_seen_at": null,
    "churned_at": null,
    "flows_paused_at": null,
    "signup_at": "2026-11-04T10:00:00.000Z",
    "created_at": "2026-11-04T10:00:00.000Z",
    "updated_at": "2026-11-04T10:00:00.000Z",
    "identifiers": [
      {
        "kind": "email",
        "provider": null,
        "connection": null,
        "value": "sandbox+204d2c04@example.com",
        "source": "api",
        "created_at": "2026-11-04T10:00:00.000Z"
      },
      {
        "kind": "external",
        "provider": null,
        "connection": null,
        "value": "sandbox_204d2c04",
        "source": "api",
        "created_at": "2026-11-04T10:00:00.000Z"
      }
    ]
  },
  "subscription": {
    "object": "subscription",
    "id": "nsub_ee6FgdZrth7eEGKc",
    "customer": "cst_bunoEakFaTiPtdny",
    "stripe_subscription_id": null,
    "external_id": "sandbox_sub_204d2c04",
    "livemode": false,
    "status": "active",
    "plan": "Team",
    "amount_cents": 4900,
    "currency": "usd",
    "interval": "month",
    "interval_count": 1,
    "mrr_cents": 4900,
    "current_period_end": "2026-11-09T10:00:00.000Z",
    "cancel_at_period_end": false,
    "canceled_at": null,
    "paused_until": null,
    "trial_end": null,
    "created_at": "2026-11-04T10:00:00.000Z"
  },
  "payment_method": {
    "object": "payment_method",
    "id": "npm_wv5NxAxFfWoidwEv",
    "customer": "cst_bunoEakFaTiPtdny",
    "stripe_payment_method_id": null,
    "livemode": false,
    "type": "card",
    "brand": "visa",
    "last4": "4242",
    "exp_month": 12,
    "exp_year": 2029,
    "funding": "credit",
    "country": null,
    "wallet": null,
    "is_default": true,
    "detached_at": null,
    "created_at": "2026-11-04T10:00:00.000Z"
  },
  "next": "Play a scenario: POST /v1/sandbox/failed_payment, /card_removed or /cancel_flow_run with customer: sandbox_204d2c04."
}

Sandbox: simulate a failed payment

Test mode only. An open invoice and a failed payment, then the recovery flow like a failure sent by API: the day-0 email is written in the background (never sent to a real inbox). invoice_url is the card update page of your app (billing by API); without it the email waits for review without a card link.

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): sandbox scope, test mode only.

BODY PARAMETERS

RETURNS

ERRORS

POST/v1/sandbox/failed_payment
curl https://api.nochurn.sh/v1/sandbox/failed_payment \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": "cst_bKA5uydA3TFbny8p",
    "invoice_url": "https://app.example.com/billing"
  }'
const res = await fetch("https://api.nochurn.sh/v1/sandbox/failed_payment", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "customer": "cst_bKA5uydA3TFbny8p",
    "invoice_url": "https://app.example.com/billing"
  }),
});
const sandboxFailedPayment = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/sandbox/failed_payment",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "customer": "cst_bKA5uydA3TFbny8p",
      "invoice_url": "https://app.example.com/billing"
    },
)
sandbox_failed_payment = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/sandbox/failed_payment")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "customer" => "cst_bKA5uydA3TFbny8p",
  "invoice_url" => "https://app.example.com/billing"
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
sandbox_failed_payment = JSON.parse(res.body)
RESPONSE 201
{
  "object": "sandbox_failed_payment",
  "customer": "cst_bKA5uydA3TFbny8p",
  "invoice": {
    "object": "invoice",
    "id": "ninv_r3bxXGhL1pmwzzsq",
    "customer": "cst_bKA5uydA3TFbny8p",
    "subscription": "nsub_h4fGLF2jw3Fmj949",
    "stripe_invoice_id": null,
    "external_id": "sandbox_in_5c71a02c",
    "livemode": false,
    "number": null,
    "status": "open",
    "amount_due_cents": 4900,
    "amount_paid_cents": 0,
    "amount_remaining_cents": 4900,
    "currency": "usd",
    "attempt_count": 1,
    "next_attempt_at": null,
    "failure_code": null,
    "decline_code": "insufficient_funds",
    "hosted_invoice_url": "https://app.example.com/billing",
    "paid_at": null,
    "created_at": "2026-11-04T10:00:00.000Z"
  },
  "payment": {
    "object": "payment",
    "id": "npay_36FrWRqwQscjZzbo",
    "livemode": false,
    "customer": "cst_bKA5uydA3TFbny8p",
    "subscription": "nsub_h4fGLF2jw3Fmj949",
    "invoice": "ninv_r3bxXGhL1pmwzzsq",
    "external_id": "sandbox_pay_5c71a02c",
    "stripe_charge_id": null,
    "status": "failed",
    "amount_cents": 4900,
    "currency": "usd",
    "decline_code": "insufficient_funds",
    "failure_code": null,
    "occurred_at": "2026-11-04T10:00:00Z",
    "created_at": "2026-11-04T10:00:00Z"
  },
  "recovery_run": {
    "id": "run_mmJr34RXHjDV6MZi",
    "state": "active",
    "holdout": false,
    "stop_reason": null
  },
  "skipped": null,
  "next": "The day-0 email is written in the background (test mode: never sent to a real inbox). Check it with GET /v1/integration/verify."
}

Sandbox: the customer removes their card before the renewal

Test mode only, on a sandbox card. Prevention signal card_removed: the customer is flagged, and the prevention flow writes its reminder when it is published in test mode.

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): sandbox scope, test mode only.

BODY PARAMETERS

RETURNS

POST/v1/sandbox/card_removed
curl https://api.nochurn.sh/v1/sandbox/card_removed \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": "cst_m7f9kZH5XcRMAx7x"
  }'
const res = await fetch("https://api.nochurn.sh/v1/sandbox/card_removed", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "customer": "cst_m7f9kZH5XcRMAx7x"
  }),
});
const sandboxCardRemoved = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/sandbox/card_removed",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "customer": "cst_m7f9kZH5XcRMAx7x"
    },
)
sandbox_card_removed = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/sandbox/card_removed")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "customer" => "cst_m7f9kZH5XcRMAx7x"
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
sandbox_card_removed = JSON.parse(res.body)
RESPONSE 201
{
  "object": "sandbox_card_removed",
  "customer": {
    "object": "customer",
    "id": "cst_m7f9kZH5XcRMAx7x",
    "stripe_customer_id": null,
    "external_id": "sandbox_a096134f",
    "stripe_account": null,
    "livemode": false,
    "email": "sandbox+a096134f@example.com",
    "company_domain": null,
    "name": "Sandbox customer",
    "locale": null,
    "country": null,
    "region": null,
    "timezone": "Europe/Paris",
    "timezone_source": "account",
    "attributes": {},
    "churn_score": 60,
    "churn_level": "medium",
    "flag": {
      "reason": "card_removed",
      "flagged_at": "2026-11-04T10:00:00.000Z"
    },
    "state": "active",
    "plan": "Pro",
    "mrr_cents": 4900,
    "lifetime_value_cents": 0,
    "currency": "usd",
    "payment_method": null,
    "last_seen_at": null,
    "churned_at": null,
    "flows_paused_at": null,
    "signup_at": "2026-11-04T10:00:00.000Z",
    "created_at": "2026-11-04T10:00:00.000Z",
    "updated_at": "2026-11-04T10:00:00.000Z",
    "identifiers": [
      {
        "kind": "email",
        "provider": null,
        "connection": null,
        "value": "sandbox+a096134f@example.com",
        "source": "api",
        "created_at": "2026-11-04T10:00:00.000Z"
      },
      {
        "kind": "external",
        "provider": null,
        "connection": null,
        "value": "sandbox_a096134f",
        "source": "api",
        "created_at": "2026-11-04T10:00:00.000Z"
      }
    ]
  },
  "payment_method": {
    "object": "payment_method",
    "id": "npm_r3X9MXd9dt36G7ZR",
    "customer": "cst_m7f9kZH5XcRMAx7x",
    "stripe_payment_method_id": null,
    "livemode": false,
    "type": "card",
    "brand": "visa",
    "last4": "4242",
    "exp_month": 12,
    "exp_year": 2029,
    "funding": "credit",
    "country": null,
    "wallet": null,
    "is_default": false,
    "detached_at": "2026-11-04T10:00:00.000Z",
    "created_at": "2026-11-04T10:00:00.000Z"
  },
  "flagged": true,
  "prevention_run": null
}

Sandbox: play the cancel flow

Test mode only. Opens the cancel flow, answers a reason (the first one by default), refuses or accepts the offer, then cancels in one click (click to cancel). Billing by API: the cancellation reaches your app as billing.cancel_requested.

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): sandbox scope, test mode only.

BODY PARAMETERS

RETURNS

POST/v1/sandbox/cancel_flow_run
curl https://api.nochurn.sh/v1/sandbox/cancel_flow_run \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": "cst_vyBRXyK4QwPYf9mu"
  }'
const res = await fetch("https://api.nochurn.sh/v1/sandbox/cancel_flow_run", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "customer": "cst_vyBRXyK4QwPYf9mu"
  }),
});
const sandboxCancelFlowRun = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/sandbox/cancel_flow_run",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "customer": "cst_vyBRXyK4QwPYf9mu"
    },
)
sandbox_cancel_flow_run = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/sandbox/cancel_flow_run")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "customer" => "cst_vyBRXyK4QwPYf9mu"
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
sandbox_cancel_flow_run = JSON.parse(res.body)
RESPONSE 201
{
  "object": "sandbox_cancel_flow_run",
  "session": {
    "object": "cancel_session",
    "id": "ses_E4ydMSMHe2opxRar",
    "livemode": false,
    "step": "done",
    "outcome": "cancelled",
    "locale": "en",
    "holdout": false,
    "published": false,
    "customer": {
      "id": "cst_vyBRXyK4QwPYf9mu",
      "stripe_customer_id": null,
      "external_id": "sandbox_474b675f"
    },
    "subscription": {
      "id": "nsub_G5GU7sVoHJFgxgQc",
      "plan": "Pro",
      "status": "active",
      "amount_cents": 4900,
      "currency": "usd",
      "interval": "month",
      "interval_count": 1,
      "current_period_end": "2026-11-09T10:00:00Z",
      "cancel_at_period_end": false
    },
    "subscriptions": [],
    "reasons": [
      {
        "key": "too_expensive",
        "label": "It's too expensive",
        "free_text": false
      },
      {
        "key": "missing_feature",
        "label": "A feature is missing",
        "free_text": false
      },
      {
        "key": "not_using",
        "label": "I don't use it enough",
        "free_text": false
      },
      {
        "key": "switching",
        "label": "I'm switching to another tool",
        "free_text": false
      },
      {
        "key": "temporary",
        "label": "I only need a break",
        "free_text": false
      },
      {
        "key": "project_finished",
        "label": "My project is finished",
        "free_text": false
      },
      {
        "key": "other",
        "label": "Something else",
        "free_text": true
      }
    ],
    "offer": null,
    "talk_url": null,
    "texts": {
      "review_ask": "Would you leave a short review on {{platform}}? No reward, just your honest opinion.",
      "review_cta": "Leave a review",
      "title": "Before you go",
      "reason_prompt": "Why are you cancelling?",
      "other_placeholder": "Tell us more (optional)",
      "offer_title": "One last thing",
      "accept": "Accept the offer",
      "decline": "No thanks, continue",
      "feedback_prompt": "Anything we could have done better?",
      "feedback_submit": "Continue",
      "confirm_title": "Cancel your subscription?",
      "confirm_body": "You keep access until the end of the paid period.",
      "cancel_now": "Cancel my subscription",
      "saved_title": "Thanks for staying!",
      "cancelled_title": "Your subscription is cancelled",
      "cancelled_body": "You keep access until the end of the paid period.",
      "offer_discount": "{{percent}}% off for {{months}} months",
      "offer_pause": "Pause for {{months}} months, free",
      "offer_downgrade": "Switch to {{plan}}",
      "offer_talk": "Talk to the team",
      "choose_subscription_title": "Which subscription?",
      "choose_subscription_body": "You have several subscriptions. Choose the one you want to cancel.",
      "choose_subscription_select": "Continue with this one",
      "cancel_this_one": "Cancel this one",
      "renews_on": "Renews on {{date}}"
    },
    "feedback_enabled": true,
    "feedback": {
      "enabled": true,
      "required": false
    },
    "theme": {
      "object": "cancel_flow_theme",
      "preset": "light",
      "format": "modal",
      "tokens": {
        "bg": "#f5f6fa",
        "surface": "#ffffff",
        "text": "#1c1e4a",
        "muted": "#686b85",
        "accent": "#4637d9",
        "accent_text": "#ffffff",
        "border": "#e3e5ec",
        "danger": "#c2410c",
        "font": "system-ui, sans-serif",
        "font_size": 15,
        "radius": 12,
        "radius_control": 8,
        "overlay": 0.5,
        "max_width": 480,
        "shadow": "md",
        "spacing": "regular",
        "border_width": 1,
        "border_style": "solid",
        "font_scale": 1.25,
        "font_weight_title": 600
      },
      "font_url": null,
      "brand": "logo",
      "logo_url": null,
      "app_name": "Account 1",
      "end_screen": {
        "style": "celebrate",
        "auto_close_seconds": 6
      },
      "custom_css": "",
      "cancel_button_check": "ok",
      "classes": [
        "nc-modal",
        "nc-title",
        "nc-reason",
        "nc-offer",
        "nc-btn-primary",
        "nc-btn-cancel",
        "nc-footer"
      ]
    },
    "cancel_url_now": null,
    "cancel_status": "requested",
    "cancel_effective_at": "2026-11-09T10:00:00Z",
    "result": null,
    "access_until": "2026-11-09T10:00:00Z",
    "confirmation_email_sent": false,
    "review": null,
    "expires_at": "2026-11-05T10:00:00Z",
    "created_at": "2026-11-04T10:00:00Z"
  }
}