A machine-to-machine endpoint for SIEM and SOAR. Send an email indicator, get back its public-exposure picture — sourced, corroborated, and timestamped. Deterministic by construction: no language model in the path, and never a singular verdict.
Early access. The contract is published below; request a key to get onboarded.
This endpoint exists to feed a decision, not to make one. It returns what the internet exposes about an indicator — never an identity attribution a SOAR could auto-action.
disambiguation.required is always true, and the response has no candidates field — by construction, not omission. Resolving a person stays a human-in-the-loop judgement your analysts make.
The exposure envelope is a direct projection of sourced findings. No model runs anywhere in this path. Every atom is cited.
source_confidence is a source-reliability proxy, not a threat/identity score. corroboration_count is a separate trust signal — never folded into that number.
Keys are workspace-scoped; a scan from another workspace is invisible (404). The AGPL-3.0 core means you can run the whole thing inside your own perimeter.
Every request carries an X-API-Key header. API access requires the Team plan or above.
Mint a key from the dashboard Organization → API Access (SIEM/SOAR) tab. The plaintext key is shown once on creation — store it immediately. Keys can be rotated (atomic revoke-and-reissue) or soft-revoked; revocation preserves the audit trail.
X-API-Key: xpz_live_9f2c… (shown once when you mint it)
POST /enrichhttps://api.xpose.lu/api/v1/soc/enrich
Cache-first exposure lookup for an email indicator. A fresh result returns 200 with the full envelope; a cache miss returns 202 with a request_id and a poll_url. Results are cached ~24h by default.
curl -X POST https://api.xpose.lu/api/v1/soc/enrich \
-H "X-API-Key: $XPOSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"indicator": { "type": "email", "value": "jane.doe@acme.com" }
}'indicator.type must be email (v1). An optional options.depth (default "quick") is accepted for forward compatibility.
{
"request_id": "8f14e45f-ceea-467d-9a3f-2b4c1d0e7a12",
"status": "completed",
"cache": "hit",
"indicator": { "type": "email", "value": "jane.doe@acme.com" },
"exposure": {
"findings": [
{
"source": "hibp",
"source_url": null,
"source_confidence": 0.95,
"source_tier": "high",
"corroboration_count": 2,
"corroborated_by": ["emailrep", "holehe"],
"category": "breach",
"severity": "high",
"title": "Present in the LinkedIn 2021 breach",
"indicator_type": "email",
"indicator_value": "jane.doe@acme.com",
"first_seen": "2026-05-02T09:14:00+00:00",
"last_seen": "2026-06-28T22:41:00+00:00"
}
],
"total": 1,
"by_severity": { "high": 1 }
},
"disambiguation": {
"required": true,
"reason": "person-resolution gated pending employer-domain disambiguation (R7); v1 returns indicator exposure only, no identity attribution"
}
}poll_url{
"status": "scanning",
"request_id": "8f14e45f-ceea-467d-9a3f-2b4c1d0e7a12",
"poll_url": "/api/v1/soc/enrich/8f14e45f-ceea-467d-9a3f-2b4c1d0e7a12",
"indicator": { "type": "email", "value": "jane.doe@acme.com" }
}GET /enrich/{request_id}https://api.xpose.lu/api/v1/soc/enrich/{request_id}
Poll a cache-miss scan. While it runs you get a status; on completion you get the same envelope as a cache hit (with cache: "miss_scan_complete"). Scans are tenant-isolated — a request_id from another workspace returns 404.
curl https://api.xpose.lu/api/v1/soc/enrich/8f14e45f-ceea-467d-9a3f-2b4c1d0e7a12 \ -H "X-API-Key: $XPOSE_API_KEY"
200 { "status": "running", "request_id": "…" }status echoes the scan: queued / scanning / running
200 { "status": "failed", "request_id": "…" }200 + the full envelope (cache: "miss_scan_complete")request_idthe scan id (string, UUID)statuscompleted on a resolved envelopecachehit (fresh cache) or miss_scan_complete (freshly scanned)indicatorechoes { type, value }exposurethe findings block (below)disambiguation{ required: true, reason } — always present, always required: trueexposurefindings[]array of finding atoms (below)totalcount of atomsby_severity{ severity: count }sourcethe module that produced it (e.g. hibp, holehe, emailrep)source_urlprovenance URL, or nullsource_confidence0–1 source-reliability proxy (not maliciousness/identity)source_tierreliability tier for that source (e.g. high)corroboration_counthow many independent sources agree (separate signal)corroborated_by[]up to 5 corroborating source namescategoryfinding category (e.g. breach)severitylow / medium / high (as emitted)titlehuman-readable summary of the atomindicator_type / indicator_valuethe atom's indicator, or nullfirst_seen / last_seenISO-8601 timestamps, or nullEvery error returns a stable machine code a SOAR playbook can branch on:
{ "error": { "code": "rate_limited", "message": "Rate limit exceeded for this API key" } }| Status | code | When |
|---|---|---|
| 400 | invalid_email | Indicator value isn't a valid email |
| 401 | invalid_api_key | Missing or unrecognised X-API-Key |
| 403 | plan_required | Workspace plan is below Team |
| 403 | forbidden | (key management) caller isn't a workspace admin |
| 404 | not_found | Unknown request_id, or one owned by another workspace |
| 422 | unsupported_indicator | indicator.type isn't email (v1) |
| 429 | rate_limited | Per-key hourly limit exceeded — see below |
Each key has a per-hour, plan-aware limit. On exceed you get 429 with a Retry-After header (seconds). Limits are enforced per key; a transient infrastructure hiccup fails open rather than blocking your pipeline.
Keys are managed from the dashboard by a workspace admin (not via the exposure API). The management endpoints, all under https://api.xpose.lu/api/v1/workspaces/{workspace_id}/keys:
POST …/keysmint a named key (plaintext returned once)GET …/keyslist keys (never returns the hash or plaintext)POST …/keys/{key_id}/rotateatomic revoke-and-reissueDELETE …/keys/{key_id}soft-revoke (preserves the audit trail)Request a key and we'll onboard your SIEM/SOAR integration — days, not weeks.