Cipher

Cipher SDK and API

Validate survey responses from your own application.

The Cipher API lets you score responses you collect anywhere, not just inside Surbee. Send a response plus the behavioral signals you captured, and Cipher returns a quality score and a recommendation.

You will need an API key first. See API keys.

Install the SDK

pnpm add @surbee/cipher
import { Cipher } from '@surbee/cipher';

const cipher = new Cipher({
  apiKey: process.env.CIPHER_API_KEY!,      // cipher_sk_...
  tier: 2,                                  // 1–5, which set of checks to run
  thresholds: { fail: 0.5, review: 0.75 },  // optional, defaults to { fail: 0.4, review: 0.7 }
  // endpoint: 'https://api.surbee.com/v1/cipher', // optional, this is the default
});

How scoring works

Every tier is scored on Surbee's servers, so the SDK is a thin client: it collects the response and behavioral signals and sends them to Cipher. The detection logic, weights, and thresholds are never shipped to your client and can't be inspected or reverse-engineered — which matters, since the people you are screening for fraud are exactly the ones who would read a local bundle.

The difference between tiers is what runs, not where:

  • Tiers 1–2 run fast, rule-based checks (behavioral, timing, device, content) with no AI model, so they return in milliseconds and are free.
  • Tiers 3–5 add AI-powered checks (AI-text, VPN, fraud-ring), which take a little longer and cost per response.

Because everything is server-side, an API key is required for every tier. Treat any client-side signal collection as input only — never as a trusted score.

cipher.getTierInfo().checks; // CheckId[] the configured tier runs
cipher.getTierInfo().usesAI; // false for tiers 1–2, true for 3–5
cipher.estimateCost();       // per-response price (0 for tiers 1–2)

Validate a response

POST /api/cipher/validate runs the checks for the requested tier and returns a verdict. Authenticate with a bearer token.

Request

{
  "tier": 2,
  "thresholds": { "fail": 0.5, "review": 0.75 },
  "input": {
    "responses": [
      { "question": "Would you recommend us?", "answer": "Yes" },
      { "question": "What stood out?", "answer": "It saved me time", "responseTimeMs": 8200 }
    ],
    "behavioralMetrics": { },
    "deviceInfo": { },
    "context": { }
  }
}
  • tier. Which set of checks to run, from 1 to 5. Must be within your key's tier limit.
  • thresholds. fail is the minimum score to keep a response. review is the score above which a response is accepted without review.
  • input. The response data plus any behavioral, device, and context signals you captured.

Response

{
  "score": 0.91,
  "passed": true,
  "recommendation": "keep",
  "confidence": 0.62,
  "flags": [],
  "summary": {
    "verdict": "High-quality legitimate response",
    "issues": [],
    "positives": ["Response timing appears natural", "No automation tools detected"],
    "suggestion": "Response can be accepted as-is"
  },
  "checks": [
    { "checkId": "rapid_completion", "passed": true, "score": 0, "details": null }
  ],
  "meta": {
    "tier": 2,
    "processingTimeMs": 41,
    "checksRun": 15,
    "checksPassed": 15,
    "requestId": "req_ab12cd34",
    "timestamp": 1730000000000
  }
}

Key fields:

  • score. Quality from 0 to 1, where higher is better. This is the inverse of risk.
  • recommendation. One of keep, review, or discard, derived from your thresholds.
  • flags. Human readable names of any checks that failed.
  • summary. A plain language verdict with the issues, positive signals, and a suggested action.
  • checks. The per check breakdown.

Example

// tier and thresholds come from the new Cipher({ ... }) config above —
// validate() takes just the response data.
const result = await cipher.validate({
  responses: [
    { question: 'Would you recommend us?', answer: 'Yes' },
    { question: 'What stood out?', answer: 'It saved me time', responseTimeMs: 8200 },
  ],
  behavioralMetrics, // optional, from the client-side tracker
  deviceInfo,        // optional
  context,           // optional
});

if (result.recommendation === 'discard') {
  // reject or quarantine the response
} else if (result.recommendation === 'review') {
  // queue for a human to look at
}

Predict with the ML model

POST /api/cipher/predict returns the machine learning model's fraud probability for a stored response. Useful when you have already extracted features and want the model's view directly.

Request

{ "responseId": "resp_123", "modelVersion": "latest" }

Response

{
  "fraudProbability": 0.08,
  "fraudVerdict": "low_risk",
  "confidence": 0.74,
  "topSignals": [
    { "feature": "completionTimeSeconds", "contribution": 0.03, "value": 142 }
  ],
  "modelVersion": "2025.11",
  "inferenceTimeMs": 12
}
  • fraudProbability. 0 to 1, where higher means more likely fraudulent.
  • fraudVerdict. low_risk, medium_risk, high_risk, or fraud.
  • topSignals. The features that contributed most to the prediction.

Errors

CodeMeaning
INVALID_API_KEYMissing, malformed, or inactive key
INSUFFICIENT_CREDITSThe key has no credits left
TIER_NOT_AVAILABLERequested a tier above the key's limit
SERVER_ERRORSomething went wrong on our side

Health check

GET /api/cipher/health returns the service status, for uptime monitoring.

{ "status": "operational", "service": "cipher", "version": "1.0.0" }