Everything the API does, in one page.
One core promise: paste a LinkedIn URL and get a fact-checked brief.
Every fact carries a source link and the quote it came from. Broader
interest signals and the personality read are clearly marked as a
read, not a fact. A fresh enrichment usually
takes 45–90s (occasionally up to ~2–3 min), so the primary flow is
asynchronous: submit to
/api/enrich, then poll /api/jobs/:id.
Cache hits return inline, instantly. The deprecated
/api/brief compatibility alias follows the same async
paid-admission contract.
Quickstart
Verify a named client, claim its key once, then buy credit before submitting a real enrichment.
curl -X POST https://uniquenessengine.com/api/signup \
-H 'content-type: application/json' \
-d '{"email":"you@work.com","client_name":"My integration","device_name":"Laptop"}'
# -> 202 { "status":"email_verification_required",
# "transaction_id":"...", "polling_token":"...", "expires_at":"..." }
# Approve the named request from email. Poll /api/signup/verification/:transaction_id
# with x-verification-token, then POST .../:transaction_id/claim with the same
# header. The approved key is shown exactly once and is named for client_name.
# Signup grants no live credit. If email delivery returns 503, honor Retry-After
# and start a new verification transaction; no key was created.
# Buy a permanent pack from /account. First offer: 5 credits for
# $5 USD subtotal plus applicable tax. Then submit:
# 1. submit -> 202 { "job_id": "...", "status": "queued" }
# (or 200 { "status": "done", "brief": {...} } on a cache hit)
curl -X POST https://uniquenessengine.com/api/enrich \
-H "authorization: Bearer uq_live_..." \
-H "content-type: application/json" \
-d '{"linkedin_url":"https://www.linkedin.com/in/<their-handle>"}'
# 2. poll every ~5s until status is done | refused | failed
curl https://uniquenessengine.com/api/jobs/<job_id> \
-H "authorization: Bearer uq_live_..."
# -> { "status": "done", "result": { ...the brief... } }
Prefer not to write curl by hand? Point an AI coding agent at /agents — it does this whole flow for you.
Want to see a full brief before spending a credit?
GET /api/examples/kitchen-sink returns a no-key,
fictional static fixture ("Jordan Ellis",
mode:"synthetic_fixture"). It never performs a live
lookup and is not a trial enrichment.
Response shape & the detail param
Briefs default to full in production. Pass
?detail=compact explicitly for identity, the top-ranked
safe_to_reference signals (each with its
evidence_quote + source_url), one
personality.how_to_communicate recommendation, the
compliance labels, and a capabilities block. Pass
?detail=full explicitly (or send
"detail":"full" in the JSON body) for the complete
brief — every fact array, the ranking sub-scores, and the top-level
evidence[].
Set detail explicitly to pin an
integration rather than depending on the production default. An
invalid detail value
returns 400 invalid_detail. Async
/api/enrich responses include a poll_url
that carries your resolved detail.
The capabilities block honestly declares what the API
does NOT return: outreach_angles:"removed" and
gift_hook:"disabled" (decommissioned), plus
giftable_entities /
negative_preferences (enabled|disabled
per flag), the resolved detail, and
full_available. The removed per-fact
safe_to_use_in_outreach stamp is gone too — we verify
and label facts; we don't tell you what's safe to send. Brief-body
schemas and examples are derived from the serializer; endpoint
envelopes, errors, and descriptive text are maintained separately.
The complete contract is served at
/openapi.json.
Authentication
POST /api/signup starts an opaque, named email
verification transaction. Approval mints a revocable bearer key,
shown exactly once by the claim endpoint; signup never grants a live
enrichment credit. The
account is also accessible through the passwordless /app
dashboard and legacy /account flow, and keys can be
rotated without replacing the account. Send a key as
Authorization: Bearer <key> on every
authenticated call.
Keys are server-side secrets. Never put one in client-side/browser code. If a key ever leaks, rotate it immediately — see rotate-key below.
Programmatic clients should call /api/* directly —
website pages are bot-protected and may 403 a bot-like User-Agent,
but API routes aren't. Sending a descriptive
User-Agent naming your tool or agent is good API
citizenship and helps us debug, though it isn't required — default
library User-Agents work fine on /api/*.
Endpoints
Start named email verification for a new revocable key. New and returning emails receive the same opaque response shape. No key, account details, target, or live credit appears in this response.
curl -X POST https://uniquenessengine.com/api/signup \
-H 'content-type: application/json' \
-d '{"email":"you@work.com","client_name":"Clay workspace","device_name":"Production"}'
# 202
{
"status": "email_verification_required",
"transaction_id": "...",
"polling_token": "...",
"expires_at": "..."
}
# After deliberate approval, poll GET /api/signup/verification/:transaction_id
# and claim once with POST /api/signup/verification/:transaction_id/claim.
# Send x-verification-token: <polling_token> to both.
# A verification_delivery_failed 503 returns no polling secret: honor Retry-After
# and repeat this request to create a fresh transaction.
The core endpoint. Submit one person; a fresh enrichment usually runs
45–90s server-side (up to ~2–3 min), so this returns immediately
with a
job_id to poll. Accepts linkedin_url, or
name (+ optional company,
domain, title), or a work
email. One exact credit allocation is
reserved on submit and released if the brief refuses
(needs_review) or the job fails — you are only charged
for a resolved brief. Re-submitting the same person is deduped and
served from cache.
Profiles and public social posts (X, Instagram, TikTok, Threads) are read from multiple independent public-data providers with automatic fallback, so a single provider outage doesn't drop coverage. When fewer than three verified personal facts remain, additional verified public-source lanes engage automatically before any paid source. Every candidate must pass the same identity and fact checks.
Discovery hard-denies FEC, court, obituary, wedding, and
people-search URLs, strips addresses, and drops any page that does
not name the target. Health, religion, politics, family/children,
and other sensitive categories are surfaced with a
category label, not blocked — see
sensitivity labels below. Only
home/physical addresses and private contact info are never surfaced.
curl -X POST https://uniquenessengine.com/api/enrich \
-H "authorization: Bearer uq_live_..." \
-H "content-type: application/json" \
-d '{"linkedin_url":"https://www.linkedin.com/in/<their-handle>"}'
# 202, queued -- poll /api/jobs/:id
{ "job_id": "5be41fd7-...", "status": "queued" }
# 200, cache hit -- brief returned inline, no polling needed
{ "status": "done", "brief": { ...the brief... } }
Poll the job you submitted. Poll every ~5s. Terminal statuses are
done (brief in result),
refused (returned a needs_review brief, no
charge), and failed (error set, no
charge). queued / running mean keep
polling.
curl https://uniquenessengine.com/api/jobs/5be41fd7-... \ -H "authorization: Bearer uq_live_..." # 200, done -- the resolved brief { "status": "done", "result": { "status": "ok", "identity": { "full_name": "...", "title": "...", "company": "...", "identity_confidence": 0.92, "metadata_uncertain": false }, "safe_to_reference": [ { "claim": "...", "evidence_quote": "quote from the source", "source_url": "...", "connection_type": "personal_signal", "category": "family_or_children", "special_category": false, "mentionability": 0.9, "band": "safe" } ], "personality": { "archetype": "...", "disc_label": "...", "how_to_communicate": ["..."], "predicted": true }, "capabilities": { "outreach_angles": "removed", "gift_hook": "disabled", "detail": "compact", "full_available": true }, "usage": { "cache_status": "hit|miss", "credits_charged": 1, "credits_remaining": 9 } } } # explicit compact example; production defaults to full # 200, refused -- brief returned but withheld; exact reservation released { "status": "refused", "result": { "status": "needs_review", "usage": { "credits_charged": 0 } } }
Health, recovery, religion, politics, marital status, and other
sensitive facts are surfaced — labeled with
category, not withheld; see
sensitivity labels. Only home/physical
addresses and private contact information are
never surfaced.
Compatibility alias for /api/enrich. It uses the same
paid admission, returns 200 {status:"done",brief} on a
cache hit, or 202 {job_id,status:"queued",poll_url} for
uncached work. New integrations should use
/api/enrich. GET with query params works the same as
POST with a JSON body.
curl -X POST https://uniquenessengine.com/api/brief \
-H "authorization: Bearer uq_live_..." \
-H "content-type: application/json" \
-d '{"linkedin_url":"https://www.linkedin.com/in/<their-handle>"}'
# -> 200 inline cache hit, or 202 job_id + poll_url
connection_type splits personal vs. professional
signal. Facts in safe_to_reference carry a source link
and the quote they came from. aggregated_interests
contains broader interest signals, clearly marked as a read, not a
fact.
negative_preferences remains in the full schema but is
currently disabled and normally returns [].
Credit balance, billing status, and recent usage for your key.
curl https://uniquenessengine.com/api/account -H "authorization: Bearer uq_live_..."
# 200
{
"account_id": "...",
"credits_remaining": 8,
"subscription_credits": 0,
"grandfathered_credits": 2,
"service_recovery_credits": 1,
"purchased_credits": 5,
"total_credits": 8,
"debit_order": ["plan credits with the nearest expiry", "grandfathered credits",
"service-recovery credits", "oldest purchased credits"],
"recent_usage": [ { "endpoint": "/api/enrich", "cache_status": "hit", "credits_charged": 1, "status": "success" } ]
}
Revokes the key you authenticated with, immediately, and returns a
fresh one — same account, same balance. If the key is lost, sign in
by email through /account and rotate all account keys.
curl -X POST https://uniquenessengine.com/api/account/rotate-key \
-H "authorization: Bearer <old key>"
# 200 -- old key is dead the instant this returns
{ "api_key": "uq_live_new...", "key_prefix": "uq_live_a1b2",
"message": "Old key is revoked immediately. This new key is shown once — save it now." }
Canonical, read-only pack and subscription terms consumed by the CLI and other clients. It contains no account, target, credential, Checkout action, or purchase.
curl https://uniquenessengine.com/api/billing/catalog
# -> { "commercial_contract":"hard-paid-v1", "packs":[...],
# "subscriptions":[...], "account_url":"https://.../account" }
Pack purchases and subscriptions begin from the passwordless Account page. These bearer endpoints save a payment method without enabling auto-recharge, or open Stripe's management portal.
# CLI discovery remains read-only until a human confirms a browser action
uniqueness billing packs
uniqueness billing subscriptions
uniqueness billing status
uniqueness billing auto-recharge enable|disable|retry
uniqueness billing portal
curl -X POST https://uniquenessengine.com/api/billing/setup -H "authorization: Bearer uq_live_..." # -> { "setup_url": "https://checkout.stripe.com/..." } # Saving a method makes no purchase and never enables auto-recharge. # Auto-recharge is a separate, explicit opt-in in Account. curl -X POST https://uniquenessengine.com/api/billing/portal -H "authorization: Bearer uq_live_..." # -> { "portal_url": "https://billing.stripe.com/..." } (cancel, update card, invoices)
Service status. Deliberately minimal — no vendor/stack detail.
curl https://uniquenessengine.com/api/health
# { "ok": true, "service": "uniqueness-engine", "time": "..." }
Rate a brief 👍 / 👎 so the engine keeps improving.
Pass rating ("up" or "down")
and the brief's identity.canonical_id (or the
job_id), with an optional note. This is a
quality signal only — it never charges or refunds a credit.
curl -X POST https://uniquenessengine.com/api/feedback \
-H "Authorization: Bearer $UNIQUENESS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"rating":"up","canonical_id":"<from brief.identity.canonical_id>","note":"nailed the personal signal"}'
# { "ok": true }
Also available as the submit_feedback MCP tool and the
uniqueness feedback up|down --canonical-id <id>
CLI command.
Got a delivered brief that missed? The account
owner can use Return credit from the passwordless
Account page or
POST /api/account/return-credit. This returns one
credit, never cash: once per delivered paid brief within 90 days,
capped at 10 in a rolling 90 days, with a required reason. Refusals
and failed runs cost 0. Cash refunds remain support-controlled.
Use in Clay, Freckle & no-code tools
No-code enrichment tools send one HTTP request per row and can't
poll. Pass a callback_url and we POST the finished
brief straight to that URL when it's ready — one column, no polling.
Clay (recommended: HTTP API → wait for webhook)
- Add an HTTP API enrichment column.
-
Method
POST, URLhttps://uniquenessengine.com/api/enrich, headerAuthorization: Bearer <your key>. -
Body:
{"linkedin_url": "{{LinkedIn URL}}", "callback_url": "<your Clay/webhook URL>"} -
We reply
202 {"status":"accepted"}, then POST the brief to yourcallback_urlwhen it finishes (usually ~45–90s). Map the fields from the table below.
curl -X POST https://uniquenessengine.com/api/enrich \
-H "authorization: Bearer $UNIQUENESS_API_KEY" \
-H "content-type: application/json" \
-d '{"linkedin_url":"https://www.linkedin.com/in/<their-handle>","callback_url":"https://webhook.site/<id>"}'
# -> 202 { "status": "accepted", "delivery": "callback" }
# then, when ready, we POST to your callback_url:
# { "event":"brief.completed", "job_id":"...", "status":"ok",
# "input":{"linkedin_url":"..."}, "credits_charged":1,
# "credits_remaining":41, "brief":{ ...redacted brief... } }
The callback_url must be a public https URL. We
never charge for a refusal (needs_review) or failure —
those still fire a callback (brief.refused /
brief.failed, brief:null) so your row
never hangs. If you set WEBHOOK_SIGNING_SECRET, each
callback carries X-UE-Timestamp +
X-UE-Signature: sha256=hmac(ts.body) to verify.
No webhook? Poll the job endpoint
Tools that can't receive a webhook should submit to
POST /api/enrich, then poll
GET /api/jobs/:id until terminal. Cache hits still
return inline. The deprecated /api/brief alias follows
that same pattern.
Field map — what to pull from the brief
| Field | What it is |
|---|---|
status |
ok or needs_review |
identity.full_name / title / company /
identity_confidence
|
Who we resolved + confidence (0–1) |
safe_to_reference[].claim / evidence_quote / source_url
|
The headline facts — each with a source link and the quote it
came from. Also connection_type,
mentionability, band.
|
aggregated_interests[].label / source |
Broader interest signals — a read, not a fact |
personality.archetype / primary_disc / disc_label
|
The personality read — a read, not a fact — plus how to open |
negative_preferences[].entity / sentiment / use_case
|
Present in the full schema but currently disabled; normally
returns [].
|
credits_charged / credits_remaining (callback
envelope)
|
Spend tracking — on the callback wrapper, not the brief |
safe_to_reference[].category / special_category
|
What KIND of fact this is (e.g. family_or_children,
health, authored_work) — see
sensitivity labels below.
|
capabilities |
What the API deliberately does NOT return —
outreach_angles:"removed",
gift_hook:"disabled" — plus the resolved
detail level. The removed
safe_to_use_in_outreach stamp is declared here and
never returned; we label facts, we don't advise on use.
|
Sensitivity labels
Every fact in safe_to_reference (and in
public_context, where present) carries a
category — what KIND of fact it is. Most categories are
non-sensitive (hobby_public, alma_mater,
authored_work, and similar) and need no special
handling. A smaller set is
sensitive but still surfaced:
family_or_children, health,
religion, politics, legal,
financial_distress,
fundraiser_beneficiary, trauma_or_crisis,
protected_attribute, sexual_orientation,
and death_or_grief.
Sensitivity is a label, not a block. An
explicitly-stated sensitive fact (never inferred from a photo, name,
or proximity alone) is extracted, verified, and surfaced with its
category so you — the operator — can decide how to use
it, or whether to skip it. GDPR Art.9 categories
(health, religion, politics,
protected_attribute, sexual_orientation)
additionally carry special_category: true. Only two
categories are a hard block and never leave the server:
home_location (physical addresses) and
private_contact (phone/personal email/SSN) — the
doxxing/physical-safety floor. The wrong-person identity gate is the
one other absolute lock; it is unrelated to sensitivity.
We label; we don't advise on use. The old
safe_to_use_in_outreach stamp has been removed from the
contract (declared in capabilities, never returned) —
deciding what is "safe to send" put us in the position of advising
use, which is your call, not ours. Facts carry factual labels only
(category / special_category, attribution
tier, confidence band); how you use a fact is yours to decide,
informed by those labels. Publish-gating (what we surface) is not
use-gating (what you send).
{ "claim": "He coaches his daughter's soccer team on weekends.",
"evidence_quote": "I coach my daughter's soccer team on weekends",
"source_url": "...", "connection_type": "personal_signal",
"category": "family_or_children", "special_category": false,
"attribution_tier": "self_stated", "band": "safe", "mentionability": 0.9 }
Billing & credits — the exact rules
| Question | Answer |
|---|---|
| Is there a free live trial? | No. Jordan Ellis is a fictional static fixture. Every real enrichment starts only when paid credit is available. |
| How do I start? | The repeatable 5-Pack is five permanent credits for a $5 USD subtotal plus applicable tax, with no subscription. Larger permanent packs are 20/$7.40, 50/$18.50, and 100/$37. |
| What costs a credit? |
A successful first unlock costs 1 credit, including a global
cache hit. A same-account re-pull costs 0 unless
refresh:true.
|
| What consumes no credit? |
needs_review, invalid input, auth errors, and
re-pulling a person your account has already unlocked before —
even if that first pull happened weeks ago.
|
| Someone else looked up the same person first — free for me too? |
No. The engine's cache makes it fast and cheap for us, but your
account still pays once on your own first lookup of that person.
After that, it costs 0 for your account unless
refresh:true.
|
| Out of credits — what happens? |
402 on new lookups. Re-pulling someone you've
already unlocked still works unless refresh:true.
The response recommends one sufficient permanent pack through
a short-lived Checkout action and links to Account. The unpaid
recovery path stores only HMAC
retry metadata, not the raw target.
|
| Is recharge automatic? |
No by default. Optional auto-recharge requires separate,
versioned consent for 20 credits/$7.40 plus applicable tax. A
saved card alone never enables it. It triggers only after an
exact delivered debit crosses from at least 3 credits to below
3; a failed attempt becomes action_required with no
automatic retry.
|
| Rate | The 5-Pack is $1/credit; larger packs are $0.37/credit. Monthly plans remain Starter $15/mo (45 credits), Growth $40/mo (130 credits), and Pro $90/mo (300 credits). All prices are USD subtotals plus applicable tax. |
| Cancel |
Stripe Customer Portal — POST /api/billing/portal.
|
Rate limits
Per API key: 15 requests/10s burst, 100 requests/60s sustained on
the enrichment endpoints (/api/enrich,
/api/brief). Polling /api/jobs/:id is
cheap and not rate-limited on the same budget. Per IP: 5
signups/hour. A 429 means slow down — it doesn't help
to mint a new key.
Errors
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_linkedin_url |
Not a linkedin.com/in/<handle> URL, or malformed input. |
| 400 |
invalid_email / email_not_allowed
|
Bad shape, or a reserved/disposable address. |
| 401 | unauthorized |
Missing/invalid/revoked key. |
| 402 | payment_required |
Canonical insufficient-credit response with exact required/available counts, an expiring sufficient pack offer, a billing URL, and idempotent retry metadata. |
| 429 | rate_limited |
See rate limits. |