Theme
AI in Tangent
Tangent's AI features are built on one rule that does not bend: your raw events never reach a model. What leaves your workspace is its schema — event names, property names and their types — and the question you typed. The answer comes from running the generated query on your own data, inside Tangent. That is both the privacy guarantee and the reason a small model is enough: the job is turning a question into a query, not reasoning over your users.
What it does today
Ask in English on the Insights page. Type a question — "How many pro users exported a report last week, by country?" — and Tangent builds the query, runs it, and shows the chart. The query it built is always visible and loads into the query builder, so you can see exactly what was asked of your data and adjust it. This is deliberate: an answer you cannot check is an answer you cannot trust.
If the model produces a query that names an event or property that does not exist in your workspace, Tangent refuses to run it and tells you why, rather than running something wrong and presenting it confidently. One repair attempt is made with the specific errors; after that you get "couldn't build a query for that" and an invitation to rephrase.
Where the model runs — three tiers
| Tier | The request goes to | Who pays inference | Available on |
|---|---|---|---|
| Tangent-hosted | a model Tangent runs, pinned to a named version | Tangent | every plan |
| Bring your own key | your own provider account (OpenAI-compatible or Anthropic) | you | Growth and above |
| Bring your own endpoint | a model inside your own network | you | Enterprise |
The hosted tier works the moment you sign up. The other two are for organisations with an approved AI vendor, or that require nothing about their data to reach a third party at all. One OpenAI-compatible adapter covers OpenAI, Azure OpenAI, Groq, Mistral and self-hosted servers such as vLLM and Ollama, which is what makes "bring your own endpoint" real rather than a checkbox.
Your key is encrypted at rest with the same scheme that protects single sign-on secrets, is never returned by any API after you save it (you see a hint like …7Qx2), and never appears in a log. Saving runs a real test question first; if your provider refuses, nothing is stored and you see the provider's own error and the latency.
Allowances
Questions are counted per workspace per calendar month and shown on your Usage page next to events and tracked users. The allowance depends on your plan; at the limit the question box says so and names the next plan, rather than failing silently. Questions are counted on every tier — on bring-your-own tiers this protects the service, not your bill.
A separate burst limit — questions per minute, for everyone in the workspace together — stops one runaway script consuming a month in a minute. You set it yourself under Settings → AI & models, and what you may set depends on who pays for the inference: on the Tangent-hosted tier you can only lower it below the platform default, because the model budget is ours; on bring-your-own-key and bring-your-own-endpoint you can raise it, up to a ceiling the operator publishes, because every question still costs Tangent a query on your warehouse. Leave the box blank to follow the platform default. Over the ceiling you get 400 ai_cap_too_high naming the maximum, never a silent clamp; over the limit itself, a question gets 429 ai_rate_limited with a Retry-After.
The endpoints
POST /api/ai/ask { question, from?, to?, tz? }
GET /api/ai/settings current tier, entitlement, hosted model id, usage, rateLimit
PUT /api/ai/settings { mode, provider?, baseUrl?, model?, apiKey?, perMinuteCap? }
POST /api/ai/settings/test the same test without savingPOST /api/ai/ask answers with the spec that ran, the result (including the SQL in result.meta.sql), a one-line explanation, which model answered and how fast, and your usage.
Refusals are specific, and each has its own code so a client can act on it:
| Status | error | Meaning |
|---|---|---|
| 403 | ai_not_enabled | AI is not enabled for this workspace |
| 422 | ai_could_not_build_query | the model could not produce a valid query; errors says what was wrong |
| 429 | ai_allowance_exhausted | monthly allowance used; body carries used, allowance, resetsAt, upgrade |
| 429 | ai_rate_limited | burst limit; honour Retry-After |
| 400 | ai_cap_too_high | the per-minute limit you tried to set exceeds what your tier allows |
| 429 | ai_provider_busy | the model provider is rate limiting this deployment; honour Retry-After |
| 503 | ai_hosted_not_configured | this deployment has no hosted model configured |
| 503 | ai_hosted_paused | an operator has paused the hosted tier; bring-your-own tiers keep working |
| 503 | ai_hosted_unavailable | the hosted tier is temporarily paused; bring-your-own tiers are unaffected |
Why did this move? — root-cause analysis, no model
The second AI feature involves no model at all, and that is the point: it runs on your warehouse, on every plan, and nothing about your data goes anywhere. Click Why did this move? on any Overview tile or on the totals of an Insight, and Tangent compares the range you are looking at with the comparison window, scans the breakdown dimensions — country, device, platform, operating system, browser, app version, acquisition source, medium and campaign, channel, referring domain, plan, app — and ranks the segments by their share of the change.
Every other tool in this category hands you a list of "anomalies". Ours is built around what such a scan is honestly able to say:
- Every finding is exploratory. This is observational data. A segment that moved with the metric is a hypothesis about the cause, never a demonstration of it. The panel says so at the top, on every significance chip, and in the caveats under the numbers; a screenshot cannot lose it.
- Scanning many segments guarantees false positives. At a 5% threshold, 200 segments yield about ten that look significant by chance. Each segment's own change is tested — a two-proportion z-test for rates, a two-count Poisson z-test for counts — and then every p-value is corrected across all segments tested (Benjamini–Hochberg). What you see is the corrected q-value, next to how many segments were tested and how many would have looked significant uncorrected.
- Thin segments are not tested. Below the minimum size (50 users in each window by default; 20, 50 or 100 in the panel) a segment is listed as too small to assess with the numbers that made it so. No p-value is computed from that little, because it would be a number with no meaning.
- "The mix did not change" is an answer. Each dimension gets a chi-square test of whether the segment split changed. When no dimension's mix changed, the headline is that the metric moved everywhere at once — look for a cause that touched all users (a release, a campaign, an outage, tracking) rather than for a segment. Likewise, when the overall change is within noise, the panel says so rather than ranking noise.
- The work is shown. Which dimensions were scanned, which were skipped and why (not sent, only one value), both windows, the timezone, and every SQL statement that ran.
POST /api/analytics/insights/explain
{ "metric": { "event": "signup_completed", "measure": "conversion" },
"current": { "from": "2026-09-01T00:00:00Z", "to": "2026-09-08T00:00:00Z" },
"baseline": { "from": "2026-08-25T00:00:00Z", "to": "2026-09-01T00:00:00Z" },
"dimensions": ["country", "device", "utm_source"], "minSegmentSize": 50 }event is an event type, * for any product event, or $conversion for the project's own conversion events; measure is uniques, totals, sessions or conversion (users doing the event ÷ active users); filters take the same shape as /insights/run. The answer carries the headline (moved_everywhere, driven_by_segments, mixed, within_noise, too_thin, no_change), the overall test, ranked contributors with before/after, contribution (share of the change; for rates split into a rate effect and a mix effect), pValue, qValue and status, the tooSmall rows, the dimensions scanned, a testing summary, exploratory: true, caveats, and meta.sql. It is scoped exactly like every other read: your workspace, your selected project, and refused rather than widened when a request carries no project scope.
What is not supported yet
Say it plainly rather than let you find out: no streaming answers, no conversation memory between questions, no Bedrock or Vertex adapters (use an OpenAI-compatible or Anthropic endpoint), and no narrated reports or session-replay summaries yet — those will use the same provider settings when they arrive. Root-cause analysis scans counts, unique users, sessions and conversion rates; sums and averages (revenue) are not tested yet.