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/cipherimport { 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 firedYou 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–2Individual 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.
failis the minimum score to keep a response.reviewis 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, ordiscard, 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, orfraud. - topSignals. The features that contributed most to the prediction.
Errors
| Code | Meaning |
|---|---|
INVALID_API_KEY | Missing, malformed, or inactive key |
INSUFFICIENT_CREDITS | The key has no credits left |
TIER_NOT_AVAILABLE | Requested a tier above the key's limit |
SERVER_ERROR | Something 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" }