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.
Show 1 more attributesHide 1 attributes
Schema: AttributeDefinition
{
"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_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth):
configurescope, test mode only.
BODY PARAMETERS
-
- string
- number
- boolean
- date
- sensitive boolean
Never sent to the AI.
RETURNS
-
201declared — the Attribute definition object
ERRORS
-
422attribute_type_mismatch already declared with another type
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) {
"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_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth):
configurescope, test mode only.
PATH PARAMETERS
BODY PARAMETERS
RETURNS
-
200updated — the Attribute definition object
ERRORS
-
404not_found unknown attribute
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) {
"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_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
RETURNS
-
200with their type and coverage — a list of Attribute definition object (AttributeDefinitionList)
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) {
"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_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth):
sandboxscope, test mode only.
BODY PARAMETERS
-
cst_…, cus_… or your own id.
- properties object
Any JSON object (50 keys kept).
- idempotency_key string
RETURNS
-
202accepted — a UsageEventBatch
ERRORS
-
400invalid_request unknown event name format -
404customer_not_found unknown customer (its own code and fix)
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) {
"object": "usage_event_batch",
"accepted": 1,
"inserted": 1,
"duplicates": 0,
"errors": []
} Track up to 1,000 events
- Secret key (
nc_test_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth):
sandboxscope, test mode only.
BODY PARAMETERS
-
Show child parametersHide child parameters
-
cst_…, cus_… or your own id.
- properties object
Any JSON object (50 keys kept).
- idempotency_key string
-
RETURNS
-
202errors are reported per event — a UsageEventBatch
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) {
"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_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
RETURNS
-
200nothing computed yet — a 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) {
"object": "insights",
"run": null,
"insight": null
} Recompute now (once an hour)
- Secret key (
nc_test_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth): not available.
RETURNS
-
202queued — a InsightsRefresh
ERRORS
-
429rate_limited already refreshed this hour
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) {
"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_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
QUERY PARAMETERS
-
- utm_campaign
- utm_source
- utm_medium
-
- 90d
- 12m
RETURNS
-
200rows with their sample size — a CampaignStats
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) {
"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_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
RETURNS
-
200nothing yet — a 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) {
"object": "feedback",
"answers_90d": 0,
"run": null,
"items": []
} Theme again (once an hour)
- Secret key (
nc_test_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth): not available.
RETURNS
-
202queued — a FeedbackRefresh
ERRORS
-
429rate_limited already requested this hour (counted from the click)
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) {
"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_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
RETURNS
-
200sources and fields — a list of QuerySource (QuerySourceList)
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) {
"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_…ornc_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
- saved_query string
RETURNS
-
200columns and rows — a QueryRun
ERRORS
-
400invalid_request unknown field
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) {
"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_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth): not available.
BODY PARAMETERS
RETURNS
-
200a spec to run — a QueryTranslation
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) {
"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_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
RETURNS
-
200newest first — a list of SavedQuery (SavedQueryList)
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) {
"object": "list",
"data": [],
"has_more": false,
"next_cursor": null
} Save a query
- Secret key (
nc_test_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth): not available.
BODY PARAMETERS
- question string
-
NoChurn query builder (no SQL). Either metrics (+ dimensions) or fields.
Show child parametersHide child parameters
- view enum
- table
- chart
- chart object
RETURNS
-
201saved — a SavedQuery
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) {
"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_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth): not available.
PATH PARAMETERS
BODY PARAMETERS
- name string
- question string
- spec object
NoChurn query builder (no SQL). Either metrics (+ dimensions) or fields.
Show child parametersHide child parameters
- view enum
- table
- chart
- chart object
RETURNS
-
200updated — a SavedQuery
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) {
"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_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth): not available.
PATH PARAMETERS
RETURNS
-
200deleted
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) {
"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_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth): not available.
PATH PARAMETERS
BODY PARAMETERS
-
- daily
- weekly
- monthly
- day enum | integer
weekly: monday…sunday (monday by default); monthly: 1–28 (1 by default); daily: leave out.
-
- slack
- target string
Email address (default: the account's admins).
RETURNS
-
200scheduled — a SavedQuery
ERRORS
-
400invalid_request reports only go to the account's owners and admins
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) {
"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_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth): not available.
PATH PARAMETERS
RETURNS
-
200no schedule — a SavedQuery
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) {
"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_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
RETURNS
-
200newest first — a list of Import (ImportList)
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) {
"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_…ornc_live_…). Restricted keys need write access. - AI apps (OAuth):
sandboxscope, test mode only.
BODY PARAMETERS
RETURNS
-
200dry run: proposed mapping and preview — a ImportPreview -
202queued — a Import
ERRORS
-
400invalid_request no column identifies the customer
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) {
"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"
}
]
}{
"object": "import",
"id": "imp_HGApj7P3iZ2chfFT",
"status": "pending",
"filename": "c.csv",
"mapping": {
"user_id": "external_id",
"plan": "attribute:plan",
"seats": "attribute:seats"
},
"rows_total": 3,
"rows_imported": 0,
"rows_failed": 0,
"errors": [],
"livemode": false,
"created_at": "2026-11-04T10:00:00Z",
"started_at": null,
"finished_at": null
} An import and its error report
- Secret key (
nc_test_…ornc_live_…). Restricted keys need read access. - AI apps (OAuth):
readscope.
PATH PARAMETERS
RETURNS
-
200the import — a Import
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) {
"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
}