API docs

Cuvark accepts workflow context, extracts evidence, runs cheap checks first, and returns a schema-valid scam risk verdict.

Simple scoring API
POST /v1/score
Content-Type: application/json

{
  "workflow": "booking",
  "source": "custom_integration",
  "actor": { "name": "John", "email": "support-dhl@gmail.com" },
  "context": { "expected_payment": false, "known_customer": false },
  "message": "Please verify your account here before our call.",
  "links": ["https://example.com"],
  "attachments": [],
  "options": { "decision_mode": "enforce" }
}
Advanced case flow
POST /v1/cases
POST /v1/cases/{case_id}/evidence
POST /v1/cases/{case_id}/score
GET  /v1/cases/{case_id}
POST /v1/cases/{case_id}/feedback
DELETE /v1/cases/{case_id}
Queued scoring
POST /v1/score/jobs
{
  "lane": "browser",
  "input": { "...": "same payload as /v1/score" }
}

GET /v1/score/jobs/{job_id}

With Redis, lane: "score" is processed by the score worker and lane: "browser" is processed by the Playwright browser-worker service. Transient score and browser failures are retried with bounded backoff before moving to the failed queue. The job status response includes attempts, max_attempts, next_retry_at, and result_object_key pointing to the canonical score artifact when object storage is configured. Without Redis, the job API falls back to inline local scoring.

Machine auth and webhooks
Authorization: Bearer ck_dev_...

X-Cuvark-Signature: t=1781700000,v1=<hmac_sha256(timestamp + "." + payload)>

Webhook event:
{
  "event": "case.scored",
  "case_id": "case_...",
  "verdict": {
    "trust_score": 18,
    "verdict": "block",
    "reason_codes": ["credential_harvesting"]
  }
}

API keys carry explicit scopes: score:write, cases:read, cases:write, cases:delete, and feedback:write. Keys can be rotated from Settings; the previous token is revoked and the replacement token is shown once. When REDIS_URL is configured, API rate limits use Redis-backed fixed windows shared across web instances. Local runs fall back to process memory, and CUVARK_RATE_LIMIT_REDIS_REQUIRED=true makes production fail closed if Redis is unavailable.

Output contract
{
  "case_id": "case_...",
  "trust_score": 18,
  "risk_level": "high",
  "verdict": "block",
  "confidence": "high",
  "reason_codes": ["brand_impersonation", "credential_harvesting"],
  "recommended_action": "hold_for_review",
  "decision_mode": "enforce",
  "effective_action": "hold_for_review",
  "would_have_recommended_action": null,
  "evidence_summary": "...",
  "evidence": {},
  "processing": {
    "latency_ms": 740,
    "cost_tier": "medium",
    "estimated_cost_units": 5,
    "browser_signal_collector_used": true,
    "browser_ai_agent_used": true,
    "browser_ai_agent_model": "gpt-5-mini",
    "estimated_billable_units": {
      "hosted_model_calls": 1,
      "hosted_vision_calls": 0,
      "browser_inspections": 1,
      "browser_agent_calls": 1
    }
  }
}
Shadow mode
{
  "options": {
    "decision_mode": "shadow"
  }
}

Shadow mode still computes and stores the verdict, reason codes, and normal recommended_action. It sets effective_action to allow and moves the enforcement recommendation to would_have_recommended_action, so platforms can measure Cuvark without blocking production workflows.

Chat thread evidence
{
  "workflow": "chat_thread",
  "conversation": {
    "type": "conversation",
    "participants": [{ "participant_id": "p1", "role": "external" }],
    "messages": [
      {
        "id": "m1",
        "sender_id": "p1",
        "direction": "inbound",
        "text": "Can we continue on WhatsApp?",
        "links": [],
        "attachments": []
      }
    ]
  }
}

Whole chats create parent conversation evidence, child chat_message evidence, and evidence.chat_thread_evidence with thread_summary, key_messages, conversation_risk_patterns, compound_signal_codes, and child evidence IDs. The extractor looks for multi-turn off-platform migration, payment pressure, QR verification, credential requests, identity-document requests, urgency escalation, and prompt-injection text.

Context fields with highest impact

FieldImpactWhy it matters
workflowHighBooking, form, chat, and URL-only scans have different risk expectations.
actor.emailHighEnables sender-domain and brand-claim mismatch detection.
messageHighExposes urgency, payment, credential, off-platform, and prompt-injection signals.
conversation.messagesHighPreserves chronology so Cuvark can detect off-platform migration, escalation, QR/payment turns, and key risky messages.
context.expected_paymentHighSeparates normal invoice flows from unexpected payment pressure.
attachmentsHigh if presentImages, QR codes, documents, archives, and executable/script files often carry the risky payload instead of plain text.
known_customerMediumStrong positive workflow context, but not an override for concrete malicious evidence.

Reason code glossary

known_malicious_urlthreat_feed_hitnew_domainnewly_observed_urlsuspicious_tldurl_obfuscationrisky_hosting_asnredirect_chain_suspicioushomograph_domaintypo_squattingbrand_mismatchbrand_impersonationclaimed_company_mismatchemail_domain_mismatchdisposable_emailcredential_harvestinglogin_form_detectedpassword_field_detectedpayment_requestunexpected_payment_requestcrypto_payment_requestgift_card_requestbank_transfer_requestqr_code_url_detectedqr_phishing_riskfake_invoice_patternfake_receipt_patternexecutable_attachmentmacro_enabled_documentarchive_attachment_riskmalicious_file_riskurgency_languagethreat_languageaccount_suspension_languageoff_platform_migrationverification_code_requestidentity_document_requestprompt_injection_attemptbrowser_cloaking_suspectedpage_unreachableinsufficient_contextknown_safe_domainknown_customerallowlisted_domain

Security model

All user-submitted text, webpage text, OCR text, filenames, chat messages, and browser-observed text are treated as hostile evidence. The browser collector never enters credentials, never submits forms, blocks private/local/metadata targets, rejects public hostnames that resolve to non-public addresses, validates fallback redirects before following them, and routes Playwright subrequests through the same guard with service workers disabled. It produces structured evidence instead of a final decision. File attachments are never executed by the browser collector; Cuvark records safe static metadata signals for executables, scripts, macro-enabled documents, archives, PDFs, and scriptable images.

Hosted model APIs

When OPENAI_API_KEY is configured and CUVARK_MODEL_PROVIDER is openai, Cuvark uses the OpenAI Responses API for a structured final judge pass. The deterministic scorer still runs first and hard malicious signals keep conservative handling. Image attachments with an HTTPS image URL, data URL, or base64 metadata can also use OpenAI vision extraction before scoring. Hosted calls have bounded server-side timeouts and fall back to deterministic scoring when the provider is slow or unavailable.

Default policy

Settings can maintain local threshold defaults plus allowlist and blocklist domains. Request policies are merged with the local default policy for scoring, so customer-specific context can add stricter controls without changing the public payload shape.

Cache behavior

Cuvark stores URL cache entries using a normalized URL path key. Fresh exact URL-path cache hits set processing.cache_status to hit or partial, contribute cached safe/bad signals, and avoid redundant deterministic/browser-agent work for repeated URL-only cases. Cache evidence is not reused across different paths on the same domain, and every request is still stored as a case so auditability and feedback history remain complete.

Threat intelligence

Local allowlists, blocklists, and cached verdicts always run first. When CUVARK_WEB_RISK_API_KEY is configured, Cuvark also checks public HTTP(S) URLs with Google Web Risk Lookup before browser signal collection or Browser Use escalation. Matches become threat_feed_hit and known_malicious_url signals with provider, threat type, and cache-expiry evidence. Provider timeouts or errors are recorded as evidence only, and scoring continues. Public URLs also get bounded DNS/TLS evidence when CUVARK_URL_NETWORK_ENABLED is not false, including A/AAAA record counts, private-address resolution, TLS issuer, and certificate validity windows. Browser guard DNS lookups can be tuned separately with CUVARK_BROWSER_DNS_TIMEOUT_MS.

Score artifacts

When S3-compatible bucket variables are configured, persisted scores also write a redacted JSON artifact at tenants/{tenant_id}/cases/{case_id}/score-result.json. The verdict records the object key in evidence.artifact_refs.score_result_json. If the bucket write fails, scoring continues and the verdict records evidence.artifact_error. Deleting a case also deletes recorded artifact refs on a best-effort basis.

Outcome metrics

The Evals dashboard combines synthetic cases with tenant-scoped outcome metrics from feedback labels. Cuvark uses the latest decisive label per case, maps confirmed scam labels and false-negative outcomes to scam truth, maps confirmed legit labels and false-positive outcomes to legit truth, then reports precision, recall, false-positive rate, false-negative rate, human disagreement, labeled-case latency, and cost units. insufficient_evidence labels are kept for review history but ignored for precision and recall.

Privacy and retention

Stored-data redaction is enabled by default. Before Cuvark persists case input, evidence, and model output, it redacts email addresses, phone numbers, long numeric tokens, and URL query parameters. Direct POST /v1/score requests still score the original payload in memory before storing the redacted copy.

For the advanced case flow, POST /v1/cases and POST /v1/cases/{case_id}/evidence also store redacted records. Use direct scoring when full raw context is needed for the highest-fidelity one-shot verdict, or tune redaction from Settings for controlled tenants.

Use DELETE /v1/cases/{case_id} to remove a case and linked evidence, signals, model output, actions, feedback, and webhook deliveries for the authenticated organization. Settings also includes retention purging for cases older than the saved retention window. Labeled cases are preserved by default unless explicitly included.

Local auth

Development mode allows API requests without a key. Set CUVARK_REQUIRE_API_KEY=true and CUVARK_API_KEY to require bearer-token auth.

Production deployment

The current deployment target is the Hetzner origin under deploy/hetzner/, with Cloudflare intended in front for DNS, proxying, WAF, caching, and rate-limit shielding once the DNS record is configured. Railway config is still available under railway/ as an alternate managed target. Postgres is used when DATABASE_URL is present; Redis queues are used when REDIS_URL is present; object storage is used when S3-compatible bucket variables are present.

Clerk organizations

When PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY are configured, Cuvark enables Clerk sign-in and scopes API-created cases to the active organization.