EchoScan API and Integration Guide
EchoScan provides device identity and access-risk results for sensitive actions such as sign-in, registration, and payment. After setup, the browser generates an imprint, and your server uses a secret API key to query the official Report.
Building an automated evaluator? The Agent API discovery guide covers the product catalog, OpenAPI contract, and Agent Trial flow. Human Workspace users can connect the production MCP Server through OAuth 2.1 without sharing a Secret API Key.
Recommended Flow
Follow this checklist from top to bottom. For the first integration, enter one website URL in Console. EchoScan creates the Web App and default server API key together. As you select or scroll through a step, the right panel adds the execution boundary, practical checks, and the expected result for that stage.
1. Add Your Website
Open first website setup in Console. Enter the website where Browser Verifier will run, such as https://shop.example.com. Console reduces paths and query parameters to the exact Origin, then creates the Web App and default server API key in one transaction. Copy the publishable env_... Environment ID after creation.
2. Save The Server API Key
After first website setup succeeds, Console shows the default API key secret once. Save it immediately in your backend secret manager or environment variables. Keep it out of browser code, logs, and source control. You can later create, rotate, or revoke keys independently in API Key management without changing the Web App.
3. Install Browser Verifier
Install the Browser SDK and pass the Environment ID from step 1 to createEchoScan({ environmentId }). Console owns Allowed Origins, so browser code only needs the public Environment ID.
4. Generate And Send The Imprint
Call run() when a protected action such as sign-in, registration, or payment occurs. Send the returned imprint to your own server with the business request.
5. Query The Report On Your Backend
Use the secret API key from step 2 to query the canonical Report endpoint by imprint. The official device identity and risk result come from the server Report.
6. Apply Your Business Decision
Read risk.status and, for Pro, risk.reasons. Combine the result with account, transaction, and business context to allow, challenge, review, or deny the action. Return only the minimum business result to the browser.
Browser code and server routes may share a repository. The security boundary is where code runs: environmentId is publishable, while the API key is server-only.
Generate Imprint In The Browser
Direct mode requires environmentId in the fixed env_<32 lowercase hex> format. Browser Verifier uses it only for fingerprint submission. It does not accept a Workspace ID and does not place an API key or authorization secret in the browser.
npm install @echoscan/browser-verifier
import { createEchoScan } from '@echoscan/browser-verifier'
const sdk = createEchoScan({
environmentId: 'env_0123456789abcdef0123456789abcdef'
})
const { imprint } = await sdk.run()
<script type="module">
import { createEchoScan } from 'https://cdn.echoscan.org/v1/echoscan.esm.js'
const sdk = createEchoScan({
environmentId: 'env_0123456789abcdef0123456789abcdef'
})
const { imprint } = await sdk.run()
</script>
<script src="https://cdn.echoscan.org/v1/echoscan.umd.js"></script>
<script>
const sdk = window.EchoScan.createEchoScan({
environmentId: 'env_0123456789abcdef0123456789abcdef'
})
sdk.run().then(({ imprint }) => {
console.log(imprint)
})
</script>
run() returns only { imprint }. New imprints use imp_<32 lowercase hex>.
Allowed Origins are exact browser deployment protection, not secret authentication. Register complete http or https origins such as https://staging.example.com:8443 or http://localhost:3000. Matching preserves scheme and port and lowercases the host. Paths, query strings, fragments, user info, wildcards (including *.example.com), bare domains, null, file://, and extension origins are rejected. An empty allowlist or a missing Origin header rejects every public Submit.
Browser Verifier Parameters
| Parameter | Meaning |
|---|---|
environmentId |
Publishable Browser Environment ID. The server resolves it to the trusted Workspace and validates Allowed Origins |
imprint |
Server-issued report identifier returned by a successful run() call; send it with the protected business action |
Send Imprint To The Server
Send the imprint with the protected business action using the shape that fits your API.
await fetch('/api/your-action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...yourActionPayload,
echoscanImprint: imprint
})
})
await fetch('/api/your-action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-EchoScan-Imprint': imprint
},
body: JSON.stringify(yourActionPayload)
})
await fetch('/api/echoscan/report', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ imprint })
})
/api/echoscan/report above is an example path owned by the integrator, not an EchoScan endpoint.
Lite Integration
Lite uses the canonical report endpoint. The secret key must be read only by server-executed code.
Query Report
GET https://api.echoscan.org/api/v1/fingerprint/report/{imprint}
X-API-Key: <your_lite_key>
Accept: application/json
Node.js, Go, Python, and Rust SDKs wrap the same server HTTP API.
npm install @echoscan/echoscan
import { createLiteClient } from '@echoscan/echoscan'
const echoscan = createLiteClient({
apiKey: process.env.ECHOSCAN_LITE_KEY
})
const report = await echoscan.getReport(imprint)
console.log(report.risk.status)
go get github.com/echoscan/echoscan-go@latest
package main
import (
"context"
"log"
"os"
echoscan "github.com/echoscan/echoscan-go"
)
func main() {
imprint := "imp_0123456789abcdef0123456789abcdef"
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("risk_status=%v", report["risk"].(map[string]any)["status"])
}
pip install echoscan
import os
from echoscan import EchoScanLiteClient
imprint = "imp_0123456789abcdef0123456789abcdef"
echoscan_client = EchoScanLiteClient(os.environ["ECHOSCAN_LITE_KEY"])
report = echoscan_client.get_report(imprint)
print(report["risk"]["status"])
[dependencies]
echoscan = "0.2.1"
use std::env;
use echoscan::LiteClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let imprint = "imp_0123456789abcdef0123456789abcdef";
let api_key = env::var("ECHOSCAN_LITE_KEY")?;
let echoscan = LiteClient::new(&api_key)?;
let report = echoscan.get_report(imprint).await?;
println!("{}", report["risk"]["status"]);
Ok(())
}
Lite Report
Lite returns the minimum public device identity and risk contract. It does not include product reasons, recent activity, device timestamps, or Pro network details.
Normal example:
{
"schema_version": "1.0",
"imprint": "imp_0123456789abcdef0123456789abcdef",
"created_at": "2026-07-28T01:30:00Z",
"device": {
"id": "did_A12B34C56D78",
"seen_before": false,
"access_count": 1
},
"risk": {
"status": "PASS"
},
"browser": {
"status": "PASS",
"name": "Google Chrome",
"version": "145"
},
"operating_system": {
"status": "PASS",
"name": "Windows",
"version": "11"
},
"network": {
"status": "PASS",
"observed_ip": "203.0.113.10",
"country_code": "JP"
}
}
Suspicious example:
{
"schema_version": "1.0",
"imprint": "imp_11111111111111111111111111111111",
"created_at": "2026-07-28T01:35:00Z",
"device": {
"id": "did_C98D76E54F32",
"seen_before": false,
"access_count": 1
},
"risk": {
"status": "SUSPICIOUS"
},
"browser": {
"status": "SUSPICIOUS",
"name": "Google Chrome",
"version": "145"
},
"operating_system": {
"status": "PASS",
"name": "Windows",
"version": "11"
},
"network": {
"status": "PASS",
"observed_ip": "198.51.100.20",
"country_code": "US"
}
}
Returning-device example:
{
"schema_version": "1.0",
"imprint": "imp_22222222222222222222222222222222",
"created_at": "2026-07-28T02:00:00Z",
"device": {
"id": "did_A12B34C56D78",
"seen_before": true,
"access_count": 8
},
"risk": {
"status": "PASS"
},
"browser": {
"status": "PASS",
"name": "Google Chrome",
"version": "145"
},
"operating_system": {
"status": "PASS",
"name": "Windows",
"version": "11"
},
"network": {
"status": "PASS",
"observed_ip": "203.0.113.10",
"country_code": "JP"
}
}
Field Reference
| Field | Availability | Meaning |
|---|---|---|
schema_version |
Always returned. | Public data-structure version used by this report. |
imprint |
Always returned. | Unique identifier for this detection report. |
created_at |
Always returned in UTC. | Time this report was generated. |
device |
Always returned. | Identification result and visit history for this device in the current Workspace. |
device.id |
Always returned. | Device ID within the current Workspace. |
device.seen_before |
Always returned. | Whether this device was seen before the current visit. |
device.access_count |
Always returned and includes the current visit. | Total visits for this device in the current Workspace, including this visit. |
risk |
Always returned. | Overall risk result for this visit. |
risk.status |
Always returned. | Overall risk level for this visit. |
browser |
Always returned. | Browser information identified by EchoScan and its risk status. |
browser.status |
Always returned. | Risk level for the browser identity dimension of this visit. |
browser.name |
Returned when available. | Browser name identified by EchoScan. |
browser.version |
Returned when available. | Browser version identified by EchoScan. |
operating_system |
Always returned. | Operating-system information identified by EchoScan and its risk status. |
operating_system.status |
Always returned. | Risk level for the operating-system environment dimension of this visit. |
operating_system.name |
Returned when available. | Operating-system name identified by EchoScan. |
operating_system.version |
Returned when available. | Operating-system version identified by EchoScan. |
network |
Always returned. | Network information and risk status for this visit. |
network.status |
Always returned. | Risk level for the network dimension of this visit. |
network.observed_ip |
Returned when available. | Public egress IP used when this request reached EchoScan. |
network.country_code |
Returned when available. | Country or region code associated with the request egress IP. |
Finite Values
| Field | Value | Title | Meaning | Example customer action |
|---|---|---|---|---|
device.seen_before |
true |
Previously seen | EchoScan had seen this device before the current visit. | |
device.seen_before |
false |
First record | This visit is the first record for the device. | |
risk.status |
PASS |
No risk currently needs attention | Continue processing this visit under the normal business rules. | Continue the existing business flow. |
risk.status |
SUSPICIOUS |
Risk needs attention | Evaluate the result together with the account and business context. | Add verification, rate limits, or manual review. |
risk.status |
DECEPTIVE |
Clear high-risk indicators are present | The visit shows clear signs of disguise, automation, or a high-risk network. | Apply stricter verification, restrictions, or manual review. |
browser.status |
PASS |
Browser information looks normal | No browser-information issue currently needs attention. | |
browser.status |
SUSPICIOUS |
Browser information needs attention | The browser information contains an issue that needs attention. | |
browser.status |
DECEPTIVE |
Browser information is high risk | The browser information shows clear signs of disguise. | |
operating_system.status |
PASS |
Operating-system information looks normal | No operating-system information issue currently needs attention. | |
operating_system.status |
SUSPICIOUS |
Operating-system information needs attention | The operating-system information contains an issue that needs attention. | |
operating_system.status |
DECEPTIVE |
Operating-system information is high risk | The operating-system information shows clear signs of disguise. | |
network.status |
PASS |
Network information looks normal | No network-information issue currently needs attention. | |
network.status |
SUSPICIOUS |
Network information needs attention | The network information contains a risk that needs attention. | |
network.status |
DECEPTIVE |
Network information is high risk | The network information shows clear high-risk indicators. |
Pro Integration
Pro uses the same browser imprint, server-only secret boundary, and canonical endpoint as Lite. The key plan selects Pro depth without requiring customers to change endpoints. Enterprise currently receives the Pro contract.
Query Report
GET https://api.echoscan.org/api/v1/fingerprint/report/{imprint}
X-API-Key: <your_pro_key>
Accept: application/json
History is available only to Pro and Enterprise:
GET https://api.echoscan.org/api/v1/fingerprint/imprint/{imprint}/history?days=7&recent=20
X-API-Key: <your_pro_key>
Accept: application/json
GET https://api.echoscan.org/api/v1/fingerprint/imprint/{imprint}/history?from=2026-03-01&to=2026-03-18&recent=20
X-API-Key: <your_pro_key>
Accept: application/json
npm install @echoscan/echoscan
import { createProClient } from '@echoscan/echoscan'
const echoscan = createProClient({
apiKey: process.env.ECHOSCAN_PRO_KEY
})
const report = await echoscan.getReport(imprint)
console.log(report.risk.status, report.risk.reasons)
const history = await echoscan.getHistory(imprint, { days: 7 })
go get github.com/echoscan/echoscan-go@latest
package main
import (
"context"
"log"
"os"
echoscan "github.com/echoscan/echoscan-go"
)
func main() {
imprint := "imp_0123456789abcdef0123456789abcdef"
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)
}
log.Printf("risk=%v history=%v", report["risk"], history["summary"])
}
pip install echoscan
import os
from echoscan import EchoScanProClient
imprint = "imp_0123456789abcdef0123456789abcdef"
echoscan_client = EchoScanProClient(os.environ["ECHOSCAN_PRO_KEY"])
report = echoscan_client.get_report(imprint)
history = echoscan_client.get_history(imprint, days=7)
print(report["risk"]["status"], report["risk"]["reasons"], history["summary"])
[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 = "imp_0123456789abcdef0123456789abcdef";
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["risk"]["status"], history["summary"]);
Ok(())
}
Optional: Enable Account Map (Pro)
Use Account Map when you need to answer questions such as:
- How many accounts are associated with this device?
- How many devices has this account used?
- Have this account and device appeared together before?
- Is one device receiving many new account relationships in a short period?
- Could the pattern indicate account sharing or account takeover?
Pass the stable internal user ID with the Pro server-side Report query.
Node.js
const report = await pro.getReport(imprint, {
accountRef: currentUser.id
})
Go
report, err := pro.GetReportWithOptions(
context.Background(),
imprint,
echoscan.ReportOptions{AccountRef: currentUser.ID},
)
if err != nil {
return err
}
Python
report = pro.get_report(
imprint,
account_ref=current_user.id,
)
Rust
let report = pro
.get_report_with_options(
imprint,
ReportOptions { account_ref: Some(current_user.id.clone()) },
)
.await?;
Account Map is completely optional. Use a stable, non-sensitive, immutable internal user primary key as accountRef. Do not send an email address, phone number, real name, nickname, or mutable username. Account relationships are isolated by Workspace.
Omitting accountRef preserves the existing Report request and response. Account Map v1 does not automatically change risk.status; combine these relationship signals with your own business rules.
Consistency for concurrent or delayed linking
Account Map statistics include only relationships that have completed and are visible when the response is generated.
Concurrent POST responses cannot anticipate other relationships that have not completed yet.
After all concurrent or delayed links complete, later GET responses recompute historical statistics in server event order (recorded_at, then event_id).
An earlier event keeps its historical boundary, while a later event includes visible relationships at or before its boundary.
Account Map response example:
{
"account_map": {
"account_seen_before": true,
"relationship_seen_before": false,
"accounts_on_device": 7,
"devices_on_account": 2,
"accounts_first_seen_on_device_1h": 4
}
}
| Field | Meaning |
|---|---|
account_seen_before |
Whether this account appeared on any device in the current Workspace before this visit. |
relationship_seen_before |
Whether this exact account and current device appeared together before this visit. |
accounts_on_device |
Number of distinct accounts associated with the current device, including the current relationship. |
devices_on_account |
Number of distinct devices associated with the current account, including the current relationship. |
accounts_first_seen_on_device_1h |
Number of accounts whose first relationship with the current device occurred during the previous hour. When accountRef is sent after registration, this can approximate new registrations. When sent only at sign-in, it counts accounts first observed by EchoScan and does not prove that the account was just created. |
Pro Report
Pro is a strict superset of Lite. It adds device continuity timestamps, product-level reasons, optional network details, and recent activity.
{
"schema_version": "1.0",
"imprint": "imp_0123456789abcdef0123456789abcdef",
"created_at": "2026-07-28T01:30:00Z",
"device": {
"id": "did_A12B34C56D78",
"seen_before": true,
"access_count": 42,
"first_seen_at": "2026-06-10T03:20:00Z",
"previous_seen_at": "2026-07-27T08:40:00Z"
},
"risk": {
"status": "DECEPTIVE",
"reasons": [
"BROWSER_VERSION_MISMATCH",
"PROXY_DETECTED",
"NETWORK_INCONSISTENT"
]
},
"browser": {
"status": "DECEPTIVE",
"name": "Google Chrome",
"version": "145"
},
"operating_system": {
"status": "PASS",
"name": "Windows",
"version": "11"
},
"network": {
"status": "DECEPTIVE",
"observed_ip": "198.23.233.104",
"country_code": "US",
"alternate_ip": "126.234.173.23",
"ip_consistency": "MISMATCH",
"proxy_detected": true,
"location": {
"country_name": "United States",
"region": "Illinois",
"city": "Elk Grove Village",
"timezone": "America/Chicago"
},
"provider": "Example Hosting Provider",
"connection_type": "proxy",
"asn": 36352
},
"activity": {
"5m": {
"events": 2,
"distinct_ips": 1,
"distinct_countries": 1
},
"1h": {
"events": 8,
"distinct_ips": 2,
"distinct_countries": 2
},
"24h": {
"events": 21,
"distinct_ips": 4,
"distinct_countries": 3
}
}
}
A passing Pro report still serializes an explicit empty reasons array:
{
"risk": {
"status": "PASS",
"reasons": []
}
}
The tables below provide the return rule for every field and the product meaning of each finite machine value.
Product Reasons
| Reason | Meaning |
|---|---|
BROWSER_VERSION_MISMATCH |
Browser version information is inconsistent |
BROWSER_IDENTITY_MISMATCH |
Browser identity information is inconsistent |
OS_ENVIRONMENT_MISMATCH |
Operating-system environment information is inconsistent |
AUTOMATION_DETECTED |
Automated access was detected |
ENVIRONMENT_INCONSISTENT |
Current device environment information is inconsistent |
PROXY_DETECTED |
Proxy or VPN risk was detected |
HOSTING_NETWORK_DETECTED |
Access originated from a hosting service or datacenter network |
NETWORK_INCONSISTENT |
Network identity information is inconsistent |
LOCATION_INCONSISTENT |
Location-related information is inconsistent |
SIGNAL_DATA_INCOMPLETE |
Critical data for this detection is incomplete |
Reasons describe risk categories, not severity. Severity is expressed only by risk.status.
Pro-Only Fields
| Field | Availability | Meaning |
|---|---|---|
device.first_seen_at |
Returned in Pro when available, in UTC. | Time this Workspace first saw the device. |
device.previous_seen_at |
Always returned in Pro; null on the first visit. |
Most recent time this device was seen before the current visit. |
risk.reasons |
Always returned in Pro. Returns an empty array [] for PASS. |
Product-level reason categories associated with this risk result. |
network.alternate_ip |
Returned in Pro when available. | Client public IP detected by EchoScan. |
network.ip_consistency |
Always returned in Pro. | Consistency result between the client public IP and request egress IP. |
network.proxy_detected |
Returned in Pro when available. | Whether proxy or VPN risk was detected. |
network.location |
Returned in Pro when available. | Network location information associated with the request egress IP. |
network.location.country_name |
Returned when available with network.location. |
Country or region name associated with the request egress IP. |
network.location.region |
Returned when available with network.location. |
Region or first-level administrative area associated with the request egress IP. |
network.location.city |
Returned when available with network.location. |
City associated with the request egress IP. |
network.location.timezone |
Returned when available with network.location. |
Time zone associated with the network location of the request egress IP. |
network.provider |
Returned in Pro when available. | Network organization or service provider associated with the request egress IP. |
network.connection_type |
Returned in Pro when available. | Network type associated with the request egress IP. |
network.asn |
Returned in Pro when available. | Autonomous system number associated with the request egress IP. |
activity |
Returned in Pro when available. | Summary of recent visit activity for the current device. |
activity.5m |
Always returned with activity and includes this visit. |
Visit activity for the current device during the last 5 minutes. |
activity.5m.events |
Always returned with its time window. | Number of visits in this time window. |
activity.5m.distinct_ips |
Always returned with its time window. | Number of distinct IP addresses in this time window. |
activity.5m.distinct_countries |
Always returned with its time window. | Number of distinct countries or regions in this time window. |
activity.1h |
Always returned with activity and includes this visit. |
Visit activity for the current device during the last hour. |
activity.1h.events |
Always returned with its time window. | Number of visits in this time window. |
activity.1h.distinct_ips |
Always returned with its time window. | Number of distinct IP addresses in this time window. |
activity.1h.distinct_countries |
Always returned with its time window. | Number of distinct countries or regions in this time window. |
activity.24h |
Always returned with activity and includes this visit. |
Visit activity for the current device during the last 24 hours. |
activity.24h.events |
Always returned with its time window. | Number of visits in this time window. |
activity.24h.distinct_ips |
Always returned with its time window. | Number of distinct IP addresses in this time window. |
activity.24h.distinct_countries |
Always returned with its time window. | Number of distinct countries or regions in this time window. |
Pro Finite Values
| Field | Value | Title | Meaning | Example customer action |
|---|---|---|---|---|
network.ip_consistency |
MATCH |
IP information matches | Network IP information is consistent. | |
network.ip_consistency |
MISMATCH |
IP information differs | Network IP information is inconsistent. | |
network.ip_consistency |
UNKNOWN |
No clear result | No clear IP consistency result is available. | |
network.proxy_detected |
true |
Risk detected | Proxy or VPN risk was detected. | |
network.proxy_detected |
false |
No risk detected | No proxy or VPN risk was detected. | |
network.connection_type |
residential |
Residential | Residential broadband network. | |
network.connection_type |
mobile |
Mobile | Mobile carrier network. | |
network.connection_type |
corporate |
Corporate | Business or institutional network. | |
network.connection_type |
hosting |
Hosting | Cloud, server-hosting, or datacenter network. | |
network.connection_type |
proxy |
Proxy | Proxy, VPN, or similar relay network. | |
network.connection_type |
unknown |
Unclassified | The network has not been assigned a specific type. |
Pro History
The History API is available to Pro and Enterprise only.
{
"imprint": "imp_33333333333333333333333333333333",
"range": {
"from": "2026-03-01",
"to": "2026-03-18",
"days": 18
},
"summary": {
"events": 12,
"truncated": false,
"firstSeenAt": "2026-03-01T08:10:00+09:00",
"lastSeenAt": "2026-03-18T21:34:00+09:00"
},
"timeline": [
{
"date": "2026-03-01",
"count": 2
},
{
"date": "2026-03-18",
"count": 3
}
],
"recent": [
{
"at": "2026-03-18T21:34:00+09:00",
"surface": "login"
}
]
}
days is mutually exclusive with from/to; from and to must be provided together in YYYY-MM-DD format.
History Field Reference
| Field | Meaning |
|---|---|
imprint |
Detection report identifier used for this History query |
range |
Time range applied to this History query |
range.from |
First date in the query range |
range.to |
Last date in the query range |
range.days |
Number of calendar days covered by the range |
summary |
Aggregate summary for the selected range |
summary.events |
Total visit events in the selected range |
summary.truncated |
Whether the response reached its return limit |
summary.firstSeenAt |
Earliest event time inside the selected range |
summary.lastSeenAt |
Most recent event time inside the selected range |
timeline |
Daily visit-count series |
timeline.date |
Calendar date for a timeline entry |
timeline.count |
Visit events recorded on that date |
recent |
Most recent visit-event list |
recent.at |
Time of a recent event |
recent.surface |
Customer-provided business surface identifier |
Server Decision
Use risk.status as the primary report input and combine it with account, transaction, and business context. Pro may additionally use risk.reasons, network details, activity, and History.
const decision =
report.risk.status === 'PASS'
? 'allow'
: 'challenge'
return Response.json({ decision })
This allow-or-challenge branch is an example customer policy. EchoScan Report API v1 does not return recommended_action.
Keep the full report server-side and return only the business result needed by the browser.
Error Shape
{
"error": {
"code": "auth_failed",
"message": "Authentication failed"
}
}
Branch on error.code; treat error.message as display text.
Error Field Reference
| Field | Meaning |
|---|---|
error |
Public error object |
error.code |
Stable machine code for server-side branching |
error.message |
Error description suitable for logs or interface text |
Connect AI With EchoScan MCP
Use standard MCP to connect Codex, Claude Code, VS Code, or another compatible client directly to EchoScan. The client opens browser authorization the first time it needs access to a Workspace.
Connect EchoScan to your AI
Add this remote endpoint once. Your client opens EchoScan authorization when access is required—no API key is pasted into the AI client.
https://api.echoscan.org/mcp
View setup methods
Run this once in a terminal, then approve the EchoScan workspace and permissions in your browser.
codex mcp add echoscan --url https://api.echoscan.org/mcp
Run this once in a terminal. Claude Code will start browser authorization when it first connects.
claude mcp add --transport http echoscan https://api.echoscan.org/mcp
Add this server entry to your MCP configuration. VS Code will request authorization when the server is first used.
{
"servers": {
"echoscan": {
"type": "http",
"url": "https://api.echoscan.org/mcp"
}
}
}
Create a remote Streamable HTTP server with these values. If the client supports MCP OAuth, it will discover the authorization flow automatically.
Name: EchoScan
Transport: Streamable HTTP
URL: https://api.echoscan.org/mcp
Authentication: OAuth 2.1 + PKCE
Current tools are echoscan_get_report, echoscan_get_history, and echoscan_get_usage. Current OAuth scopes are echoscan.report.lite, echoscan.report.pro, echoscan.history.read, and echoscan.usage.read.
If the AI will also change EchoScan integration code, give it these canonical sources first:
https://echoscan.org/llms.txthttps://echoscan.org/docs/ai-context.md
They are maintained against the same public API contract and validated for consistency. MCP authorization never requires you to paste an EchoScan API key into the AI client.