back to console
$ /api/v1 · v1 stable

PRISM API

Bearer-auth REST API for the PRISM multi-stage skip tracer. Submit a seed, poll for the dossier, plug into any HTTP client. The same engine that powers the Lookup tab, exposed for headless / programmatic use.

Async · POST then pollBearer auth · prsm_live_…Per-key billing + rate limitsSigned webhooksBatch up to 50Idempotent retries
00

Endpoint map

quick reference
methodpathpurpose
GET/api/v1/healthservice heartbeat (no auth)
GET/api/v1/accountkey info · balance · rate budgets
GET/api/v1/usageper-key job + credit usage
POST/api/v1/tracersubmit one trace
GET/api/v1/tracerlist jobs · cursor pagination · status filter
GET/api/v1/tracer/{id}poll a single job
DELETE/api/v1/tracer/{id}cancel + refund
POST/api/v1/tracer/batchsubmit up to 50 traces in one call
01

Authentication

bearer

Every request needs a key in the Authorization header. Generate one in Settings → API keys. Plaintext is shown once on creation; only its sha256 is stored. Revoking a key invalidates it instantly.

Authorization: Bearer prsm_live_abc123…
Content-Type: application/json

/api/v1/healthis the only endpoint that doesn't require auth.

02

Rate limits

per key

Sliding-window in-memory limiter on every endpoint, keyed on your api key id. Exceed and you get 429 with Retry-After. Layered on top of the edge per-IP cap (120 req/min).

tierendpointslimit
writePOST /tracer · DELETE /tracer/{id}60 / minute / key
batchPOST /tracer/batch10 / minute / key (each call may submit ≤50)
readGET /tracer · /tracer/{id} · /account · /usage600 / minute / key
healthGET /health240 / minute / IP (unauth)

Every successful response returns X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (unix seconds). 429 responses also return Retry-After in seconds.

03

Submit a trace

POST /api/v1/tracer

Returns immediately with a job id and status: queued. The engine runs asynchronously. Poll the status endpoint until the job terminates.

curl -X POST https://prism-tools.vip/api/v1/tracer \
  -H "Authorization: Bearer prsm_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-unique-id-123" \
  -d '{
    "seed":       { "email": "jane@example.com" },
    "depth":      1,
    "useAi":      true,
    "cluster":    { "kind": "managed", "id": "managed:sonnet+search" },
    "webhookUrl": "https://your.app/prism-webhook"
  }'

Response (HTTP 202):

{
  "job":      { "id": "8f2c…", "status": "queued", "seed": {...}, "depth": 1, "use_ai": true, ... },
  "charged":  4,
  "balance":  46,
  "poll_url": "/api/v1/tracer/8f2c…"
}
04

Idempotency

Idempotency-Key header

Send Idempotency-Key: <opaque-string> on any POST. PRISM caches the response for 24 hours scoped to (api_key, idempotency_key). Replays return the cached body without re-executing — safe for client retries on timeout / network blip.

# initial
curl -X POST https://prism-tools.vip/api/v1/tracer \
  -H "Authorization: Bearer prsm_live_…" \
  -H "Idempotency-Key: trace-2026-05-06-abc" \
  ... → 202 { job: {...}, charged: 1 }

# retry within 24h with the same key
curl -X POST https://prism-tools.vip/api/v1/tracer \
  -H "Authorization: Bearer prsm_live_…" \
  -H "Idempotency-Key: trace-2026-05-06-abc" \
  ... → 202 (cached) { job: {same id}, ... }   # no second debit

Response carries Idempotent-Replayed: true on cache hits.

05

Seed shape

JSON object

The seed object accepts at least one of the following selectors. Combine multiple to disambiguate.

fieldexamplebehavior
emailj.doe@example.comdirect skip-trace
phone+1 415 555 0100direct skip-trace
nameJane Doepair with city/state
address1600 Amphitheatre Pkwy, Mountain View CA 94043single line
usernamejdoe87pivots through github / gravatar
domainexample.compivots through whois / dns / rdap
ip8.8.8.8pivots through ip-api / asn
dob1987-09-14narrows ambiguous matches
ssn_last44823narrows on identity overlap
city / state / zipOrem · UT · 84058address components
06

Cluster (AI) selection

cluster param

Control which AI cluster (or none) cross-references and organizes the dossier. The cluster choice multiplies the per-trace cost; see /account for your balance.

clustercost multnotes
{ kind: "managed", id: "off" }AI off · heuristic correlator only
{ kind: "managed", id: "managed:haiku" }Haiku 4.5 · structure only
{ kind: "managed", id: "managed:haiku+search" }Haiku 4.5 + web search
{ kind: "managed", id: "managed:sonnet+search" }Sonnet 4.6 + web search
{ kind: "byo", configId: <int> }Bring Your Own LLM (free uplift) — config in /settings
07

Cancel

DELETE /api/v1/tracer/{id}

Cancel a job that hasn't terminated. Refunds the credit debit (idempotent). Returns 410 if the job is already done, failed, orcancelled.

curl -X DELETE https://prism-tools.vip/api/v1/tracer/8f2c… \
  -H "Authorization: Bearer prsm_live_…"

# 200 { job: {...status: "cancelled"}, refunded: 4 }
08

Batch submit

POST /api/v1/tracer/batch

Up to 50 traces per call. Each item is independently debited and queued. Results pair back to inputs by index. Honors Idempotency-Key for the whole batch.

curl -X POST https://prism-tools.vip/api/v1/tracer/batch \
  -H "Authorization: Bearer prsm_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: nightly-import-2026-05-06" \
  -d '{
    "items": [
      { "seed": { "email": "a@x.com" }, "depth": 1 },
      { "seed": { "phone": "+14155550100" } },
      { "seed": { "name": "Jane Doe", "address": "Boston, MA" } }
    ]
  }'

# 202
{
  "submitted": 3, "failed": 0,
  "results": [
    { "ok": true,  "index": 0, "job": {...}, "charged": 2, "balance": 98, "poll_url": "..." },
    { "ok": true,  "index": 1, "job": {...}, "charged": 2, "balance": 96, "poll_url": "..." },
    { "ok": true,  "index": 2, "job": {...}, "charged": 2, "balance": 94, "poll_url": "..." }
  ]
}
09

Poll a job

GET /api/v1/tracer/{id}

Returns the job in its current state. result is null until done.

curl https://prism-tools.vip/api/v1/tracer/8f2c… \
  -H "Authorization: Bearer prsm_live_…"
10

List jobs

GET /api/v1/tracer

Cursor pagination via before=<ISO>. Status filter via status=.

curl "https://prism-tools.vip/api/v1/tracer?status=done&limit=50" \
  -H "Authorization: Bearer prsm_live_…"

# response includes meta.next_cursor (ISO timestamp); pass it as ?before=… for the next page
11

Account snapshot

GET /api/v1/account

Useful at app boot — lists key info, balance, rate-limit budget, and the webhook secret derivation hint.

curl https://prism-tools.vip/api/v1/account \
  -H "Authorization: Bearer prsm_live_…"
12

Usage stats

GET /api/v1/usage

Counts by status across rolling 24h / 7d windows, credits spent, average duration of completed jobs. For dashboards.

curl https://prism-tools.vip/api/v1/usage \
  -H "Authorization: Bearer prsm_live_…"

{
  "usage": {
    "jobs_24h": { "queued": 3, "running": 1, "done": 41, "failed": 2 },
    "jobs_7d":  { "done": 287, "failed": 14, ... },
    "credits_spent": { "day": 47, "week": 312 },
    "duration_done_7d": { "avg_ms": 312000, "max_ms": 891000, "n": 287 }
  }
}
13

Webhooks

signed delivery

Pass webhookUrl on submit (single or batch) and PRISM will POST the terminal job (done / failed) to that URL with retries (3 attempts, exponential backoff, 30s per-attempt timeout). HTTPS only; SSRF-blocked against private/loopback/cloud-metadata hosts.

Headers PRISM sends:

POST https://your.app/prism-webhook
content-type: application/json
user-agent: PRISM-Webhook/1.0
x-prism-event:     trace.done   |   trace.failed
x-prism-event-id:  <job uuid>   (idempotency)
x-prism-timestamp: 1714972800
x-prism-signature: t=1714972800,v1=<hex_hmac>

{
  "event": "trace.done",
  "job_id": "8f2c…",
  "status": "done",
  "seed": {...},
  "duration_ms": 287214,
  "records_count": 7,
  "result": {...},
  "error": null,
  "finished_at": "..."
}

Verifying the signature: the secret is sha256(your_api_key). PRISM signs <timestamp>.<rawBody> with HMAC-SHA-256. Reject events older than ~5 minutes.

JavaScript verifier:

import crypto from "node:crypto";

function verifyPrismWebhook(rawBody, header, apiKey, toleranceSec = 300) {
  if (!header) return false;
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!Number.isFinite(t)) return false;
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - t) > toleranceSec) return false;
  const secret = crypto.createHash("sha256").update(apiKey).digest("hex");
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  try {
    return crypto.timingSafeEqual(Buffer.from(parts.v1, "hex"), Buffer.from(expected, "hex"));
  } catch { return false; }
}

Python verifier:

import hashlib, hmac, time

def verify_prism_webhook(raw_body: bytes, header: str, api_key: str, tolerance_sec: int = 300) -> bool:
    if not header: return False
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts.get("t", 0))
    if abs(time.time() - t) > tolerance_sec: return False
    secret = hashlib.sha256(api_key.encode()).hexdigest()
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(parts.get("v1", ""), expected)
14

Status lifecycle

state machine
  1. 1. queued — accepted, debit posted, awaiting worker
  2. 2. running — engine is fanning queries through the BFS frontier
  3. 3. doneresult populated; final state
  4. 3. failederror set; debit refunded automatically
  5. 3. cancelled — operator cancelled; debit refunded
15

Errors

HTTP status codes
codenamemeaning
202acceptedtrace queued; poll the returned id
400bad_requestmissing / malformed JSON, empty seed
401unauthorizedmissing / invalid / revoked bearer
402insufficient_creditstop up to continue · response includes balance + cost
404not_foundpolling a job that doesn't belong to this key
410already_terminalDELETE on a job that already finished/failed/cancelled
413batch_too_largePOST /tracer/batch with >50 items
429rate_limitedtier specified in body · check Retry-After
500internal_errorengine fault · job auto-fails + refunds
16

Doctrine

don't be a creep
PRISM only refracts already-publicrecords. Operators must have a stated lawful basis for every query (debt collection, fraud investigation, journalism, lawful process). Don't use this against private individuals without justification — see the doctrine for what we will and won't enable.