EchoScan API and Integration Guide
EchoScan helps evaluate browser-environment risk around sensitive visits and business actions such as signup, login, checkout, payment, withdrawal, coupon redemption, voting, form submission, and password reset.
The core flow is short: browser-executed code generates an imprint, sends it to server-executed code with the protected request, and the server uses an EchoScan secret key to query the report and make the final risk decision.
Recommended Flow
- Choose a visit or business action you want to protect.
- Run Browser Verifier in browser-executed code to generate an
imprint. - Send the
imprintto your server with the current business request. It can live in the body, a request header, or a separate risk endpoint that you own. - Read the EchoScan secret key only in server-executed code and query a Lite or Pro report.
- Return a minimal server decision, such as
allow,challenge,review, ordeny.
If you use Next.js, Nuxt, Remix, SvelteKit, Laravel, Rails, Django, Vercel Functions, or Cloudflare Workers, browser code and server routes may live in the same repository. The important boundary is runtime location, not folder layout: the secret key belongs only in the server runtime.
Generate Imprint In The Browser
Browser Verifier only collects browser-environment signals and returns an imprint. It does not return the final report and it never holds an EchoScan secret key.
npm install @echoscan/browser-verifier
import { createEchoScan } from '@echoscan/browser-verifier'
const { imprint } = await createEchoScan().run()
CDN loading is useful for no-build pages and quick demos. Production applications should prefer package-manager installation and the normal build pipeline.
<script type="module">
import { createEchoScan } from 'https://cdn.echoscan.org/v1/echoscan.esm.js'
const { imprint } = await createEchoScan().run()
</script>
<script src="https://cdn.echoscan.org/v1/echoscan.umd.js"></script>
<script>
window.EchoScan.createEchoScan().run().then(({ imprint }) => {
console.log(imprint)
})
</script>
Send Imprint To The Server
The imprint only needs to reach server-executed code with the protected action. Choose the transport shape that fits the current API.
Body transport fits JSON business requests.
await fetch('/api/your-action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...yourActionPayload,
echoscanImprint: imprint
})
})
Header transport fits projects that do not want to change the business body.
await fetch('/api/your-action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-EchoScan-Imprint': imprint
},
body: JSON.stringify(yourActionPayload)
})
You can also keep the risk flow separate by creating your own server endpoint.
await fetch('/api/echoscan/report', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ imprint })
})
/api/echoscan/report is only an example path that must be implemented by your server. It is not an EchoScan official endpoint.
Lite Integration
Lite is the fastest way to run public and lightweight risk checks. A Lite key is still a secret key, so keep it in server environment variables, a secret manager, or an equivalent server-side secret store.
Node.js
npm install @echoscan/echoscan
import { createLiteClient } from '@echoscan/echoscan'
const echoscan = createLiteClient({
apiKey: process.env.ECHOSCAN_LITE_KEY
})
export async function getEchoScanReport(imprint) {
return echoscan.getReport(imprint)
}
Go
go get github.com/echoscan/echoscan-go@latest
package main
import (
"context"
"log"
"os"
echoscan "github.com/echoscan/echoscan-go"
)
func main() {
imprint := "fp_session_xxx"
echoscanClient, err := echoscan.NewLiteClient(os.Getenv("ECHOSCAN_LITE_KEY"))
if err != nil {
log.Fatal(err)
}
report, err := echoscanClient.GetReport(context.Background(), imprint)
if err != nil {
log.Fatal(err)
}
log.Printf("lyingCount=%v", report["lyingCount"])
}
Python
pip install echoscan
import os
from echoscan import EchoScanLiteClient
imprint = "fp_session_xxx"
echoscan_client = EchoScanLiteClient(os.environ["ECHOSCAN_LITE_KEY"])
report = echoscan_client.get_report(imprint)
print(report.get("lyingCount"))
Rust
[dependencies]
echoscan = "0.2.1"
use std::env;
use echoscan::LiteClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let imprint = "fp_session_xxx";
let api_key = env::var("ECHOSCAN_LITE_KEY")?;
let echoscan = LiteClient::new(&api_key)?;
let report = echoscan.get_report(imprint).await?;
println!("{}", report["lyingCount"]);
Ok(())
}
Lite Report
Lite report is the minimal public result returned after the server queries an imprint. It keeps the summary fields needed for risk decisions while hiding raw collection values, internal evidence, and detection implementation details.
You can read Lite report as a summary plus details: lyingCount is the strong-risk summary, and projection is the module-level result map. The server uses these results to produce its own allow, challenge, review, or deny decision.
Normal example:
{
"analysis": {
"browser": {
"brand": "Google Chrome",
"browser_version": "126.0.x",
"rendering_engine": "Chromium",
"browser_version_consistency_status": "PASS"
},
"os": {
"os": "Windows",
"version": "11",
"os_consistency_status": "PASS"
},
"hash": {
"accessCount": 1,
"stableHash": "stb_8f4d2c1a",
"uniqueHash": "unq_3a9b7e42"
}
},
"lyingCount": 0,
"projection": {
"navigator": {
"status": "PASS"
},
"screen": {
"status": "PASS"
},
"webgl": {
"status": "PASS"
}
}
}
Suspicious example:
{
"analysis": {
"browser": {
"brand": "Google Chrome",
"browser_version": "126.0.x",
"rendering_engine": "Chromium",
"browser_version_consistency_status": "FAIL"
},
"os": {
"os": "Windows",
"version": "11",
"os_consistency_status": "PASS"
},
"hash": {
"accessCount": 1,
"stableHash": "stb_8f4d2c1a",
"uniqueHash": "unq_9c0f2d31"
}
},
"lyingCount": 1,
"projection": {
"navigator": {
"status": "DECEPTIVE"
},
"screen": {
"status": "PASS"
},
"webgl": {
"status": "SUSPICIOUS"
},
"webrtc": {
"status": "SUSPICIOUS"
}
}
}
Complete Lite surface example:
{
"analysis": {
"browser": {
"brand": "Google Chrome",
"browser_version": "126.0.x",
"rendering_engine": "Chromium",
"browser_version_consistency_status": "PASS"
},
"os": {
"os": "Windows",
"version": "11",
"os_consistency_status": "PASS"
},
"hash": {
"accessCount": 7,
"stableHash": "stb_8f4d2c1a",
"uniqueHash": "unq_3a9b7e42"
}
},
"lyingCount": 0,
"projection": {
"audio": {
"status": "PASS"
},
"canvas": {
"status": "PASS"
},
"font": {
"status": "PASS"
},
"ip-geo-timezone": {
"status": "PASS"
},
"navigator": {
"status": "PASS"
},
"screen": {
"status": "PASS",
"resolution": "1920x1080"
},
"speech": {
"status": "PASS",
"default_voice_name": "Microsoft Jenny Online"
},
"webgl": {
"status": "PASS",
"unmasked_vendor": "Google Inc. (NVIDIA)",
"unmasked_renderer": "ANGLE (NVIDIA GeForce RTX 4060)"
},
"webrtc": {
"status": "PASS"
}
}
}
Field reference
| Field | Example | Meaning |
|---|---|---|
analysis |
{ ... } |
Normalized access-environment summary for server logs, admin review, and risk investigation |
analysis.browser |
{ ... } |
Browser summary, including brand, version, rendering engine, and version consistency |
analysis.browser.brand |
Google Chrome |
Detected browser brand, such as Chrome, Firefox, or Safari |
analysis.browser.browser_version |
126.0.x |
Detected browser version |
analysis.browser.rendering_engine |
Chromium |
Browser rendering engine, such as Chromium or WebKit |
analysis.browser.browser_version_consistency_status |
PASS |
Browser-version consistency result; FAIL means version spoofing or strong inconsistency was found |
analysis.os |
{ ... } |
Operating-system summary, including OS name, version, and consistency |
analysis.os.os |
Windows |
Detected operating system |
analysis.os.version |
11 |
Detected OS version |
analysis.os.os_consistency_status |
PASS |
Operating-system consistency result |
analysis.hash |
{ ... } |
Access-environment identifiers and repeat-visit summary |
analysis.hash.accessCount |
7 |
Observed count associated with the stable access environment; useful for continuity, not an account request count |
analysis.hash.stableHash |
stb_8f4d2c1a |
Stable environment identifier for continuity, repeat visits, and risk correlation |
analysis.hash.uniqueHash |
unq_3a9b7e42 |
More granular identifier for the current access environment |
lyingCount |
0 |
Strong-risk summary that can be used for quick risk segmentation |
projection |
{ ... } |
Module-level result map, such as navigator, screen, webgl, and webrtc |
projection.<module>.status |
SUSPICIOUS |
Public status for that module; servers usually use it as part of the final decision |
projection.screen.resolution |
1920x1080 |
Screen-resolution summary |
projection.speech.default_voice_name |
Microsoft Aria |
Browser default-voice name summary |
projection.webgl.unmasked_vendor |
Google Inc. |
WebGL graphics vendor summary |
projection.webgl.unmasked_renderer |
ANGLE |
WebGL renderer summary |
Status reference
| status | Meaning | Common handling |
|---|---|---|
PASS |
No obvious abnormality was found in the module | Usually allow |
SUSPICIOUS |
Suspicious or low-trust signals were found | Challenge, observe, or raise risk score |
DECEPTIVE |
Strong spoofing or inconsistency signals were found | Challenge, review, or deny |
Policy code can keep compatibility with legacy WARN and FAIL.
Server Decision
After the server receives a report, the common pattern is to turn it into a minimal decision result, such as allow, challenge, review, or deny, and return that result to the browser.
The server implements these decisions according to business risk, false-positive cost, and user experience. Common handling looks like this:
| decision | Common handling |
|---|---|
allow |
Allow the request and continue the signup, login, checkout, or other business flow |
challenge |
Require extra verification, such as CAPTCHA, SMS, email verification, or a second confirmation |
review |
Send the request to manual review, a back-office queue, or delayed processing |
deny |
Reject the request and stop the protected business action |
The snippet below is a minimal decision example. Production policy can be tuned for the application’s risk model and user experience. Keep the complete report in server-side logs, admin systems, or risk platforms when needed.
const riskStatuses = new Set([
'SUSPICIOUS',
'DECEPTIVE',
'WARN',
'FAIL'
])
const hasStrongRisk = report.lyingCount > 0
const hasRiskStatus = Object.values(report.projection ?? {}).some((module) =>
riskStatuses.has(module?.status)
)
const decision = hasStrongRisk || hasRiskStatus
? 'challenge'
: 'allow'
return Response.json({ decision })
Pro Integration
Pro uses the same browser imprint and server-side secret boundary, but is intended for richer risk policy and operations analytics.
- Compared with Lite, Pro adds stronger bot, IP/network risk, and historical access analysis signals
- Pro exposes richer module observations for rule composition, attribution, and internal dashboards
- Pro history queries support
daysorfrom/towindows and returnsummaryplustimeline
Node.js
npm install @echoscan/echoscan
import { createProClient } from '@echoscan/echoscan'
const echoscan = createProClient({
apiKey: process.env.ECHOSCAN_PRO_KEY
})
const report = await echoscan.getReport(imprint)
const historyByDays = await echoscan.getHistory(imprint, { days: 7 })
const historyByRange = await echoscan.getHistory(imprint, {
from: '2026-03-01',
to: '2026-03-18'
})
Go
go get github.com/echoscan/echoscan-go@latest
package main
import (
"context"
"log"
"os"
echoscan "github.com/echoscan/echoscan-go"
)
func main() {
imprint := "fp_session_xxx"
days := 7
echoscanClient, err := echoscan.NewProClient(os.Getenv("ECHOSCAN_PRO_KEY"))
if err != nil {
log.Fatal(err)
}
report, err := echoscanClient.GetReport(context.Background(), imprint)
if err != nil {
log.Fatal(err)
}
history, err := echoscanClient.GetHistory(context.Background(), imprint, echoscan.HistoryQuery{
Days: &days,
})
if err != nil {
log.Fatal(err)
}
_, _ = report, history
}
Python
pip install echoscan
import os
from echoscan import EchoScanProClient
imprint = "fp_session_xxx"
echoscan_client = EchoScanProClient(os.environ["ECHOSCAN_PRO_KEY"])
report = echoscan_client.get_report(imprint)
history = echoscan_client.get_history(imprint, days=7)
print(report.get("lyingCount"), history.get("summary"))
Rust
[dependencies]
echoscan = "0.2.1"
use std::env;
use echoscan::{HistoryQuery, ProClient};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let imprint = "fp_session_xxx";
let api_key = env::var("ECHOSCAN_PRO_KEY")?;
let echoscan = ProClient::new(&api_key)?;
let report = echoscan.get_report(imprint).await?;
let history = echoscan
.get_history(imprint, HistoryQuery::Days { days: 7, recent: None })
.await?;
println!("{} {}", report["lyingCount"], history["summary"]);
Ok(())
}
Pro Report
{
"analysis": {
"browser": {
"brand": "Google Chrome",
"browser_version": "146.0.x",
"browser_version_consistency_status": "PASS",
"rendering_engine": "Blink"
},
"os": {
"os": "Windows",
"os_consistency_status": "PASS",
"version": "11"
},
"ip": {
"status": "PASS"
},
"hash": {
"accessCount": 31,
"calculatedAt": "2026-03-19T10:27:25+09:00",
"stableHash": "stb_8f4d2c1a",
"uniqueHash": "unq_3a9b7e42"
},
"proxy": {
"status": "PASS"
}
},
"lyingCount": 0,
"projection": {
"bot_detection": {
"status": "PASS",
"data": {
"is_bot": "No"
}
},
"screen": {
"status": "PASS",
"data": {
"resolution": "1920 x 1080"
}
},
"speech": {
"status": "PASS",
"data": {
"default_voice_lang": "en-US",
"default_voice_name": "Microsoft David - English (United States)"
}
},
"webgl": {
"status": "PASS",
"data": {
"unmasked_vendor": "Google Inc. (NVIDIA)",
"unmasked_renderer": "NVIDIA GeForce RTX 3060"
}
}
}
}
Pro History
{
"imprint": "fp_session_...",
"range": {
"from": "2026-03-01",
"to": "2026-03-18"
},
"recent": 20,
"summary": {},
"timeline": []
}
daysis mutually exclusive withfrom/tofromandtomust be provided together- Date format is fixed to
YYYY-MM-DD
Error Shape
{
"code": "auth_failed",
"httpStatus": 401,
"message": "Authentication failed",
"requestId": "req_...",
"retryable": false
}
Branch on code; treat message as display text.
API Keys And Security Boundary
- Sign in to EchoScan Console and create a Lite or Pro key from the API Keys page.
- The plaintext key is shown only once at creation time. Store it immediately in a server environment variable or secret manager.
- Do not put EchoScan secret keys in browser code, frontend environment variables, localStorage, logs, git, or publicly accessible files.
- The browser generates and submits
imprint; report lookup and final risk decision should run on the server. - Integration and technical support:
support@echoscan.org - Privacy and security issues:
security@echoscan.org
AI Coding Tools
If you want Claude, Cursor, Codex, or another coding agent to integrate EchoScan, ask it to read:
https://echoscan.org/llms.txthttps://echoscan.org/docs/ai-context.md
These files are generated from the same documentation facts as this page. If the agent cannot read them, it should stop and say that it cannot confirm the EchoScan API context instead of inventing package names, fields, or integration paths.