Resources

API Docs

Connect any custom or headless platform to Telltale. Push orders and customers, get the same retention brain as Shopify, WooCommerce and BigCommerce merchants.

Overview

Telltale is a retention brain. You push orders and customers; Telltale computes per-customer churn risk, lifecycle state (Stable, Slowing, Overdue, Lapsed), predicted next purchase and lifetime value, then builds campaign audiences (Sightings), sends them through your connected email or SMS provider, attributes the orders that follow, and measures lift against a ~5% universal holdout.

Base URL: https://api.telltale.pro. All request and response bodies are JSON. Timestamps are ISO‑8601; always include a UTC offset.

Using an AI coding tool? Add the Telltale MCP server and it can read these docs, validate and push your data, and run a sync for you.

What the minimum push unlocks

order_id, customer_id, order_date, order_amount, product_name plus email and/or phone on every order is enough for revenue, cohorts, customer profiles, churn scoring and attribution. A trained churn model (rather than heuristic scoring) needs roughly 100 customers with two or more orders, so push about 12 months of history.

Three rules that keep billing honest

Telltale's performance fee is computed only from statistically significant incremental revenue measured against the holdout, net of refunds. That means: (1) refunds must land on the same order_id; (2) the customer_id on orders must be the same identity Telltale messaged; (3) holdout customers must not be contacted through side channels.

Getting credentials

One credential authorizes the ingest surface: an ingest key (tk_live_…), minted right here by an owner or admin. The key carries your account, so the account id header is optional. Keys are stored hashed, shown once, rotate with a grace window, and can be revoked instantly. Sandbox keys (tk_test_…) only open your sandbox workspace.

Your workspace

Account id (optional header X-Telltale-Account-Id)

Ingest keys are generated, rotated and revoked in the console below. They carry the account, so this id is only needed with the legacy tile token.

Console target

Ingest keys · live

Owners and admins manage keys.

Test a key

Paste a key to confirm it authenticates and which workspace it opens. The key is checked server-side and never stored.

Send a sample order

Dry run only on the live workspace: see exactly how a sample order is validated, nothing is written.

Sandbox workspace

A separate, zero-egress workspace for testing your integration end to end. Push data with tk_test_ keys, run sync, look at the dashboard, reset, repeat.

Ingest activity · live

No batches yet. Every /ingest call shows up here with counts and error codes (never payload data).

CredentialWhereNotes
Ingest keySigned in: the console above → Generate key (owner or admin). Public page: sign in first.Sent as Authorization: Bearer tk_live_…. Hashed at rest, shown once, rotate/revoke here. Up to 10 live keys.
Account idShown above when signed in.Optional with tk_ keys (must match if sent). Required with the legacy tile token.
Legacy tile tokenIntegrations → Custom API tile → Webhook panel.Still accepted with X-Telltale-Account-Id. Prefer tk_ keys.
Pixel key (pk_…)Settings → On-site tracking.Publishable browser key with an origin allow-list. Rotate from the same screen.

Enterprise keys do not authorize ingest

If your workspace has Settings → API & webhooks, the sk_live_ keys minted there manage outbound webhook subscriptions only. Do not use them for the endpoints on this page.

Auth, errors, limits

Authentication

SurfaceCredentialHow to send it
/ingest/*Ingest key tk_live_… / tk_test_…Authorization: Bearer <key>. The account comes from the key; X-Telltale-Account-Id is optional and must match if sent. Keys in the query string are rejected. Legacy tile tokens still work with the account id header (or ?account_id=&token=).
POST /webhooks/custom-apiSame ingest keyAuthorization: Bearer <key> (preferred). Legacy: ?account_id=<uuid>&token=<tile token>.
POST /webhooks/zero-partyZero-party tile tokenQuery string only. Separate key from the Zero-Party Data tile.
POST /pixel/collectPixel keyIn the JSON body as key, plus a browser Origin header on the allow-list.

The account is derived from the authenticated key alone. Order and customer bodies never carry an account id, so one client can never write into another's data.

Auth failures with an ingest key, in the order they are checked:

StatusCause
401Key sent in the query string, or key invalid, expired, revoked, or not for the account in X-Telltale-Account-Id
400Legacy path only: account id missing or not a UUID
404Legacy path only: no connected Custom API tile for the account
429More than 30 failed authentications from one IP in a minute (returned instead of 401/404)

Errors

Errors are JSON {"detail": "…"} unless noted. There is no path version today; unknown request fields on orders and customers are silently ignored (see FAQ).

StatusWhenBody
400Empty batch; missing or invalid account id; webhook payload with no identityJSON detail
401Ingest key missing or mismatched; webhook token mismatchJSON detail
403Pixel: invalid or revoked key, origin not allowed, demo accountJSON detail
404Custom API or Zero-Party tile not connectedJSON detail
413Body over 32 MiB on orders or customersPlain text payload too large, checked before the row count
413More than 5,000 rowsJSON Batch too large (N > 5000). Page your upload.
422Schema failure; bad subscription_status; non-IANA store_timezone; pixel unknown field or over 50 eventsValidation body
429Over 600 requests/min on one key (or one account for the legacy token); more than 30 failed auths/min from one IP; POST /ingest/sync more than once per 10 min; pixel per-IP/per-key limitsJSON detail Rate limit exceeded — try again in ~Ns. plus a Retry-After header
500Orders: any failure rolls back the whole batch (nothing written). Customers: a mid-batch failure can leave earlier rows committed; re-POST the batch, it is idempotent.JSON detail

Limits

LimitValueApplies to
Rows per batch5,000orders, customers
Body size32 MiBorders, customers only
Rate limit600 requests/min per ingest key (per account for the legacy token); 30 failed auths/min per IP; POST /ingest/sync once per 10 min per account. Exceeding any returns 429 with Retry-After.ingest, webhooks
Pixel600 req/min per IP, 6,000/min per key; 50 events per batch; 48 KB body; events older than 30 days or more than 1 h in the future are dropped/pixel/collect
TimestampsNaive order_date values are read as UTC and counted in naive_timestamps with a warningorders
Currency3-letter ISO code or omit (USD). Longer strings fail the whole orders batch.orders

Integration walkthrough for a custom platform

Step 1 — Verify the key

Set TELLTALE_API_KEY in your environment (the account is derived from the key), then:

curl -s https://api.telltale.pro/ingest/health \
  -H "Authorization: Bearer $TELLTALE_API_KEY"
response 200
{"ok":true,"account_id":"…","connected_integrations":["custom-api"],"orders_ingested":0,"store_timezone":null,"hint":"…"}

Then set your store timezone (IANA name only):

curl -s -X PATCH https://api.telltale.pro/ingest/account \
  -H "Authorization: Bearer $TELLTALE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"store_timezone":"America/New_York"}'

Step 2 — Historical backfill

  1. Dry-run a sample first. POST /ingest/orders?dry_run=true (and /ingest/customers?dry_run=true) validates every row and writes nothing: per-row error codes and warnings come back so you fix the export before loading a year of history. See the Dry run reference below.
  2. Customers first (consent and subscriptions): POST /ingest/customers in pages of up to 5,000. Include external_id (must equal the customer_id you use on orders), email, phone in E.164, consent, and subscription_status if you run subscriptions.
  3. Orders: POST /ingest/orders in pages of up to 5,000 rows and under 32 MiB, oldest first, at least 12 months. Overlapping pages are safe (idempotent on order_id); if a run dies, restart from the last page you got a 200 for. GET /ingest/batches is your audit trail of what each page did.
  4. Size batches well under both caps. On a plain-text 413, split by bytes; on a JSON 413, split by rows. On a 500 from orders nothing was written, so fix and retry the same batch.
  5. Read the response counters. Treat dropped_invalid > 0 as a bug in your export: it means an unparseable order_date or order_amount, or a row whose customer id, email and phone are all blank. A missing required field is a 422 for the whole request instead (see FAQ).

Step 3 — Ongoing sync

  • New or updated orders: POST a batch of one, or micro-batch every minute. Re-posting the same order_id merges lifecycle fields: refunds and cancels only ever move forward, and order_date and customer_id never change.
  • Refunds: re-POST the whole order row (all required fields, unchanged order_amount) with the new cumulative refunded_amount. order_amount stays the gross total; Telltale nets refunds itself. This drives net revenue and any fee credits.
  • Cancellations: re-POST with cancelled_at.
  • Consent changes (STOP, unsubscribe): re-POST the customer with consent.sms=false or consent.suppressed=true, or send one event to POST /webhooks/custom-api. Telltale otherwise learns suppressions only from connected email and SMS providers.
  • The nightly recompute at 02:00 UTC picks everything up. Call POST /ingest/sync only after large loads.

Step 4 — Install the on-site pixel (optional)

  1. Settings → On-site tracking: add your storefront origins (scheme://host, no path, up to 50) and copy the snippet.
  2. Consent: add data-telltale-consent="required" and call window.telltale('consent','granted') from your consent manager. Global Privacy Control is honoured. Events without granted consent are dropped silently.
  3. Single-page apps: call window.telltale('track', 'page_view' | 'add_to_cart' | 'checkout_started') on route changes. Set product_external_id to the same product id you send on orders.
html
<script async src="https://api.telltale.pro/pixel/v1/telltale-pixel.js"
  data-telltale-key="pk_…" data-telltale-endpoint="https://api.telltale.pro/pixel/collect"></script>

Known limitation for Custom API accounts

Events are accepted and stored, but browse-to-customer stitching and the on-site rollups currently run only for accounts with a Shopify, WooCommerce or BigCommerce connection. Enabling them for Custom API accounts is on the roadmap below. There is no identify event yet; sessions are stitched when a landing URL carries a Telltale utm_content.

Step 5 — Marketing consent

Consent is boolean today: consent.marketing_email, consent.sms, consent.suppressed, consent.bounced. SMS sends are gated on sms alone, so only set it true where you hold prior express written consent, and keep your own proof record. Consent provenance fields (when, how, source) are planned.

Step 6 — Coupons and attribution

Coupons today. Telltale never creates discount codes on any platform. Shopify, WooCommerce and BigCommerce libraries sync automatically; for a custom platform, enter codes by hand in Sightings → Templates & offers. Send discount_codes, discount_total and discount_pct on orders for discount-sensitivity analytics; codes are not used for attribution.

Attribution today. Every Telltale call-to-action links to your storefront with utm_source=telltale, utm_medium=<channel>, utm_campaign=<type> and utm_content=<tracking_id>. Orders are attributed either URL-precisely (when the order's source carries that utm_content) or by 7-day last-touch on the customer.

Keep source = acquisition channel for now

A full Telltale landing URL placed in source is classified as the campaign channel and overrides the order's true acquisition channel. Until a separate landing_url field ships, send the acquisition channel in source and rely on last-touch attribution.

Also make sure your storefront domain is set on the account (the team can set it for you today; a self-serve field is planned). Telltale builds every call-to-action on top of it.

Step 7 — Trigger sync and verify

curl -s -X POST https://api.telltale.pro/ingest/sync \
  -H "Authorization: Bearer $TELLTALE_API_KEY"
response 200
{"queued":true,"task_id":"…","status_url":"/ingest/sync/…","message":"Analytics recompute queued. Poll status_url; fresh numbers appear once it completes."}

Poll the status_url (GET /ingest/sync/{task_id} with the same key) until status is done, failed or skipped; a few minutes is typical. Then check the app: the Nest Report shows metrics, Audience Builder lists scored customers, and Sightings shows opportunities. GET /ingest/health confirms the row count and GET /ingest/batches shows what happened to each page.

curl -s https://api.telltale.pro/ingest/sync/<task_id> \
  -H "Authorization: Bearer $TELLTALE_API_KEY"

Endpoint reference

All ingest routes take X-Telltale-Account-Id, Authorization: Bearer and Content-Type: application/json.

Health

GET/ingest/healthAvailable now

Verifies the key and reports current state. connected_integrations lists every connected provider, orders_ingested is the total order count for the account.

json
{"ok": true, "account_id": "<uuid>", "connected_integrations": ["custom-api", "klaviyo"],
 "orders_ingested": 12034, "store_timezone": "America/New_York",
 "hint": "POST /ingest/orders and /ingest/customers to load data, then POST /ingest/sync to recompute analytics."}

Orders

POST/ingest/ordersAvailable now

Batch upsert of 1 to 5,000 orders as {"orders": [ … ]}. The customer identity on each order is upserted too, so buyers do not need a separate customers call. Idempotent on order_id. The batch is one transaction.

FieldTypeRequiredNotes
order_idstringyesYour order id. Idempotency key.
customer_idstringyesYour stable customer id. Must match customers.external_id.
order_datestringyesISO‑8601 with offset. Unparseable → row dropped; naive → read as UTC with a warning.
order_amountnumberyesGross order total in major units (send the pre-refund total; refunds go in refunded_amount). Must be a JSON number; a non-numeric value is a 422.
product_namestringyesPrimary product on the order (single line today; line items are planned).
emailstringnoBuyer email.
phonestringnoNormalised to E.164 (default region US).
customer_namestringnoBuyer name.
sourcestringnoAcquisition channel (e.g. email, sms, paid_social, organic). See Step 6.
order_channelstringnoe.g. email / web / pos. Used only to flag email-attributed orders.
currencystringno3-letter ISO code. Defaults to USD.
shipping_city / shipping_state / shipping_zipstringnoGeo enrichment.
refunded_amountnumbernoCumulative refunded amount. Merges forward only.
discount_total / discount_pctnumbernoDiscount applied.
discount_codesstring[]noCodes used.
cancelled_atstringnoISO‑8601 when cancelled.

Unknown fields are ignored, not rejected

Fields such as line_items, fulfilled_at, delivered_at, shipping_country, financial_status and landing_url are accepted silently and discarded today. They are on the roadmap; do not rely on them yet.

Behaviour: an in-batch duplicate order_id is last-write-wins; rows with no customer id, email or phone are dropped and counted in dropped_invalid.

response 200
{"received": 2, "distinct_order_ids": 2, "deduped_in_batch": 0, "accepted": 2, "dropped_invalid": 0,
 "orders_upserted": 2, "customers_upserted": 1, "naive_timestamps": 0,
 "next": "POST /ingest/sync to recompute analytics for this account.",
 "warning": "<only when naive_timestamps > 0>"}
curl -s -X POST https://api.telltale.pro/ingest/orders \
  -H "Authorization: Bearer $TELLTALE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"orders":[{"order_id":"A1001","customer_id":"C42","order_date":"2026-09-20T14:03:00-04:00","order_amount":86.4,"currency":"USD","product_name":"Oat Milk 12pk","email":"jo@example.com","phone":"+12125551212","source":"email","shipping_city":"Brooklyn","shipping_state":"NY","shipping_zip":"11201","discount_codes":["WELCOME10"],"discount_total":8,"discount_pct":8.5,"refunded_amount":0}]}'

Customers

POST/ingest/customersAvailable now

Batch upsert of 1 to 5,000 customers as {"customers": [ … ]}. Identity resolves on external_id, then email, then phone, then creates a new customer. Use it for consent, addresses and subscription state; buyers are already created by orders.

FieldTypeRequiredNotes
external_idstringnoSame value as orders.customer_id.
email / phone / namestringnoAt least one of external_id, email or phone is required, otherwise the row is counted in skipped_no_identity.
addressobjectno{street, city, state, zip, country}. Written when street, city or zip is present; country defaults to US.
consentobjectno{marketing_email, sms, suppressed, bounced} booleans. Omitted keys keep their current value.
source_labelstringnoProvenance label. Defaults to custom-api.
subscription_statusenumnoactive | paused | cancelled. Anything else → 422.
subscription_started_atstringnoISO‑8601. Write-once, earliest wins.
response 200
{"received": 2, "customers_upserted": 2, "skipped_no_identity": 0, "subscriptions_applied": 1}
curl -s -X POST https://api.telltale.pro/ingest/customers \
  -H "Authorization: Bearer $TELLTALE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customers":[{"external_id":"C42","email":"jo@example.com","phone":"+12125551212","name":"Jo Park","address":{"street":"1 Main St","city":"Brooklyn","state":"NY","zip":"11201","country":"US"},"consent":{"marketing_email":true,"sms":false},"subscription_status":"active","subscription_started_at":"2026-02-01T00:00:00Z"}]}'

Account settings

PATCH/ingest/accountAvailable now

Body {"store_timezone": "America/New_York"}. IANA names only; abbreviations like CST return 422. The value is stored and echoed on /ingest/health. Note: the recompute pipeline does not consume it yet, so local-time analytics still assume UTC until that lands.

Sync

POST/ingest/syncAvailable now

No body. Queues a full analytics recompute for the account. Throttled to one call per 10 minutes per account (429 with Retry-After otherwise); the nightly recompute runs regardless.

response 200
{"queued": true, "task_id": "…", "status_url": "/ingest/sync/<task_id>", "message": "Analytics recompute queued. Poll status_url; fresh numbers appear once it completes."}
curl -s -X POST https://api.telltale.pro/ingest/sync \
  -H "Authorization: Bearer $TELLTALE_API_KEY"

Sync status

GET/ingest/sync/{task_id}Available now

Status of a recompute this account queued. Task ids from other accounts (or unknown ids) return 404.

response 200
{"task_id": "…", "queued_at": "2026-09-22T20:00:00+00:00", "status": "queued | running | done | failed | skipped", "orders_added": 1200, "customers_added": 300, "duration_ms": 42000}

skipped carries a reason (for example already_running). Counters appear only when done.

Dry run

POST/ingest/orders?dry_run=trueAvailable now
POST/ingest/customers?dry_run=trueAvailable now

Same body and caps as the real call; nothing is written. Each row is checked exactly the way the write path would treat it. The call is logged in GET /ingest/batches with dry_run: true.

response 200
{"dry_run": true, "received": 2, "would_accept": 1, "would_drop": 1,
 "errors": [{"index": 1, "order_id": "A2", "codes": ["no_usable_identity"]}],
 "warnings": [{"index": 0, "order_id": "A1", "code": "unknown_fields_ignored", "fields": ["line_items"]}],
 "errors_truncated": 0, "next": "Fix any errors, then POST again without ?dry_run=true."}
CodeKindMeaning
no_usable_identityerror (row dropped)Customer id, email and phone are all blank.
unparseable_order_date_or_amounterror (row dropped)The date or amount cannot be parsed.
invalid_currency_code_fails_whole_batcherror (whole batch fails)Currency is not a 3-letter code; on a real call this aborts the entire batch.
unknown_fields_ignoredwarningFields the API does not store (listed in fields).
duplicate_in_batch_last_winswarningThe same order_id appears twice; the later row wins.
naive_timestamp_read_as_utcwarningorder_date has no UTC offset.

Up to 50 errors and 50 warnings are returned; errors_truncated counts the rest.

curl -s -X POST "https://api.telltale.pro/ingest/orders?dry_run=true" \
  -H "Authorization: Bearer $TELLTALE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"orders":[{"order_id":"A1001","customer_id":"C42","order_date":"2026-09-20T14:03:00-04:00","order_amount":86.4,"currency":"USD","product_name":"Oat Milk 12pk","email":"jo@example.com","phone":"+12125551212","source":"email","shipping_city":"Brooklyn","shipping_state":"NY","shipping_zip":"11201","discount_codes":["WELCOME10"],"discount_total":8,"discount_pct":8.5,"refunded_amount":0}]}'

Ingest activity

GET/ingest/batches?limit=50Available now

The last 1 to 200 batches for the account: endpoint, dry-run flag, status (accepted, rejected, error, queued), received/accepted/dropped counts, error and warning codes, duration, the sync task_id, and the key id used. Rows carry your own order and customer ids in error entries but never names, emails or phones. If you send an X-Request-Id header it is stored with the row so support can find it. Retained 90 days.

curl -s "https://api.telltale.pro/ingest/batches?limit=20" \
  -H "Authorization: Bearer $TELLTALE_API_KEY"

Customer event webhook

POST/webhooks/custom-api?account_id=&token=Available now

Single customer identity or consent event. Body is a free-form object with external_id (alias customer_id), email, phone, name, address, consent, event_type (default custom) and source_label. At least one of email, phone or external id is required.

Deduplicated per account on event_type + external_id (or a hash of the body). A duplicate returns {"received": true, "duplicate": true}; a failed attempt is re-processed on retry. This call also flips the Integrations tile from "Awaiting setup" to connected.

response 200
{"received": true, "customer_id": "<uuid>", "link_method": "provider_id|email|phone|fuzzy|created", "is_new": true}
curl -s -X POST https://api.telltale.pro/webhooks/custom-api \
  -H "Authorization: Bearer $TELLTALE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"external_id":"C42","phone":"+12125551212","event_type":"sms_stop","consent":{"sms":false,"suppressed":true}}'

Zero-party data webhook

POST/webhooks/zero-party?account_id=&token=Available now

Quiz and preference submissions. Requires the separate token from the Zero-Party Data tile. Body: email, external_customer_id (aliases external_id, customer_id), interests[], attributes{}, source_label, quiz_version, completed_at. At least one of email or external id is required. Deduplicated on content, so a changed submission is a new event.

On-site events

POST/pixel/collectAvailable now

Browser-only. Requires an Origin header on the pixel key's allow-list. Up to 50 events per call, 48 KB body. Unknown fields are rejected. Returns 204 on accept and also on a quiet drop (no consent, out of window).

json
{"key":"pk_…","events":[{"event_id":"<uuid>","event_type":"page_view|product_view|search|add_to_cart|checkout_started",
  "occurred_at":"2026-09-22T10:00:00Z","anonymous_id":"<1..128>","session_id":"<≤128>","url_path":"/p/oat-milk",
  "page_title":"…","referrer_host":"google.com","product_external_id":"p_88","collection":"…","search":"…",
  "price_cents":4000,"dwell_ms":1200,"consent_state":"granted","raw_props":{"utm_content":"<tracking_id>","sku":"OM-12"}}]}
GET/pixel/v1/telltale-pixel.jsAvailable now

The universal loader used by the snippet in Step 4. Cached for one hour.

Outbound webhooks (Telltale → you)

Available to workspaces with the API & webhooks settings page. Subscriptions are created in the app. Each delivery is a JSON POST {"event": "<type>", "data": { … }} with headers X-Telltale-Event, X-Telltale-Delivery (deduplicate on it) and X-Telltale-Signature, a hex HMAC‑SHA256 of the raw body with your whsec_… secret. Failed deliveries are retried up to three times with growing gaps (15, then 30, then 45 minutes).

Events today

Only campaign.sent is emitted, with {campaign_type, provider, tracking_id}. Customer-level events (customer.churned, recovery.attributed, lift.proven, sync.completed) are on the roadmap and will carry your customer_external_id.

Roadmap

Planned additions, in priority order. Nothing here is callable yet. If one of these blocks your integration, tell us in the chat and we will prioritise it.

PriorityPlannedWhy
P1A versioned /v1/… path prefix for the ingest surface (today: /ingest/*)Stable contract for generated SDKs. Keys, scopes, rotation, dry run, sync status and rate limits already ship.
P0Richer orders: line_items[], subtotal, financial_status, fulfilled_at, delivered_at, tracking, shipping.country, landing_urlUnlocks product-level insights, shipping signals and URL-precise attribution without overwriting the acquisition channel.
P0Customers: consent provenance (when, how, source), tags, richer subscription stateCompliance proof and payment-recovery plays.
P0Account: store_domain, primary_currency, and timezone-aware recomputeEvery call-to-action needs a storefront to point at.
P0Send claim + confirm (POST /v1/sends) and audience exports for merchants sending from their own stackWithout it, self-sent campaigns cannot be attributed or measured against the holdout.
P0Pixel stitching and rollups for Custom API accountsMakes on-site events useful for the accounts this API serves.
P1Coupon library (POST /v1/coupons), generic events (POST /v1/events), product catalog, pixel identify, key-authed reads (customers, campaigns, reports, metrics, proof), webhook management, more outbound events (customer.churned, recovery.attributed, lift.proven, sync.completed)Parity with the native connectors.
P2Streaming backfill jobs, storefront offer lookup, audience push, GDPR redactScale and compliance conveniences.

FAQ & gotchas

413 comes before the row count. The 32 MiB cap is checked while the body streams in and returns plain text. A JSON "Batch too large" is the 5,000-row cap. Only orders and customers are byte-capped.

Idempotency. Orders upsert on order_id; refunds and cancels only move forward; order_date and customer_id never change on re-POST. Customers resolve on external id, then email, then phone. The customer webhook deduplicates per event; the zero-party webhook deduplicates on content.

Timezones. Always send an offset. Naive values are read as UTC and reported in naive_timestamps.

Currency. Omit or send a 3-letter ISO code. Unknown codes convert at 1.0.

Unknown fields are ignored on orders and customers. Check the schema tables above rather than assuming a field is stored.

The Integrations tile says "Awaiting setup". The chip flips on real evidence. Ingest calls alone do not flip it; one call to POST /webhooks/custom-api does.

Attribution of campaigns you send yourself. Telltale attributes orders to Telltale-dispatched sends, or to orders whose source carries a Telltale utm_content. Campaigns sent from your own stack are not attributed or billable until the send claim endpoint ships.

Sync status. POST /ingest/sync returns a status_url; poll it until done. One trigger per 10 minutes per account; the nightly recompute runs at 02:00 UTC regardless.

422 on a whole request. A missing or mistyped required field (for example no product_name, or a string order_amount) fails the entire request with a validation body even in dry run; each entry's loc is ["body", "orders", <row index>, "<field>"]. Row-level content problems (identity, dates, currency) are reported per row by ?dry_run=true instead.

Ten live keys. The cap counts keys that still authenticate; revoked keys and keys past their rotation grace do not count. Revoke old ones from the console.

AI tools. Claude Code, Claude.ai, Cursor and any MCP client can drive this API through the hosted MCP server: MCP & AI tools. Machine-readable docs: https://api.telltale.pro/llms.txt, /llms-full.txt, /v1/openapi.json.

Questions? Use the chat bubble, OWL, or email owl@telltale.pro. Developers on your team can be invited under Team.