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
});

Run offline (tiers 1–2)

Tiers 1 and 2 are fully on-device — no API key, no network call. Pass offline: true and use validateSync to score a response locally. Only the offline checks for the tier run; the AI-powered checks (tiers 3+) are skipped.

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

const cipher = new Cipher({ tier: 2, offline: true }); // no apiKey needed

const result = cipher.validateSync({
  responses: [
    { question: 'Would you recommend us?', answer: 'Yes' },
  ],
  behavioralMetrics: tracker.getMetrics(), // optional
  deviceInfo: tracker.getDeviceInfo(),     // optional
});

result.score;          // 0–1
result.recommendation; // 'keep' | 'review' | 'discard'
result.flags;          // names of checks that fired

You can also call the async validate() / validateBatch() on an offline instance — they resolve locally. getTierInfo() lists the checks a tier runs, and estimateCost() returns the per-response price (0 for tiers 1–2).

cipher.getTierInfo().checks; // CheckId[] for the configured tier
cipher.estimateCost();       // 0 for tiers 1–2

Individual checks are importable too, for custom pipelines:

import { checkWebDriverDetected, checkStraightLining } from '@surbee/cipher/checks';

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" }