API REFERENCE v1

DATA

Data & AI

The Attribute definition object

Custom data, churn drivers, campaigns, queries and feedback.

ATTRIBUTES

  • object enum
    • attribute_definition
  • id string
  • key string
  • type enum

    Inferred at the first send, then locked.

    • string
    • number
    • boolean
    • date
  • sensitive boolean

    Never sent to the AI.

  • customers_count integer

    Customers of this mode with a value.

  • coverage number | null

    customers_count / customers of this mode; null without customers.

  • last_seen_at string | null date-time
Show 1 more attributesHide 1 attributes

Schema: AttributeDefinition

THE ATTRIBUTE DEFINITION OBJECT
{
  "object": "attribute_definition",
  "id": "atd_rtVRQsuCTXLR8ka2",
  "key": "plan",
  "type": "string",
  "sensitive": false,
  "customers_count": 1,
  "coverage": 1,
  "last_seen_at": "2026-11-04T10:00:00Z",
  "created_at": "2026-11-04T10:00:00Z"
}

Declare an attribute before its first send

Locks the type from the start (same rules as the first send: snake_case key, 100 per account and mode). Declaring it again with the same type answers 200; another type is attribute_type_mismatch.

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

BODY PARAMETERS

  • key string REQUIRED
  • type enum REQUIRED
    • string
    • number
    • boolean
    • date
  • sensitive boolean

    Never sent to the AI.

RETURNS

ERRORS

POST/v1/attribute_definitions
curl https://api.nochurn.sh/v1/attribute_definitions \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "seats",
    "type": "number"
  }'
const res = await fetch("https://api.nochurn.sh/v1/attribute_definitions", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "key": "seats",
    "type": "number"
  }),
});
const attributeDefinition = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/attribute_definitions",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "key": "seats",
      "type": "number"
    },
)
attribute_definition = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/attribute_definitions")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "key" => "seats",
  "type" => "number"
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
attribute_definition = JSON.parse(res.body)
RESPONSE 201
{
  "object": "attribute_definition",
  "id": "atd_7donEcLpikLuJtpn",
  "key": "seats",
  "type": "number",
  "sensitive": false,
  "customers_count": 0,
  "coverage": 0,
  "last_seen_at": null,
  "created_at": "2026-11-04T10:00:00Z"
}

Mark an attribute sensitive

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

PATH PARAMETERS

  • key string REQUIRED

BODY PARAMETERS

RETURNS

ERRORS

PATCH/v1/attribute_definitions/{key}
curl https://api.nochurn.sh/v1/attribute_definitions/atd_rtVRQsuCTXLR8ka2 \
  -X PATCH \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "sensitive": true
  }'
const res = await fetch("https://api.nochurn.sh/v1/attribute_definitions/atd_rtVRQsuCTXLR8ka2", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "sensitive": true
  }),
});
const attributeDefinition = await res.json();
import requests

res = requests.patch(
    "https://api.nochurn.sh/v1/attribute_definitions/atd_rtVRQsuCTXLR8ka2",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "sensitive": True
    },
)
attribute_definition = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/attribute_definitions/atd_rtVRQsuCTXLR8ka2")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "sensitive" => true
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
attribute_definition = JSON.parse(res.body)
RESPONSE 200
{
  "object": "attribute_definition",
  "id": "atd_6EfKZkNvSS7YEkpS",
  "key": "plan",
  "type": "string",
  "sensitive": true,
  "customers_count": 1,
  "coverage": 1,
  "last_seen_at": "2026-11-04T10:00:00Z",
  "created_at": "2026-11-04T10:00:00Z"
}

Custom attributes of the account

  • Secret key (nc_test_… or nc_live_…). Restricted keys need read access.
  • AI apps (OAuth): read scope.

RETURNS

GET/v1/attribute_definitions
curl https://api.nochurn.sh/v1/attribute_definitions \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/attribute_definitions", {
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const page = await res.json();
import requests

res = requests.get(
    "https://api.nochurn.sh/v1/attribute_definitions",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
page = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/attribute_definitions")
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)
RESPONSE 200
{
  "object": "list",
  "has_more": false,
  "next_cursor": null,
  "data": [
    {
      "object": "attribute_definition",
      "id": "atd_rtVRQsuCTXLR8ka2",
      "key": "plan",
      "type": "string",
      "sensitive": false,
      "customers_count": 1,
      "coverage": 1,
      "last_seen_at": "2026-11-04T10:00:00Z",
      "created_at": "2026-11-04T10:00:00Z"
    }
  ]
}

Track a usage event

Rolled up each night into last_active_at, events_30d and <key event>_30d (custom attributes). Deduplicated on idempotency_key. Never sent to the AI.

  • 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/usage_events
curl https://api.nochurn.sh/v1/usage_events \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": "u_1",
    "name": "export_used",
    "idempotency_key": "e1"
  }'
const res = await fetch("https://api.nochurn.sh/v1/usage_events", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "customer": "u_1",
    "name": "export_used",
    "idempotency_key": "e1"
  }),
});
const usageEventBatch = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/usage_events",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "customer": "u_1",
      "name": "export_used",
      "idempotency_key": "e1"
    },
)
usage_event_batch = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/usage_events")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "customer" => "u_1",
  "name" => "export_used",
  "idempotency_key" => "e1"
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
usage_event_batch = JSON.parse(res.body)
RESPONSE 202
{
  "object": "usage_event_batch",
  "accepted": 1,
  "inserted": 1,
  "duplicates": 0,
  "errors": []
}

Track up to 1,000 events

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

BODY PARAMETERS

  • events array of objects 1000 items at most REQUIRED
    Show child parametersHide child parameters

RETURNS

POST/v1/usage_events/batch
curl https://api.nochurn.sh/v1/usage_events/batch \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "customer": "u_1",
        "name": "login"
      },
      {
        "customer": "u_404",
        "name": "login"
      }
    ]
  }'
const res = await fetch("https://api.nochurn.sh/v1/usage_events/batch", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "events": [
      {
        "customer": "u_1",
        "name": "login"
      },
      {
        "customer": "u_404",
        "name": "login"
      }
    ]
  }),
});
const usageEventBatch = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/usage_events/batch",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "events": [
        {
          "customer": "u_1",
          "name": "login"
        },
        {
          "customer": "u_404",
          "name": "login"
        }
      ]
    },
)
usage_event_batch = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/usage_events/batch")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "events" => [
    {
      "customer" => "u_1",
      "name" => "login"
    },
    {
      "customer" => "u_404",
      "name" => "login"
    }
  ]
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
usage_event_batch = JSON.parse(res.body)
RESPONSE 202
{
  "object": "usage_event_batch",
  "accepted": 1,
  "inserted": 1,
  "duplicates": 0,
  "errors": [
    {
      "index": 1,
      "code": "customer_not_found",
      "message": "No customer matches \"u_404\" in test mode."
    }
  ]
}

Churn drivers and AI insights

The last statistics run (relative risk, Wilson interval, Benjamini-Hochberg q-value per bucket; sample_ok false under 30 customers or 5 departures) and the last validated AI insight.

  • Secret key (nc_test_… or nc_live_…). Restricted keys need read access.
  • AI apps (OAuth): read scope.

RETURNS

GET/v1/insights
curl https://api.nochurn.sh/v1/insights \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/insights", {
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const insights = await res.json();
import requests

res = requests.get(
    "https://api.nochurn.sh/v1/insights",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
insights = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/insights")
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) }
insights = JSON.parse(res.body)
RESPONSE 200
{
  "object": "insights",
  "run": null,
  "insight": null
}

Recompute now (once an hour)

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): not available.

RETURNS

ERRORS

POST/v1/insights/refresh
curl https://api.nochurn.sh/v1/insights/refresh \
  -X POST \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/insights/refresh", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const insightsRefresh = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/insights/refresh",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
insights_refresh = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/insights/refresh")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
insights_refresh = JSON.parse(res.body)
RESPONSE 202
{
  "object": "insights_refresh",
  "queued": true
}

Churn by acquisition campaign

From utm_* attributes of customers who signed up in the period. Correlation, not causation.

  • Secret key (nc_test_… or nc_live_…). Restricted keys need read access.
  • AI apps (OAuth): read scope.

QUERY PARAMETERS

  • group_by enum default "utm_campaign"
    • utm_campaign
    • utm_source
    • utm_medium
  • period enum default "90d"
    • 90d
    • 12m

RETURNS

GET/v1/campaigns
curl https://api.nochurn.sh/v1/campaigns \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/campaigns", {
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const campaignStats = await res.json();
import requests

res = requests.get(
    "https://api.nochurn.sh/v1/campaigns",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
campaign_stats = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/campaigns")
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) }
campaign_stats = JSON.parse(res.body)
RESPONSE 200
{
  "object": "campaign_stats",
  "group_by": "utm_campaign",
  "period": "90d",
  "customers": 1,
  "churned": 0,
  "churn_rate": 0,
  "tracked": 1,
  "best": null,
  "worst": null,
  "min_sample": 30,
  "rows": [
    {
      "value": "black_friday",
      "customers": 1,
      "churned": 0,
      "churn_rate": 0,
      "relative_risk": null,
      "avg_ltv_cents": 0,
      "avg_score": null,
      "mrr_lost_cents": 0,
      "sample_ok": false
    }
  ]
}

Feedback themes and latest answers

  • Secret key (nc_test_… or nc_live_…). Restricted keys need read access.
  • AI apps (OAuth): read scope.

RETURNS

GET/v1/feedback
curl https://api.nochurn.sh/v1/feedback \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/feedback", {
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const feedback = await res.json();
import requests

res = requests.get(
    "https://api.nochurn.sh/v1/feedback",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
feedback = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/feedback")
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) }
feedback = JSON.parse(res.body)
RESPONSE 200
{
  "object": "feedback",
  "answers_90d": 0,
  "run": null,
  "items": []
}

Theme again (once an hour)

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): not available.

RETURNS

ERRORS

  • 429 rate_limited already requested this hour (counted from the click)
POST/v1/feedback/refresh
curl https://api.nochurn.sh/v1/feedback/refresh \
  -X POST \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/feedback/refresh", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const feedbackRefresh = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/feedback/refresh",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
feedback_refresh = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/feedback/refresh")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
feedback_refresh = JSON.parse(res.body)
RESPONSE 202
{
  "object": "feedback_refresh",
  "queued": true
}

What a query can read

NoChurn's query builder: no SQL. Sources and their fields (email as domain only, sensitive attributes excluded), with a description and, for enum-like fields, their values.

  • Secret key (nc_test_… or nc_live_…). Restricted keys need read access.
  • AI apps (OAuth): read scope.

RETURNS

GET/v1/query_fields
curl https://api.nochurn.sh/v1/query_fields \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/query_fields", {
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const page = await res.json();
import requests

res = requests.get(
    "https://api.nochurn.sh/v1/query_fields",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
page = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/query_fields")
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)
RESPONSE 200
{
  "object": "list",
  "has_more": false,
  "next_cursor": null,
  "data": [
    {
      "source": "customers",
      "label": "Customers",
      "description": "The people and companies who pay you, one row per customer.",
      "fields": [
        {
          "field": "id",
          "type": "string",
          "label": "NoChurn id",
          "description": "NoChurn id of the customer (cst_…)."
        },
        {
          "field": "external_id",
          "type": "string",
          "label": "Your id",
          "description": "The customer's id in your app, as you sent it."
        },
        {
          "field": "email_domain",
          "type": "string",
          "label": "Email domain",
          "description": "The part after @ of the customer's email (the address itself is never shown)."
        },
        {
          "field": "country",
          "type": "string",
          "label": "Country",
          "description": "Two-letter country code, from billing.",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "plan",
          "type": "string",
          "label": "Plan",
          "description": "Name of the customer's current plan.",
          "values": [
            {
              "value": "Pro",
              "label": "Pro"
            }
          ],
          "values_kind": "observed"
        },
        {
          "field": "state",
          "type": "string",
          "label": "Billing state",
          "description": "Where the customer stands: trialing, active, in recovery, paused, churned, or no subscription.",
          "values": [
            {
              "value": "none",
              "label": "No subscription"
            },
            {
              "value": "trialing",
              "label": "Trialing"
            },
            {
              "value": "active",
              "label": "Active"
            },
            {
              "value": "in_recovery",
              "label": "In recovery"
            },
            {
              "value": "paused",
              "label": "Paused"
            },
            {
              "value": "churned",
              "label": "Churned"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "churn_score",
          "type": "number",
          "label": "Churn score",
          "description": "NoChurn's churn risk score, 0 to 100 (higher is riskier)."
        },
        {
          "field": "churn_level",
          "type": "string",
          "label": "Churn level",
          "description": "The churn score as a level: low, medium or high.",
          "values": [
            {
              "value": "low",
              "label": "Low"
            },
            {
              "value": "medium",
              "label": "Medium"
            },
            {
              "value": "high",
              "label": "High"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "mrr_cents",
          "type": "number",
          "label": "MRR (cents)",
          "description": "Monthly recurring revenue, in cents of the customer's currency."
        },
        {
          "field": "lifetime_value_cents",
          "type": "number",
          "label": "Lifetime value (cents)",
          "description": "Everything the customer paid so far, in cents of their currency."
        },
        {
          "field": "currency",
          "type": "string",
          "label": "Currency",
          "description": "Three-letter currency code, lowercase (eur, usd…).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "flagged",
          "type": "boolean",
          "label": "Early signal",
          "description": "An early churn signal is raised on the customer."
        },
        {
          "field": "created_at",
          "type": "date",
          "label": "Created",
          "description": "When the customer was created."
        },
        {
          "field": "churned_at",
          "type": "date",
          "label": "Churned",
          "description": "When the customer churned (empty if they did not)."
        },
        {
          "field": "last_seen_at",
          "type": "date",
          "label": "Last seen",
          "description": "Last activity NoChurn knows of in your product."
        },
        {
          "field": "utm_source",
          "type": "string",
          "label": "UTM source",
          "description": "Where the customer came from (utm_source at signup: google, facebook, newsletter…).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "utm_medium",
          "type": "string",
          "label": "UTM medium",
          "description": "The marketing channel at signup (utm_medium: cpc, email, social…).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "utm_campaign",
          "type": "string",
          "label": "UTM campaign",
          "description": "The campaign the customer signed up from (utm_campaign).",
          "values": [],
          "values_kind": "observed"
        }
      ]
    },
    {
      "source": "subscriptions",
      "label": "Subscriptions",
      "description": "Each plan a customer subscribes to, with its status and amount.",
      "fields": [
        {
          "field": "id",
          "type": "string",
          "label": "NoChurn id",
          "description": "NoChurn's id of the row."
        },
        {
          "field": "customer",
          "type": "string",
          "label": "Customer",
          "description": "NoChurn id of the customer (cst_…)."
        },
        {
          "field": "status",
          "type": "string",
          "label": "Status",
          "description": "The billing status: active, trialing, past due, canceled…",
          "values": [
            {
              "value": "incomplete",
              "label": "Incomplete"
            },
            {
              "value": "incomplete_expired",
              "label": "Incomplete (expired)"
            },
            {
              "value": "trialing",
              "label": "Trialing"
            },
            {
              "value": "active",
              "label": "Active"
            },
            {
              "value": "past_due",
              "label": "Past due"
            },
            {
              "value": "canceled",
              "label": "Canceled"
            },
            {
              "value": "unpaid",
              "label": "Unpaid"
            },
            {
              "value": "paused",
              "label": "Paused"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "plan",
          "type": "string",
          "label": "Plan",
          "description": "Name of the plan.",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "interval",
          "type": "string",
          "label": "Interval",
          "description": "How often it bills (month, year…).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "amount_cents",
          "type": "number",
          "label": "Amount (cents)",
          "description": "Price per interval, in cents of its currency."
        },
        {
          "field": "currency",
          "type": "string",
          "label": "Currency",
          "description": "Three-letter currency code, lowercase (eur, usd…).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "cancel_at_period_end",
          "type": "boolean",
          "label": "Cancels at period end",
          "description": "Canceled, but runs until the end of the paid period."
        },
        {
          "field": "started_at",
          "type": "date",
          "label": "Started",
          "description": "When the subscription started."
        },
        {
          "field": "canceled_at",
          "type": "date",
          "label": "Canceled",
          "description": "When it was canceled."
        },
        {
          "field": "ended_at",
          "type": "date",
          "label": "Ended",
          "description": "When it actually stopped."
        },
        {
          "field": "created_at",
          "type": "date",
          "label": "Created",
          "description": "When it was created."
        }
      ]
    },
    {
      "source": "invoices",
      "label": "Invoices",
      "description": "Bills sent to customers, with what was due, what was paid and the last decline code.",
      "fields": [
        {
          "field": "id",
          "type": "string",
          "label": "NoChurn id",
          "description": "NoChurn's id of the row."
        },
        {
          "field": "customer",
          "type": "string",
          "label": "Customer",
          "description": "NoChurn id of the customer (cst_…)."
        },
        {
          "field": "status",
          "type": "string",
          "label": "Status",
          "description": "draft, open (not paid yet), paid, uncollectible or void.",
          "values": [
            {
              "value": "draft",
              "label": "Draft"
            },
            {
              "value": "open",
              "label": "Open"
            },
            {
              "value": "paid",
              "label": "Paid"
            },
            {
              "value": "uncollectible",
              "label": "Uncollectible"
            },
            {
              "value": "void",
              "label": "Void"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "amount_due_cents",
          "type": "number",
          "label": "Amount due (cents)",
          "description": "What the customer owes, in cents."
        },
        {
          "field": "amount_paid_cents",
          "type": "number",
          "label": "Amount paid (cents)",
          "description": "What was paid, in cents."
        },
        {
          "field": "currency",
          "type": "string",
          "label": "Currency",
          "description": "Three-letter currency code, lowercase (eur, usd…).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "decline_code",
          "type": "string",
          "label": "Decline code",
          "description": "Why the last payment of the invoice was refused (insufficient_funds…).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "attempt_count",
          "type": "number",
          "label": "Attempts",
          "description": "How many times payment was attempted."
        },
        {
          "field": "created_at",
          "type": "date",
          "label": "Created",
          "description": "When the invoice was created."
        }
      ]
    },
    {
      "source": "payments",
      "label": "Payments",
      "description": "Each payment attempt (a charge), succeeded or failed, with its decline code.",
      "fields": [
        {
          "field": "id",
          "type": "string",
          "label": "NoChurn id",
          "description": "NoChurn's id of the row."
        },
        {
          "field": "customer",
          "type": "string",
          "label": "Customer",
          "description": "NoChurn id of the customer (cst_…)."
        },
        {
          "field": "status",
          "type": "string",
          "label": "Status",
          "description": "succeeded, failed or pending.",
          "values": [
            {
              "value": "succeeded",
              "label": "Succeeded"
            },
            {
              "value": "failed",
              "label": "Failed"
            },
            {
              "value": "pending",
              "label": "Pending"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "amount_cents",
          "type": "number",
          "label": "Amount (cents)",
          "description": "Amount of the attempt, in cents."
        },
        {
          "field": "amount_refunded_cents",
          "type": "number",
          "label": "Refunded (cents)",
          "description": "Amount refunded, in cents."
        },
        {
          "field": "currency",
          "type": "string",
          "label": "Currency",
          "description": "Three-letter currency code, lowercase (eur, usd…).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "decline_code",
          "type": "string",
          "label": "Decline code",
          "description": "Why the bank or the payment provider refused this attempt.",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "occurred_at",
          "type": "date",
          "label": "Date",
          "description": "When the payment was attempted."
        }
      ]
    },
    {
      "source": "cancel_sessions",
      "label": "Cancel sessions",
      "description": "Each time a customer opened the cancel flow, with the reason they chose and what happened.",
      "fields": [
        {
          "field": "id",
          "type": "string",
          "label": "Session",
          "description": "NoChurn id of the cancel session."
        },
        {
          "field": "customer",
          "type": "string",
          "label": "Customer",
          "description": "NoChurn id of the customer (cst_…)."
        },
        {
          "field": "reason",
          "type": "string",
          "label": "Reason",
          "description": "The cancel reason the customer chose (its key in your cancel flow).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "outcome",
          "type": "string",
          "label": "Outcome",
          "description": "saved (they stayed), cancelled, or abandoned (closed without deciding).",
          "values": [
            {
              "value": "saved",
              "label": "Saved"
            },
            {
              "value": "cancelled",
              "label": "Cancelled"
            },
            {
              "value": "abandoned",
              "label": "Abandoned"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "offer_accepted",
          "type": "boolean",
          "label": "Offer accepted",
          "description": "The customer took the offer."
        },
        {
          "field": "source",
          "type": "string",
          "label": "Source",
          "description": "Where the cancel flow was opened: widget, hosted page or API.",
          "values": [
            {
              "value": "widget",
              "label": "Widget"
            },
            {
              "value": "hosted",
              "label": "Hosted"
            },
            {
              "value": "api",
              "label": "API"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "saved_mrr_cents",
          "type": "number",
          "label": "MRR saved (cents)",
          "description": "MRR kept when the customer stayed, in cents."
        },
        {
          "field": "currency",
          "type": "string",
          "label": "Currency",
          "description": "Currency of the subscription (or of the customer).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "created_at",
          "type": "date",
          "label": "Date",
          "description": "When the cancel flow was opened."
        }
      ]
    },
    {
      "source": "flow_runs",
      "label": "Flow runs",
      "description": "Each time a NoChurn flow (prevention, recovery, cancel, win-back) ran for a customer, and its outcome.",
      "fields": [
        {
          "field": "id",
          "type": "string",
          "label": "Run",
          "description": "NoChurn id of the run."
        },
        {
          "field": "customer",
          "type": "string",
          "label": "Customer",
          "description": "NoChurn id of the customer (cst_…)."
        },
        {
          "field": "flow",
          "type": "string",
          "label": "Flow",
          "description": "Which flow: prevention, recovery, cancel or win-back.",
          "values": [
            {
              "value": "prevention",
              "label": "Prevention"
            },
            {
              "value": "recovery",
              "label": "Recovery"
            },
            {
              "value": "cancel",
              "label": "Cancel"
            },
            {
              "value": "winback",
              "label": "Win-back"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "state",
          "type": "string",
          "label": "State",
          "description": "active (still running), succeeded, failed or stopped.",
          "values": [
            {
              "value": "active",
              "label": "Active"
            },
            {
              "value": "succeeded",
              "label": "Succeeded"
            },
            {
              "value": "failed",
              "label": "Failed"
            },
            {
              "value": "stopped",
              "label": "Stopped"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "outcome",
          "type": "string",
          "label": "Outcome",
          "description": "What the run achieved: prevented, recovered, saved, won back, lost…",
          "values": [
            {
              "value": "prevented",
              "label": "Prevented"
            },
            {
              "value": "recovered",
              "label": "Recovered"
            },
            {
              "value": "saved",
              "label": "Saved"
            },
            {
              "value": "won_back",
              "label": "Won back"
            },
            {
              "value": "lost",
              "label": "Lost"
            },
            {
              "value": "cancelled",
              "label": "Cancelled"
            },
            {
              "value": "abandoned",
              "label": "Abandoned"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "confirmation",
          "type": "string",
          "label": "Confirmation",
          "description": "Whether the outcome held for 30 days: pending, confirmed or reverted.",
          "values": [
            {
              "value": "pending",
              "label": "Pending"
            },
            {
              "value": "confirmed",
              "label": "Confirmed"
            },
            {
              "value": "reverted",
              "label": "Reverted"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "holdout",
          "type": "boolean",
          "label": "Holdout",
          "description": "In the control group (NoChurn did nothing, to measure the lift)."
        },
        {
          "field": "outcome_cents",
          "type": "number",
          "label": "Amount (cents)",
          "description": "Amount recovered, in cents."
        },
        {
          "field": "outcome_mrr_cents",
          "type": "number",
          "label": "MRR (cents)",
          "description": "MRR kept or won back, in cents."
        },
        {
          "field": "currency",
          "type": "string",
          "label": "Currency",
          "description": "Three-letter currency code, lowercase (eur, usd…).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "started_at",
          "type": "date",
          "label": "Started",
          "description": "When the run started."
        },
        {
          "field": "outcome_at",
          "type": "date",
          "label": "Outcome date",
          "description": "When the outcome was reached."
        }
      ]
    },
    {
      "source": "usage_events",
      "label": "Usage events",
      "description": "What customers do in your product, as sent to NoChurn (events and page views).",
      "fields": [
        {
          "field": "customer",
          "type": "string",
          "label": "Customer",
          "description": "NoChurn id of the customer (cst_…)."
        },
        {
          "field": "name",
          "type": "string",
          "label": "Event",
          "description": "Name of the event (page_view, or what you send with track).",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "occurred_at",
          "type": "date",
          "label": "Date",
          "description": "When it happened."
        }
      ]
    },
    {
      "source": "messages",
      "label": "Emails",
      "description": "The emails NoChurn sent to customers, and whether they were opened or clicked.",
      "fields": [
        {
          "field": "id",
          "type": "string",
          "label": "Email",
          "description": "NoChurn id of the email."
        },
        {
          "field": "customer",
          "type": "string",
          "label": "Customer",
          "description": "NoChurn id of the customer (cst_…)."
        },
        {
          "field": "template",
          "type": "string",
          "label": "Template",
          "description": "Which email of the flow it was.",
          "values": [],
          "values_kind": "observed"
        },
        {
          "field": "status",
          "type": "string",
          "label": "Status",
          "description": "queued, sent, delivered, bounced, failed…",
          "values": [
            {
              "value": "queued",
              "label": "Queued"
            },
            {
              "value": "sending",
              "label": "Sending"
            },
            {
              "value": "sent",
              "label": "Sent"
            },
            {
              "value": "delivered",
              "label": "Delivered"
            },
            {
              "value": "bounced",
              "label": "Bounced"
            },
            {
              "value": "complained",
              "label": "Complained"
            },
            {
              "value": "failed",
              "label": "Failed"
            },
            {
              "value": "suppressed",
              "label": "Suppressed"
            },
            {
              "value": "cancelled",
              "label": "Cancelled"
            }
          ],
          "values_kind": "closed"
        },
        {
          "field": "opened",
          "type": "boolean",
          "label": "Opened",
          "description": "The customer opened it."
        },
        {
          "field": "clicked",
          "type": "boolean",
          "label": "Clicked",
          "description": "The customer clicked a link in it."
        },
        {
          "field": "ai_generated",
          "type": "boolean",
          "label": "Written by AI",
          "description": "The text was written by NoChurn's AI."
        },
        {
          "field": "sent_at",
          "type": "date",
          "label": "Sent",
          "description": "When it was sent."
        }
      ]
    }
  ]
}

Run a query

Read only, 5 s at most, 1,000 rows, 60 runs an hour. Each run is recorded.

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): not available.

BODY PARAMETERS

  • spec object

    NoChurn query builder (no SQL). Either metrics (+ dimensions) or fields.

    Show child parametersHide child parameters
    • source enum REQUIRED
      • customers
      • subscriptions
      • invoices
      • payments
      • cancel_sessions
      • flow_runs
      • usage_events
      • messages
    • metrics array of objects 5 items at most
      Show child parametersHide child parameters
      • fn enum REQUIRED
        • count
        • count_distinct
        • sum
        • avg
        • min
        • max
      • field string
    • dimensions array of objects 3 items at most
      Show child parametersHide child parameters
      • field string REQUIRED
      • bucket enum
        • day
        • week
        • month
        • quarter
        • year
    • fields array of strings 20 items at most
    • filters array of objects 10 items at most
      Show child parametersHide child parameters
    • period object | null
      Show child parametersHide child parameters
    • sort object | null
      Show child parametersHide child parameters
      • by string

        metric_0…, a dimension or a field.

      • dir enum
        • asc
        • desc
    • limit integer 1 to 1000
  • saved_query string

RETURNS

ERRORS

POST/v1/queries/run
curl https://api.nochurn.sh/v1/queries/run \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "spec": {
      "source": "customers",
      "metrics": [
        {
          "fn": "count"
        }
      ],
      "dimensions": [
        {
          "field": "plan"
        }
      ]
    }
  }'
const res = await fetch("https://api.nochurn.sh/v1/queries/run", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "spec": {
      "source": "customers",
      "metrics": [
        {
          "fn": "count"
        }
      ],
      "dimensions": [
        {
          "field": "plan"
        }
      ]
    }
  }),
});
const queryRun = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/queries/run",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "spec": {
        "source": "customers",
        "metrics": [
          {
            "fn": "count"
          }
        ],
        "dimensions": [
          {
            "field": "plan"
          }
        ]
      }
    },
)
query_run = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/queries/run")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "spec" => {
    "source" => "customers",
    "metrics" => [
      {
        "fn" => "count"
      }
    ],
    "dimensions" => [
      {
        "field" => "plan"
      }
    ]
  }
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
query_run = JSON.parse(res.body)
RESPONSE 200
{
  "object": "query_run",
  "id": "qrn_QWkMX2B2yQAhTU1K",
  "status": "succeeded",
  "spec": {
    "source": "customers",
    "metrics": [
      {
        "fn": "count"
      }
    ],
    "dimensions": [
      {
        "field": "plan"
      }
    ],
    "fields": [],
    "filters": [],
    "period": null,
    "sort": null,
    "limit": 100
  },
  "columns": ["plan", "count"],
  "rows": [
    ["Pro", 1]
  ],
  "row_count": 1,
  "duration_ms": 42,
  "created_at": "2026-11-04T10:00:00Z"
}

Ask anything

The AI turns the question into a query spec from the catalogue only (never rows); the same validator applies.

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): not available.

BODY PARAMETERS

RETURNS

POST/v1/queries/translate
curl https://api.nochurn.sh/v1/queries/translate \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "How many customers?"
  }'
const res = await fetch("https://api.nochurn.sh/v1/queries/translate", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "question": "How many customers?"
  }),
});
const queryTranslation = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/queries/translate",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "question": "How many customers?"
    },
)
query_translation = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/queries/translate")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "question" => "How many customers?"
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
query_translation = JSON.parse(res.body)
RESPONSE 200
{
  "object": "query_translation",
  "question": "How many customers?",
  "spec": {
    "source": "customers",
    "metrics": [
      {
        "fn": "count"
      }
    ],
    "dimensions": [],
    "fields": [],
    "filters": [],
    "period": null,
    "sort": null,
    "limit": 100
  },
  "explanation": "Count."
}

Saved queries

  • Secret key (nc_test_… or nc_live_…). Restricted keys need read access.
  • AI apps (OAuth): read scope.

RETURNS

GET/v1/saved_queries
curl https://api.nochurn.sh/v1/saved_queries \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/saved_queries", {
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const page = await res.json();
import requests

res = requests.get(
    "https://api.nochurn.sh/v1/saved_queries",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
page = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/saved_queries")
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)
RESPONSE 200
{
  "object": "list",
  "data": [],
  "has_more": false,
  "next_cursor": null
}

Save a query

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): not available.

BODY PARAMETERS

  • name string REQUIRED
  • question string
  • spec object REQUIRED

    NoChurn query builder (no SQL). Either metrics (+ dimensions) or fields.

    Show child parametersHide child parameters
    • source enum REQUIRED
      • customers
      • subscriptions
      • invoices
      • payments
      • cancel_sessions
      • flow_runs
      • usage_events
      • messages
    • metrics array of objects 5 items at most
      Show child parametersHide child parameters
      • fn enum REQUIRED
        • count
        • count_distinct
        • sum
        • avg
        • min
        • max
      • field string
    • dimensions array of objects 3 items at most
      Show child parametersHide child parameters
      • field string REQUIRED
      • bucket enum
        • day
        • week
        • month
        • quarter
        • year
    • fields array of strings 20 items at most
    • filters array of objects 10 items at most
      Show child parametersHide child parameters
    • period object | null
      Show child parametersHide child parameters
    • sort object | null
      Show child parametersHide child parameters
      • by string

        metric_0…, a dimension or a field.

      • dir enum
        • asc
        • desc
    • limit integer 1 to 1000
  • view enum
    • table
    • chart
  • chart object

RETURNS

POST/v1/saved_queries
curl https://api.nochurn.sh/v1/saved_queries \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Customers by plan",
    "spec": {
      "source": "customers",
      "metrics": [
        {
          "fn": "count"
        }
      ],
      "dimensions": [
        {
          "field": "plan"
        }
      ]
    },
    "view": "chart"
  }'
const res = await fetch("https://api.nochurn.sh/v1/saved_queries", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Customers by plan",
    "spec": {
      "source": "customers",
      "metrics": [
        {
          "fn": "count"
        }
      ],
      "dimensions": [
        {
          "field": "plan"
        }
      ]
    },
    "view": "chart"
  }),
});
const savedQuery = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/saved_queries",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "name": "Customers by plan",
      "spec": {
        "source": "customers",
        "metrics": [
          {
            "fn": "count"
          }
        ],
        "dimensions": [
          {
            "field": "plan"
          }
        ]
      },
      "view": "chart"
    },
)
saved_query = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/saved_queries")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "name" => "Customers by plan",
  "spec" => {
    "source" => "customers",
    "metrics" => [
      {
        "fn" => "count"
      }
    ],
    "dimensions" => [
      {
        "field" => "plan"
      }
    ]
  },
  "view" => "chart"
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
saved_query = JSON.parse(res.body)
RESPONSE 201
{
  "object": "saved_query",
  "id": "qry_b6R81JjTFD5ZJFpL",
  "name": "Customers by plan",
  "question": null,
  "spec": {
    "source": "customers",
    "metrics": [
      {
        "fn": "count"
      }
    ],
    "dimensions": [
      {
        "field": "plan"
      }
    ],
    "fields": [],
    "filters": [],
    "period": null,
    "sort": null,
    "limit": 100
  },
  "view": "chart",
  "chart": {},
  "created_at": "2026-11-04T10:00:00Z",
  "schedule": null
}

Rename or change a saved query

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): not available.

PATH PARAMETERS

  • id string REQUIRED

BODY PARAMETERS

  • name string
  • question string
  • spec object

    NoChurn query builder (no SQL). Either metrics (+ dimensions) or fields.

    Show child parametersHide child parameters
    • source enum REQUIRED
      • customers
      • subscriptions
      • invoices
      • payments
      • cancel_sessions
      • flow_runs
      • usage_events
      • messages
    • metrics array of objects 5 items at most
      Show child parametersHide child parameters
      • fn enum REQUIRED
        • count
        • count_distinct
        • sum
        • avg
        • min
        • max
      • field string
    • dimensions array of objects 3 items at most
      Show child parametersHide child parameters
      • field string REQUIRED
      • bucket enum
        • day
        • week
        • month
        • quarter
        • year
    • fields array of strings 20 items at most
    • filters array of objects 10 items at most
      Show child parametersHide child parameters
    • period object | null
      Show child parametersHide child parameters
    • sort object | null
      Show child parametersHide child parameters
      • by string

        metric_0…, a dimension or a field.

      • dir enum
        • asc
        • desc
    • limit integer 1 to 1000
  • view enum
    • table
    • chart
  • chart object

RETURNS

PATCH/v1/saved_queries/{id}
curl https://api.nochurn.sh/v1/saved_queries/qry_gpKxkTzgkCTFJzo6 \
  -X PATCH \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Renamed"
  }'
const res = await fetch("https://api.nochurn.sh/v1/saved_queries/qry_gpKxkTzgkCTFJzo6", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Renamed"
  }),
});
const savedQuery = await res.json();
import requests

res = requests.patch(
    "https://api.nochurn.sh/v1/saved_queries/qry_gpKxkTzgkCTFJzo6",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "name": "Renamed"
    },
)
saved_query = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/saved_queries/qry_gpKxkTzgkCTFJzo6")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "name" => "Renamed"
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
saved_query = JSON.parse(res.body)
RESPONSE 200
{
  "object": "saved_query",
  "id": "qry_gpKxkTzgkCTFJzo6",
  "name": "Renamed",
  "question": null,
  "spec": {
    "sort": null,
    "limit": 100,
    "fields": [],
    "period": null,
    "source": "customers",
    "filters": [],
    "metrics": [
      {
        "fn": "count"
      }
    ],
    "dimensions": [
      {
        "field": "plan"
      }
    ]
  },
  "view": "table",
  "chart": {},
  "created_at": "2026-11-04T10:00:00Z",
  "schedule": null
}

Delete a saved query (and its schedule)

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): not available.

PATH PARAMETERS

  • id string REQUIRED

RETURNS

  • 200 deleted
DELETE/v1/saved_queries/{id}
curl https://api.nochurn.sh/v1/saved_queries/qry_A7F5EsvVczn9SwwU \
  -X DELETE \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/saved_queries/qry_A7F5EsvVczn9SwwU", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const savedQuery = await res.json();
import requests

res = requests.delete(
    "https://api.nochurn.sh/v1/saved_queries/qry_A7F5EsvVczn9SwwU",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
saved_query = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/saved_queries/qry_A7F5EsvVczn9SwwU")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer nc_test_…"

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
saved_query = JSON.parse(res.body)
RESPONSE 200
{
  "object": "saved_query",
  "id": "qry_A7F5EsvVczn9SwwU",
  "name": "Q",
  "question": null,
  "spec": {
    "sort": null,
    "limit": 100,
    "fields": [],
    "period": null,
    "source": "customers",
    "filters": [],
    "metrics": [
      {
        "fn": "count"
      }
    ],
    "dimensions": [
      {
        "field": "plan"
      }
    ]
  },
  "view": "table",
  "chart": {},
  "created_at": "2026-11-04T10:00:00Z",
  "schedule": null,
  "deleted": true
}

Schedule a report

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): not available.

PATH PARAMETERS

  • id string REQUIRED

BODY PARAMETERS

  • cadence enum REQUIRED
    • daily
    • weekly
    • monthly
  • day enum | integer

    weekly: monday…sunday (monday by default); monthly: 1–28 (1 by default); daily: leave out.

  • destination enum REQUIRED
    • email
    • slack
  • target string

    Email address (default: the account's admins).

RETURNS

ERRORS

POST/v1/saved_queries/{id}/schedule
curl https://api.nochurn.sh/v1/saved_queries/qry_b6R81JjTFD5ZJFpL/schedule \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "cadence": "weekly",
    "day": "thursday",
    "destination": "email"
  }'
const res = await fetch("https://api.nochurn.sh/v1/saved_queries/qry_b6R81JjTFD5ZJFpL/schedule", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "cadence": "weekly",
    "day": "thursday",
    "destination": "email"
  }),
});
const savedQuery = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/saved_queries/qry_b6R81JjTFD5ZJFpL/schedule",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "cadence": "weekly",
      "day": "thursday",
      "destination": "email"
    },
)
saved_query = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/saved_queries/qry_b6R81JjTFD5ZJFpL/schedule")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "cadence" => "weekly",
  "day" => "thursday",
  "destination" => "email"
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
saved_query = JSON.parse(res.body)
RESPONSE 200
{
  "object": "saved_query",
  "id": "qry_tDhxTamfnwgkYVK4",
  "name": "Q",
  "question": null,
  "spec": {
    "sort": null,
    "limit": 100,
    "fields": [],
    "period": null,
    "source": "customers",
    "filters": [],
    "metrics": [
      {
        "fn": "count"
      }
    ],
    "dimensions": [
      {
        "field": "plan"
      }
    ]
  },
  "view": "table",
  "chart": {},
  "created_at": "2026-11-04T10:00:00Z",
  "schedule": {
    "cadence": "weekly",
    "day": "thursday",
    "time": "11:00",
    "timezone": "Europe/Paris",
    "destination": "email",
    "target": null,
    "next_run_at": "2026-11-05T10:00:00Z",
    "last_run": null
  }
}

Stop a report

  • Secret key (nc_test_… or nc_live_…). Restricted keys need write access.
  • AI apps (OAuth): not available.

PATH PARAMETERS

  • id string REQUIRED

RETURNS

DELETE/v1/saved_queries/{id}/schedule
curl https://api.nochurn.sh/v1/saved_queries/qry_b6R81JjTFD5ZJFpL/schedule \
  -X DELETE \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/saved_queries/qry_b6R81JjTFD5ZJFpL/schedule", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const savedQuery = await res.json();
import requests

res = requests.delete(
    "https://api.nochurn.sh/v1/saved_queries/qry_b6R81JjTFD5ZJFpL/schedule",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
saved_query = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/saved_queries/qry_b6R81JjTFD5ZJFpL/schedule")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer nc_test_…"

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
saved_query = JSON.parse(res.body)
RESPONSE 200
{
  "object": "saved_query",
  "id": "qry_1qvCv4aVywRRALhc",
  "name": "Q",
  "question": null,
  "spec": {
    "sort": null,
    "limit": 100,
    "fields": [],
    "period": null,
    "source": "customers",
    "filters": [],
    "metrics": [
      {
        "fn": "count"
      }
    ],
    "dimensions": [
      {
        "field": "plan"
      }
    ]
  },
  "view": "table",
  "chart": {},
  "created_at": "2026-11-04T10:00:00Z",
  "schedule": null
}

Imports

  • Secret key (nc_test_… or nc_live_…). Restricted keys need read access.
  • AI apps (OAuth): read scope.

RETURNS

GET/v1/imports
curl https://api.nochurn.sh/v1/imports \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/imports", {
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const page = await res.json();
import requests

res = requests.get(
    "https://api.nochurn.sh/v1/imports",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
page = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/imports")
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)
RESPONSE 200
{
  "object": "list",
  "data": [],
  "has_more": false,
  "next_cursor": null
}

Import a CSV of customers and attributes

Send the file as multipart file, or its text as csv. Columns are mapped by name (customer, external_id, email, stripe_customer, name, country, locale; anything else → attribute:<snake_case>), override with mapping. dry_run returns the mapping and the first rows. A row with an unknown customer and an external_id or email creates it.

  • 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/imports
curl https://api.nochurn.sh/v1/imports \
  -H "Authorization: Bearer nc_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "csv": "User ID,Email,Plan\nu_1,ana@example.test,pro\n",
    "dry_run": true
  }'
const res = await fetch("https://api.nochurn.sh/v1/imports", {
  method: "POST",
  headers: {
    Authorization: "Bearer nc_test_…",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "csv": "User ID,Email,Plan\nu_1,ana@example.test,pro\n",
    "dry_run": true
  }),
});
const importPreview = await res.json();
import requests

res = requests.post(
    "https://api.nochurn.sh/v1/imports",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
    json={
      "csv": "User ID,Email,Plan\nu_1,ana@example.test,pro\n",
      "dry_run": True
    },
)
import_preview = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/imports")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer nc_test_…"
req["Content-Type"] = "application/json"
req.body = {
  "csv" => "User ID,Email,Plan\nu_1,ana@example.test,pro\n",
  "dry_run" => true
}.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
import_preview = JSON.parse(res.body)
RESPONSE
{
  "object": "import_preview",
  "columns": [
    {
      "name": "User ID",
      "target": "external_id"
    },
    {
      "name": "Email",
      "target": "email"
    },
    {
      "name": "Plan",
      "target": "attribute:plan"
    }
  ],
  "rows_total": 1,
  "rows": [
    {
      "User ID": "u_1",
      "Email": "ana@example.test",
      "Plan": "pro"
    }
  ]
}

An import and its error report

  • Secret key (nc_test_… or nc_live_…). Restricted keys need read access.
  • AI apps (OAuth): read scope.

PATH PARAMETERS

  • id string REQUIRED

RETURNS

GET/v1/imports/{id}
curl https://api.nochurn.sh/v1/imports/imp_T6rNbRAuJxkNV4gX \
  -H "Authorization: Bearer nc_test_…"
const res = await fetch("https://api.nochurn.sh/v1/imports/imp_T6rNbRAuJxkNV4gX", {
  headers: {
    Authorization: "Bearer nc_test_…",
  },
});
const import = await res.json();
import requests

res = requests.get(
    "https://api.nochurn.sh/v1/imports/imp_T6rNbRAuJxkNV4gX",
    headers={
        "Authorization": "Bearer nc_test_…",
    },
)
import = res.json()
require "net/http"
require "json"

uri = URI("https://api.nochurn.sh/v1/imports/imp_T6rNbRAuJxkNV4gX")
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) }
import = JSON.parse(res.body)
RESPONSE 200
{
  "object": "import",
  "id": "imp_T6rNbRAuJxkNV4gX",
  "status": "pending",
  "filename": null,
  "mapping": {
    "email": "email"
  },
  "rows_total": 1,
  "rows_imported": 0,
  "rows_failed": 0,
  "errors": [],
  "livemode": false,
  "created_at": "2026-11-04T10:00:00Z",
  "started_at": null,
  "finished_at": null
}