Skopio API
B2B OSINT engine over a simple REST interface. Bearer auth, JSON in/out, flat-rate per contract.
Authentication
All endpoints require Authorization: Bearer skopio_live_xxx. Keys are issued manually after contract signing. Stored as SHA-256 hash; plaintext shown once at mint time.
Authorization: Bearer skopio_live_arEvkb...JU1MEndpoints
/api/v1/scanCreates a request and queues it in the engine. ETA is typically 60-180 seconds.
curl -X POST https://skopio.io/api/v1/scan \
-H "Authorization: Bearer skopio_live_..." \
-H "Content-Type: application/json" \
-d '{"target":"john@example.com","kind":"email"}'
# Response 202
{
"ok": true,
"scan_id": "req-mp9ne00k-14242242",
"kind": "email",
"recipes_to_run": 6,
"eta_seconds": 180,
"poll_url": "https://skopio.io/api/v1/scan/req-mp9ne00k-14242242"
}/api/v1/scan/{id}Poll every 10-15 seconds. When status=completed the payload is ready.
curl https://skopio.io/api/v1/scan/req-mp9ne00k-14242242 \
-H "Authorization: Bearer skopio_live_..."
# Response (status=completed)
{
"ok": true,
"scan_id": "req-...",
"status": "completed",
"target": "john@example.com",
"kind": "email",
"completed_at": "2026-05-17T12:34:56Z",
"result": {
"risk": {"score": 65, "band": "medium"},
"counts": {"cases": 6, "entities": 192, "findings": 187},
"profiles": {"verified": [...], "weak": [...]},
"contacts": {"emails": [...], "phones": [...]},
"breaches": [{"name":"LinkedIn","date":"2016-05","accounts":117000000,"classes":["Email","Password"]}],
"phone_profile": null,
"ai_summary": "Email фигурирует в 3 утечках...",
"artifacts": {"pdf": "...", "person_card": "..."}
}
}Supported target types
john@example.com+15551234567johndoeRate limits by tier
| Tier | Scans / day | / hour | Inflight | Best for |
|---|---|---|---|---|
| Pro | 100 | 20 | 3 | Pro — solo integrations, freelance security, journalists. |
| Business | 1,000 | 100 | 10 | Business — agencies, HR background checks, due diligence, threat intel. |
| Enterprise | 10,000 | 500 | 25 | Enterprise — fraud detection, SOC, monitoring large customer bases. SLA + dedicated support. |
Tiers control THROUGHPUT only. Every API call deducts 1 credit from your account balance, regardless of tier. Exceeding rate-limit returns 429 with no deduction. Balance < 1 → 402 insufficient_balance.
Error codes
| HTTP | error | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or malformed Bearer header |
| 401 | invalid_token | Token not found or revoked |
| 400 | target_too_short | target must be ≥ 3 chars |
| 429 | rate_limit_inflight | Too many concurrent scans |
| 429 | rate_limit_hour | Hourly quota exceeded |
| 429 | rate_limit_day | Daily quota exceeded |
| 403 | forbidden | Scan belongs to another customer |
| 502 | engine_submit_failed | Engine unreachable; retry in 30s |
Bulk lookup (up to 100 targets per call)
Business/Enterprise tiers. One POST returns an array of scan_id with a shared bulk_id. Each scan completes independently (its own webhook), every webhook includes the bulk_id field so you can group them. There's also an aggregate GET endpoint to poll the whole batch with one call instead of N.
/api/v1/scan/bulkcurl -X POST https://skopio.io/api/v1/scan/bulk \
-H "Authorization: Bearer skopio_live_..." \
-H "Content-Type: application/json" \
-d '{"targets":[
{"target":"alice@example.com","kind":"email"},
{"target":"bob@example.com","kind":"email"},
{"target":"+15551234567","kind":"phone"}
]}'
# Response 202
{
"ok": true,
"bulk_id": "bulk_xxx",
"total": 3,
"queued": 3,
"rejected": [],
"scans": [
{"scan_id":"req-aaa","target":"alice@example.com","kind":"email","poll_url":"..."},
{"scan_id":"req-bbb","target":"bob@example.com","kind":"email","poll_url":"..."},
{"scan_id":"req-ccc","target":"+15551234567","kind":"phone","poll_url":"..."}
],
"aggregate_poll_url": "https://skopio.io/api/v1/scan/bulk/bulk_xxx"
}/api/v1/scan/bulk/{bulk_id}# Aggregate poll — one call returns all scans in the batch
curl https://skopio.io/api/v1/scan/bulk/bulk_xxx \
-H "Authorization: Bearer skopio_live_..."
# Response 200
{
"ok": true,
"bulk_id": "bulk_xxx",
"total": 3,
"queued": 1,
"running": 0,
"completed": 2,
"failed": 0,
"all_done": false,
"scans": [
{"scan_id":"req-aaa","status":"completed","target":"alice@example.com","result":{...}},
{"scan_id":"req-bbb","status":"completed","target":"bob@example.com","result":{...}},
{"scan_id":"req-ccc","status":"queued","target":"+15551234567"}
]
}- Max
100targets per bulk call. - Rate limits:
bulk_size + current_inflightmust fit your tier (e.g. Business = 10 inflight). Also counts againstrate/hourandrate/day. - Invalid targets land in
rejected[]; valid ones still get queued. - Each scan completes independently. If a webhook is configured, every per-scan webhook carries the shared
bulk_idfield so you can group them.
Webhooks (push, no polling)
Set the URL in /app/api-keys/[id]. Skopio POSTs the result as soon as the scan completes. Each request is signed with HMAC-SHA256 — verify with your stored secret.
Headers sent on every webhook POST:
X-Skopio-Event: scan.completed | scan.failed
X-Skopio-Signature: sha256=<hex>
X-Skopio-Delivery: <delivery_id>
X-Skopio-Timestamp: <unix_ms>
User-Agent: Skopio-Webhook/1
Content-Type: application/jsonVerify the signature (Node.js):
import {createHmac, timingSafeEqual} from "node:crypto";
function verify(rawBody, sigHeader, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(sigHeader));
}Body shape is the same as the GET response \`result\` field:
{
"event": "scan.completed",
"scan_id": "req-...",
"bulk_id": "bulk_xxx", // only present if scan was part of a bulk submission
"target": "john@example.com",
"kind": "email",
"status": "completed",
"completed_at": "2026-05-17T12:34:56Z",
"result": {
"risk": {"score": 65, "band": "medium"},
"profiles": {...}, "breaches": [...], "ai_summary": "...",
...
}
}Retries: failed deliveries retry up to 3 times (immediate, +30s, +5min). Return any 2xx to acknowledge. All attempts are logged in /app/api-keys/[id].
Pricing & tiers
Tiers control THROUGHPUT only. Every API call deducts 1 credit from your account balance, regardless of tier. Exceeding rate-limit returns 429 with no deduction. Balance < 1 → 402 insufficient_balance.
Get API access →