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