Developers › Ingestion API

Identity & Profiles API

Developer Reference Identity & Profiles API The endpoints behind returning-customer recognition and customer profiles, all on your ingestion key: ask whether a person is already known, follow the pointers back to the lead or the merged customer, resolve a same-person verdict over candidates you supply, and assert a merge LeadLogiX could not infer. Most integrations need only the first two; the standard pipeline already recognises returning visitors and builds profiles on its own.

What each call answers

What each call answers All on X-API-Key, all problem+json, all off until the workspace is enabled. Endpoint What it answers Returns Notes POST /api/v1/identity/lookup Does this workspace already know the person I am speaking to? A verdict and POINTERS — never customer data Searches your leads itself. The task, end to end: Recognise a returning customer . GET /api/v1/leads/{leadId}/context Follow a lookup's leadId The lead, through the paywall and scrub Your workspace only; read contactAvailability first. GET /api/v1/profiles/{profileId} Follow a lookup's customerProfileId The merged customer Read verticalRollups, never the flat identity block. POST /api/v1/identity/resolve Are these candidates the same person as this session? A verdict, confidence and decision band, with the reasoning for every candidate You supply the candidates — up to 50 . POST /api/v2/identity/resolve The same, with the handle to act on The same, plus the identity the session was filed under and how firmly Up to 20 candidates — v2 does far more work per pair. POST /api/v1/profiles/merge Merge these conversations into one person, for a reason LeadLogiX cannot see 202 , a requestId and a statusUrl Asynchronous and zero-trust — Profile merge . GET /api/v1/profiles/merge/{requestId}/status Did the merge finish? QUEUED → RUNNING → DONE or REJECTED Poll until terminal. Off by default: every call answers 403 until LeadLogiX enables it for your workspace Identity resolution and the Customer Profiles API are restricted capabilities, enabled per workspace on request — ask at support@evas.live . Until then identity calls return 403 IDENTITY_API_NOT_ENABLED and profile calls 403 PROFILES_API_NOT_ENABLED , whatever your plan. Every status code is on Ingestion errors & limits ; the lookup and both read-backs share one rate budget.

Resolve: you supply the candidates

Resolve: you supply the candidates v1 and v2 — the batch case, not the live one. You submit the current session plus one or more candidate sessions, each with the evidence you captured, and LeadLogiX answers whether they belong to the same person — a verdict , a confidence and a decision band (a clear match, a borderline “needs review”, or no match). It is a stateless call: LeadLogiX looks nothing up for you. Building an agent that recognises callers mid-conversation? That is the lookup, not this — Recognise a returning customer . v1 v2 Answers Same person or not: verdict, confidence, decision band The same, plus the identity the session was filed under and what was done with it Candidates per call v1 accepts up to 50 v2 up to 20 — far more work per pair Every candidate comes back Yes — including the ones that did not match, with the reasoning for each Yes Authentication X-API-Key , JSON, problem+json refusals Same The candidate cap differs by version Over either limit is 422 TOO_MANY_CANDIDATES ; deciding which candidates are worth sending, and in what order, is the caller's job. Results come back in a deterministic order that is not the order you sent, so match each result to its candidate by session id, never by position. The request and response schema come with access The evidence a resolve call accepts, the response fields and the tuning profiles are not published here. They are part of the integration kit you receive when identity resolution is enabled for your workspace — ask support@evas.live . Send what you captured: a signal you leave out simply does not contribute, and having more kinds of signal on each side resolves more cleanly than having more candidates. Refusals are problem+json — 403 IDENTITY_API_NOT_ENABLED , 404 UNKNOWN_PROFILE , 422 MALFORMED_EVIDENCE , 422 SESSION_ID_MISSING , 422 CANDIDATES_NOT_ARRAY , 422 TOO_MANY_CANDIDATES , 429 RATE_LIMITED ; what each means is on Ingestion errors & limits .

Verdicts, and what to do with each

Verdicts, and what to do with each This is the decision you branch on. Verdict Means Act on it by EXACT_MATCH Same person as one candidate, above the upper band. Resume. The matched session is returned. PROFILE_MERGE_DETECTED Same person, and this links two profiles that were previously separate. Resume, and expect a profile merge. The matched session is returned. POSSIBLE_MATCH Probably the same person, but not enough to act unchallenged. Ask a confirming question. Do not resume. NEW_PROFILE No candidate matches — treat as a new person. Start fresh. SESSION_FORK The same identity appeared somewhere it could not have been — split into separate subjects. Treat as separate subjects; attach no history. INSUFFICIENT_EVIDENCE Too few comparable signals to conclude anything. Collect more evidence and ask again. This is not a NO. A possible match never hands you the matched session, deliberately The matched session is returned only on the two match-band verdicts. A possible match is a challenge-and-confirm signal, never a resume — so the API withholds the handle rather than trusting each caller to remember the rule. Branch on the verdict first; an integration that looks only for a matched session silently does nothing on the one case that most needed handling. Why a strong score can still come back as “no match” A safety rule overrode the arithmetic. The same identity seen in two places too far apart to be one person forks the pair outright; a shared device with contradicting names, contact details reused across different people, or a match that nothing independent corroborates each pull it down a band. The response says which rule fired — log it: it is the difference between “we disagreed” and “we were not allowed to agree”.

What v2 does with the identity

What v2 does with the identity v2 also files the session under an identity — and says how firmly. Outcome When What to do Filed under the matched person A confident match Attach this session to that customer. Filed provisionally, pending reconciliation It matched two or more profiles Usable now but provisional : a background worker reconciles the profiles and the identity may change. Re-read before you rely on it, and never treat it as final in an export. Linked, not merged A possible match Surface it as “possibly related”; never combine the records. New person Nobody matched Treat as a first-time person. Not enough to tell Candidates existed but none could be scored Ask again later with more evidence. Quarantined under its own identity The same identity appeared in two places too far apart Route it for review. Do not attach history. The quarantine identity is derived from the context, so repeat appearances group together instead of polluting a real customer. Store the outcome, not just the verdict The verdict is the reasoning; the outcome is which identity the session ended up under. A record that keeps the outcome can always be re-explained later; one that keeps only the verdict cannot.

Profile merge

Profile merge POST /api/v1/profiles/merge, then poll its status. You submit a new conversation and a list of candidate conversations you believe are the same person. LeadLogiX doesn't take your word for it — it re-fetches each conversation, re-verifies every pair with the identity engine, and merges only the ones the evidence confirms. Uncertain matches are held out; conflicting ones are kept separate. It's zero-trust and asynchronous : you pass conversation IDs (never internal tags or an asserted result), you get back a request id, and you poll for the outcome.

Submitting a merge

Submitting a merge Same auth as ingestion. Submit, then poll the statusUrl until it is terminal. Field Where What it is newConversationId request The anchor — the conversation you just finished. candidateConversationIds request 1–50 prior conversation ids you believe are the same person. Over 50 is 422 TOO_MANY_CANDIDATES . requestId 202 + status Your handle for the job. Poll statusUrl , or build it yourself. status status QUEUED → RUNNING → DONE , or REJECTED . Only those four. profile.profileId status The public id ( cprof_… ). Null until the merge completes. profile.url status A live dashboard link — always current, whatever the summary state. profile.summaryState status PENDING or READY . Not the merge's state: DONE means merged, not summarised — gate on status , use this only to decide whether to show the summary. memberOutcomes status Per-conversation results: which candidates were absorbed and which were not. POST /api/v1/profiles/merge Content-Type: application/json X-API-Key: val_key__… { "newConversationId": "sess_now", "candidateConversationIds": ["sess_prior_1", "sess_prior_2"] } # → 202 ACCEPTED # { "requestId": "pmr_…", "status": "QUEUED", # "statusUrl": "/api/v1/profiles/merge/pmr_…/status" } GET /api/v1/profiles/merge/pmr_…/status # → 200 OK when DONE: # { "status": "DONE", # "profile": { "profileId": "cprof_…", "url": "https://…/?view=customer_profiles&profile=cprof_…", # "summaryState": "PENDING" }, # "memberOutcomes": [ { "conversationId": "sess_prior_1", "action": "CONFIRMED", "confidence": 0.997 } ] } A profile spans lines of business — its economics do not A customer profile is deliberately cross-vertical : one person, one graph, so someone who bought a house last year and is now insuring their life resolves to a single profile. That is the point — cross-sell is only visible because the graph spans lines. But their economics are not comparable across lines. LQI is calibrated per line of business, role vocabularies are disjoint ( BUYER / SELLER versus OWNER / INSURED / BENEFICIARY ), and budget may be a purchase price in one line and a monthly premium in another. So read profile.verticalRollups[] — one entry per line of business, each with its own topLqi , role , stage, intent and recommended channel — rather than the flat profile.identity.{role,budget,financingStatus} and signalRollup.topLqi , which take a newest-wins pick across every line and are retained only for backward compatibility. A budget is null unless exactly one conversation could have produced it. budget and financingStatus describe an errand , not a person: someone who was buying at $230,000 in January and is renting in May has one role and two budgets, and a single field can only answer that by borrowing across errands. So both are populated only when the line of business holds one conversation, and null otherwise. The figure is not lost — it is on profile.dynamic , attached to the conversation and the date that produced it. role is not gated: it is a posture, and the newest one a person stated is the honest answer. processorMode: null on a rollup means the line of business could not be determined. Render it as Unknown — never as Real Estate. profile.verticalRollups // GET /api/profiles/:publicId → profile.verticalRollups "verticalRollups": [ { "processorMode": 2, "label": "REALESTATE", "conversationCount": 3, "topLqi": 84, "role": "BUYER", "budget": null, // 3 conversations — see below "financingStatus": null, "latestStage": "DECISION_STAGE", "latestIntent": "BUY_SOON", "recommended": { "channel": "agent_or_in_app_followup", "windowHours": 24 } }, { "processorMode": 3, "label": "LIFE_INSURANCE_US", "conversationCount": 1, "topLqi": 61, "role": "OWNER", "budget": { "amount": 40, "currency": "USD" }, // 1 conversation — a MONTHLY premium "financingStatus": null, "latestStage": "AWARENESS", "latestIntent": "RESEARCHING", "recommended": { "channel": "agent_or_in_app_followup", "windowHours": 24 } } ] // ⚠ budget is null on the first row because THREE conversations could have produced it, // and a budget belongs to the errand that stated it. Read profile.dynamic for the // per-conversation figures, each with its date. // ⚠ 40 is a MONTHLY premium, not a price. Label a budget from the line of business's own // vocabulary ("Purchase budget" / "Monthly premium"), never as a bare "Budget". // ⚠ recommended is per line of business: the channel and window are vertical-calibrated. A requestId that does not exist and one belonging to another workspace return the same 404 PROFILE_MERGE_NOT_FOUND — distinguishing them would let anyone with a key enumerate other workspaces' merges.

Do you actually need a merge?

Do you actually need a merge? Most teams do not. LeadLogiX already merges conversations into Customer Profiles as part of normal processing — you get profiles without calling anything. This endpoint exists for the case where you know two conversations are the same person for a reason LeadLogiX cannot see: they authenticated in your app, or your CRM already links them. If you want to… Use Why ask whether a caller is already known, during a conversation Guides → Recognise a returning customer That is a read, and it is far cheaper. This page is a write. read a merged customer's history into a model MCP get_customer_profile Returns the composed profile at the caller's entitlement. assert a merge LeadLogiX could not infer This endpoint Even then it re-verifies — LeadLogiX does not take your word for it, and a candidate that fails scoring is rejected rather than merged. let your team work profiles by hand Dashboard → Customer Profiles No API involved.