API Reference
Complete endpoint documentation for the Sentiqra Analyze API.
Base URL
https://sentiqra.tech/v1
Authentication
Bearer token in the Authorization header.
Request headers
| Header | Required | Description |
|---|---|---|
| Authorization | Protected endpoints | Bearer API key. Keys start with sq_live_. |
| Idempotency-Key | Optional | Safe-retry key on POST endpoints - replays return the recorded response without re-billing. |
| X-Retry-Failed | Optional | true + an existing Idempotency-Key retries only the analyzers recorded as failed - billed per newly-completed run. |
| Content-Type | POST requests | application/json |
Response headers
| Header | Description |
|---|---|
| X-Sentiqra-Request-ID | Unique request identifier - include it in support requests and audit logs. |
| X-Sentiqra-Detection-Config | Analyzer configuration version that produced the response (sqcfg_...). |
| verification.fingerprint | (response body field, not a header) Deterministic analysis fingerprint (fp2_... - RFC 8785 canonical payload) - store it with the signature to verify the response later. |
| X-Sentiqra-Analyzers-Run | Analyzer runs billed to this request (1 or 2 per analyze call). |
| MCP-Protocol-Version | MCP protocol version on /mcp responses. Analyzer runs billed by an MCP call are reported in the result's structuredContent (billed_runs), not a header. |
| X-Sentiqra-API-Contract | API contract version that served the response. |
| X-RateLimit-Limit | Maximum requests per second for your plan |
| X-RateLimit-Remaining | Remaining requests in the current window |
| X-RateLimit-Reset | Unix timestamp when the rate limit resets |
/v1/analyze
Run one or two analyzers and return a flat list of structured signals.
Auth: Bearer token
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| text | string | Yes | The text to analyze (max 4,096 Unicode code points, counted after stripping surrounding whitespace; no Unicode normalization). Longer input is rejected with input_too_long - it is never truncated; split long documents into chunks yourself. |
| language | string | No | Language code supported by the requested analyzer(s) — ISO 639-1, or 639-3 where no 639-1 code exists (e.g. arz, pcm). Defaults to en. |
| analyzers | array | Yes | One or two explicit analyzer slugs (e.g. ["trust-safety", "cybersecurity"]). Required - there is no run-everything option. |
| detection_config | string | No | Configuration pin - an sqcfg_* identifier from verification.configuration, valid only for the analyzer set that issued it. Omitted = current configuration. See Configuration pinning below for lifecycle states and errors. |
Example Request
{
"text": "you are a worthless idiot",
"language": "en",
"analyzers": ["trust-safety"]
}
Example Response
{
"language": "en",
"language_source": "declared",
"signals": [
{
"analyzer": "trust-safety",
"code": "insults_derogation",
"label": "Insults & Derogation",
"score": 0.86,
"level": "category"
},
{
"analyzer": "trust-safety",
"code": "insult",
"label": "Insult",
"score": 0.99,
"level": "label",
"parent": "insults_derogation"
},
{
"analyzer": "trust-safety",
"code": "belittling",
"label": "Belittling",
"score": 0.99,
"level": "label",
"parent": "insults_derogation"
}
],
"meta": {
"analyzers": [
{
"name": "trust-safety",
"status": "completed",
"applicability": "analyzable",
"assessment": "signals_detected",
"signal_count": 3,
"billed": true
}
]
},
"usage": {
"analyzer_runs": 1,
"billed": true
},
"verification": {
"version": 3,
"configuration": "sqcfg_ZZW77NM9",
"fingerprint": "fp2_8a72c4...19f",
"input_commitment": "ic3_31bd5e...a42",
"signature": "sig_ed25519_4c1f...e2a",
"key_id": "edk_af4f218e",
"binding_key_id": "bk_31bd5e42f7a019c8"
}
}
/mcp
JSON-RPC 2.0 endpoint for AI-agent tool calls (MCP protocol 2025-06-18) - a separate orchestration surface from /v1/analyze with the same Bearer API key, the same plan analyzer/language entitlements, and the same account usage ledger. tools/list is the authoritative inventory: every tool declares its analyzer set and maximum billable runs under _meta (contract sentiqra/mcp-tool/v1). The billing unit is the analyzer run, not the tool invocation - each single-analyzer detect_* tool bills at most one run per call, while detect_all executes a fixed set of the eight MCP-exposed analyzers and can consume up to eight runs (the REST-only analyzers insurance, dating-safety, fraud-scam, and wellness are not exposed over MCP). A tool's declared maximum is reserved from account quota before execution and only completed analyzer runs are billed, so a call can never exceed its budget - and any analyzer outside your plan or key scope fails the whole call rather than being skipped. initialize, tools/list, ping, and notifications are authenticated but never billed.
Auth: Bearer token (Sentiqra API key - re-validated server-side; OAuth or third-party tokens are not accepted)
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| method | string | Yes | JSON-RPC method: initialize, tools/list, tools/call, or ping. |
| params | object | No | tools/call takes {"name": "<tool>", "arguments": {"text": "...", "language": "en"}}. Tool inputs are text + language only - there is no detection_config parameter; every call runs the current deployed configuration, reported in the result. |
Example Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "detect_trust_safety",
"arguments": {"text": "you are a worthless idiot", "language": "en"}
}
}
Example Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{"type": "text", "text": "Analysis results for Trust & Safety (language: en):\n\nSignals (1):\n 1. Insults & derogation - score: 90.0%\n\nRaw JSON:\n{...}"}],
"structuredContent": {
"analyzers": [{"analyzer": "trust-safety", "status": "completed", "billed": true}],
"analyzer_runs": 1,
"billed_runs": 1,
"max_billable_runs": 1
}
}
}
// detect_all returns the same envelope with one analyzers[] entry per
// MCP-exposed analyzer and max_billable_runs: 8. statuses: completed,
// failed, timeout, overloaded, denied - only billed:true runs settle.
/v1/meta/domains
List production analyzer identifiers, names, and availability - everything needed to construct a valid request. Send a Bearer API key to additionally receive supported languages, taxonomy counts, family, and the catalog version.
Auth: Optional - Bearer token unlocks detailed metadata
Example Response
{
"domains": [
{
"id": "trust-safety",
"name": "Trust & Safety",
"status": "production",
"v2_endpoint": "/v1/analyze/trust-safety",
"aliases": []
}
]
}
// With Bearer token, responses also include:
// "catalog_version": "dcat_R7K2N4"
// per domain: family, taxonomy counts, supported languages
/v1/meta/domains/{domain}
Get canonical metadata for one production analyzer. Anonymous responses contain identifier and availability only; send a Bearer API key for languages and taxonomy counts. Planned catalog products are not exposed as active domains.
Auth: Optional - Bearer token unlocks detailed metadata
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| domain | string | Yes | Canonical domain identifier such as trust-safety or customer-service |
Example Request
GET /v1/meta/domains/customer-service
/v1/verify
Verify a detection receipt signature. Submit the verification object from a detection response; returns whether the signature is authentic. Accepts current Ed25519 receipts (fingerprint + input_commitment + signature) and legacy HMAC receipts (fingerprint + signature).
Auth: None
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| fingerprint | string | Yes | Result fingerprint (fp_…) from the receipt |
| input_commitment | string | No | Caller-keyed input commitment (ic_…). Required on current receipts. |
| signature | string | Yes | sig_ed25519_… for current receipts, sig_… for legacy receipts |
Example Request
{
"fingerprint": "fp_8a72...19f",
"input_commitment": "ic_31bd...a42",
"signature": "sig_ed25519_4c1f...e2a"
}
Example Response
{
"valid": true,
"configuration": "sqcfg_ZZW77NM9",
"fingerprint": "fp_8a72...19f"
}
/v1/verify/public-key
Publish the Ed25519 public key that signs detection receipts, enabling fully offline receipt verification. key_id in each receipt selects the key after rotation.
Auth: None
Example Request
GET /v1/verify/public-key
Example Response
{
"algorithm": "ed25519",
"key_id": "edk_af4f218e",
"public_key": "b697a3c4aca5cbba3ac724f2abba75ca29781e490f7e6d8367320632de0f899b",
"signed_payload": "fingerprint + '|' + input_commitment"
}
/v1/account/usage
Current billing-period usage for the authenticated account: analyzer runs consumed, quota, and overage state.
Auth: Bearer token
Example Request
GET /v1/account/usage
Example Response
{
"plan": {"tier": "growth", "family": "analysis"},
"period": {"starts_at": "2026-11-01T00:00:00Z", "ends_at": "2026-12-01T00:00:00Z"},
"usage": {"used": 12034, "limit": 150000, "remaining": 137966, "overage_units": 0}
}
/v1/status
Report each analyzer's operational state and the opaque configuration alias (sqcfg_*) it currently runs - the same value receipts carry in verification.configuration and the value to copy into a detection_config pin.
Auth: None
Example Request
GET /v1/status
Example Response
{
"domains": {
"trust-safety": {
"state": "operational",
"configuration": "sqcfg_ZZW77NM9",
"indexes_loaded": 1,
"indexes_available": 21
}
}
}
// One entry per analyzer. Aliases rotate with each configuration change -
// an older alias stays valid for receipt verification but is not a valid pin.
/v1/meta/webhook-events
List the webhook event types your account can subscribe to.
Auth: None
Example Request
GET /v1/meta/webhook-events
/v1/openapi.json
Machine-readable OpenAPI contract for the public API - the source of truth SDKs and Postman collections are generated from.
Auth: None
Example Request
GET /v1/openapi.json
Error Codes
| Status | Code | Description |
|---|---|---|
| 200 | OK | Detection completed successfully |
| 400 | invalid_request · bad_request | Missing or invalid parameters, or malformed JSON. Related codes: unknown_parameter, unsupported_language, input_too_long (text over 4,096 Unicode code points), invalid_detection_config (malformed configuration pin). Request-contract codes: duplicate_field (repeated JSON property name), unknown_field, request_too_deep (nesting over 64), invalid_unicode (unpaired \uXXXX surrogate), invalid_utf8, ambiguous_request (query parameters on a body-only endpoint, or a body on a query-only endpoint) |
| 401 | missing_api_key · invalid_api_key | Missing or invalid API key |
| 403 | domain_not_in_plan · language_not_in_plan | API key does not have access to the requested analyzer or language. Reserved for restricted products, scoped API keys, and custom arrangements - all GA analyzers and their supported languages are included on every plan. Also: account_suspended, account_terminated, ip_not_allowed, version_pinning_not_available (detection_config without the pinning entitlement) |
| 409 | idempotency_conflict · idempotency_in_progress | Same Idempotency-Key replayed with a different body, or the original request is still running |
| 410 | configuration_retired | Pinned detection configuration reached the unavailable lifecycle state - the current configuration is never substituted. See Configuration pinning |
| 413 · 415 | payload_too_large · unsupported_media_type | Body over the 1 MiB contract limit, or a non-JSON Content-Type on a JSON endpoint — the request is rejected before any processing. |
| 422 | configuration_scope_mismatch · configuration_unavailable · insufficient_content | Pinned configuration belongs to a different analyzer set, or is unknown/unresolvable - the request fails before any run executes and the current configuration is never substituted. insufficient_content: the text contains no analyzable letters (emoji-only, numeric-only, punctuation-only) - nothing executed, nothing billed. On /v1/analyze this appears per-analyzer as not_applicable instead. |
| 429 | rate_limit_exceeded | Rate limit exceeded - check X-RateLimit-Reset. Also: monthly_quota_exceeded, overage_cap_reached, spend_cap_reached, key_spend_cap_reached |
| 500 | internal_error | Server error. Retry with exponential backoff. |
| 503 | usage_unavailable · service_unavailable | Usage metering or detection engine temporarily unavailable |
Request Contract — One Interpretation
Every JSON endpoint enforces a single canonical interpretation of the request before authentication, validation, analysis, idempotency, or billing see it. A request the proxy, the parser, and the analyzer could read differently is rejected outright — it can never authorize under one reading and execute or bill under another. Rules applied to every request body:
- •Content type. JSON endpoints accept
application/jsononly, UTF-8 encoded. Form submissions,text/plain, or a non-UTF-8 charset declaration return415 unsupported_media_type. - •Body shape. Exactly one JSON object per request — maximum 1 MiB and 64 levels of nesting, nothing before or after it. A repeated property name is a
400 duplicate_fielderror at any depth: parsers disagree about which occurrence wins, so the request is ambiguous by definition. - •Unicode. The body must be valid UTF-8, and
\uXXXXescapes must encode real code points — a lone surrogate half isinvalid_unicode, never silently replaced. - •Unknown fields. Fields outside the endpoint's schema are
400 unknown_parametererrors — including unsupported configuration selectors and any field attempting to set tenant, account, actor, or key identity. Identity comes only from the Bearer credential; a body field can never change who is authorized or billed. - •Query vs. body. Endpoints whose contract is body-only reject query parameters, and GET detection endpoints (query-only) reject request bodies — both as
ambiguous_request. Parameters never silently compete or get dropped.
A rejected request executes nothing, reserves nothing, and bills nothing — the check runs before entitlement and usage accounting. The same parsed request then drives authorization, configuration selection, analysis, the idempotency request hash, and usage settlement end to end.
Text Limits
text accepts at most 4,096 Unicode code points counted on the submitted UTF-8 string after stripping leading and trailing whitespace. A code point is one Unicode character - a combining accent counts separately (e.g. e + ◌́ = 2), and an emoji outside the Basic Multilingual Plane counts as 1 (not the 2 UTF-16 units a JavaScript .length reports). Input is validated as submitted - no Unicode normalization is applied and input is never truncated. Oversized input is rejected with 400 input_too_long.
Longer material is chunked in your environment, before submission: each chunk is analyzed as an independent snippet, and every successfully analyzed chunk consumes analyzer runs from your quota - a 3-chunk document analyzed by 2 analyzers consumes 6 runs. Chunk-level signals describe the submitted snippet only; a signature or receipt verifies what was sent in that request, not your source document, and any document-level conclusion is a customer-side aggregation. How you extract, normalize, split, and combine is outside Sentiqra's reproducibility guarantee unless you record it - the input-preparation contract (sentiqra/prepare/v1) does that: deterministic sentence-atomic chunking that preserves speaker turns and quoted material, explicit NFC normalization, marked overlap, per-chunk language assignment, and a customer-held manifest binding each request and returned receipt back to source offsets.
Language & Input Handling
language is a declaration, not a detection result. Sentiqra analyzes text under the language you declare; omitting it applies the documented English default and reports language_source: "default" (vs. "declared") at the top level. The API does not detect or split languages:
- •Mixed-language text is analyzed under the single declared language. Signal quality on the non-declared portions is unspecified - split mixed-language input upstream when it matters.
- •Incorrect declarations produce a valid result computed against the wrong language's coverage - the API cannot detect a wrong declaration. Declare accurately.
- •Language fallback. When the declared language is supported by the analyzer but is not a dedicated coverage language, analysis runs under English coverage and reports
applicability: "language_fallback"pluseffective_language. When the analyzer has no coverage at all, it reportsstatus: "not_applicable"+applicability: "unsupported_language"(unbilled; 400 when no requested analyzer covers the code). - •No analyzable content (emoji-only, numeric-only, punctuation-only, symbol-only): single-analyzer endpoints return
422 insufficient_content;/v1/analyzemarks each requested analyzernot_applicable. Nothing executes and nothing is billed. - •Fragments and short inputs execute normally - weak evidence resolves to
no_signalsorabstained, never a forced guess. - •Encoded content (base64, hex, quoted-printable) is analyzed literally as submitted. Decode upstream when the encoded payload is what you want analyzed.
Coverage is per-analyzer - the supported-language set differs across analyzers. Check the analyzer's language matrix before declaring a code. See the conditions table for how to route each outcome.
Score Semantics & Output Profiles
A score expresses signal-match strength under a particular analyzer configuration. It is not a probability that the signal or a downstream action is correct. Validate action thresholds on representative labeled data.
Scores are not comparable across labels or analyzers unless validated on your own labeled traffic. Two versioned output profiles intentionally expose different score precision - a profile's field set and semantics never change in place; a different shape ships as a new profile name and version. The response example above shows profile full.v1; see Understanding signals for the field-level reference.
| Output profile | Applies to | score | confidence |
|---|---|---|---|
| full.v1 | All non-free entitlement tiers | Continuous value in [0, 1] | Absent |
| reduced.v1 | Anonymous traffic (playgrounds, demos) and free-tier API keys | Band's representative value (at most 5 signals) | Present - band name |
Under reduced.v1, confidence names the band and score carries that band's representative value:
| confidence | Returned score | Band boundary (public) |
|---|---|---|
| very_high | 0.9 | ≥ 0.85 |
| high | 0.7 | 0.65 – < 0.85 |
| medium | 0.5 | 0.45 – < 0.65 |
| low | 0.3 | 0.00 – < 0.45 |
Interfaces
Sentiqra exposes the same analyzers through three interfaces with deliberately different capabilities. REST is the request/response interface; MCP is the agent interface; the playground is an anonymous demo. The matrix below is the published contract - it is generated from the same registry as the rest of this page.
| REST API | MCP server | Anonymous playground / demo | |
|---|---|---|---|
| Endpoints | POST /v1/analyze, POST /v1/analyze/{analyzer} | POST /mcp (JSON-RPC tools/call) | portal demo surface (curated samples via server-side demo key) |
| Analyzers | all GA analyzers | 8 MCP-exposed analyzers via detect_* tools; detect_all runs exactly those eight | the 6 demo analyzers (trust-safety, cybersecurity, crisis-intelligence, disinformation, emotions, customer-service) |
| Max analyzers / call | 2 | 8 | 1 |
| Configuration pinning | detection_config field (paid-tier entitlement) | not available - always the current deployed configuration | not available |
| Output profile | full.v1 for non-free tiers, reduced.v1 for free | per-key entitlement, same as REST (free keys get reduced shaping) | reduced.v1 (banded scores, max 5 signals) |
| Receipt | signed v3 receipt on every 200 response (incl. all-not_applicable) | classification fingerprint + configuration reported per result; no signed REST-style receipt | server-reminted v3 receipt over the reduced response |
| Authentication | Bearer sq_live_* API key | Bearer sq_live_* API key | none (server-side demo credential; anonymous quota) |
| Retries | Idempotency-Key replay (24h) + X-Retry-Failed child retries | no idempotency layer - retry a call as a new request; billing is per executed analyzer run | not applicable - re-run the demo |
Configuration Pinning (detection_config)
Send detection_config to pin the configuration a request runs against. The value is an opaque sqcfg_* identifier copied from verification.configuration in a previous response, or from GET /v1/status for the current configuration of each analyzer.
A configuration ID is scoped to an analyzer set: a single analyzer slug, or the canonical sorted set a multi-analyzer request produced (the composite scope in its receipt). A pin is valid only for the exact analyzer set in the request - submitting a single-analyzer ID for a two-analyzer request, or a different analyzer's ID, is a scope mismatch. Output profile (full/reduced) and language do not change the configuration.
When detection_config is omitted the request runs the current configuration for the requested analyzer set. Omission never selects a historical configuration and never carries a previous request's pin forward.
Configuration lifecycle
| State | Requests | Meaning |
|---|---|---|
| current | accepted | The configuration each analyzer runs today. Its alias appears in every receipt and per domain in GET /v1/status. Requests that pin it execute normally. |
| supported historical | accepted | A previously current configuration still within its published support window. Requests that pin it reproduce the original canonical result for identical input. No longer the default - requests without a pin run the current configuration. |
| deprecated | accepted | A supported historical configuration with an announced retirement date. Requests still execute and reproduce results; callers should migrate pins before the state becomes unavailable. |
| unavailable | rejected | A configuration outside its support window. Requests that pin it fail with a documented error; the current configuration is never substituted. |
The current configuration is immutable while it is current. Configurations that rotate out remain supported historical for the window published in the changelog entry that introduced their successor; on Enterprise agreements the support window is a contractual commitment for the life of the contract - subject to the security-suspension rule below, which can pause execution (never verification) of any configuration while it applies.
Replay an analysis using its original input and supported configuration ID. Configuration availability follows the published support policy or your contract.
Security suspension
A configuration pins LOGICAL behavior, not a permanently running build. When a dependency, runtime, or deployment component serving a configuration becomes unsafe, a confirmed security_vulnerability incident may suspend execution of the affected scope: pinned and unpinned requests that would execute it fail with configuration_suspended; nothing is substituted and no different detector answers in its place. Execution resumes only after the patched build passes the compatibility attestation path (reproducibility report proving the configuration's promised behavior is unchanged), or the configuration is retired through the documented lifecycle with notice. Where neither is immediately possible, a narrowly-scoped replay arrangement - an isolated deployment approved per engagement - may serve historical replay while the suspended build stays out of the general path. Receipt verification is independent of execution: a suspended or retired configuration's receipts still verify against the published key set - /v1/verify performs cryptographic verification, never re-execution, so availability of one never depends on the other.
Pinning errors
A rejected pin fails the request before any analyzer run executes and is never billed. The current configuration is never silently substituted.
| Status | Code | When |
|---|---|---|
| 400 | invalid_detection_config | The value is not an sqcfg_* identifier. |
| 403 | version_pinning_not_available | The plan does not include the version-pinning entitlement. |
| 410 | configuration_retired | The pinned configuration reached the unavailable lifecycle state. |
| 422 | configuration_scope_mismatch | The pin was issued for a different analyzer set than the request's. |
| 422 | configuration_unavailable | The ID is unknown or can no longer be resolved. |
| 503 | configuration_suspended | Execution of the configuration is suspended by a confirmed quality or security incident. Verification of its receipts is unaffected. Pin a supported historical configuration outside the incident scope, or retry after restoration. |
Replaying an analysis
- Run an analysis. The receipt names the configuration that produced it in
verification.configuration. - Replay the same input with that ID in
detection_config. - Compare the canonical result: identical
language, identicalsignals(order, codes, scores), and identicalverification.configuration.
curl -s https://sentiqra.tech/v1/analyze \
-H "Authorization: Bearer $SENTIQRA_API_KEY" -H "Content-Type: application/json" \
-d '{"text": "you are a worthless idiot", "language": "en", "analyzers": ["trust-safety"]}'
# → "verification": { "configuration": "sqcfg_ZZW77NM9", "fingerprint": "fp2_…", … }
curl -s https://sentiqra.tech/v1/analyze \
-H "Authorization: Bearer $SENTIQRA_API_KEY" -H "Content-Type: application/json" \
-d '{"text": "you are a worthless idiot", "language": "en", "analyzers": ["trust-safety"], "detection_config": "sqcfg_ZZW77NM9"}'
# → same signals and the same verification.configuration.
Replay is guaranteed while the configuration remains current or supported historical under the published support policy or your contract — not indefinitely, and subject to the security-suspension rule: an unsafe execution environment pauses execution (never receipt verification) until a patched build passes compatibility attestation.
Rate Limits
Rate limits vary by plan. Exceeding the limit returns 429 rate_limit_exceeded - check X-RateLimit-Reset for the retry time.
Monthly quota works differently per plan. Free is a hard cap: quota exhaustion returns 429 monthly_quota_exceeded until the next billing cycle or an upgrade. Paid plans continue past quota into automatic overage billing at the plan's per-1K rate; you get 429 overage_cap_reached or 429 spend_cap_reached only at the plan's overage ceiling or your configured cap.
| Plan | RPS | Monthly Analyzer Runs | Price |
|---|---|---|---|
| Free | 2 | 5,000 | Free |
| Build | 10 | 50,000 | $149/mo |
| Growth | 25 | 150,000 | $349/mo |
| Production | 50 | 500,000 | $799/mo |
| Scale | 200 | 2,500,000 | $2,999/mo |
| Enterprise | By agreement | By agreement | By agreement |
Plan prices and quotas are also shown on the pricing page - both read the same source.
Idempotency & partial results
Send an Idempotency-Key header on POST /v1/analyze to make retries safe. Re-submitting the same key with the same canonical request replays the recorded response — marked by X-Sentiqra-Idempotent-Replay: true — with no additional charge. The request identity covers the request fields (including detection_config); the same key with a different body returns 409 idempotency_conflict. Idempotency records hold the key, a hash of the request fields, per-analyzer outcomes, and the response for 24 hours — your submitted text is never persisted. A fresh key is a new logical operation and bills normally.
When one of two requested analyzers fails, the response still returns the completed analyzer's signals. Check each entry in meta.analyzers[] — status is completed, failed, overloaded, or not_applicable, and billed shows whether the run counted against your quota. A failed analyzer is never reported as a successful empty result — meta.partial marks the response incomplete.
Three separate fields describe what happened per analyzer: status (did it execute), applicability (analyzable, language_fallback, insufficient_content, or unsupported_language), and assessment for completed runs (signals_detected, no_signals, abstained). An empty signals array alone is never a verdict — no_signals means nothing crossed threshold (not proof of safety), abstained means a validated rule declined to report, and not_applicable means nothing ran at all. See the Production Guide conditions table for routing.
A plain replay returns the recorded outcome verbatim — it never silently retries failed analyzers. To retry them, resubmit the same key and body with X-Retry-Failed: true: this explicit child operation re-executes only the failed analyzers, merges their results into the recorded response, and bills only newly-completed runs (usage.billed_runs shows this request's charge). A second child with nothing left to retry returns 409 no_failed_analyzers. See the Production Guide for the full failure-mode matrix.
Versioning & Changelog
The current API version is v1. We use URL-based versioning (/v1/analyze). Breaking changes require a new version number and are announced at least 90 days before the old version is retired; non-breaking changes (new endpoints, new optional parameters, new response fields) are added to the current version.
Quickstart Guide
First API call in under 5 minutes
Playground
Test detection live - no API key
Indicator Dictionary
Browse all 210 detection labels
T&S Docs
Product-specific documentation
Determinism Spec
Signal fingerprint guarantee & RFC 8785
Benchmark Results
Per-language Top-3 Hit Rate
Pricing
From Free to Enterprise
FAQ
Common questions answered
Support
Get help from our team