Developer API

Run the Trust Score from your own tools

One REST endpoint, one uniform JSON response, five scoring pipelines underneath. Mint a bearer token inside your dashboard, send a request, get a verdict with an explanation, a confidence rating, and three recommended next steps.

Mint a key
Public REST
One endpoint
/api/v1/trust-score — POST any of the five kind payloads.
Bearer auth
Authorization: Bearer <key>. Keys are mint-then-revoke, with the plaintext shown once at mint.
Owner-scoped
Every admin endpoint is owner-scoped on the session user; the public endpoint is scoped on the bearer. There is no shared admin key.

Authentication

Pass your plaintext key in the Authorization header with a Bearer prefix. The plaintext is shown to you exactly once, when you mint a key — store it then. A missing or empty header returns 401 { "error": "Unauthorized" }; an unknown or revoked plaintext returns 401 { "error": "invalid api key" }.

BASH
Request a score from your terminal
curl -X POST /api/v1/trust-score \
  -H "Authorization: Bearer <your-plaintext-key>" \
  -H "Content-Type: application/json" \
  -d '{"kind":"url","url":"https://example.com/login"}'

Scopes

Today the Developer API exposes a single scope: read:trust. Every key minted through the dashboard is granted it. A key without read:trust is rejected by the gateway with 403 { "error": "Forbidden — api key missing read:trust scope" }, regardless of whether its plaintext authenticated. New scopes will be listed here as they ship.

Request — pick a kind

The request is a discriminated union keyed on kind. Each variant carries only the scalar payload its pipeline needs.

FieldTypeRequired
kind"url" | "email" | "sms" | "qr" | "screenshot"yes
<variant field>url · content · text · dataUrl · extractedyes
Check a URL
POST /api/v1/trust-score with the body below.
{
  "kind": "url",
  "url": "https://example.com/login"
}
Check an email
POST /api/v1/trust-score with the body below.
{
  "kind": "email",
  "content": "From: noreply@example.com\nSubject: Verify your account\n\nReply with the 6-digit code we sent."
}
Check an SMS
POST /api/v1/trust-score with the body below.
{
  "kind": "sms",
  "text": "USPS: Your package has unpaid toll. Reply with your code within 24 hours."
}
Decode a QR
POST /api/v1/trust-score with the body below.
{
  "kind": "qr",
  "dataUrl": "data:image/png;base64,<…paste your base64 PNG here…>"
}
Check a screenshot (already OCR-ed)
POST /api/v1/trust-score with the body below.
{
  "kind": "screenshot",
  "extracted": {
    "text": "Apple ID locked. Verify now: https://bit.ly/abc",
    "urls": [
      "https://bit.ly/abc"
    ],
    "textLength": 48,
    "urlCount": 1,
    "preview": "Apple ID locked. Verify now: …"
  }
}

Response — symmetric across all kind values

Every kind returns the same shape — score, verdict, riskLevel, aiExplanation, confidence, recommendedActions, meta, limitReached, upgradeHref. The envelope is deterministic: the explanation names the worst-fired signal, the confidence rating is calibrated against the fired-signal count, and the recommended actions list always has exactly three entries. The two limitReached / upgradeHref keys are optional and only stamped when an anonymous bearer crosses the Free-tier monthly cap.

FieldDescription
scoreInteger 0–100. 75+ reads as low risk; 40–74 caution; below 40 high risk.
verdictShort human-readable verdict — "no major warning signs detected" or "potential warning signs detected".
riskLevelBand label used by the result-page chip ("Low Risk", "Medium Risk", "High Risk").
aiExplanationOne-paragraph narrative naming the worst-fired signal so you can quote or surface the reason.
confidenceInteger 0–100 — distance from 100 plus a penalty per warn/bad signal that fired.
recommendedActionsThree short next-step strings keyed off the band. Always three entries.
metaBlock with `kind`, `target` (the human-readable identity), `signals`, and `redFlags`.
limitReachedOptional — present (true) only when an anonymous bearer crossed the shared Free-tier monthly cap. Signed-in Premium / Family keys skip the meter and never see this field.
upgradeHrefOptional — companion to `limitReached`; `/pricing` so clients that read it can render an upgrade prompt without hardcoding a URL.
JSON
Example response

Confidence climbs as the score nears 100 and falls per warn/bad signal; actions length is 3 across all bands.

{
  "score": 56,
  "verdict": "potential warning signs detected",
  "riskLevel": "Medium Risk",
  "aiExplanation": "Potential warning signs detected — the analyzer flagged urgency or threat language alongside one or two neutral signals. Verify before acting.",
  "confidence": 38,
  "recommendedActions": [
    "Verify the sender through a separate channel before clicking any link surfaced by this request.",
    "Reply via the original thread or surface only — never use contact info pulled out of the message itself.",
    "Forward the original message to the brand through their published support address if you are unsure."
  ],
  "meta": {
    "kind": "sms",
    "target": "+1 (555) 010-1234",
    "signals": [
      { "id": "urgency-language", "label": "Urgency or threat language", "tone": "bad", "weight": 14 }
    ],
    "redFlags": ["urgency-language"]
  }
}

Errors

Operational notes