We are currently building something exciting and will be launching soon. Registration is temporarily closed. Launching soon — registration temporarily closed.

Developer Docs API version: v1

From signup to a verified, spend-capped integration.

Sentiqra returns structured signals from deterministic, versioned analyzers. Your application decides what happens next.

Free plan · 5,000 analyzer runs/month · No credit card required · One path, start to finish - no SDKs needed

When you're done, you'll have

A verified account and a server-side API key
A working REST request in your language of choice
A pinned configuration and a verified receipt
A per-key spend limit enforced server-side

Every step below works over plain HTTP - no SDK required

1

Request access

Self-serve registration is temporarily closed. Request access through the contact form (production_evaluation topic) - accounts are provisioned manually. Once provisioned, the free plan includes 5,000 analyzer runs per month with no credit card required, and every step below applies unchanged.

Request access
2

Verify your email

We send a verification link to the address you registered with. Click it to unlock the dashboard - the link signs you in and lands you back on the dashboard. API key creation is only available to verified accounts.

Didn't get the email? The verification page has a Resend Verification Email button - no support ticket needed.

3

Create a server-side API key

From your dashboard, open:

API Keys → Create API key

Give it a name and pick an environment (production for real traffic). The full key is shown once in the confirmation dialog - copy it before closing. It looks like:

sq_live_...

Keep it server-side.

This is a secret credential - store it in your server's environment or secrets manager, never in client-side code or source control. One key works across every analyzer included in your plan.

For the examples below:

export SENTIQRA_API_KEY="sq_live_..."
4

Send your first analysis

This example uses Trust & Safety. The same authentication and request shape work for all analyzers - just pass the analyzers parameter. An authenticated call is a billable request: each analyzer you run counts against your monthly quota (the free plan covers it).

curl -X POST 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"
    ]
}'

The POST /v1/analyze endpoint accepts the same minimal input for every analyzer:

{
    "text": "you are a worthless idiot",
    "language": "en",
    "analyzers": [
        "trust-safety"
    ]
}

analyzers is required - pass 1–2 explicit analyzer slugs. There is no "run everything" option and no model, temperature, threshold or Top-K parameter to configure.

text accepts up to 4,096 Unicode code points (counted after stripping surrounding whitespace). Input is validated as submitted (no normalization, never truncated); longer input returns 400 input_too_long. Longer material is prepared customer-side: each chunk is analyzed independently, every analyzed chunk consumes analyzer runs, and per-chunk signals describe only the snippet submitted - not the whole document. To keep the transformation reproducible (boundaries, normalization, overlap, receipts), use the versioned contract in Input Preparation. See API Reference → Text Limits.

5

Inspect the response and statuses

A successful request returns a signals array with category-level and label-level detections, per-analyzer outcomes in meta.analyzers, billed usage, and a verification receipt for reproducibility.

Response — reduced.v1 profile: what the free plan actually returns (banded scores, confidence labels, up to 5 signals).

{
    "language": "en",
    "language_source": "declared",
    "signals": [
        {
            "analyzer": "trust-safety",
            "code": "insults_derogation",
            "label": "Insults & Derogation",
            "score": 0.9,
            "level": "category",
            "confidence": "very_high"
        },
        {
            "analyzer": "trust-safety",
            "code": "insult",
            "label": "Insult",
            "score": 0.9,
            "level": "label",
            "parent": "insults_derogation",
            "confidence": "very_high"
        },
        {
            "analyzer": "trust-safety",
            "code": "belittling",
            "label": "Belittling",
            "score": 0.9,
            "level": "label",
            "parent": "insults_derogation",
            "confidence": "very_high"
        }
    ],
    "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"
    },
    "output_profile": "reduced.v1"
}

Paid tiers receive the full.v1 profile - continuous scores instead of bands. The request, receipt fields, and outcome semantics are identical.

Verification receipt

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

Category-level signals describe the detected mechanism at a higher level. Label-level signals provide more specific evidence within that category.

Score values describe the analyzer result. Sentiqra does not determine your moderation or enforcement action.

code is stable

Use code in your policy logic, not label - labels are display text.

level gives depth

category is broad, label is specific - nested via parent.

verification is replayable

Replay an analysis using its original input and supported configuration ID - availability follows the published support policy or your contract.

Signals concept → · Verification spec →

Per-analyzer status: meta.analyzers[]

Each requested analyzer reports its own outcome - completed, failed, overloaded, or not_applicable - plus applicability, assessment, and whether it was billed. An analyzer that never ran (not_applicable: unsupported language or insufficient content) is not billed and returns no signals; an empty signals array is never a "safe" verdict.

"meta": { "analyzers": [
  { "name": "trust-safety",  "status": "completed",      "applicability": "analyzable",
    "assessment": "signals_detected", "signal_count": 1, "billed": true },
  { "name": "cybersecurity", "status": "not_applicable", "applicability": "unsupported_language",
    "signal_count": 0, "billed": false }
] },
"language_source": "declared",
"usage": { "analyzer_runs": 1, "billed": true }

Live status - no auth, not billable

GET https://sentiqra.tech/v1/status reports each analyzer's operational state and the opaque configuration alias it currently runs - the value receipts carry in verification.configuration. GET https://sentiqra.tech/api/v1/status reports overall platform availability.

curl -s https://sentiqra.tech/v1/status

# → { "status": "operational", "domains": {
#      "trust-safety": { "state": "operational",
#        "configuration": "sqcfg_ZZW77NM9" }, … } }

Same input + same analyzer + same configuration + same output profile = same canonical result. (Anonymous and free-tier responses use the reduced.v1 profile; a replay under a different profile fingerprints differently.)

The fingerprint covers the canonical result fields (requested analyzer set, per-analyzer outcomes, configuration, language, signals — exactly as emitted); the input commitment binds your submitted text with an HMAC keyed by a purpose-separated binding key derived from your credential (HKDF, spec v3 — so revoking the credential later does not lose verification ability); the signature is Ed25519 - verifiable offline with the public key from GET /v1/verify/public-key. Request IDs, latency and timestamps are excluded. You can also submit the receipt to /v1/verify to confirm authenticity at any time.

Learn about reproducibility
6

Pin the configuration (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
400invalid_detection_configThe value is not an sqcfg_* identifier.
403version_pinning_not_availableThe plan does not include the version-pinning entitlement.
410configuration_retiredThe pinned configuration reached the unavailable lifecycle state.
422configuration_scope_mismatchThe pin was issued for a different analyzer set than the request's.
422configuration_unavailableThe ID is unknown or can no longer be resolved.
503configuration_suspendedExecution 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

  1. Run an analysis. The receipt names the configuration that produced it in verification.configuration.
  2. Replay the same input with that ID in detection_config.
  3. Compare the canonical result: identical language, identical signals (order, codes, scores), and identical verification.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.

You can also look up the current alias for an analyzer set without spending a run: GET https://sentiqra.tech/v1/config/alias?scope=trust-safety.

7

Verify the receipt

Every response carries a signed receipt. Confirm it against the service - no API key needed for the signature check:

curl -s https://sentiqra.tech/v1/verify \
  -H "Content-Type: application/json" \
  -d '{
    "fingerprint": "fp2_8a72c4...19f",
    "input_commitment": "ic3_31bd5e...a42",
    "signature": "sig_ed25519_4c1f...e2a"
  }'

# → { "valid": true, "signature_valid": true,
#     "fingerprint_match": "not_checked",
#     "input_commitment": "not_checked", … }

signature_valid

The receipt was issued by Sentiqra. Also checkable fully offline with GET /v1/verify/public-key.

fingerprint_match

Submit payload (the canonical result fields) to prove the fingerprint covers exactly that result.

input_commitment

Submit input (text, language, and your API credential or bk_ binding key) to prove the receipt binds that exact input.

Prefer a point-and-click check? The standalone verifier checks receipts offline in your browser (test vectors: receipt-test-vectors.json), or use the verification tool under Dashboard → Verify. A dedicated receipt-verification package is planned separately from the SDKs - today, /v1/verify and the offline public key are the supported path. See the receipt spec for field details.

8

Set a spend limit

Cap what a key can spend per month - enforced server-side, not just an alert.

API Keys → your key → Production Controls → Spending budget → Edit

Enter a monthly USD amount (or set it at creation with the Spend cap field). Once the key's billed spend reaches the cap, further requests are rejected until the next billing period. Leave it empty to remove the cap.

A spend cap is not an alert.

Alerts & Notifications sends email/dashboard notifications at quota thresholds (80/90/100% by default). The per-key spending budget is a hard stop - it rejects requests. Use both: alerts for visibility, the cap for enforcement.

Put the result into a workflow

Signals are evidence - your code turns them into a decision. Route on the stable code and a threshold your policy owns:

# pseudocode - shape depends on your stack; SDKs are in preview
resp = analyze(text, language="en", analyzers=["trust-safety"])
run  = resp.meta.analyzers["trust-safety"]

# A signal only exists if the analyzer actually ran on this input.
if run.status != "completed" or run.assessment != "signals_detected":
    route(run.status)                     # your triage for empty/failed runs
elif any(s.code == "threat" and s.score >= 0.90 for s in resp.signals):
    review_queue.enqueue(message_id)
elif any(s.code == "insult" and s.score >= 0.80 for s in resp.signals):
    moderation_queue.enqueue(message_id)
else:
    publish(message_id)

Thresholds above are illustrative. Validate them against your own labeled data and policy before production. See Policy Decisions & Appeals for a runnable, versioned policy evaluator, and the Production Guide for retries, idempotency, and partial-result handling.

Same integration, every analyzer

The authentication method and request body stay the same - just change the analyzers parameter:

"analyzers": ["trust-safety"]        // harmful-language signals
"analyzers": ["cybersecurity"]       // phishing & threat signals
"analyzers": ["customer-service"]    // support-ticket signals
"analyzers": ["trust-safety", "cybersecurity"]  // two per request, max

One key, one endpoint - upgrading your plan doesn't change the integration. Full catalog: all 12 production analyzers →

Next: three things to read

Ready to build?

Start with 5,000 free analyzer runs per month. No credit card required.