# LeadLogiX — Full Documentation > GENERATED FILE — do not edit by hand. > Produced from the documentation source by scripts/build_doc_index.mts. > Edit the TSX under src/components/dashboard/docs/ and run `npm run docs:index`. Documents: 46. Sections: 336. Words: 75500. ## Overview ### LeadLogiX Tour URL: /docs/leadval-tour The LeadLogiX Platform Talk to everyone. Know exactly who's worth it — and prove why. Custom AI Agents hold real conversations wherever interest starts — your site, your ads, a phone number. LeadLogiX is what reads them: it verifies who is behind each one, prices it with math you can audit, and remembers them when they return. Every decision recorded 5 integrity checks per lead AI reads, code decides 0 unexplained numbers Contact sales or try the API Playground Agents that act, not avatars Answer from your catalogue, run the page, handle the objection, book the appointment — and summon a human the moment one is worth it. Pay only for real people Every lead runs an underwriter's gauntlet. Fail one hard gate and it is suppressed outright — never sold cheap. Numbers you can defend AI reads the conversation; deterministic code sets every score and price, and shows its working. The whole customer, one profile Every conversation, ticket and CRM record — pre-sale through renewal — assembled into one evidence-verified profile. #### One engine, calibrated per line of business Anchor: /docs/leadval-tour#one-engine-calibrated-per-line-of-business The platform One engine, calibrated per line of business A strong property enquiry looks nothing like a strong life-insurance application — so LeadLogiX never scores them with one ruler. Each line of business gets its own calibration: its own fact set, its own role vocabulary, its own cohorts and price ladder. Scores are never mixed across lines. Real Estate Budgets, financing, target property, viewing intent. Life Insurance (US) Product type, coverage amount, term length, residence state. Your line of business Built and calibrated with you. The engine is line-agnostic; the calibration never is. The roles differ too: in real estate buyers are measured against buyers and sellers against sellers, while life insurance tells the policy owner, the insured and the beneficiary apart. New lines are added by calibration, not by rebuild — which is why the list keeps growing. Every demonstrator on this page follows your choice, and each one is labelled with the line it shows. #### From hello to handoff Anchor: /docs/leadval-tour#from-hello-to-handoff Act I · The promise From hello to handoff Five stages, three owners: your AI Agents engage and capture, LeadLogiX understands and prices, your tools receive. Click through the journey. Voice, video, and chat conversations Tool actions captured — products opened, appointments booked Marketing attribution kept with every lead #### Verified like an underwriter, not a web form Anchor: /docs/leadval-tour#verified-like-an-underwriter-not-a-web-form Act II · The lead Verified like an underwriter, not a web form Before a lead is worth anything, LeadLogiX proves someone real is behind it — and that you can actually reach them. Every email and phone runs a gauntlet of independent checks, from syntax to carrier records. Pick a lead and run it. Disposable domains, fake patterns, and VoIP lines flagged Stated name cross-checked against carrier records Suspect ≠ rejected — doubt is scored, on the record #### It knows when someone comes back Anchor: /docs/leadval-tour#it-knows-when-someone-comes-back Act II · The lead It knows when someone comes back Most conversations arrive nearly anonymous — a first name, a mobile number, a shared device. LeadLogiX resolves them into people: Tuesday's phone call and Friday's quote request read as one person, one story. The identity record on a real lead Record-linkage science, not vibes Matching is built on the Fellegi–Sunter record-linkage framework — the same statistical approach used in census and clinical de-duplication — corroborated across independent evidence channels, with speaker separation untangling multi-person conversations. #### See what the visitor actually did Anchor: /docs/leadval-tour#see-what-the-visitor-actually-did Act II · The lead See what the visitor actually did Words say “interested.” Actions say “compared three plans and booked a consultation.” LeadLogiX reconstructs the visit from the Agent's tool activity, concludes where the visitor is in their journey, and keeps the marketing source attached. The journey, rebuilt step by step Where they are — with confidence Which campaign produced them Attribution that pays for itself Every lead carries its source, campaign, and click IDs — so you can finally see which ad money produces leads that actually close, not just leads that exist. #### Scoring you can defend — not a black box Anchor: /docs/leadval-tour#scoring-you-can-defend-not-a-black-box Act III · The number Scoring you can defend — not a black box Most AI lead scoring asks you to trust a number nobody can explain. LeadLogiX splits the work: AI reads the conversation, and auditable code does all the math. Same inputs, same score — every time, with the working shown. Monotonic: a positive signal never lowers a score Versioned & timestamped — recomputable line by line #### Every estimate gets a second opinion Anchor: /docs/leadval-tour#every-estimate-gets-a-second-opinion Act III · The number Every estimate gets a second opinion Behind every score sits the Statistical Machine: a cohort of comparable real leads from the same line of business, weighted for likeness and freshness, outliers capped. Every AI reading is cross-examined against that baseline, disagreements are resolved on the record, and the baseline moves as the cohort grows. #### Only real, reachable leads get through Anchor: /docs/leadval-tour#only-real-reachable-leads-get-through Act III · The number Only real, reachable leads get through Five integrity dimensions — fraud, geographic fidelity, conversational authenticity, financial credibility, intent certainty — each judged twice and fused. Cross the line on any one of them and the lead is suppressed outright, no matter how high it scored. Flip the five integrity checks yourself on Quality & Trust . #### Priced like a portfolio, not a gut feel Anchor: /docs/leadval-tour#priced-like-a-portfolio-not-a-gut-feel Act III · The number Priced like a portfolio, not a gut feel A lead's price is an expected-value calculation: what the deal is realistically worth, times how likely it is to close, cut to a slice you can rationally spend. Walk the chain. The AI never sets a price. Ever. The AI reads the conversation and hands over bounded, validated signals. The price itself is computed by plain, versioned code from what was recorded — so it can be reproduced, and defended, line by line. Every dollar traces to expected deal value × probability Unpriceable leads are suppressed — never mispriced #### Leads are conversations. Customers are stories. Anchor: /docs/leadval-tour#leads-are-conversations-customers-are-stories Act IV · The customer Leads are conversations. Customers are stories. Real buyers come back — and most platforms hand you each return as another anonymous lead. LeadLogiX recognizes the person, verifies the match, and assembles everything they've discussed into one Customer Profile. Watch three sessions become the whole story. Every link re-verified, shown with its confidence Uncertain matches are held out — never forced in Stage, budget, and score tracked across visits #### The whole relationship, on one screen Anchor: /docs/leadval-tour#the-whole-relationship-on-one-screen Act IV · The customer The whole relationship, on one screen A profile isn't a summary card — it's the full record a salesperson works from: verified identity and preferences, every conversation with its journey stage and follow-up steps, the complete activity trail of what they searched and viewed, and where they came from. Open the tabs; expand a conversation. Detailed conversation histories with CJ stage + confidence Per-conversation activity: searches, views, service requests, live-rep handoffs Every fact traces to a conversation — nothing invented #### Knowing your customer changes the whole business Anchor: /docs/leadval-tour#knowing-your-customer-changes-the-whole-business Act IV · The customer Knowing your customer changes the whole business One lead closes one deal. Knowing who your customers really are — and what they prefer — reshapes marketing, sales, product, and attribution. Full-journey marketing attribution — which campaigns make real buyers Sharper audiences, warmer sales calls, a roadmap from real demand Every touch feels personal — the profile remembers Pick a goal and see which part of a profile answers it on Customer Profiles . #### The right lead reaches the right person Anchor: /docs/leadval-tour#the-right-lead-reaches-the-right-person Act V · The workflow The right lead reaches the right person Tags segment your pool: applied by AI, by deterministic rules, by hand, or by automations — each assignment recorded with its source. Assign tags to an agent and the server itself scopes what they see. AI, rule, manual, or automation — every assignment auditable Tags also drive automations, alerts, and CRM filters #### Built for both sides of the desk Anchor: /docs/leadval-tour#built-for-both-sides-of-the-desk Act V · The workflow Built for both sides of the desk Agents get everything to make the call — a brief, the best channel, timing, next steps, and verified ways to reach them. Procurement gets everything to make the buy — score, price, expected value, and routing. Same lead, each role in its own language. The agent's Action Center on a real lead #### Your numbers, not benchmarks Anchor: /docs/leadval-tour#your-numbers-not-benchmarks Act V · The workflow Your numbers, not benchmarks Connect your CRM's closed-deal history and LeadLogiX projects what each lead is worth to your business — your close rates, your deal sizes, segment by segment. It's kept strictly separate from the objective score and price, which stay the same for everyone. The estimate card on a real lead #### Plays well with your stack Anchor: /docs/leadval-tour#plays-well-with-your-stack Act VI · The platform Plays well with your stack Results don't sit in a dashboard waiting to be exported. They flow — to your CRM exactly once, to your chat channels the moment a lead runs hot, to inboxes on your own sending domain, and to anything else through the API. #### Run it your way — and prove it Anchor: /docs/leadval-tour#run-it-your-way-and-prove-it Act VI · The platform Run it your way — and prove it Give every team, brand or region its own workspace under one bill, with a per-team breakdown on each invoice. Roles are enforced on the server, and privacy is engineered in — not promised. #### What you can check, and what it costs Anchor: /docs/leadval-tour#what-you-can-check-and-what-it-costs Act VI · The platform What you can check, and what it costs Nothing here is a benchmark or a customer quote. These are properties of how the platform is built, each one something you can inspect on a lead or an invoice. A scoring ledger on every lead Versioned and time-stamped: the inputs, the engine version, and every statistic that could not be computed, by name. Recomputable from the record The same recorded inputs return the same score and price. Pricing is plain code with no AI in it. Suppressed means unsold A lead that fails an integrity gate is never offered for sale and never billed. Delivered to your CRM exactly once Each lead is delivered, confirmed, then marked — never sent twice. How plans work There are two plans. On Pay-Per-Lead, each lead you procure is billed at its recorded price. On Unlimited, a subscription covers every lead, and each one is still itemized on the invoice at $0.00. What a plan costs for your volume is quoted on request; how invoices are built is in Billing & Plans . Talk to us about your line of business Tell us what you sell and how a good customer usually arrives. If your line is already installed, we calibrate it to your market; if it is not, we build and calibrate it with you. Contact sales ### The AI Agents URL: /docs/agents Product · Engage Agents that do the work. Not avatars that do the smiling. Anyone can put a face on a chat widget. What matters is what the agent can do: answer, show, verify, book — and bring in a human at the exact moment one is worth it. #### Built with you, run as one platform Anchor: /docs/agents#built-with-you-run-as-one-platform Built with you, run as one platform The suite EVAS Avatar Studio puts a voice-and-video agent wherever your customers already are — websites and landing pages, inside your app, on social, even inside an email; EVAS Voice Studio answers a phone number and texts back. Both hold the conversations, both are built around what you sell — your questions, your tone, your hard rules — and both stream into LeadLogiX, which holds no conversation of its own. One suite, one bill — and LeadLogiX also reads an agent you already run, a CRM or a help desk, because it is the intelligence layer and not the mouth. #### It runs the page while it talks Anchor: /docs/agents#it-runs-the-page-while-it-talks It runs the page while it talks In the conversation A chat widget answers and waits. An agent with control of the page can do the thing it just described. Opens the right page Puts what is being discussed in front of them. Answers from your catalogue Reads the answer back instead of returning results. Books the appointment Inside the conversation, not via a calendar link. Fills in the form Asks only for what it does not already know. Recognises a returning visitor Picks up where the last conversation ended. Verifies who someone is Before it says anything account-specific. Handles the objection "No approval yet" becomes steps, a link, a name to call. Surfaces the next step A call booked, the right specialist matched — in the conversation. Uses what it knows Prior sessions applied live. Nobody repeats themselves. Built around what you sell — your questions, your tone, your rules about what it may never say. Yours to change afterwards. #### A person, mid-conversation Anchor: /docs/agents#a-person-mid-conversation A person, mid-conversation The handoff Not a calendar link — a rep in the same session, while the question is still live. This is the part that shortens a sales cycle. In thirty minutes they are a meeting invite. By Tuesday, a no-show. In thirty seconds, they are a conversation — summoned by high intent, detected live. The rep arrives briefed Nobody re-asks the qualifying questions. Most people never need one Reserving the human for the few who do is what makes the human affordable. #### Wherever the interest starts Anchor: /docs/agents#wherever-the-interest-starts Wherever the interest starts Reach An avatar is not a website widget. The same agent runs on your site and landing pages, inside your own app, on a social profile, inside an ad and inside an email — so someone can be qualified before they ever reach your website, with the source recorded natively. Your site and landing pages Inside your own app Social profiles and posts Inside an email Inside an ad A phone number, answered at 2am Print and signage #### The numbers behind the design Anchor: /docs/agents#the-numbers-behind-the-design The numbers behind the design Why not a form The industry's figures, not ours. They are here because they explain the shape of the product. 5 min A lead answered within five minutes qualifies at many times the rate of one answered after thirty. An agent answers in seconds. ~70% Most of a buyer's decision is made before they ever speak to a seller. 5–25× The cost of winning a customer versus keeping one — so an agent that works only the top of the funnel solves the cheaper half. #### What it will say, and what it keeps Anchor: /docs/agents#what-it-will-say-and-what-it-keeps What it will say, and what it keeps Governance An agent talking to your customers unsupervised is a governance question before it is a sales one. It only says what you approved The disclosure is on the record Which text was shown, what they answered, and when. Consent is recorded, not enforced What a refusal means for your outreach stays your policy. Personal details are shown by role Nothing is kept forever Audit trails carry a floor no setting can go below. Leaving is supported A closed workspace takes its data with it. #### One holds the conversation, one judges it Anchor: /docs/agents#one-holds-the-conversation-one-judges-it One holds the conversation, one judges it The division of labour A clean split, on purpose: the agent holds the conversation; LeadLogiX judges what it was worth. Swap either side without losing the other. ### Lead Intelligence URL: /docs/intelligence Product · Know A click tells you which ad was pressed. A conversation tells you why. What someone wants, what would stop them buying, and what they compared you against — because they said so, not because a model inferred it. #### A transcript is not a lead Anchor: /docs/intelligence#a-transcript-is-not-a-lead A transcript is not a lead The hard part A form hands you five clean fields. A conversation hands you twenty free-form minutes: two stakeholders, background noise, a budget named three times with three different numbers. Summarise that and you get a tidy paragraph of the same mess. Speakers separated Who said what, and who decides. Conflicting mentions reconciled Evidence recorded, not averaged away. Intent tracked as it moves Not snapshotted at the end. Every signal bounded Validated before it touches a score. The difference between what was said and what it is worth. #### One conversation, five answers Anchor: /docs/intelligence#one-conversation-five-answers One conversation, five answers The yield All of it from a single finished conversation, and none of it inferred from a click path. #### Qualified while they are interested Anchor: /docs/intelligence#qualified-while-they-are-interested Qualified while they are interested Qualification The agent asks what a good rep would ask: what, by when, on whose budget, and who else decides. Nothing waits in a queue to be qualified on Monday. #### Attribution nothing else can produce Anchor: /docs/intelligence#attribution-nothing-else-can-produce Attribution nothing else can produce Attribution Every other model infers intent from behaviour. This one has the buyer's own sentence. Why they came Not the last touch that happened to be measurable. What nearly stopped them The objection in their words, countable across every conversation this month. Which campaign produced customers Not which produced clicks. Who they compared you against Named by the buyer, not guessed from a referrer. #### Where the next hour should go Anchor: /docs/intelligence#where-the-next-hour-should-go Where the next hour should go Prioritisation The point of scoring is choosing between forty leads, not describing one. Each lead arrives with a quality index, a value calibrated against deals you actually closed, and its history — how many conversations, what changed between them, whether this person has bought before. The rep opens a sorted list. The statistics under that index are set out in Scoring & Pricing ; how the numbers on an open lead explain themselves, and which leads they are compared against, is in Working a Lead . #### Into the tools your team already uses Anchor: /docs/intelligence#into-the-tools-your-team-already-uses Into the tools your team already uses Where it lands Leads sync to your CRM under the fields and tags you choose; hot ones raise an alert on the channel the team already watches. A judgement nobody sees changes nothing. #### A brief, not a transcript Anchor: /docs/intelligence#a-brief-not-a-transcript A brief, not a transcript The deliverable Every conversation ends as a brief: what they explored and liked, what they asked, what was scheduled, and the next step — organised for whoever picks it up. Follow-up starts warm. ### Customer Profiles URL: /docs/profiles-product Product One profile. The whole customer. Every conversation your agents hold, every ticket your help desk closes, every record your CRM already has — one living profile: history, preferences, likes, dislikes. From first hello through support and renewal, the relationship never starts over. #### From anonymous to known Anchor: /docs/profiles-product#from-anonymous-to-known From anonymous to known Most conversations arrive nearly anonymous — a first name, a noisy line, a stakeholder who is not the buyer. LeadLogiX resolves them into people, corroborated across independent evidence, every match carrying its confidence. Never blind-merged. #### What a profile holds that a lead does not Anchor: /docs/profiles-product#what-a-profile-holds-that-a-lead-does-not What a profile holds that a lead does not Real buyers rarely decide in one conversation, so their history is spread across several. You see the arc, not a snapshot A single conversation says "browsing, maybe $500k." The profile says the budget grew 30%, the stage moved awareness → decision, and the score climbed 52 → 88. That trajectory is the buying signal. No more cold opens Your agent picks up the third conversation already knowing the timeline, the budget, and the constraint that actually decides it — because the first two conversations came with it. Verified, never guessed Conversations are linked by returning-visitor recognition and matching conversation evidence, re-verified before any merge — and every link shows its confidence, right on the profile. Value you already paid for Those earlier conversations are already in your workspace. Profiles turn them from forgotten rows into the context that closes the current one. #### Watch three conversations become one customer Anchor: /docs/profiles-product#watch-three-conversations-become-one-customer Watch three conversations become one customer Three anonymous sessions on three days — most tools log three mediocre leads. Recognised as one person, they become one decision-stage customer with a rising score, a need that grew between visits, and a request to talk — with how sure the match is shown on every link. Click through that moment, in real estate or life insurance, on the LeadLogiX Tour . #### Fed by everything you connect Anchor: /docs/profiles-product#fed-by-everything-you-connect Fed by everything you connect A profile is only as complete as what reaches it. CRM, help desk, data lakes, backend systems — connected securely and compliantly, scoped to exactly what your deployment needs. Every imported fact carries its source; identity matching runs with the same evidence-verified rigor as conversations. #### Before the sale, and long after Anchor: /docs/profiles-product#before-the-sale-and-long-after Before the sale, and long after A lead is a moment; a customer is years. Pre-sales questions, purchase, onboarding, support, renewal — all of it lands in the profile and is analysed. The upsell call knows what support heard in March. #### The record gets thicker, not longer Anchor: /docs/profiles-product#the-record-gets-thicker-not-longer The record gets thicker, not longer One customer, one record — no re-introduction at any point. Most systems start a new row per deal, so the second conversation begins with nothing. The record here starts at the first conversation and is still the same record when support answers a question two years later. #### Knowing the customer, and the market Anchor: /docs/profiles-product#knowing-the-customer-and-the-market Knowing the customer, and the market A profile is useful to a rep. A thousand profiles are useful to whoever decides what you sell next. What buyers in a segment keep asking for, what keeps stopping them, and which of your own assumptions about your market your customers have been quietly contradicting. #### What teams use profiles for Anchor: /docs/profiles-product#what-teams-use-profiles-for What teams use profiles for Pick a goal to see which part of a profile answers it. A lead helps you close a deal. A profile helps you run a business — because it's the difference between a session and a person you actually know. When you know who your customers really are and what they prefer, you market to the right people, coach your team on real journeys, build what customers keep asking for, and make every touch feel personal. Better marketing attribution Because a profile stitches every conversation's source together, you see the whole path a real buyer took — not one disconnected click — and can tell which campaigns actually produce customers. Sharper campaigns & audiences Segment by what profiles reveal — decision-stage, budget band, stated preferences — to build look-alike audiences and target the people most like your real buyers. Better-trained sales teams Every rep opens warm — the whole relationship in front of them — and your best real journeys become the playbook for what a ready buyer looks like. Better products & services Aggregate preferences and requests across thousands of profiles and the pattern is clear: what to build, what to staff, what to stop guessing about. More relevance, less friction A profile remembers, so the next conversation continues instead of restarting — customers feel known, not re-interrogated. Grounded in real evidence Every fact traces to a conversation and every link carries its confidence — so the decisions you build on top are trustworthy. Working with profiles day to day Finding a customer's profile, reading its tabs, what the AI summary contains and how pay-per-lead affects what you can see are all in the Customer Profiles guide. ### How LeadLogiX Works URL: /docs/how-it-works Overview How LeadLogiX works Custom AI Agents advance the deal inside the conversation. LeadLogiX turns every one of those conversations into a verified, priced, auditable lead — and a customer you actually know. This is where it sits and what it produces. #### Where LeadLogiX sits in the chain Anchor: /docs/how-it-works#where-leadlogix-sits-in-the-chain Where LeadLogiX sits in the chain The one-line version Your agents produce conversations. LeadLogiX reads them and decides what each one is worth. Your tools receive the answer. Swap either end without losing the middle. Upstream · the agents Produce EVAS Avatar Studio and Voice Studio, or an agent you already run. They hold the conversation; LeadLogiX holds none of its own. LeadLogiX · the intelligence layer Refine Verifies who is behind the conversation, extracts what was actually said, scores and prices it with code you can audit, and remembers the person. Downstream · your tools Act Your CRM, exactly once. The channels your team already watches. Anything else through the API. #### What LeadLogiX figures out for you Anchor: /docs/how-it-works#what-leadlogix-figures-out-for-you What LeadLogiX figures out for you From a single conversation. Who Confirms identity and recognises returning visitors — the seed of a Customer Profile. What they want Separates each speaker and pulls out intent and the details discussed. How good A quality score and tier, so your team works the best leads first. Where Pins the location for routing and for pricing. How much A price, set by deterministic code — never by the AI. Whether at all Fail one hard integrity gate and the lead is suppressed — unsold and unbilled. #### AI reads, code decides Anchor: /docs/how-it-works#ai-reads-code-decides AI reads, code decides The split that makes the numbers defensible. AI is used for the thing AI is good at: reading a messy human conversation and turning it into bounded, validated signals. Every score and every price is then computed by plain, versioned code — same inputs, same number, forever, with the working shown on the lead. The depth behind that — the cohort baselines, the honesty states, the audit trail — is set out in Scoring & Pricing and Quality & Trust. ### Scoring & Pricing URL: /docs/scoring Overview The AI never sets a price. Ever. Every lead arrives as a messy human conversation and leaves as a number your team can act on: a quality score, a set of tags, and a price. Here's how LeadLogiX gets there — and why it's built to be trustworthy, not a black box. Find it: Main Menu > Clearing Pool. where you see each lead's quality score and price #### Two kinds of thinking, on purpose Anchor: /docs/scoring#two-kinds-of-thinking-on-purpose Two kinds of thinking, on purpose LeadLogiX deliberately splits the smart-but-fuzzy part from the exact-and-auditable part. AI reads the conversation Judgement The AI turns unstructured talk into tidy, bounded signals — how urgent someone sounds, where they are with financing, what they're actually looking for. It's great at understanding messy human language. Code does the math Exact Every score, probability, and price is then computed by plain, hard-coded logic — not by the AI. Given the same inputs it always returns the same number, and every step is written down. #### Why split them? Anchor: /docs/scoring#why-split-them Why split them? This is the whole point — and your best answer to “can I trust the number?” The AI never decides money Letting a language model directly set a price invites “hallucinated” numbers you can't reproduce or defend. So the AI only describes the lead; auditable code makes every financial and routing decision. Repeatable Same conversation in → same score out. Always. Auditable Every score keeps a record of the inputs, the version, and the rules applied. Predictable More positive signals never lower a score — the math is monotonic by design. #### The Lead Quality Index (LQI) Anchor: /docs/scoring#the-lead-quality-index-lqi The Lead Quality Index (LQI) A single 0–100 score for how workable a lead is. The LQI is three things multiplied together , each a probability between 0 and 1: Factor The question it answers What moves it Reachable Will at least one recorded contact channel actually reach this person? Verified phone and email. Each distinct validated channel adds to it; a duplicate does not. Genuine Is this a real human prospect rather than a fake or a bot? Fraud and authenticity checks. A serious risk finding can suppress the lead outright, whatever the rest looks like. Intent Do they actually intend to transact? Stated budget, financing signals where the role makes them relevant, how clear the target is, and how complete the profile is. They multiply — a weak one cannot be made up for This is the part worth internalising, because it is the opposite of how a points-based score behaves. A perfectly genuine, highly intentful person nobody can contact scores near zero, and no amount of intent raises it. Fixing the weakest factor is always worth more than improving one that is already strong. Price then follows the LQI on a curve, so a small gain at the top is worth more than the same gain at the bottom. To feel which way each signal pulls, toggle them in the demonstrator on the LeadLogiX Tour , in either live line of business. #### Grounded in real outcomes — the Statistical Machine Anchor: /docs/scoring#grounded-in-real-outcomes-the-statistical-machine Grounded in real outcomes — the Statistical Machine LeadLogiX doesn't score in a vacuum. For each new lead, LeadLogiX quietly assembles a cohort of similar past leads and uses how those actually performed as the baseline. A lead's quality and price are anchored to comparable real history — not a guess — and leads that didn't process cleanly are left out of the baseline so the numbers stay honest. Your outcomes inform you — they cannot move your bill Connect your CRM and LeadLogiX reads your closed-deal history to project what each lead is worth to your business — your close rates, your deal sizes, segment by segment. That projection is kept structurally separate from the score and the price: your own outcome data can never quietly raise what you are charged. The baseline underneath the score moves only as the cohort of comparable real leads grows. #### Every number is real — or honestly absent Anchor: /docs/scoring#every-number-is-real-or-honestly-absent Every number is real — or honestly absent A statistic on a lead is in exactly one of three states. There is no fourth. Computed Real Calculated from real inputs, with the sample size recorded. This is the only state in which a number influences quality or price. Insufficient data Honest Not enough comparable leads to measure against — the lead shows that , along with how many were found and how many were needed. LeadLogiX never substitutes a guess or an industry average. Flagged Recoverable Something failed while computing. The lead is marked for reprocessing rather than shipped with a silent default. Why this matters when you're spending money Most scoring products quietly fall back to a default when data runs out — so a confident-looking number can mean “we had nothing to go on.” LeadLogiX's contract is the opposite: if a number is on the lead, it was computed; if it couldn't be, the lead says so, in the open. #### How the baseline is built Anchor: /docs/scoring#how-the-baseline-is-built How the baseline is built Each lead is measured against a cohort of comparable recent leads — never against a hunch. Assemble the cohort LeadLogiX gathers similar past leads — comparable intent, market, and situation — then narrows to the ones in the same role : buyers are measured against buyers, sellers against sellers. Role changes what a number means, so mixing them would compare a seller's buying intent against people who were shopping. A minimum cohort size is required before any baseline is computed, and it applies after that narrowing. Below the minimum, the statistic reports “insufficient data” instead of pretending — including when there are plenty of similar leads but not enough in this lead's own role. Weight for likeness and freshness Cohort members don't count equally: closer matches count more, and older leads count exponentially less as they age, so the baseline tracks your market as it is now — not as it was last quarter. Tame the extremes Averages are computed with the statistical extremes capped, not deleted (statisticians call this winsorizing). One freak lead can't drag the baseline, and no data is quietly thrown away either. Place the lead against it The new lead's signals are compared to that baseline as a standard-deviation distance and a percentile — “stronger than N% of comparable leads” — which is what feeds quality and pricing. #### What the numbers on a lead mean Anchor: /docs/scoring#what-the-numbers-on-a-lead-mean What the numbers on a lead mean Plain-language definitions — the full glossary is folded below. These are the statistics LeadLogiX computes — there are no others behind the curtain. Every one is deterministic: the same cohort and the same lead always produce the same value, and each carries the sample size it was built from, so you can always see how much weight a number deserves. Consistency checks A lead's story is tested across channels — does the stated intent match the behavior, the pace, the tone? A large gap between them is flagged against a calibrated threshold, not eyeballed. Cohort profile Every baseline records how many leads were behind it and how fresh they were, so you can judge the evidence, not just the verdict. Spread measures (quartiles, robust ranges) ride along with every baseline so an average can never hide a wild distribution. All of it is computed by the same auditable code path — the AI that reads conversations never touches these numbers . The headline score and its three factors Lead Quality Index (LQI) 0–100 The headline score, worked out as 100 × Reachability × Authenticity × Intent . Because the three multiply rather than add, a weak showing on any one of them holds the whole score down — they cannot compensate for each other. A perfectly reachable, entirely genuine lead with no real intent still scores low, which is the intended behaviour. Reachability (R) Factor The probability that at least one recorded contact channel actually reaches this person. Channels combine as "at least one works" , so each additional distinct channel raises it. The same phone number recorded twice counts once — duplicates are collapsed before the calculation, so padding a record with repeats cannot inflate the score. Authenticity (A) Factor The probability that the lead is a real, non-fabricated person and story. Low values come from internal contradictions, placeholder or test data, and security signals — never from a judgement about the person themselves. Intent (I) Factor How strongly the conversation shows genuine buying or selling intent. Read from what was actually said, then damped by how well the rest of the record backs it up — so a confident claim with nothing supporting it does not carry full weight. The centre — what the cohort typically looks like Weighted mean (the baseline) Centre The cohort average, in which closer matches and more recent leads count for more than distant or older ones. This is the figure a lead is measured against. It is always an average of real recorded values from real leads — never a default, an estimate, or a seeded number. Winsorized mean Centre The same average with the most extreme values pulled in to a percentile cap rather than deleted. If it differs noticeably from the plain mean, a few outliers were dragging the average around. Capping rather than discarding means the sample size never silently shrinks. Median (p50) Centre The middle value — half the cohort sits above it, half below. Compare it with the mean: a wide gap between the two means the distribution is lopsided, and the average on its own would mislead you. Spread and shape — how much the average is worth Standard deviation Spread The typical distance from the average. Small means the cohort broadly agrees, so being above average is meaningful. Large means the cohort is genuinely mixed, and "above average" says less than it appears to. Quartiles (p25 / p75) and IQR Spread The boundaries of the middle half of the cohort, and the width between them. A robust view of spread that a single extreme value cannot distort — which is why it is shown alongside the standard deviation rather than instead of it. MAD (median absolute deviation) Spread The typical distance from the median. Preferred over the standard deviation when the data contains outliers, because one extreme value barely moves it while it can visibly inflate a standard deviation. Outlier density Spread The share of cohort weight sitting beyond the calibrated threshold. A high value means the baseline rests on a lumpy population rather than a consistent one, so comparisons against it deserve more caution. Skewness Shape Whether the cohort leans high or low instead of sitting symmetrically. Near zero is balanced. Positive means a long tail of high values pulling the mean upward; negative means the reverse. Strong skew is your signal to read the median rather than the mean. Excess kurtosis Shape How much of the cohort sits out in the tails versus bunched in the middle. Near zero behaves like a normal bell curve. High positive means fat tails — extreme leads turn up more often than a bell curve predicts, so "unusual" is less unusual than it first looks. Where this lead sits — percentile, deviation, divergence Percentile Position The share of the cohort this lead scores above. "84th percentile" means stronger than 84% of comparable recent leads. The most intuitive single comparison, and the one to quote to someone who does not want the statistics. Deviation (z-score) Position Distance from the cohort average, measured in standard deviations. Around 0 is ordinary; beyond roughly ±2 is genuinely unusual. Unlike a percentile it tells you how far from normal, not just how many leads were beaten. Divergence verdict Position Whether the gap between this lead and its baseline exceeds a calibrated threshold. Within band is ordinary. Divergent flags a metric worth a human look. Not performed means there was no comparable population to judge against — an honest absence, not a quiet pass. How far to trust a baseline Cohort size (usable / matched) Confidence How many comparable leads were found, and how many survived filtering into the baseline. A wide gap between the two means most candidates were unusable — worth investigating rather than trusting. Minimum sample size Confidence The floor below which no baseline is computed at all. Underneath it, every dependent statistic reports insufficient data . This is why a new workspace sees honest blanks instead of confident-looking numbers built on three leads. Effective sample size Confidence The sample size once uneven weighting is taken into account. If a handful of members carry most of the weight, twenty leads can behave statistically like five — and this number says so, where a raw count would quietly overstate your confidence. Freshness decay Confidence How much the baseline is discounted for age, falling off as its members get older. 1.00 means current. Lower values mean the baseline leans on older leads, and so describes the market as it was rather than as it is today. Weighting is deliberate — and it is always reported Cohort members do not count equally: closer matches and fresher leads carry more weight. That makes the baseline more relevant to your lead, but it also means a raw count overstates how much evidence is really behind it. That is precisely why the effective sample size is published next to the plain count rather than instead of it. #### How a lead's price is built Anchor: /docs/scoring#how-a-leads-price-is-built How a lead's price is built The four terms behind the amount you are charged — each one visible on the lead. Price is not a judgement call and it is not a rate card. It is four terms multiplied together, all four shown on every lead so you can reproduce the figure yourself: base value × role multiplier × conversion curve = gross price . Country base value Term 1 What one lead is worth in this country before quality is considered. Set per country during calibration, because the same lead is genuinely worth more in some markets than others. Role multiplier Term 2 Adjusts the base value for what the person is actually trying to do — a buyer, seller, renter and landlord are not worth the same. ×1.0 leaves the base value unchanged. Conversion curve Term 3 Turns the 0–100 quality score into a price factor, as (LQI/100) raised to an exponent above 1. Because the exponent is above 1, price falls away faster than quality does — a lead at half the quality is worth well under half as much. That is deliberate: it stops mediocre leads being priced as though they were nearly good. Minimum LQI for pricing Floor The quality floor. A lead scoring below it is not priced or sold at all — it is recorded as below the pricing floor rather than quietly offered cheap. Gross, reserve and net — only one of them is a charge Gross price is the lead's sealed clearing price — the number the four terms above produce, recorded on the lead and never recomputed afterwards. The platform reserve is the platform's share of that gross price, recorded for bookkeeping — it is already inside the gross price, never added on top. Net value is the gross price minus that reserve. Reserve and net are bookkeeping views of the same charge, so adding them to the gross price would count the same money twice. The gross price is not, by itself, what you are billed What reaches the invoice depends on your plan. On Pay-Per-Lead the gross price is billed for each lead you procure, and any contract discount you hold is applied at settlement as its own separate line — the lead's own recorded price is the undiscounted one, so a lead never carries an inflated "before" figure. On an Unlimited plan every lead is still itemized, but at $0.00 and marked as included in the plan, because the subscription fee already paid for it. Either way the lead's gross price is the price of the lead ; the invoice is where it becomes a charge. See Billing & Plans . #### Why you can defend these numbers Anchor: /docs/scoring#why-you-can-defend-these-numbers Why you can defend these numbers The properties that make the math trustworthy — and hard to game. Reproducible forever Every lead stores which engine version scored it and what the cohort looked like. Any historical number can be recomputed and checked — months later, in a dispute, exactly. No magic constants Every threshold and weight is a named, versioned calibration setting — reviewed and tuned as real outcomes come in. Nothing is hard-coded folklore. No silent fallbacks Zero variance in a cohort? The statistic says “insufficient data” — it does not invent a spread to divide by. Every check that couldn't run is listed on the lead by name. AI describes, code decides AI-extracted signals pass a strict validation gate (ranges, allowed values) before statistics ever see them. A misreading can be caught; it can never place itself directly into a score. The exact recipe stays calibrated, on purpose The mechanics above are public because they're what make the numbers defensible. The specific weights and thresholds are calibrated to live market outcomes and aren't published — that's what keeps scores meaningful and ungameable, the same way a credit bureau publishes its factors but not its coefficients. #### From score to price Anchor: /docs/scoring#from-score-to-price From score to price Pricing is an expected-value calculation, not a sticker. Estimate the deal What a transaction at this budget is realistically worth. Expected deal value The firm's net value on a deal like that. Adjust for what the lead is A buyer, seller, renter and landlord are not worth the same to a firm, so each carries its own multiplier. Weight by how likely it closes Multiply by the lead's real close likelihood (this is where the LQI and history come in). Quality doesn't scale in a straight line — a strong lead is worth proportionally more than its score alone suggests, because good leads close at a disproportionately higher rate. Charge only an affordable slice Take the fraction of that expected revenue a firm can rationally spend to acquire a lead, then apply a safety margin. That's the fair price . Why buyers like this Every dollar of a lead's price traces back to expected deal value × probability. It's a defensible number, not a vibe — and the exact rates are calibrated to your market, not published for competitors to copy. #### A score per lead — a trajectory per customer Anchor: /docs/scoring#a-score-per-lead-a-trajectory-per-customer A score per lead — a trajectory per customer The arc is the signal A lead's LQI scores a single conversation. When a returning customer has had several, their Customer Profile shows the score climbing over time — from a first curious visit to a decision-ready return. That trajectory is often a stronger buying signal than any one number (see Customer Profiles ). Scores are per line of business, and never mixed If your workspace handles more than one line of business — say real estate and life insurance — the quality score is tuned separately for each, because what makes a strong property enquiry is not what makes a strong life-insurance application. So a customer profile shows a section per line of business , each with its own best score, role and budget. We never combine them into a single number: a "top score" that silently came from a different line of business would look authoritative and mean nothing. Conversations also carry a small badge naming their line, so you can always see which business a given call was about. ### Quality & Trust URL: /docs/quality Overview Only real, reachable people become leads Knowing who is worth your time is only useful if the knowing can be trusted. Before LeadLogiX prices anything it checks whether the person is real, whether you can actually reach them, and it keeps the evidence behind every judgement — so a number your team doubts can be opened rather than argued about. Find it: Main Menu > Clearing Pool. open any lead to inspect its full record #### Is this a real person? — the vetting gate Anchor: /docs/quality#is-this-a-real-person-the-vetting-gate Is this a real person? — the vetting gate Five integrity checks, each run two ways. Every session is screened across five dimensions — fraud / scam risk , geographic fidelity , conversational authenticity , financial credibility , and intent certainty . Each one is judged twice — by deterministic rules and by AI — and the two are fused into a single risk. If any dimension crosses the safe line, the lead is suppressed outright — no matter how high its quality score. Try it: #### Can you actually reach them? — reachability Anchor: /docs/quality#can-you-actually-reach-them-reachability Can you actually reach them? — reachability A perfect lead you can't phone is worthless. Every channel checked Each phone and email is validated and given a real probability of reaching the person — not a guess. Freshness matters Older, recovered contact details are discounted, so a stale number doesn't look as good as a fresh one. Honest zero No validated way to reach someone means a reachability of zero — never a hopeful default. #### Every lead is auditable — the integrity trail Anchor: /docs/quality#every-lead-is-auditable-the-integrity-trail Every lead is auditable — the integrity trail Traceable to the source, verifiable step by step. Each lead carries a complete, versioned and time-stamped ledger of how it was scored: every statistic that was computed, from how many comparable leads, and every one that couldn't be computed and why. There are no silent steps . Because the math is reproducible, feeding the same inputs back always returns the same numbers — so any lead can be traced back to the original conversation and re-verified line by line. Versioned Stamped with the exact engine and calibration versions in force at scoring time. Reproducible Same inputs in, same score out — the record can be recomputed and checked. Complete Inputs, results, and the “couldn't compute” cases are all recorded — nothing hidden. Built to be defended Open any lead in the Clearing Pool inspector to see its trail. This is what lets buyers prove compliance, track ROI precisely, and defend how a lead was scored and priced. The honesty contract LeadLogiX never papers over a gap with a neutral guess, and it distinguishes two kinds of gap. When a component fails — a check that should have run and did not — the lead is degraded and flagged for reprocessing. When the answer is simply not in the evidence — an address that does not resolve to a market LeadLogiX covers, or too few comparable leads to build a baseline — that is recorded by name on the lead and it processes normally, because reprocessing cannot produce a fact the conversation never contained. Either way you get the named reason rather than a confident-looking number. A number you can't trust is worse than an honest “not yet known.” #### The same rigor recognizes customers Anchor: /docs/quality#the-same-rigor-recognizes-customers The same rigor recognizes customers Evidence-verified — never a blind merge The identity checks that confirm who a lead is also decide when two conversations belong to the same person . Before any conversations join a Customer Profile , the match is re-verified from the actual conversations, and every link carries its confidence — if the evidence disagrees they're kept separate, if it's uncertain it's held out. Matching tolerates the small differences voice conversations produce — a nickname, an accent mark, a spelling slip in a name won't split a real customer in two — but a close-but-different name is never enough on its own to join two conversations. You'll never find two different people fused into one profile (see Customer Profiles ). ### Built for How You Work URL: /docs/built-for-you Product Built for your business, then handed to you Fitted to what you sell and how you qualify — without a data team to build it, or one to run it afterwards. #### Who this is for Anchor: /docs/built-for-you#who-this-is-for Who this is for Fit Companies where the conversation decides whether somebody buys, and nobody has a quarter to spend on a platform project. Too serious for a chatbot A scripted widget cannot answer, verify and book. Too small for a build The enterprise version of this is a platform team and two quarters. Selling something considered Insurance, services, high-value goods — anything that takes more than one visit. Already running a CRM Yours stays the system of record. #### Configured with you, run by you Anchor: /docs/built-for-you#configured-with-you-run-by-you Configured with you, run by you The split The hard configuration happens once, with people who do it daily. Nothing in the right-hand column needs us afterwards. We set up You control, from day one The agents: what they know, ask, and may say Their answers, their tone, the questions they lead with The scoring calibration for your market The quality floor your team works above The CRM connection and the fields it writes Which leads sync, when, and under what tags The first automations and schedules Everything after — new rules, alerts, recipients Roles and who sees what People, permissions and the tags each agent works #### How far it bends Anchor: /docs/built-for-you#how-far-it-bends How far it bends Range Same platform, different instructions — an insurer asks about coverage and renewal dates, a lender about affordability, a services firm about scope and start dates. Your own signals on the lead Anything your agents capture that LeadLogiX does not model natively, visible to the roles you choose. Automation without a developer Alert this channel, sync those leads, email that report — configured and scheduled, not written. A real API when you have one Published endpoints and standards-based authorization. Your stack, your rules Which sources feed profiles, scoring context and reporting is decided with you, at configuration. Two lines of business are installed and live today, Real Estate and Life Insurance (US), each with its own facts, roles, cohorts and price ladder. A new line is built and calibrated with you — see one engine, calibrated per line of business . #### An AI assistant can work with your leads Anchor: /docs/built-for-you#an-ai-assistant-can-work-with-your-leads An AI assistant can work with your leads Your other tools Only with your permission, and only for what you allow: the software your team already uses — an AI assistant, a sales tool, something a partner built — can be given access to this workspace without anyone copying a password anywhere. How it starts The application asks. You see exactly what it wants, in plain words, and approve or decline each part. What it can see Only what you allowed. Customer names and phone numbers are a separate permission from the leads themselves, and each contact channel is cleared for AI use one at a time. What it can do Read, by default. It can only make your workspace act — send a message, push to your CRM — if you approve that separately, and then only through the specific automations we publish for it. Staying in control Every application is listed under Connected Apps with who approved it and when it was last used. Cut one off and its access ends immediately. You are handing over a key to one room, not to the building. Anything an application does appears in your own run history, named, beside the work your schedules did — so the record does not depend on the application telling you. #### What you can check, and how plans work Anchor: /docs/built-for-you#what-you-can-check-and-how-plans-work What you can check, and how plans work Before you ask Nothing below is a benchmark or a customer quote — each is a property of how the platform is built, and something you can inspect yourself. A scoring ledger on every lead Versioned and time-stamped: the inputs, the engine version, and every statistic that could not be computed, by name. Recomputable from the record The same recorded inputs return the same score and price. Pricing is plain code with no AI in it. Suppressed means unsold A lead that fails an integrity gate is never offered for sale and never billed. Delivered to your CRM exactly once Each lead is delivered, confirmed, then marked — never sent twice. How plans work There are two plans. On Pay-Per-Lead, each lead you procure is billed at its recorded price. On Unlimited, a subscription covers every lead, and each one is still itemized on the invoice at $0.00. What a plan costs for your volume is quoted on request; how invoices are built is in Billing & Plans . #### What it takes to begin Anchor: /docs/built-for-you#what-it-takes-to-begin What it takes to begin Starting What is involved From your side Somebody who knows what a good lead looks like, access to your CRM, and a decision about where the agent should appear. From ours The agent build, the calibration, the CRM mapping and the first automations. Ongoing A workspace your admin runs — people, tags, schedules, numbers. If you outgrow it Sub-workspaces for teams or regions, with billing that stays consolidated. Tell us what you sell and how a good customer usually arrives. Reach us at sales@evas.live . ## Start here ### Quick Setup URL: /docs/getting-started Quick Setup Take a new workspace from empty to sending its first hot-lead alert, in the order that avoids rework. Before you start: An administrator role in this workspace — every step here is admin-only. Your AI Agents already writing finished conversations to LeadLogiX. About twenty minutes. Steps 5 and 6 can be done later. Do these in order — two of them block the others Lead Tags come before automations, because an automation picks the tags it covers and cannot pick one that does not exist yet. Email Engine comes before any email alert, because without it every send fails and the run still reports success. #### 1 · Invite your team Anchor: /docs/getting-started#1-invite-your-team 1 · Invite your team Organization → Workspaces & Users → Invite team member Invite each person with their role Press Invite team member , enter the email, pick Admin , Procurement or Agent , send. The role decides what they see before they ever log in. Give every agent at least one tag On their row: Scoped tags , then Match mode . An agent with no tags sees an empty pool — the usual reason a new agent reports seeing nothing. Roles and scoping in full: Team, Roles & Access . #### 2 · Add your Lead Tags Anchor: /docs/getting-started#2-add-your-lead-tags 2 · Add your Lead Tags Settings → Lead Tags → Use starter pack Click Use starter pack Use starter pack sits above your tag list. Seventeen ready-made tags appear — Hot Lead, Cash Buyer, First-Time Buyer, Asked for Viewing and the rest. Adding them again later skips the ones you have. Delete what you will not use Rewrite the descriptions in your own words For a tag with auto-assign on, the description is the instruction the AI follows. This is the single highest-leverage thing on this page. Leave Returning Customer and Reached a Live Rep alone They are set from what actually happened. How to write a description that tags cleanly: Lead Tags & AI . #### 3 · Connect your Email Engine Anchor: /docs/getting-started#3-connect-your-email-engine 3 · Connect your Email Engine Settings → Email Engine — do this before any email alert Choose your provider and sender Pick Email Provider — Amazon SES, Google Workspace or any SMTP server — then fill in Sender Email , the From address recipients see. Use an address on a domain you control. Enter the credentials for that provider Google Workspace wants an App Password , not the login password. SES wants the SMTP username and password from the SES console, not your AWS console login. Press Save Email Engine Send Test Email to Me stays greyed out until a configuration is saved. Re-opening the page shows masked dots: that means it saved. Press Send Test Email to Me A test arrives at the address you are signed in with, naming the host it went through. A failure names the problem. Until the test lands, assume nothing sends — sending is fail-closed . Ports, providers and the form field by field: Email Engine reference . #### 4 · Turn on your first alert Anchor: /docs/getting-started#4-turn-on-your-first-alert 4 · Turn on your first alert Settings → Automations Switch on Email me about hot leads It needs no credential beyond step 3. Choose which Lead Tags it covers Any tag with nobody on it is flagged right there as · no email address . Add the recipients to those tags Settings → Lead Tags , open each tag you picked, add the email addresses. Press Schedule, then enable the job Under Settings → Scheduled Jobs — Schedule creates it switched off. The job shows a next-run time. Remember it as tags say WHO, automations say WHICH . Texts, Slack and digests: Alerts & Notifications . #### 5 · Connect your CRM Anchor: /docs/getting-started#5-connect-your-crm 5 · Connect your CRM Settings → Scheduled Jobs → Connect a CRM — optional, do it when ready Press Connect a CRM and approve access in the pop-up A few providers instead need a key pasted into the Secret Vault under the exact name the screen shows. Enable the sync job when you are happy with its filters Scored leads flow out on a timer, with no CSV in the middle. Salesforce, HubSpot and webhook walkthroughs: Connect a CRM . #### 6 · Check billing Anchor: /docs/getting-started#6-check-billing 6 · Check billing Organization → Billing & Invoices — admin only Confirm the plan Add a payment method Invoices are collected from it automatically on their due date. You are charged only for leads that clear every quality gate, so the invoice and the pool should agree — see Billing & Plans . You'll know it worked: Your first scored lead appears in Main Menu → Clearing Pool with a quality score and a price. Until a conversation has been received and processed, an empty pool is the correct state and not a misconfiguration. If something is not working The alert never sends Usually the job is still switched off, then a badge on the automation's row, then a tag with no recipients. Work down the ordered list . An agent sees no leads They have no assigned tags, or none of their tags match anything yet. Anything else Start from the Troubleshooting index . ### Team, Roles & Access URL: /docs/workspaces Team, Roles & Access Invite your team, give each person the right role, and scope agents to the leads they should work. Find it: Organization > Workspaces & Users. in the left sidebar of your workspace Before you start: An administrator role — only admins can invite people or change a role. The email address of each person you are inviting. A decision about who sees what: agents see leads for their assigned tags, procurement sees the whole pool with pricing. #### Invite a teammate Anchor: /docs/workspaces#invite-a-teammate Invite a teammate Press Invite team member Invite team member opens the form: enter the email and pick the role. The dropdown only offers roles you are allowed to grant. Send the invitation The person appears in the list with that role. Their access begins when they accept, not when you send. #### The roles Anchor: /docs/workspaces#the-roles The roles Role Sees and does Cannot Administrator Runs the workspace: people, tags, automations, schedules, billing. Sees every lead. Add another admin — the platform does that on request. Procurement The whole pool with pricing, and can purchase. Open settings or billing, or invite anyone. Agent Only leads carrying the tags you assign them. See pricing, or manage anyone. You can add and act on people below your own level — as an admin, Procurement and Agents. Procurement and Agent are peers; neither manages the other. #### Practice: what each role reaches Anchor: /docs/workspaces#practice-what-each-role-reaches Practice: what each role reaches Click a role to see what it can open, then try the invite form. #### Scoping an agent to the right leads Anchor: /docs/workspaces#scoping-an-agent-to-the-right-leads Scoping an agent to the right leads The most consequential thing on this page, and the easiest to get wrong. Assign the tags Open the agent's row and pick from Scoped tags . Create tags first under Settings → Lead Tags . Set Match mode Any — lead has at least one of these tags is the usual choice and widens the view as you add tags. All — lead has every one of these tags narrows fast. The agent's Clearing Pool shows only matching leads, enforced on the server. An agent with no tags sees an empty pool No error message — just nothing. It is the most common “the product is broken” report from a newly invited agent; the fix is to assign a tag. The agent scope form, field by field #### Change or remove someone's access Anchor: /docs/workspaces#change-or-remove-someones-access Change or remove someone's access Change a role or revoke access from their row You can act on anyone below your level. Update your own profile from the same panel Removing your own admin rights is not offered — the workspace would be left with nobody who can restore it. A role change ends that person's session everywhere, immediately — see Sessions . The In CRM column shows whether each agent and procurement member exists in your connected CRM — see Which of your people are in the CRM . #### Sub-workspaces Anchor: /docs/workspaces#sub-workspaces Sub-workspaces Give each team, office or partner its own people and leads, billed through you. Add a sub-workspace In Workspaces & Users : a name, and the email of the person who will be its admin. That admin is emailed an invite. They run their own team and leads without seeing anyone else's. Manage it from the parent when you need to Rename, suspend, reactivate or remove it, and manage its people yourself. There is no billing setup for them. Who Can You (parent admin) Create, rename, suspend, delete your sub-workspaces; invite and manage their users; see their leads. Sub-workspace admin Runs their own team and settings. No billing. Cannot create further sub-workspaces. Procurement / Agent Work leads within their workspace. They manage no one. Deleting a sub-workspace removes its users and leads Suspend it instead if you may want it back. Sub-workspaces are one level deep, and charges roll up to the parent: one consolidated invoice with a per-team breakdown, and sub-workspace admins never see billing — see Billing & Plans . How sub-workspaces are organized You'll know it worked: The person appears in the list with the role you chose, and an invitation reaches their inbox; access begins when they accept it. A sub-workspace appears nested under the parent with its own member list, and its charges appear on the parent's invoice rather than one of its own. A new agent sees nothing They have no scoped tags, or Match mode is All and few leads carry every tag. I cannot invite another admin Adding an Admin is done by the platform on request, so no single account can quietly hand out full control. I need someone out right now Change their role or suspend their account — both end their session immediately, everywhere. Who did what, and when? The security log — see Security . Why scope is enforced on the server An agent's view is built from the tags on their row, applied on the server on every read. It is not a saved filter they can clear or a URL they can edit, so it holds however they reach the data — including through connected applications. Every extra tag in All mode is another condition the same lead has to satisfy, and most leads carry only a handful, which is why Any is the usual choice. Authority flows down, never up: you can only assign or act on roles below your own, sub-workspaces cannot create their own, and adding an admin goes through the platform. ### Sign-in & SSO URL: /docs/sign-in Sign-in & Single Sign-On Let staff sign in with your company's identity provider, and know how sessions and API keys behave. Find it: Organization > Company Profile. the Single Sign-On card Before you start: An administrator role. Your identity provider's OpenID Connect details — issuer URL, client ID and client secret. The email domain your team signs in with. Plus the time your DNS takes to publish a record. #### Set up SSO Anchor: /docs/sign-in#set-up-sso Set up SSO You'll need admin access to your identity provider. Create an OpenID Connect application in your identity provider Use the redirect (callback) URL shown in the Single Sign-On card. Copy the issuer URL , client ID and client secret . Enter them in LeadLogiX Organization → Company Profile → Single Sign-On : the three values plus your email domain (for example yourcompany.com). Check the two fields below first. The client secret is stored encrypted and never shown again — not even to you. Publish the verification token as a DNS record on that domain SSO stays switched off until the check passes. Test with a real sign-in from the login page Sign out, enter a work email, sign in at your provider. You return signed in. First-time users are created automatically with the default role you chose. Keep one password-based admin until that round trip works An SSO configuration that half-applies can otherwise lock everybody out. And test only from the login page: creating an account another way skips the checks a genuine SSO sign-in runs, so it cannot tell you the connection works. Any provider that speaks OpenID Connect works — if it gives you an issuer URL, a client ID and a client secret. SAML is not supported ; create an OpenID Connect application alongside it — most platforms support both. #### The two fields people get wrong Anchor: /docs/sign-in#the-two-fields-people-get-wrong The two fields people get wrong Both fail in confusing ways. The issuer is not the page where you created the app. It is the address that publishes your provider's configuration. Paste the console URL and setup fails with a redirect error. Identity provider Issuer URL to enter Google / Google Workspace https://accounts.google.com Microsoft Entra ID https://login.microsoftonline.com//v2.0 Okta https://.okta.com OneLogin https://.onelogin.com/oidc/2 Anything else Ask your provider for its issuer . Adding /.well-known/openid-configuration to it should return a page of settings. The email domain is yours, not your provider's — the part after the @ in your staff addresses. On Google Workspace? Your domain is still your own Staff addresses are @yourcompany.com, so the domain is yourcompany.com , not gmail.com. Public providers like gmail.com or outlook.com are refused outright: nobody can verify ownership of them, and you would be left with a DNS record you cannot add. On Google, also set the hosted-domain restriction ( hd ) on your OAuth client to your domain. LeadLogiX already refuses a sign-in whose email domain does not match, but it is kinder to stop a personal account at Google's screen. #### Turning SSO off Anchor: /docs/sign-in#turning-sso-off Turning SSO off Two different actions — the difference matters. Action What happens Use it when Disable SSO New SSO logins stop. Settings and the verified domain are kept, so switching back on is one click. Pausing — an incident, a provider migration, or testing. Remove settings Deletes the configuration and releases the domain , so it can be set up again from scratch — here or by another workspace. Starting over with a different provider or domain. Neither signs anybody out: people already signed in stay signed in until their session ends. To end sessions now, change the affected people's roles or suspend their accounts in Workspaces & Users . #### Sessions Anchor: /docs/sign-in#sessions Sessions How long people stay signed in. A session ends after a period with no activity — no clicking, typing or scrolling — in any open LeadLogiX tab. Shortly before, every open tab shows a countdown; activity in any one keeps the session going. Signing out in one tab signs you out in all, and each says why. Changing someone's role, resetting a password, suspending an account or suspending the workspace ends that session immediately and everywhere. LeadLogiX has no two-step verification of its own. If you require it — and for admins you should — enable it in your identity provider and connect it with SSO; everyone signing in through it inherits it. #### How automation signs in Anchor: /docs/sign-in#how-automation-signs-in How automation signs in Not with SSO — and not with a password. Software uses an API key , minted in Settings → API Playground and sent as an X-API-Key header. Keys belong to the workspace, not a person, so they keep working when staff change. The consent screen you see when connecting a CRM is LeadLogiX asking for access to that service — it creates no way to sign in to LeadLogiX, and the API accepts no outside account or token. You'll know it worked: Signing out and returning to the sign-in page offers your provider, and a test sign-in lands in this workspace with the role you mapped. Setup fails with a redirect error The issuer is the console page you created the app in. Use the issuer from the table above. The domain is refused It is a public provider (gmail.com, outlook.com) — enter your own domain. If it is already claimed by another workspace (one domain, one workspace), contact support rather than re-registering it. “This account has been disabled” — nobody can sign in When everyone is locked out at once it is neither SSO nor passwords: the workspace was almost always suspended over an unpaid invoice, which disables every account together. See Billing → suspended , or email . Your data is not deleted and the suspension is reversible. A personal Google account signs in and is turned away Set the hosted-domain restriction ( hd ) on your Google OAuth client. A new person signing in through SSO is not added If the workspace is suspended or scheduled for deletion, SSO sign-in is refused like every other method and first-time users are not created. The message points at without naming a reason, because it is shown before anyone has proved who they are. Why SSO works the way it does Email-and-password is the default: people are added by invitation, set their own password from the emailed link, and can reset it themselves. SSO is optional and works side by side — it does not remove password sign-in for people who have it, and anyone whose email is on your verified domain is sent to your identity provider automatically. When someone leaves and you disable them in your identity provider, they lose LeadLogiX access too. Domain verification is what stops anyone else claiming your company's email domain. A disabled configuration keeps its domain claim, deliberately — pausing should not let someone else claim your domain mid-migration. Only Remove settings releases it, so to reconfigure with a different domain, remove first. SSO decides who gets in ; roles decide what they can do . New people arrive with the default role you picked — change it from Workspaces & Users like any other member. ### Troubleshooting index URL: /docs/troubleshooting Troubleshooting index Find the page that owns your symptom, and land on the check that most often fixes it. Before you start: The symptom, in your own words — rows are phrased the way problems are usually reported. For most fixes, an administrator role. #### Alerts, reports and automations Anchor: /docs/troubleshooting#alerts-reports-and-automations Alerts, reports and automations Symptom Most likely cause Go to Nothing is sending The job was created switched off. The ordered checklist A run shows green and no email arrived No Email Engine connected — sending is fail-closed. Fail-closed Texts never arrive The tag has no phone numbers, or a Twilio secret is missing. A tag with nobody on it Row says secrets missing The vault name is not an exact match. Secret Vault A new job will not save An identical job already exists. Scheduled jobs Jobs stopped mid-day The workspace hit 100 job runs for the day. Scheduled jobs My report ignores my wording Lead Summary and Pipeline Health build their own. Report types #### People and access Anchor: /docs/troubleshooting#people-and-access People and access Symptom Most likely cause Go to An agent sees no leads No scoped tags, or Match mode All. Scope an agent Everyone is locked out The workspace was suspended over an unpaid invoice. Suspension API keys stopped working The same suspension. Suspension SSO setup fails with a redirect error The issuer is a console URL. Two fields people get wrong An app was approved but every call is refused Application access is not enabled for the workspace. When access fails An app can no longer save anything Its storage allowance is full. Application storage The security log export was refused One export a minute, shared by CSV and JSON. Security log #### Integrations Anchor: /docs/troubleshooting#integrations Integrations Symptom Most likely cause Go to Leads aren't in the CRM The sync job is off, or its tag filter matched nothing. Connect a CRM → Troubleshoot A synced Salesforce lead looks empty The fields are not on your Lead page layout. Salesforce A synced HubSpot contact looks empty The properties are not on the record view. HubSpot A rep isn't getting CRM work They are not in the CRM, or their account is disabled. In CRM chips Expected value is blank No closed deals pulled, or none mapped to a line. CRM Insights → Troubleshoot #### Leads and data Anchor: /docs/troubleshooting#leads-and-data Leads and data Symptom Most likely cause Go to The Clearing Pool is empty Nothing processed yet — the correct state for a new workspace. Working Leads → Troubleshoot A tag never appears The rule names a field that is not on the list. Lead Tags → Troubleshoot A custom signal is blank The field is not arriving under that exact name. Custom Signals → Troubleshoot No Website Journey card The agent's tools are not registered. Tool evidence An agent is missing from the Analytical Hub It produced no lead in the period you picked. Analytical Hub → Troubleshoot A customer has no profile Only one conversation so far — correct. Customer Profiles → Troubleshoot You'll know it worked: You land on the section that owns your symptom, and its first check is the one that is the cause most often. If no row fits, the How Routing Works diagram shows the three places a send fails silently. ## Daily work ### Working Leads URL: /docs/clearing-pool Working Leads Filter the Clearing Pool down to the leads worth your time, open one, and act on it — or buy it. Find it: Main Menu > Clearing Pool. in the left sidebar of your workspace; open any lead for its full record Before you start: Conversations that have been received and processed — the pool shows scored leads, not raw sessions. A role that can see the pool: admin, procurement, or an agent for their assigned tags. #### Filter the pool Anchor: /docs/clearing-pool#filter-the-pool Filter the pool Your filters stick between visits. Set the Quality floor (LQI) Raise the slider until the pool only shows leads worth a call. It starts from your workspace's recommended floor; once you move it, your choice is remembered for you alone. Narrow by tier, status and tags Status includes whether to show or hide suppressed leads; tags focus a segment — hot leads, cash buyers, sellers. Sort the pool By score or by cost. You can also filter by how a lead was processed. Each row shows the lead's quality score (LQI) and tier, its cost, its tags and its status. #### Suppressed leads stay out Anchor: /docs/clearing-pool#suppressed-leads-stay-out Suppressed leads stay out A suppressed lead tripped the fraud/authenticity hard gate: it is held out of the sellable pool however high it scored, and is never offered for sale even when you choose to show it — see Quality & Trust . #### Open a lead Anchor: /docs/clearing-pool#open-a-lead Open a lead The same lead shows each role what it needs. Role What the lead shows Agent The Sales Agent Follow-Up view: who the lead is and what they want, the best channel to reach them, a suggested follow-up window, numbered next steps, and a short narrative of the conversation. No pricing or internal detail. Procurement The quality score (LQI) and tier, the price to procure, the expected value to you (when a CRM is connected — see CRM Insights ), a routing recommendation, and a financial narrative explaining the number. If the person has talked to your AI Agents before, the lead belongs to a Customer Profile — every prior conversation, the stage they have reached, and how sure LeadLogiX is that it is the same person. Check it before you buy or dial. #### Preview and purchase Anchor: /docs/clearing-pool#preview-and-purchase Preview and purchase Pay-per-lead workspaces only. You'll know it worked: Your filters are still applied when you come back, and the quality floor is yours alone — a colleague's view is not changed by it. What you do to a lead is reflected in its status and recorded in its history with who made the change and when. Nothing you do to a lead is silent. The pool is empty In order: nothing has been processed yet (the correct state for a new workspace); you are an agent with no scoped tags; the quality floor is set above every lead. A lead I expected is missing It is suppressed and the status filter hides suppressed leads, or it is below your quality floor. A lead's percentile changed, but the lead did not The statistics compare against the leads your filters leave on screen. Change a filter and the population moves — see The statistics beside a lead, below. An agent cannot see what a lead costs By design — that figure is for admins and procurement only. Grounded, not guessed None of a lead's recommendations or narratives are invented on the fly. Each is built from the checks recorded when the lead was processed, so you can open the lead's trail and see why it says what it says (see Quality & Trust ). Roles are enforced too: agents cannot see procurement's pricing, and each person only sees the leads they are allowed to (see Team, Roles & Access ). The statistics beside a lead Every number explains itself in place. Each figure behind a lead's price carries a note beside it, written for whoever opens the lead — an agent, a buyer — saying what the term is and what it is not (the note on the gross price states that what reaches an invoice depends on your plan). None names any one industry's roles, because the same card renders on a property lead, an insurance lead, and a lead that was never valued. What the four pricing terms mean, and why gross, reserve and net are three views of one charge, is on Scoring & Pricing . Calibration constants. The card says which values it shows: those recorded with this lead when it was processed, or the platform's current ones, shown because this lead recorded none of its own. A setting that could not be read is shown as not recorded , never as a confident zero. The comparison is your current filter view. The statistics rank the lead against the leads your filters leave on screen — not your whole workspace, and not the benchmarked cohort of comparable past leads that scoring itself uses. The boundary is exactly the filters above the pool: search text, tier, suppression setting, quality floor, date range, processor type and tags. Move one and a lead's standing can change while the lead has not, which is why every panel names the population it used and carries its count. The figure What it is measured against Percentile rank The share of the scored leads in view at or below this one — so the strongest lead on screen reads 100%. Standard offset Distance from the mean of those same leads, in standard deviations, drawn on a ±3σ bar and called out beyond 1.96σ. The procurement view and the audit ledger carry the same statistic under the same name. Box plot The middle half of those leads, on the fixed 0–100 quality scale. The cohort's own measured high and low are stated in words beside it, so the ends of the scale are never mistaken for them. Interval around the mean Descriptive only: the spread of that mean across the leads in view. Not an estimate of a wider population, which it never sampled. Mean completeness The average of the completeness figures actually recorded, across the leads that recorded one. A lead that was never scored is not a zero. The statistics count only what was measured: When What the panel does A lead in view was never scored It is absent from the mean, the median, the spread, the percentile and the box plot. Missing data is not a bad result. A lead genuinely scored zero Zero is a measurement: that lead is ranked and averaged like any other scored one. The open lead has no score It is not ranked, graded or offset; the panel says it records no quality score. No completeness figure was recorded Mean completeness reports that it was not recorded — never 0.0%. Fewer than two scored leads are in view Nothing to rank against, and the panel says so instead of printing a percentile. No lead in view carries a price Price statistics are reported as not computable. A quality score is never read as a dollar amount to fill the gap. No processed leads are in view The card reports that statistics are not computable, rather than a page of zeroes. What the cards on a lead look like Three cards you will rely on most, shown with sample data. They mirror the real cards' design; the live cards fill in from the checks recorded when each lead was processed, so only the values differ. Customer journey stage — where the lead is, with a confidence rating Website Journey — the steps rebuilt from the Agent's tool actions Marketing Attribution — where the lead came from ### Customer Profiles URL: /docs/customer-profiles Customer Profiles Read everything a returning customer has told your agents in one place, check how sure each link is, and act on the whole story. Find it: Main Menu > Customer Profiles. in the left sidebar of your workspace Before you start: More than one conversation from the same person — a profile is what links them. A role that can open a lead. Pay-per-lead workspaces see the shape of a profile before buying and its content after. #### Open a profile Anchor: /docs/customer-profiles#open-a-profile Open a profile Four tabs, one picture — click the tabs, and expand a conversation. Open Main Menu → Customer Profiles and pick a customer Read the Profile tab first Verified contact and location, then a signal summary per line of business, then What we know. Expand a conversation on the Conversations tab Its journey stage and confidence, duration, summary and suggested follow-up steps — and a link straight to that lead in the Clearing Pool. Tab What it holds Profile Verified contact and location; a signal summary per line of business (conversations, best score, latest stage, intent, the channel to try next with its follow-up window); how reachable the person has proved to be; their stable facts, preferences and recent context; and how each conversation was linked, with confidence. Conversations The full history, newest first. Every conversation is a lead — a profile organises your leads, it does not replace them. Attribution Campaigns and referrers, channels, the AI agents they spoke with and where each was placed (the site or room), the ad click identifiers that join them back to the click you paid for, and a per-conversation activity trail — searches, listings viewed, services requested, live-rep handoffs. CRM Which of this customer's conversations were pushed to your CRM, and when. #### Check how sure each link is Anchor: /docs/customer-profiles#check-how-sure-each-link-is Check how sure each link is Read the label on each conversation “Recognized returning visitor · 99% confidence”, “Linked by matching details”, or First recognized conversation for the one the profile is anchored on (no percentage — the others were matched to it). Read which signals agreed Phone number, surname, device fingerprint — which matched exactly, which were close, how many could be compared, and any reason the match is worth a second look. Signal names only, never the values. Percentages are rounded down : a link shown at 99% is at least 99%. If the evidence disagrees, conversations are kept separate; if a match is uncertain it is held out for the next conversation to settle. #### “What we know” Anchor: /docs/customer-profiles#what-we-know “What we know” Three lists, always populated, every line traceable to something the customer said. List Contains Stable facts What holds across conversations — their role in each line of business, the details that decide eligibility or routing, the household on the errand. A changed fact names the value it replaced. Preferences What they want and what they ruled out — natural light, a backyard, good schools, no high-rise. A preference that repeats a listed fact is not shown twice. Recent context One dated line per conversation, newest first, naming its line of business: what they were after, the verified facts it established, the budget with any movement named, financing, other interests, and where it got to. The profile is usable the moment conversations merge. An AI-written summary lands on top, usually within a couple of minutes; meanwhile the page says AI summary building and the three lists are assembled directly from the conversations. If the summary cannot be produced, the assembled view stays. #### On pay-per-lead: see the whole customer, pay only for what you open Anchor: /docs/customer-profiles#on-pay-per-lead-see-the-whole-customer-pay-only-for-what-you-open On pay-per-lead: see the whole customer, pay only for what you open A profile is composed from the conversations you have purchased . Unpurchased ones appear as locked entries — date and price only — each a click from procuring it in the Clearing Pool. Buying the lead opens the entry; there is no separate charge for the profile. “3 conversations · +2 locked” means this customer kept coming back. Agents see profiles built only from the leads assigned to them — no pricing, no locked entries, no purchasing — following your team's scopes live. #### Recognising someone mid-conversation Anchor: /docs/customer-profiles#recognising-someone-mid-conversation Recognising someone mid-conversation Your AI agent can ask “have we spoken to this person before?” in the first seconds of a call. What it does What it does not do Searches your own scored leads for the person your agent is speaking to. It never searches other workspaces. Another company's identical device is invisible to you, and yours to them. Returns a pointer — which conversation, and which customer, if they have been merged. It returns no names, emails, phone numbers or transcripts. Those come from a second, separately-permissioned request. Says possibly the same device when only the browser matches. It will not claim this is definitely them on a device alone. Two people sharing a family laptop can never be merged into one by this. Confirms a person when a name, email, phone or voice actually matches. It cannot be talked into a match. The rule lives in the scoring engine, not in the agent's request. Before you start: Identity access enabled for your workspace — the same switch the other identity features use. Ask us and we turn it on. An AI agent that sends device information when a conversation starts (your developers will know). It costs nothing extra — part of your plan, not billed per call, with a per-key rate limit normal volume will not approach. A well-built agent uses the cautious answer to say “welcome back” and the confirmed one to say “welcome back, Jordan”. Request shape, verdicts and a test recipe: Recognise a returning customer (Developers). You'll know it worked: The profile lists every linked conversation with the confidence of each link, and the lead card shows the profile it belongs to. A single-conversation customer having no profile is correct — there is nothing yet to join. A customer has no profile They have one conversation so far, or their other sessions were processed for identity only or only ran automations — neither kind ever joins a profile. The profile says AI summary building Normal for the first couple of minutes after a merge. What we know is filled from the conversations meanwhile. Entries are locked Pay-per-lead: buy that conversation's lead in the Clearing Pool and the entry opens. Some conversations carry no score or stage They only recognised the visitor or only ran your automations. They stay in the history and are counted under the signal summary — “1 conversation resolved this visitor’s identity only” — never listed as a line of business. How conversations are joined, and what a profile will never show Returning visitors are recognised when they come back; separate sessions are linked on what was actually said — names, contact details, context that lines up. Before two conversations join, the identity engine re-checks the match from the full source conversations. You will never find two different people fused into one profile because a tool got optimistic. Only conversations scored for a line of business can join. A session processed for identity only has no name, phone, email or voice on record and could be recognised by device alone — the weakest evidence, and the kind that confuses two people sharing a computer. An automation-only session has no identity evidence at all. Both stay in your leads and remain searchable. Every AI-summary sentence is checked against the conversation it came from before it is shown; one that cannot be traced is left out. If vetting flagged a concern in any conversation — a security concern, a claim that could not be corroborated — the summary opens with it, naming the conversation. Either way, a claim that failed verification is never shown as a fact, and nothing is padded or invented. There is no budget in the signal summary: a budget belongs to what someone was doing, not to who they are. A customer buying at one figure and now renting has one role and two budgets, so budgets, financing and everything else a conversation established appear in Recent context, against the conversation and date that produced them. Agents are listed by who they actually are: two agents both named “Eva”, on two sites or from two providers, stay two entries, each with its own conversation count. The same applies to placement. Why profiles matter commercially is on the Product tab: Customer Profiles . Profiles in your CRM, and the Customer Profiles API A lead synced to your CRM carries its profile: a point-in-time summary — conversation count, first and last seen, latest stage, top score, the AI-written summary once ready — plus a live link that always opens the current profile. Field detail: Connect a CRM → Customer Profiles ride along . Profiles are built automatically. For teams that want to stitch conversations together programmatically there is an off-by-default Customer Profiles API : submit a new conversation plus the earlier ones it belongs with, and LeadLogiX re-verifies and merges them, returning a safe profile reference and a live link. See Customer profiles API , and email to enable it. ### Analytical Hub URL: /docs/analytics Analytical Hub Read how your pipeline is moving, find an AI agent that has gone quiet, and measure what your marketing spend returns. Find it: Main Menu > Analytical Hub. in the left sidebar — visible to admins and procurement Before you start: An admin or procurement role — the hub is not visible to agents. Processed leads in the period you are looking at. Ad spend entered, if you want cost-per-lead and return on ad spend rather than volume alone. #### Read the funnel Anchor: /docs/analytics#read-the-funnel Read the funnel The money flow, top to bottom. Pick a period 7 days, 30 days, 90 days, or quarter-to-date. The KPI tiles and the funnel recalculate for that period. Read the drop-off between stages Sessions received → Leads → Qualified → Procured (or Included) → Value. Click a tile or stage to work those leads The Clearing Pool opens filtered to exactly what it counted. Your plan Fourth stage KPI tile Pay-per-lead Procured — leads you bought, and the share of qualified leads that came to. Lead spend — the fees you paid. Subscription Included — the leads your plan covered, the same ones itemised on your invoice. Lead value — what those leads priced at, since the fee figure is always zero. The agent scorecard, the marketing table and the lines-of-business table follow the same rule, so a word means the same thing on every card. #### Find an agent that has gone quiet Anchor: /docs/analytics#find-an-agent-that-has-gone-quiet Find an agent that has gone quiet The scorecard — per agent, per placement. Sort the scorecard's last-lead column quietest first It shows when each placement last sent you a lead — “today”, “yesterday”, “20d ago” — and on how many separate days it produced anything. Click the row to see its leads The Clearing Pool opens with the agent's name in the search box and the quality floor dropped to zero, so nothing it produced is hidden by a setting left over from another task. Not listed? Widen the period An agent appears only because it produced a lead in the period you picked. “Quiet” and “broken” look the same here The column counts leads produced , not conversations attempted. An agent whose integration broke, still starting conversations every day, reads exactly like one that was switched off. Check with whoever runs it before assuming it was removed. #### Is the marketing money working? Anchor: /docs/analytics#is-the-marketing-money-working Is the marketing money working? The one number you supply. Enter what you spent per channel Cost-per-lead and return on ad spend appear for each channel. Connect a CRM for realizable value Without one, pipeline value reflects the budgets buyers stated. With one, the hub uses realizable value and net ROI from your own close rates — see CRM Insights . The hub caps the few most extreme stated budgets before totalling and says how many it capped, so one conversation cannot move the headline by a third. A channel with spend saved and no leads in the period still appears, and its money counts in your totals. #### Running more than one line of business Anchor: /docs/analytics#running-more-than-one-line-of-business Running more than one line of business One funnel across two different businesses describes neither. Start from the portfolio With more than one line, the default view shows whole-workspace counts and a sortable table with one row per line — leads, qualified rate beside the bar it was measured against, what it procured, the budget in that line's own units, and whether it has CRM history. Pick a line above the KPI tiles, or click its row The funnel, KPI tiles, quality panel, conversation figures, momentum chart, agent scorecard and tag mix switch to that line. Each row's Leads button opens that whole line in the Clearing Pool without changing which line this page shows. Lead scores are calibrated per line: a 70 in one line and a 70 in another do not mean the same thing, so rank leads within a line, never across lines. Workspaces that sell one thing see none of this. You'll know it worked: The KPI tiles show numbers for the period you picked and the funnel shows the drop-off between stages. Clicking a tile lands you in the Clearing Pool filtered to exactly what the tile counted — if it does not, the tile and the pool disagree and that is worth reporting. An agent has no row It produced no lead in the period. Widen the range. Past about a quarter, older leads are cleared by your retention settings, so an agent quiet for longer cannot be shown at all. Return on ad spend is blank Your lines measure value in different units (a purchase price and a monthly premium cannot be added). Cost per lead is unaffected. Picking a line does not fill it in either: your spend covers every line's leads. The card says so where the figure would be. Talk time says 2 of 5 timed, or no lengths recorded Your AI agent does not always send the conversation length — a recommended field, not a required one. Ask whoever builds it to send it; the developer documentation lists it as expected. Three panels are missing What they're shopping for, who they are and what your CRM says appear once you select a line — each describes one line in its own vocabulary. A line has conversations but no leads The page names the reason — still processing, or did not finish. Zeros there mean nothing to measure yet. Reading the cards: definitions and caveats Sessions vs. meaningful conversations. A session is any time an agent is engaged. Anything over a set number of seconds — 20 by default, and shown on the page so you see the figure in force (set per line of business) — counts as a meaningful conversation; shorter ones rarely carry enough to become a lead. That mark classifies conversations; it is not a billing rule . You are billed for leads that clear the quality gates, never for sessions. Agent scorecard. Each row shows leads and buyers produced, value, meaningful conversations (total sessions smaller beneath), and talk time in hours and minutes with an average. Rows are split by placement — the site the agent is embedded on — and carry its embed ID. Time says what it measured: 2 of 5 timed means the figures cover two of five conversations. Several embeds of the same agent on the same website share one row labelled with the embed count, so one going quiet can be hidden by another. Clicking an agent that runs on more than one website brings back all of its sites at once, while this scorecard counts each site as its own row; the pool says so when it happens. If a period holds more activity than the page can load, a notice says so and the active-day count shows ≥ ; the last-lead date is always exact. How to read these figures. One line near the top lists whatever caveat applies to the range — a capped window (a single line's figures are then a minimum), budgets in more than one currency, too few leads for percentages to be worth reading. Open it for the full explanation. Lines of business. A lead is qualified only if it clears its own line's quality bar, and budgets mean different things per line, so counts add up while rates and budgets are shown per line with their scale. Sessions do not split cleanly by line: a conversation that never became a lead belongs to none, so per-line session counts sum to less than the total. Marketing efficiency stays whole-workspace and labels itself, because nothing links a campaign to a line — splitting it would divide one line's leads by every line's spend. Questions a line never asks are not answered: the financial-credibility rows (pre-approved, cash buyer) are mortgage questions and do not appear for insurance, rather than reading zero. What your CRM says. With a CRM connected, one card per line reads your closed deals: close rate, average and median deal size, how long a deal takes, why you lose, where open deals stand, and close rate by lead source, deal type and deal size. Anything the card cannot compute it names, with whether to fill it in on your side or your CRM does not report it; the connection form lists what each CRM can and cannot tell you before you connect. Every figure is badged with a confidence level and the deal count behind it; realizable value carries the same note. What they're shopping for. What leads ask for in your line's own terms — product type, coverage reason and term length for insurance; asset class, use and property type for real estate. Fixed lists get a full breakdown; free text or stated figures get a count of leads that mentioned it. A row reading not stated by any lead in this range is a real answer. Who's in the pipeline. Customer journey (browsing to ready to transact); contact on file (phone or email left, and verified reachable); where they are from, shown as e.g. Florida (US); intent mix plus budget-stated, pre-approved and cash-buyer share. Every trait is captured in conversation and vetted before a lead is priced. What was filtered out. The Quality panel breaks down what was removed before it could become a billable lead — sessions under 20 seconds, integrity and fraud gates, unreachable contacts. None of it reaches your pipeline or your bill. ## Routing & alerts ### How Routing Works URL: /docs/routing How Routing Works Know which of four settings decides who hears about a lead, which leads qualify, when it runs and who it is sent from — so you can tell which one is broken. Before you start: Nothing to configure — this page explains the pieces the setup pages refer to. An administrator role, when you go on to change any of them. Every email and text alert passes through all four. Each red badge is a place where the run still reports success and nothing arrives. #### The four pieces Anchor: /docs/routing#the-four-pieces The four pieces Piece Decides Where Fails silently when Lead Tag WHO hears: the email addresses and phone numbers on the tag. Tags also decide which agents can see the lead. Settings → Lead Tags The tag has no address for the channel. Automation WHICH leads qualify (score, tags, lines of business) and HOW they are sent. Settings → Automations No Lead Tags chosen — it sends nothing. Scheduled job WHEN it runs. Settings → Scheduled Jobs It was never enabled. Email Engine Who mail is FROM — your own provider and domain. Settings → Email Engine It is not connected. #### Tags say WHO, automations say WHICH Anchor: /docs/routing#tags-say-who-automations-say-which Tags say WHO, automations say WHICH A Lead Tag carries the people — the email addresses and phone numbers that should hear about leads with that tag. An automation carries everything else: which tags it covers, and how it sends. So Email me about hot leads picks its tags, and each of those tags decides who gets the mail. That is what lets the Naples desk hear about Naples leads and nobody else's. Two exceptions. Slack and Teams do not route per tag: a chat address is the channel, you save one per workspace, and everyone in that room sees every lead it sends. Reports (digests) take their recipients on the job — see Alerts & Notifications . #### The three silent failures Anchor: /docs/routing#the-three-silent-failures The three silent failures 1 · A tag with nobody on it delivers nothing Point the text alert at a tag that has email addresses but no phone numbers and nobody is texted — the job still runs and still reports success. When you pick tags on an automation, any that cannot be reached are marked · no phone number or · no email address right there. 2 · Pressing Schedule creates the job switched off Nothing runs until you enable it under Settings → Scheduled Jobs . A job with no next-run time is not scheduled, however complete its settings look. This trips up almost everyone once. 3 · Sending is fail-closed Every report and email alert sends only through your connected Email Engine — there is no shared LeadLogiX fallback address. With no engine connected an email automation still runs, still shows a green history, and delivers nothing. Its row under Automations shows not set up ; that badge is the warning. #### Sent once per tag Anchor: /docs/routing#sent-once-per-tag Sent once per tag Each lead is alerted once per tag, ever , and marked so no later run repeats it. Adding a second alerting tag to a lead that was already alerted tells the people on the new tag. A lead carrying two tags that both reach the same person reaches them twice — keep overlapping tags off the same recipient list. Because of this marker, overlapping look-back windows never double-send. You'll know it worked: For any alert you can name the tag that holds its recipients, the automation that picks its leads, the job that runs it and the engine that sends it — and each shows as ready: the tag has recipients for the channel, the automation's row carries no badge, the job has a next-run time, and the Email Engine shows connected. Why sending is fail-closed, and why recipients live on the tag Mail goes out from your own provider, never a shared LeadLogiX address, so recipients see a sender they recognise on a domain they trust — better deliverability, and LeadLogiX can never email on your behalf without your setup. The cost is that a missing engine fails quietly, which is why the badge exists. Recipients live on the tag rather than on each automation so that one tag reaches one team, whichever channel is sending. Two tags can reach two different desks from the same automation, and a desk's list survives the automation being rebuilt. ### Lead Tags & AI URL: /docs/lead-tags Lead Tags & the AI Create the tags that route, alert on and scope your leads — with descriptions the AI applies correctly. Find it: Settings > Lead Tags. in the left sidebar of your workspace Before you start: An administrator role. A clear idea of what the tag means — the wording is what the AI matches against. #### Set up your tags Anchor: /docs/lead-tags#set-up-your-tags Set up your tags Click Use starter pack Seventeen common tags are added — hot lead, first-time buyer, cash buyer, phone consent and more. Pressing it again skips tags you already have. Delete the tags you will not use A short catalogue routes better than a long one: the AI adds at most five tags to a lead. Rewrite each description as a rule somebody could apply by hand Say exactly which leads should get it. “Leads who explicitly asked to schedule a viewing” beats “interested people”. Choose AI, a rule, or both Turn auto-assign on for judgement calls (“sounds ready to buy”). Leave it off and add a rule for exact conditions (“budget is at least $1,000,000”). Leave Returning Customer and Reached a Live Rep alone Both are set from what actually happened — a recognised returning visitor, a conversation handed to a human. Turning auto-assign on for them only adds guesswork. With auto-assign on, the description IS the instruction The AI applies the tag from what you wrote. Vague description, wrong tags; specific description, clean tags. #### Practice: watch a vague vs. specific description Anchor: /docs/lead-tags#practice-watch-a-vague-vs-specific-description Practice: watch a vague vs. specific description Edit the description, run the AI on four sample leads, and see who gets tagged. #### Tag on a field your agents send Anchor: /docs/lead-tags#tag-on-a-field-your-agents-send Tag on a field your agents send A rule matches exact fields; the AI reads the conversation and never these fields. A fact you want judged belongs in the conversation; a fact you want matched exactly belongs in the data. Ask whoever builds your AI Agents for the field name For example partnerCampaignId , or preferences.tier for a nested value. Add a rule row with the sourceMetadata. prefix path=sourceMetadata.partnerCampaignId , operator=equals , value=PC-4471 — or operator=exists to tag every lead that carries it. A field name that is not on the list matches nothing The tag simply never appears. Built-in fields are spelled exactly as listed under Reference below; sourceMetadata. fields use the name your Agents send, not a Custom Signal's name. #### Who an AI agent may actually contact Anchor: /docs/lead-tags#who-an-ai-agent-may-actually-contact Who an AI agent may actually contact Settings → Lead Tags → open a tag → AI agent disclosure Open the tag and find AI agent disclosure There are two boxes: one for the tag's email addresses, one for its phone numbers. Tick a box only for a contact you would print on a business card A published desk line or a team inbox. Ticked, an AI agent may give the address to a caller, put a live call through to the number, or send the desk a message about the enquiry. The same alerts still go out. Unticked (the default), the address still receives alerts. An AI agent asked for that desk says it is reachable and offers to have somebody make contact — it is never given the address and cannot read it out or transfer the call. The same two boxes exist on a person, under Workspaces & Users → open a member → AI agent disclosure ; they are off by default and only an admin can change them. Ticking a box does not switch alerts on These boxes govern disclosure only . Whether anyone is emailed or texted about a tagged lead is set up under Automations — a new workspace has none configured — and unticking a box never stops an alert that is already working. #### When your team is actually there Anchor: /docs/lead-tags#when-your-team-is-actually-there When your team is actually there Organization → Company Profile → Opening hours, and per desk under Settings → Lead Tags Set the workspace hours Organization → Company Profile → Opening hours . Pick your time zone, tick the days you are open, and set the times. Copy Monday to fills the rest — weekdays or every day . Every desk with no hours of its own uses these. Give a desk its own hours, if it keeps different ones Settings → Lead Tags → open a tag → When is this desk staffed? — an after-hours rota, or a team in another country. You enter It means Opening and closing time the same Open 24 hours — not a zero-length window. The editor reads it back as Open 24 hours. Closing earlier than opening (22:00 to 06:00) An overnight desk, read back as 22:00 until 06:00 the next morning. Nothing at all Hours not set — never reported as closed. Agents make no claim either way and use your contacts normally. Outside your hours an AI agent must not put a live caller through : it offers a callback, says when you reopen, and still sends the message , telling the caller when it will be read. A closed desk gives an agent exactly the same contacts as an open one. Hours read from your CRM, and how to switch them off If you have connected Salesforce or HubSpot, LeadLogiX reads what they already know. From Salesforce it reads your organisation’s business hours (the support hours for the whole company) and, if you use Field Service or Lightning Scheduler, each person’s own operating hours from the territory they are assigned to — both can appear, and that is not a contradiction. From HubSpot it reads each team member’s working hours, their time zone, and whether they have marked themselves available or away. Nothing is copied into LeadLogiX and nothing is written back — it is read once a night and shown alongside your own settings. An agent is shown each source separately, labelled in plain words (“the workspace’s own opening hours, set in LeadLogiX on Company Profile”, “this person’s own working hours, set in HubSpot”) with when each system was last read. If they disagree, both are shown and neither is overruled — LeadLogiX never silently reconciles them. To switch any of it off: Settings → Scheduled Jobs , open the CRM connection. The people list, opening hours, personal hours, availability status and time zone each have their own switch. Off means LeadLogiX stops reading it entirely — no API call, nothing stored — and agents are told an administrator switched it off. A person's own hours and away status are personal data Individual working hours (from HubSpot or a Salesforce service territory) and the available/away flag are disclosed only to applications you have authorised to read contact details — the same permission that governs email and phone. An application without it is told the information exists and is withheld, never that the person is unavailable. Time zones are stored as zones, not fixed offsets, so daylight saving is handled in both directions. The picker shows the current local time in the zone you select — the quickest check that you picked the right one. You'll know it worked: New leads that fit start arriving with the tag on their card. Tagging applies to leads processed after the tag exists; it does not reach back over leads you already have unless you switch on Re-tag existing leads . A tag never appears on new leads In order: the rule names a field that is not on the list (or misses the sourceMetadata. prefix); auto-assign is off and there is no rule; the description is too vague for the AI to apply; you are looking at leads processed before the tag existed. An agent sees no leads, or too few Their scope is set on Team, Roles & Access : no tags means an empty pool, and match mode All narrows fast. An AI agent will not give out a desk's number The AI agent disclosure box for that channel is unticked — the default. Tick it only for a published line. Callers are offered a callback while the desk is open Check the time zone on Opening hours, then whether the desk has its own hours under When is this desk staffed? Why tags carry so much A tag is the handle everything else grabs: alerts fire on tags, CRM sync selects on tags, AI automations filter on tags, and tags decide which of your people see which leads. If you want a lead routed, escalated or hidden, it happens through a tag. One field from your Agents can be shown as a Custom Signal and drive a tag; you do not have to choose, and neither needs the other. Every assignment records how it got there — AI, rule, manual or automation. A tag you remove stays removed: rules and the AI do not put it back until someone adds it again. The AI adds at most five tags, and only ones it is confident about; your own rules are never budgeted — every rule that matches applies. Hours never withhold a contact, deliberately. One contact serves two jobs: putting a live caller through, which is wrong at nine in the evening, and sending a message to be read when the desk next opens, which is exactly right then. If hours could hide a contact, the after-hours message would stop going out to prevent the after-hours transfer — and the enquiry you most want to catch is the one that arrives out of hours. An AI agent is always told which disclosure case applies. When a desk has no number recorded it is told so, and it does not fall back to somebody's personal line. It can tell “nobody has cleared this for me” from “there is nothing here”, so a caller hears “I can have the Naples team call you back” rather than an invented number. Most workspaces route callers to a tag's desk rather than an individual: a desk survives a person changing roles. Paired with customer profiles, tag segments become audiences you can market to — see Customer Profiles on the Product tab. What a rule can look at A rule reads a fixed set of fields, spelled exactly like this: leadRole , score , budgetAmount , budgetCurrencyCode , stateCode , countryCode , serviceInterests , isReturningVisitor and reachedLiveRep . It can also read anything your AI Agents send, with a sourceMetadata. prefix. What follows the dot is the field name your Agents send it under — not a Custom Signal's name. If they send partnerCampaignId: "PC-4471" , the rule is path=sourceMetadata.partnerCampaignId , operator=equals , value=PC-4471 . Use sourceMetadata.preferences.tier to reach inside a nested value. This is also the only way to tag on something specific to your business that LeadLogiX does not score. The tag form, field by field ### Alerts & Notifications URL: /docs/alerts Alerts & Notifications Tell the right people about the right leads — by email, text, Slack or Teams — or send them a scheduled digest. Find it: Settings > Automations. alerts are automations; reports are jobs under Settings → Scheduled Jobs Before you start: A mail sender connected under Settings → Email Engine, for anything sent by email. Without one, every send fails. An administrator role. The recipients, and the Lead Tag or job the alert is about. New to this? Read How Routing Works first — two minutes, and it explains why each recipe touches three screens. #### Recipe: email or text a desk about hot leads Anchor: /docs/alerts#recipe-email-or-text-a-desk-about-hot-leads Recipe: email or text a desk about hot leads Put the people on the tag Settings → Lead Tags , open the tag, add the email addresses — and phone numbers, for texts. Switch on the automation Settings → Automations → Email me about hot leads or Text me about hot leads . Choose which Lead Tags it covers A tag that cannot be reached is marked · no email address or · no phone number . Pick none and it sends nothing. For texts only: add the Twilio secrets TWILIO_ACCOUNT_SID and TWILIO_BASIC in the Secret Vault . Press Schedule, then enable the job Settings → Scheduled Jobs . The job shows a next-run time. #### Recipe: post leads to Slack or Teams Anchor: /docs/alerts#recipe-post-leads-to-slack-or-teams Recipe: post leads to Slack or Teams Copy the channel's webhook URL The automation's row says where to get it in Slack or Teams. Save it in the Secret Vault Name SLACK_WEBHOOK_URL or TEAMS_WEBHOOK_URL ; the URL is the value. Switch on the Slack or Teams automation and choose which leads it sends By score, by tag, or either. Press Schedule, then enable the job Qualifying leads post to the channel on the next run. Everything goes to the one channel you saved, and everyone in it sees all of it. Choosing tags picks the leads, not the people. #### Recipe: send a scheduled report Anchor: /docs/alerts#recipe-send-a-scheduled-report Recipe: send a scheduled report Add a Report job Settings → Scheduled Jobs , pick the report type and a schedule. Choose recipients Notification tags send each person only the leads carrying the tags they are on — two desks, two different emails from one job. Plain addresses on the job get every lead in the report. You can use both. Optionally write your own subject and body Only Returning Customers, Weekly Digest and Aging Leads use it. Preview it below first. Enable the job The report arrives in the inbox you named at the next run. Returning Customers is already in your workspace as a weekly job (Monday 8am), switched off: add a recipient address and enable it. #### Practice: preview the email Anchor: /docs/alerts#practice-preview-the-email Practice: preview the email Pick an alert, fill in “Email template”, and see exactly what lands in the inbox. #### The five report types, and which accept your own wording Anchor: /docs/alerts#the-five-report-types-and-which-accept-your-own-wording The five report types, and which accept your own wording Report What it sends Your wording Returning Customers People who came back — recognised across two or more conversations. One row per person: their public reference, how many conversations, when they came back. Skips the send when nobody returned. Yes Weekly Digest A 7-day summary. Always sends — a quiet week is still signal. Yes Aging Leads Leads sitting untouched past the age you set. Yes Lead Summary Last-24-hour lead counts — a daily heartbeat. No Pipeline Health Last-24-hour job executions — a daily heartbeat. No Lead Summary and Pipeline Health ignore the subject and body you write Both build their own. Customise either and nothing you typed will ever appear. Looking for a hot-lead report? Use the first recipe. The old Hot Leads report duplicated the hot-lead automations and could send the same lead twice, so it was retired. You'll know it worked: The alert or report arrives where you sent it. If the run reports success and no mail arrives, check the sender first — sending is fail-closed , so there is no fallback address it could have gone to instead. Nothing arrives at all In order: the job is still switched off; the Email Engine is not connected; the tags you picked have no address for this channel; texts are missing a Twilio secret. Full list: Automations & Jobs → Troubleshoot . One lead was announced, then never again Correct: each lead is alerted once per tag, ever. A new tag on the lead tells the people on that tag. Someone received the same lead twice The lead carries two tags that both reach that person. Keep overlapping tags off one recipient list. My custom subject never appears Lead Summary and Pipeline Health ignore custom wording; only the other three use it. Placeholders show odd values The placeholder {{workspace}} is not your company name, and {{date}} is not a friendly date. The practice preview above shows each one. What an alert email contains, and why Alert emails carry a short lead id, the score and the tags — never names or phone numbers . Someone who needs contact details opens the lead in LeadLogiX, where their role and tag scope still apply. Returning Customers reports on the person , not the lead: LeadLogiX merges a returning visitor's conversations into one customer profile, so somebody who has spoken to your agents four times is one row with a four. Names and contact details stay out of that email and behind your team's permissions. An automation can also fire the moment a profile merges and carry the whole history into what it sends — see what else an automation can do . ### Automations & Jobs URL: /docs/scripts Automations & Jobs Switch on an automation, give it the credential it needs, choose which leads it sends, and put it on a schedule. Find it: Settings > Automations. the “Automations” tab; credentials live in “Secret Vault” beside it; timing under Settings → Scheduled Jobs Before you start: An administrator role. Any credential the automation requires, already in the Secret Vault under the exact name it looks for. The Lead Tags you want it to cover, if it is one that filters by tag. #### Turn on an automation Anchor: /docs/scripts#turn-on-an-automation Turn on an automation Every automation is set up the same way. Ten minutes for your first, two for the next. Switch it on The toggle makes it available to your workspace. Nothing runs yet. Open the row and read its numbered instructions They are written for that automation, including who will see what it sends. Add the credential it names In the Secret Vault , under the exact name shown — for example SLACK_WEBHOOK_URL . Choose which leads it should send Only the settings that apply to how it runs for you are shown. Press Schedule — if the row offers it — then enable the job Under Settings → Scheduled Jobs . An automation LeadLogiX triggers itself has no Schedule button. The job has a next-run time; after it passes, the row shows the last run. #### Practice: set up a Slack hot-lead alert end to end Anchor: /docs/scripts#practice-set-up-a-slack-hot-lead-alert-end-to-end Practice: set up a Slack hot-lead alert end to end Credential, automation, leads, schedule — in the order the product asks for them. #### Read a row's status Anchor: /docs/scripts#read-a-rows-status Read a row's status Label Meaning Do No credentials needed Runs without anything in your vault. Nothing. secrets missing A credential it requires is absent. It will fail until you add it. Add it under the exact name the row lists. needs setup A required setting is empty. It runs and does nothing. Open the row and fill it in. not set up It sends email and no Email Engine is connected. Connect one — why . runs automatically LeadLogiX triggers it. No Schedule button, nothing to press. Nothing. no automatic trigger Nothing is driving it. Set a schedule if the row offers one; otherwise contact support. scheduled A job already runs it. Manage timing under Scheduled Jobs. managed Your workspace cannot switch it off. Nothing. last run failed It ran and failed. Hover for the reason. Read the reason in History. ran — delivered nothing It ran and sent nothing. Hover for the reason. Usually no qualifying leads, or no recipients. #### Choosing which leads get sent Anchor: /docs/scripts#choosing-which-leads-get-sent Choosing which leads get sent Every sending automation asks “Which leads should this send?” Answer Then set Use it for Leads that score highly enough A minimum lead score — LeadLogiX's 0–100 rating. Most workspaces start around 70. Volume control. Leads carrying certain tags Tags from your own catalogue. A category you always want forwarded. Either Both. A lead qualifies on score OR tag. Sending a low-scoring lead that matters anyway — someone who asked for a callback. This picks the leads , not the people. For Slack, Teams and webhook automations everything goes to the single address in your vault; email and text recipients are set on the tag ( tags say WHO, automations say WHICH ). #### Running it for one line of business Anchor: /docs/scripts#running-it-for-one-line-of-business Running it for one line of business On the job itself, under Scheduled Jobs — not on the Automations page. Open Scheduled Jobs and edit the job Find What this automation covers , under the question about which leads qualify. Tick the lines it should cover Each CRM connection is its own automation, so this is set per CRM: property leads to one CRM, insurance leads to another, neither seeing the other's. The job list shows your choice on the row. You choose Result Leave it empty Every line, now and later — a line you add next year is included automatically. This is how every existing automation already works. Pick one Leads in your other lines are skipped — not failed, not suppressed, outside the job. The run summary says how many were left out. Pick several The same, with more than one line ticked. Empty means every line — including ones you add later Running one line today, ticking it changes nothing now and everything later: an automation left empty follows you into a new business the moment its first leads arrive — your CRM receives them, alerts fire, briefs are paid for. Tick your line even if you only have one. A tag filter is not a substitute for this A tag filter picks leads out of the ones the automation already covers; What this automation covers decides what it covers at all. Narrowing tags instead gives the right result while the other line's leads are still scanned, counted and paid for on every run — the same tag can be applied in both businesses. Lines of business: the details It is on the job because what an automation covers travels with its schedule; the Automations page sets its other settings. Empty is the default rather than a list of today's lines precisely so that nothing existing changed when lines arrived — which is also the reason to tick, if you would rather a new business not be swept in. It is the setting that saves money. Automations that write an AI brief, send a text or push to a CRM cost something for every lead they touch; restricting one to the line where it is useful is the difference between paying once and paying twice. You may see a second group below your lines, Processing depths , naming records processed at a shallower depth — identity-only records : we resolved who someone is without valuing the enquiry. They are never shown as a line of business because nothing was sold in them, and they carry no score, so most automations find nothing to do there. You can scope an automation to them deliberately, and a CRM will then receive only those records. Reports that count your whole workspace, and the returning-customers report, have no Lines of Business setting: a returning customer is one person who can appear in both businesses. If one is set anyway on a report that cannot use it, the run says so rather than pretending it applied. #### Secret Vault Anchor: /docs/scripts#secret-vault Secret Vault Settings → Automations → Secret Vault tab — admins only Open Settings → Automations, then the Secret Vault tab It lists your secrets by name, with an Add secret button above them. Click Add secret The New secret form asks for Secret name , Secret value and an optional Description . Enter the exact name, and paste the credential as the value For a webhook, the URL is the value; the name is the label automations look it up by. Save secret The secret is listed by name only; the automation that needed it stops showing secrets missing. The name must match exactly — a near-miss reads as absent If an automation needs SLACK_WEBHOOK_URL , then SLACK_WEBHOOK or Slack_Webhook_URL is not found, and the row keeps saying secrets missing while a secret that looks right sits in your vault. You can replace a value at any time but not a name — to fix a typo, create it again under the right name and delete the old entry. Name Used for SLACK_WEBHOOK_URL Posting leads to a Slack channel TEAMS_WEBHOOK_URL Posting leads to a Microsoft Teams channel CUSTOM_WEBHOOK_URL Posting leads to an HTTPS endpoint you own TWILIO_ACCOUNT_SID Texting leads — required TWILIO_BASIC Texting leads — required, the base64 auth pair TWILIO_FROM Texting leads — optional; skip it if a Messaging Service carries the sender HUBSPOT_TOKEN Syncing to HubSpot, when you connect it by key rather than approval SALESFORCE_TOKEN Syncing to Salesforce, same CRM_WEBHOOK_KEY Syncing to any other CRM over a webhook Texting needs TWO secrets, not one TWILIO_ACCOUNT_SID and TWILIO_BASIC are both required — with only one the automation cannot send and shows secrets missing . TWILIO_FROM is optional when your Twilio Messaging Service supplies the sending number. What belongs in the vault The vault keeps each secret encrypted and hands it to an automation only when it runs, by its name, so a credential is never pasted into every automation or visible to your team. Values are never shown again — admins only see names. Rule of thumb: if a setup screen asks you to paste a webhook URL, API key, token or secret, it goes in the vault. If it sends you to another service to approve access, that credential is stored for you automatically and appears here afterwards. You do not have to remember the names: each automation's row lists the exact names it needs, and the Scheduled Jobs form picks them from your vault rather than asking you to type one. A name not in your vault cannot be selected — which is the moment to come here and add it. The secret form, field by field #### Scheduled jobs Anchor: /docs/scripts#scheduled-jobs Scheduled jobs Settings → Scheduled Jobs Create the job — or press Schedule on an automation Pick its type and settings. Choose the schedule and time zone Presets start at every 15 minutes; Advanced accepts a raw cron expression. Switch on Enabled A next-run time appears. A job with no next run is not scheduled. Workspace cap: 100 job runs a day, across all jobs A one-minute schedule burns the whole budget by midday and stops your other jobs too. A second job that does the exact same thing — same script, same CRM target or same report — is blocked, with a pointer to the job that already covers it, because it would do the work twice and could double-send. Edit the existing job to change timing. To stop a job, disable or delete it here; reports and alerts stop immediately, no billing or support needed. What you can schedule Job What it does Reports & alerts Hot-lead alerts, weekly digests, aging-lead nudges — emailed to your team. CRM sync Pushes newly scored leads to your CRM on a cadence, each carrying its customer profile: a summary plus a live link to the full profile. Learn from my CRM Reads closed-deal history for value insights — see CRM Insights. Automations Runs a ready-made automation, including profile-aware ones that act on a whole customer. Call my webhook POSTs to an HTTPS endpoint you own on a schedule, with secrets pulled from your vault. Lead cleanup Tidies up old leads on a retention window you choose (7-day minimum). A lead you paid for, or one belonging to a customer recognised more than once, is always kept the longest. The schedule form, field by field — and an interval calculator #### Email Engine Anchor: /docs/scripts#email-engine Email Engine Settings → Email Engine — admin only. Setup steps are in Quick Setup. Reports and email alerts send from your own provider. Follow Quick Setup step 3 to connect it; this section is the reference. Provider What it asks for SMTP Host, port, username, password. Ports 465 and 2465 use TLS directly; 25, 587 and 2525 upgrade to TLS. AWS SES The SMTP username and password from the SES console — not your AWS console login — and the SES endpoint hostname. Google Workspace An App Password — not the account's login password — and the Google email address. Saved values are masked server-side: the browser only ever receives dots, so a masked field on a re-opened page means it saved. Send Test Email to Me stays greyed out until a configuration is saved; the test goes to the address you are signed in with and reports which host it went through. The connection form, field by field #### Add AI briefs to your leads Anchor: /docs/scripts#add-ai-briefs-to-your-leads Add AI briefs to your leads Three ready-made automations write a short brief on each qualifying lead — one for OpenAI, one for Claude, one for Gemini. Put your provider's key in the Secret Vault Under the name the automation asks for. Until it is there the automation runs and stops, naming the missing secret. Switch on the automation for the provider you use Its row shows no badge. Set which leads qualify, and the two caps Minimum lead score, tag filters and look-back. Most leads per run is a batch size; Most leads per day (starts at 100) is the actual spending ceiling. Choose Response length About 90, 200 or 450 words. Press Schedule, then enable the job Briefs appear in each lead's detail panel, visible to admins and procurement. Cost is yours, and personal details are stripped by default Each lead briefed is a request to your account at the model provider. Names, phone numbers and email addresses are removed from what the provider sees, including inside the conversation summary; the switch that sends them anyway should stay off unless you have accepted that provider's terms for your customers' personal data. AI briefs: billing, CRM and length rules Enabling more than one does not bill you twice. Each lead is briefed once, by whichever provider reaches it first, so a second one can be kept configured as a fallback. A lead is never re-briefed: changing the model or the length applies to later leads only. On pay-per-lead, only purchased leads are briefed — the brief would sit behind the paywall with the rest of the paid detail. Buy a lead and the next run briefs it however long it sat unsold, because on pay-per-lead the look-back window counts from when a lead was purchased , not when it arrived. The run history says how many it skipped and why. It reaches your CRM, and your CRM waits for it. The brief is pushed as LeadLogiX AI Brief in HubSpot and Salesforce. A lead is sent to your CRM only once, so CRM sync briefly holds a lead still waiting for its brief — capped at six hours by default, so a provider outage delays your CRM rather than stopping it. The sync's history says how many it is holding. Ask us to turn the wait off for a connection if you would rather have leads immediately. Length. The model is asked for roughly 90, 200 or 450 words and told to finish its last sentence. A hard ceiling sits above that; on the rare run that reaches it, the brief is trimmed to its last complete sentence and the run history says so. A long brief is collapsed on the lead with a Show more link. The caps. The schedule multiplies Most leads per run — hourly at ten is 240 a day. When a day's briefs reach Most leads per day the automation stops, records why, and picks up after midnight UTC; skipped leads are still eligible tomorrow. A run also stops early rather than overrunning, and whatever it did not reach is picked up next time. A model can be wrong. Use briefs for things a human reads next — a summary, a draft, a suggestion — not for anything that silently decides where a lead goes. LeadLogiX's own scoring stays deterministic and auditable for exactly that reason. #### Sending a captured record for full valuation Anchor: /docs/scripts#sending-a-captured-record-for-full-valuation Sending a captured record for full valuation Capture cheaply, then decide which sessions are worth the full engine. Choose the line of business Settings → Automations , open Send records for full valuation , and pick which line the record should be valued as. Switch it on, and ask for the rule you want It runs automatically, so it needs a rule saying which records to act on. Ask your platform contact to narrow that rule to the records worth valuing. A promoted record comes back with a score, a price and tags, as though it had arrived that way. Every promotion is a full engine run, and it is billed On pay-per-lead a promoted record is billed at the price of the line you chose. A rule that promotes everything turns a cheap capture into a full-price lead every time. Captured records and promotion A captured session is recorded and marked complete with no scoring, pricing or tags, at a fraction of a valued lead's cost. The line you pick decides which questions the engine asks of the conversation and which price ladder the answer is read against — the wrong line produces a score that means nothing. A record can be promoted once, and leads that were already valued are left alone: re-running one under a different line would rewrite the billing and the basis its score was calculated against. You'll know it worked: The automation's row stops showing secrets missing or needs setup , its job has a next-run time, and after that time a run appears in its history with what it did. An automation that runs and reports success while doing nothing is the failure mode to watch for — the badges exist to catch it first. When something does not fire 1 · Is the job enabled? Pressing Schedule creates the job switched off. Enable it under Settings → Scheduled Jobs . This is the answer far more often than anything below it. 2 · Is the automation switched on? The toggle on its row. 3 · Does the row carry a badge? secrets missing , needs setup and not set up each mean it cannot work yet. last run failed and ran — delivered nothing mean it ran; hover either for the reason. A badge about the last run appears only when something went wrong. 4 · Have you chosen the Lead Tags it should cover? With none chosen it sends nothing. Unreachable tags are marked · no email address or · no phone number , whichever that automation needs. 5 · Is the look-back shorter than the gap between runs? A run only considers leads from its look-back window. Hourly with a 30-minute look-back means leads arriving in the other 30 minutes are seen by no run at all. Widen it — overlap is safe, because each lead is sent once per tag, ever. 6 · Was it already sent? Each lead is alerted once per tag, ever. Adding a second tag to an already-alerted lead does tell the people on the new tag. 7 · Does it say runs automatically, or no automatic trigger? Some automations react to a lead arriving rather than to a clock. runs automatically means something drives it; no automatic trigger means nothing does — set a schedule if the row offers one, and contact support if it does not. 8 · Check the run log — it shows every kind of run Settings → Scheduled Jobs → History lists every run labelled Scheduled , Event , Follow-up or Application , with the outcome and the reason, and you can filter to one. Automatic runs used to be missing from this page, so if you looked before and saw nothing, look again. A run skipped because you switched the automation off is marked skipped, not success or failure. What else an automation can do An automation is a general mechanism: something happens → your logic runs → something else happens . It can call any service you can reach over HTTPS, read and write LeadLogiX's own data, and use vault credentials without exposing them. LeadLogiX builds and publishes every automation on request — describe what should happen and when, and it appears ready to switch on, with its settings, credential name, badges and run history. See Built for How You Work . Trigger Example A schedule Every 15 minutes, hourly, Monday at 8am — sweeps, digests and syncs. An event A lead finished processing, was rejected, had its identity resolved — or a customer profile merged (someone came back), or its AI-written summary became ready. A record changing LeadLogiX reacts the moment a record appears or updates. Something it promised earlier A follow-up: work put aside for later, below. Shapes people use besides alerting: push each qualifying lead to a system without a built-in sync (a scheduling tool, a ticketing system, your own database) in whatever shape it expects; enrich a lead from a property, company or address service; check every processed lead against your own do-not-contact list and tag it so the CRM sync's exclude filter keeps it out; flag leads missing something your team depends on (no phone, no budget, no consent); act on a returning customer the moment a profile merges and send the whole story — “back after 3 conversations, now at decision stage, budget up to $650k” — to whoever owns that relationship, or attach a customer's history to any alert or webhook; and reconcile LeadLogiX against another system on a schedule, posting the differences. AI automations can also classify leads against your own taxonomy, draft a first reply for an agent to edit, or extract fields LeadLogiX does not model (a lot number, a policy type, a licence class). The brief is what ships today. Other settings you will meet Setting What it does Each run looks back this many minutes A run only considers leads from this window — set it longer than the gap between runs. Overlap never double-sends. Hidden for automations that run automatically: those are handed one lead at a time. Most leads in one message Between 1 and 100. Anything over the limit waits for the next run — nothing is dropped. Payload shape (webhook automations) Summary: id, score, tags and a link — smallest and most stable, for mapping fields by hand. Standard: adds a readable sentence. Whole record: adds the lead exactly as someone in your workspace would see it — unpurchased leads stay masked, internal diagnostics are never included. Minimum score 0 Also forwards records with no score at all (captured without valuation). Each record says which kind it is, so the receiver need not guess from a missing score. Follow-ups: work an automation promises to do later A follow-up is a single piece of work with a date on it — call them back in three days, re-check an enrichment tomorrow, send the second message on Friday — not a schedule that keeps firing. An automation hands it to LeadLogiX and finishes; LeadLogiX starts the automation again when it comes due, with whatever the first run chose to carry forward. Rule Detail A fixed number of tries Failed work is retried, a little later each time, up to the attempts asked for. An attempt is used when the work starts, not when it succeeds — so work that breaks its run stops after its attempts. A deadline Past it the work is not carried out — a call-back after the quote expired is worse than none — and it is recorded as having run out of time, not as failed. The lead is checked first If the named lead is gone (deleted, or in another workspace) the follow-up ends quietly. That is an ordinary ending: a 60-day follow-up outlives some leads by design. Never promised twice A follow-up can carry a name — usually the lead's id. Asking for the same name again keeps the first promise and its date, so a retried run cannot move a date somebody was told. Tests never create real work A test run can read the queue and cannot add to or remove from it — work scheduled from a test would be carried out for real, days later. These runs appear in History with the source Follow-up , at the moment the work is carried out. ## Integrations ### Connect a CRM URL: /docs/crm Connect a CRM Send scored, tagged leads into your CRM on a schedule — each lead once, with no CSV exports. Find it: Settings > Scheduled Jobs. use “Connect a CRM”; you'll either approve access or paste a key into Automations → Secret Vault Before you start: An administrator role in LeadLogiX, and enough rights in the CRM to approve access or issue a key. Lead Tags set up first, if you want to send only some leads — a sync job picks the tags it covers. For a key-based CRM: the credential in Settings → Automations → Secret Vault before the job runs. #### Pick your tab Anchor: /docs/crm#pick-your-tab Pick your tab Salesforce and HubSpot : you approve access in their own sign-in window, the credential is stored for you encrypted, and two jobs are created — leads out, closed deals back in. Any other CRM: a webhook , with its key in the Secret Vault. Every sync job is created switched off, and each CRM connection can be limited to one line of business ( how ). Salesforce #### Salesforce, start to finish Anchor: /docs/crm#salesforce-start-to-finish Salesforce, start to finish Both routes end with leads landing on your Lead object. Press Connect Salesforce Settings → Scheduled Jobs → Connect a CRM . Approve access in the Salesforce window — with a user who can add fields; a System Administrator is the simple answer. Two jobs appear, both switched off: Salesforce Lead Sync (leads out) and Salesforce Insights (closed deals back). Open the Lead Sync job and press Add LeadLogiX fields to Salesforce The one step that is not automatic — Add LeadLogiX fields to Salesforce adds fields to your Lead object, so we ask first. Safe to press twice. Put the fields on your Lead page layout In Salesforce: Setup → Object Manager → Lead → Page Layouts , edit your layout, drag the LeadLogiX fields on, Save. Put LeadLogiX Lead URL near the top. Pick a cadence and enable one job or both The Lead Sync's last run reports how many leads it sent. Skip the layout step and a synced lead looks empty Salesforce does not add new fields to a layout. Every value is on the record — in reports and list views — but not on the Lead screen until you drag it there. Other Salesforce profiles may also need the fields made visible by your admin; they are made visible to the person who connected. Building the job yourself instead: New job → CRM Sync → provider Salesforce , then the job form's Connect button, then step 2 above. Use it for one direction only, a specific schedule, or a tag filter from the start. #### Two ways leads reach Salesforce Anchor: /docs/crm#two-ways-leads-reach-salesforce Two ways leads reach Salesforce Chosen on the Lead Sync job. Option What happens Direct to Salesforce Recommended, and the default for new jobs. LeadLogiX writes Leads using the connection you approved — no Apex class, no middleware, no URL to find. Send to my own endpoint Each batch is posted to a URL you own — an Apex REST service or your middleware — for orgs that route all inbound data through something they control. Existing jobs keep whatever they were set to. Your own endpoint must be inside your own Salesforce org A destination outside Salesforce is refused: your Salesforce access would otherwise be handed to whatever service you named. Use an endpoint in your org, or switch the job to a credential that belongs to where you are sending. What LeadLogiX adds to your Lead object Add LeadLogiX fields to Salesforce creates 44 fields on your Lead object — the quality score and its label, the role and journey stage, the recommended channel and follow-up window, the vetting verdicts, consent, your line of business and its own facts, your tags, the customer's history across every conversation, a link back to the lead in LeadLogiX and one to the customer profile. It only ever adds: pressed twice, it reports what was already there. Every field is prefixed LeadVal_ and suffixed __c , so they sort together and never collide with a field your org had. LeadLogiX writes only these — never a standard field, another tool's field, or one you created. Group Count What it carries Identity 1 The key, LeadVal_Lead_ID__c — matched on every send so a repeated run updates the same Lead instead of creating a second. The sync will not start without it. Scoring 4 Quality index, quality label, suppression flag, billing status. Qualification verdicts 5 Intent, financial, geographic, authenticity and security — each the engine's own verdict. Working the lead 7 Role, journey stage, financing status, contact consent, contactability tier, recommended channel, follow-up window. Line of business 3 The line, the facts that line asks about, and what the budget figure means in it. Commercial 4 Lead price, budget amount, budget currency, service interests. Narrative 4 Business summary, AI brief, deficiencies, identity note. Links and tags 2 Lead URL — the conversation, how it was scored and the evidence — and tags. Customer Profile 14 The person behind the lead, including the profile URL — see Customer Profiles ride along . A field with no value is not written, so a lead with no profile leaves the profile fields blank — which is why a freshly connected org sees fields appear before it sees them populated. Your edits win. Correct a misheard name, fix a phone number or write notes in Description and the next sync will not write over them. A blank field still gets filled; what LeadLogiX works out itself — the score, the verdicts — keeps updating. The run notes name the fields it left alone. HubSpot #### HubSpot, start to finish Anchor: /docs/crm#hubspot-start-to-finish HubSpot, start to finish One connection, two jobs: leads out, closed deals back in. Press Connect HubSpot Settings → Scheduled Jobs → Connect a CRM . Approve in the HubSpot window, choosing the account leads should land in. Two jobs appear, both switched off: HubSpot Lead Sync and HubSpot Insights. Open the Lead Sync job and press Add LeadLogiX properties to HubSpot Add LeadLogiX properties to HubSpot is safe to press again; it only ever adds. Put the properties you care about on the contact record In HubSpot: Settings → Objects → Contacts → Record customization . They are grouped under LeadLogiX . Optionally turn on deals, then enable the jobs Deals are off by default; on, each lead also creates a deal linked to the person's contact. Skip step 3 and a synced contact looks empty The contact sidebar shows a handful of default properties. Everything LeadLogiX sent sits behind View all properties — present, and usable in lists, filters and reports. Deals are opt-in, and the choice is made at connect time Deal properties are only created if you asked for them when connecting. Turning them on later means running the connection step again so the properties exist before a send references them. What LeadLogiX writes into HubSpot Contacts — one per person , matched on email address: a second conversation updates the existing contact. A lead with no email gets its own contact, because guessing that two anonymous conversations are the same person is how CRMs fill with wrong merges. HubSpot records a person, not a conversation. Deals — one per conversation , optional, carrying the scoring and evidence that justify them. A new deal enters your pipeline's first stage and stays until someone moves it — sort on LeadLogiX LQI and LeadLogiX Quality Label instead of reading quality from the stage. The deal amount is left empty on purpose: LeadLogiX cannot know what the opportunity is worth, and filling it with what the lead cost would make your pipeline total read as your LeadLogiX spend — which Insights would then read back as revenue. Your edits win. A later conversation can fill a blank field but never replaces something you put right — and once a deal exists, its stage and amount are yours alone. Everything lands in a property group called leadval , so it is one collapsible block on the contact record rather than 44 fields scattered through the sidebar; every property is prefixed leadval_ . Contacts get 44 properties, keyed on leadval_lead_id — the same set as Salesforce, field for field. Deals get a deliberate subset of 28: the scoring and evidence, plus the profile id and URL so someone looking at an opportunity can reach the person, without the person-level fields that would be noise on every deal. HubSpot AI agents can read your leads directly Beyond the sync, LeadLogiX publishes a HubSpot app whose Model Context Protocol server lets an agent in Agent Builder query your workspace live — quality index, buying intent, qualification verdict and the journey summary — without the leads having been synced. The sync The MCP server What it does Writes LeadLogiX's fields onto HubSpot records on a schedule. Answers an agent's questions against LeadLogiX, when it asks. Direction LeadLogiX → HubSpot, one way. HubSpot agent → LeadLogiX, read-only. What it needs A connection and a sync job. The LeadLogiX app installed, and a workspace member to approve access. Scope granted Whatever the sync job is configured to send. Read leads without contact details ( leads:read ). LeadLogiX is not in HubSpot's own MCP Client connector list (Notion, Asana, Gong and others); it reaches Agent Builder as an installed app with its own MCP server component — same outcome, different route. The server is read-only and returns no contact details. Developers: MCP hosted server . Webhook (any CRM) #### Any other CRM, over a webhook Anchor: /docs/crm#any-other-crm-over-a-webhook Any other CRM, over a webhook Zapier, Make, or an endpoint you built. Save the key your endpoint expects in the Secret Vault For example CRM_WEBHOOK_KEY — see Secret Vault . Create the job Settings → Scheduled Jobs → new job → CRM Sync , CRM Generic / Raw (full lead document) . Enter the Webhook URL and the Auth Header Secret Webhook URL is your endpoint; Auth Header Secret takes the vault name , not the value. Set batch size, minimum score and tag filters, then the schedule Each run posts qualifying leads to your endpoint; the raw payload includes the full customer-profile snapshot. Enable the job To post leads to an endpoint on a schedule outside CRM sync, use the Call my webhook job or the custom-webhook automation — see Automations & Jobs . Getting results out by API instead: Webhooks & polling . One account per CRM, per workspace Connect again and sign in to a different Salesforce or HubSpot account, and that becomes the account every job for that CRM uses — leads land there and closed-deal figures come from there. Nothing is deleted; reconnect the original account to switch back. LeadLogiX says so on screen and records it in your security log. Reconnecting the same account after an expiry or revocation only refreshes the credential. #### What each lead cost is not sent unless you ask Anchor: /docs/crm#what-each-lead-cost-is-not-sent-unless-you-ask What each lead cost is not sent unless you ask A lead's price is visible to admins and procurement only, and most people who open a CRM record are agents — so LeadLogiX Lead Price is created but left empty. Turn on Include what each lead cost you in the Lead Sync job if your whole team should know; it applies to Salesforce, HubSpot and a raw webhook alike. If you added the Salesforce fields before this setting existed, press Add LeadLogiX fields once more so the price field exists — otherwise the sync quietly has nowhere to put the number. #### Customer Profiles ride along Anchor: /docs/crm#customer-profiles-ride-along Customer Profiles ride along Your CRM gets the person, not just the lead. A synced lead that belongs to a customer profile carries it: conversation count, first and last seen, latest stage, top score, a compact summary, and a live link to the full profile. HubSpot and Salesforce get these as profile fields — Add LeadLogiX fields creates them at the right type and width, including the long-text summary — and the raw webhook gets the full snapshot object. How profile fields behave The fields are a labelled point-in-time snapshot and never blank while something is known. The AI-written summary is used once ready; until then stable facts, preferences and recent context carry what LeadLogiX assembled from the conversations, and a summary state field says which you are reading. The live link is the freshness channel — the record never needs re-sending to stay useful. Profile text is capped to fit a CRM field. When it does not fit, whole entries are kept and the field ends with … (+2 more) rather than stopping mid-sentence. #### Insights: calibrate value from closed deals Anchor: /docs/crm#insights-calibrate-value-from-closed-deals Insights: calibrate value from closed deals Read-only. It never writes to your CRM. The Insights job reads your closed deals daily and turns them into the expected-value figure on your leads. Set-up, line-of-business mapping and what to do when the figure is blank: CRM Insights . #### Which of your people are in the CRM Anchor: /docs/crm#which-of-your-people-are-in-the-crm Which of your people are in the CRM Read-only. It answers a question you can otherwise only check by hand. Once a day LeadLogiX reads the list of people in each connected CRM and matches them by email to your team. The result is the In CRM column in Organization → Workspaces & Users — one chip per connected CRM on each agent and procurement member. Hover a chip for the reason and when it was checked. What you see What it means What to do In CRM Found, their account is live, and work can be assigned to them. Nothing. Disabled Found, but the account is switched off or archived in the CRM. Re-enable them in the CRM, or expect anything routed there to go unread. Not a seat The matching entry isn't a person — an integration account, or a shared queue. Match them to their own CRM user instead. Ambiguous More than one CRM user has this email address. Tidy the duplicates in the CRM. LeadLogiX won't guess which one is them. Not in CRM We checked, and nobody in that CRM has their email address. Add them, if they're meant to be working leads there. Unverified We couldn't check — the CRM was unreachable, or the connection has no permission to read its people list. Usually a reconnect. Hover the chip for the reason. “Unverified” is not the same as “not in the CRM” Unverified means the last check did not complete and says nothing about the person; only Not in CRM means we looked and they are not there. HubSpot connections made before this feature lack the permission to read people, so they show Unverified until you reconnect HubSpot. Salesforce needs nothing extra. A dash instead of a chip means the member is an admin (admins are not checked) or no CRM is connected. Each CRM is shown separately — someone can be live in one and absent from another. #### How duplicates are avoided Anchor: /docs/crm#how-duplicates-are-avoided How duplicates are avoided Every lead is sent with a stable id, and a per-lead marker is written only after your CRM confirms it received it; the next run skips anything marked. So each lead reaches your CRM once , even across overlapping or retried runs, and your CRM matches on the id to update rather than duplicate. You'll know it worked: The sync job's last run reports how many leads it sent, and the records exist in the CRM with LeadLogiX's fields filled in. A run that reports zero sent and no error means the filter matched nothing — check the tags before suspecting the connection. Zero sent, no error The job's tag filter or minimum score matched nothing in its window. Check the tags first. Nothing is reaching the CRM at all The sync job is still switched off — both CRM jobs are created that way. Then check the connection has not expired (reconnect the same account), and the line-of-business scope on the job. A synced lead or contact looks empty Salesforce: the fields are not on your page layout. HubSpot: the properties are not on the record view. Add LeadLogiX fields fails The Salesforce user who connected cannot add fields. The connection still works, but the sync cannot run without the fields — reconnect as a System Administrator. Leads are landing in the wrong account Somebody connected a different account. Reconnect the original one. A rep is not getting leads in the CRM Check their In CRM chip: Not in CRM, Disabled, Not a seat or Ambiguous each explain it. The CRM is waiting for leads that exist If an AI-brief automation is on, CRM sync holds a lead until its brief is written, for up to six hours by default — see AI briefs . ### CRM Insights URL: /docs/crm-insights CRM Insights & Value Projections Teach LeadLogiX what a lead is worth to your business from your own closed deals — without touching the objective score or price. Find it: Settings > Scheduled Jobs. New job → “Learn from my CRM”; projections then appear on each lead Before you start: A connected CRM — this reads from it and never writes. Closed deals in that CRM with amounts filled in. Deals closed without a value still count as closed but cannot tell LeadLogiX what one is worth. #### Turn it on Anchor: /docs/crm-insights#turn-it-on Turn it on Create the job New job → Learn from my CRM , then authorise on your CRM's own sign-in screen — there is no key to copy. (Connecting Salesforce or HubSpot creates this job for you as their Insights job.) Set a commission rate, unless your deals carry one Expected value is close probability × commission, so without either it stays blank. If you run more than one line of business, map your deals to lines On the connection: HubSpot deal pipelines or Salesforce Record Types, each to a line. Uses another field? Name it in Field to read and map its values. Enable the job It runs daily. Its run notes say how many closed deals it found, and how many were placed on a line. #### What you get — expected value to you Anchor: /docs/crm-insights#what-you-get-expected-value-to-you What you get — expected value to you A projection grounded in your own outcomes. Each lead shows a projected close probability , expected deal value and commission , an expected value (close probability × commission), and a typical sales-cycle length — labelled with the segment it came from and a confidence level. With too few comparable deals, LeadLogiX shows nothing rather than a shaky guess. #### If you run more than one line of business Anchor: /docs/crm-insights#if-you-run-more-than-one-line-of-business If you run more than one line of business Two businesses, two sets of economics — never one average of both. Each line gets its own value picture, and a lead is valued against the closed deals from its own line. The map is yours to edit — nothing in your CRM moves. Until you map anything, your workspace keeps one blended picture, and a line with no closed deals of its own falls back to it. A run that says SUCCESS can still be applied to nothing Success means deals were pulled — a separate question from whether they could be used. Deals your map does not cover are left out of the per-line figures and counted. Read the run notes: they say how many deals were placed on a line, or that none were, and why. You'll know it worked: An expected-value figure appears on your leads, and the calibration job's run notes say how many closed deals it used. Thin numbers with a note explaining why are the honest outcome — that is the job telling you it needs more history, not failing. If no expected value appears 1 · No closed deals have been pulled yet Nothing has been read from a CRM. Connect one, or check the calibration job has run — its history says how many closed deals each run found. 2 · Deals were pulled, but none could be placed on a line of business Your deals are healthy; LeadLogiX cannot tell which line each belongs to, so it refuses to apply them rather than credit one business with another's revenue. The message names the field it read and what it found — usually a value missing from your map, or a field your instance does not use. Both are fixed on the calibration job in minutes. 3 · Your deals are mapped, but not to this lead's line Nothing to fix — this line has no closed deals of its own yet, and fills in as you close business in it. Close probability shows, expected value is blank No commission figure on your deals and no commission rate on the job. The card says why. The numbers look thin Deals closed without an amount are left out of the averages, and the run notes say so. The firewall: insights never change the score or price CRM insights are display-only . They are built from your private history and kept strictly separate from LeadLogiX's LQI and price, which stay identical for every buyer. That separation is enforced in the platform itself, not just by policy: the scoring engine cannot reach the code that reads your CRM history, and the only writer of that history is the scheduled job that pulls it — nothing in your browser can edit it. Disconnecting is clean: delete a CRM's Insights job and everything learned from that CRM goes with it, including the history kept. Remove them all and LeadLogiX stops showing what a lead is worth to you and says so — scores, prices and spend carry on unchanged, because none of them came from your CRM. How it calculates: closed deals — what they closed for, won or lost, how long they took — are rolled into your own baselines of close rates, deal values, commissions and sales-cycle length, broken down by segment (such as geography or lead type) with a sample size each. Every new lead is matched to its closest segment. Expected value uses commission, not deal value, because what your client pays is not what you earn. Connected to more than one CRM, each is calibrated separately and the one with the most closed-deal history is used — never an average, because a number you cannot trace to one system is not worth much. Deal-size bands follow each line's scale, so a $400 premium is an ordinary insurance deal, not a tiny one. Insights tell you what a lead is worth; profiles tell you who the customer is and which campaigns produce them — see Customer Profiles on the Product tab. What Insights adds, and what it needs Capability Needs Pipeline projection from your own closed deals, not a benchmark Closed deals with amounts. Expected value per lead, traceable to your commissions and close rates At least one calibrated CRM. Quality-to-outcome comparison: where LeadLogiX's score and your outcomes agree Won/lost outcomes recorded. Field to read : leave blank for your CRM's usual field (pipeline, Record Type), or type the name of any field whose value tells one line from another — a deal type, a product, a business unit — exactly as your CRM stores it. LeadLogiX simply reads a different column. This page pulls history in ; pushing leads out is Connect a CRM . Use either or both. ### Custom Signals & Tool Evidence URL: /docs/custom-signals Custom Signals & Tool Evidence Put the extra fields your AI Agents send onto the lead, and turn the actions their tools record into a journey timeline and graded evidence. Find it: Settings > Custom Signals. admin only; tool evidence shows in any lead's drawer under Website Journey Before you start: An administrator role. The value already arriving with the session — a signal surfaces data your agents send, it does not create it. A decision about who should see it: signals are scoped by role. For tool evidence: your developer can send a tool's schema and one real sample, or your connected application can submit its own definitions if LeadLogiX has enabled tool submissions for this workspace. #### Add a custom signal Anchor: /docs/custom-signals#add-a-custom-signal Add a custom signal Start by asking whoever builds your AI Agents what the field is called. Get the field name from your Agent team Ask what key they send it under — for example partnerCampaignId , or preferences.tier for a nested value. Point the signal at it Metadata path = that name; a Label your team will recognise; Value type matching what they send — text, number or yes/no. Choose who sees it, and where Visible to roles decides who; Target tab decides which tab of the lead. LeadLogiX warns you if a role cannot see the chosen tab. Choose how it renders A field, a coloured badge for a short flag, or a metric with a unit and an aggregation (count, sum, average, min, max) under “Your metrics” in the Analytics Hub. Try each below. Open a lead that arrived after you saved The value is on the tab you chose. Older leads never get it. A name or type mismatch shows nothing A path that does not match what your Agents send arrives empty, and a wrong value type skips the value rather than showing it wrong. Neither raises an error. #### Three ways to use a signal Anchor: /docs/custom-signals#three-ways-to-use-a-signal Three ways to use a signal Pick a value and see how each one renders. #### Want it to DO something? Use a Lead Tag on the same field Anchor: /docs/custom-signals#want-it-to-do-something-use-a-lead-tag-on-the-same-field Want it to DO something? Use a Lead Tag on the same field Signals are display-only : they change what your team sees and never touch the LQI, price or identity. A Tag signal is a badge, not a Lead Tag — alerts, CRM sync and visibility key off Lead Tags. Both read the same data, so point a Lead Tag rule at the same field — path=sourceMetadata.partnerCampaignId , operator=exists — and one field does both jobs. See Tag on a field your agents send . #### Tool calls and the Website Journey Anchor: /docs/custom-signals#tool-calls-and-the-website-journey Tool calls and the Website Journey A transcript records what someone said; a tool call records what they did. Illustrative payloads; the card, chips and wording are the shipped ones. Your developer sends the tool's JSON Schema and one real sample A genuine console sample of one call and one result. You say what each parameter means Which business fact it represents — the part only you can answer. You flag anything private Names, emails, phone numbers, addresses — withheld until the lead is purchased. Send it to the platform Email with the schema, the sample and your answers. Or a connected application submits the definition itself through the API. A LeadLogiX administrator sets the mapping and approves it. From then on, every new lead from that tool gets a journey row and graded evidence. Submitted definitions are listed under Settings → Connected Apps on the Tool Registrations tab — waiting for review, approved and in use, not approved (with the reason), turned off, or expired — and you are emailed at each change (switch notices off per type under Company profile → Notifications ). A waiting definition does nothing: its calls are recorded as unattributed observations. An approval affects new leads only. Calls from unregistered tools are retained verbatim and counted, so nothing is lost while you wait. The tool name must stay exactly stable Matching is case-sensitive: start_quote and Start_Quote are different tools, so a rename or casing change after a refactor silently ends the evidence. Have alternate names registered as aliases. A result sent as a list vanishes from the timeline A result body must be one JSON object. A bare list is kept for review but produces no journey row and no facts — the most common way a result silently goes missing. You'll know it worked: The signal renders in the form you picked — a field or badge on the next lead you open, or a total under “Your metrics” in the Analytics Hub. A registered tool's calls appear as rows on the lead's Website Journey card, in transcript order, with a detail line built from the mapped parameters. A new lead shows no signal value The field is not arriving under that exact name, or the value type does not match. You are looking at a lead from before you saved. If the name is right, it is a question for your Agent team, not a setting here. No Website Journey card at all No recognised tool activity on this lead. If other leads show calls kept for review, registration is the gap. A count of calls kept for review Those tools are unknown, disabled, or scoped to another workspace. Send the tool name and a sample. Rows appear but the detail line is empty The tool is registered, but no parameters are mapped to facts. Decide what they mean and have them mapped. Rows never say paired Only one side of each exchange is sent. Ask your developer to emit the result as a second turn for the same tool. Evidence stopped after a release Almost always a renamed or re-cased tool. Compare the mono tool name on old and new leads; register an alias. Why tool calls are graded evidence Anyone can say they are ready to buy; a booking either exists or it does not. That is why tool calls are graded as their own class of evidence, and why two workspaces running the same agent can produce leads of very different quality — the gap is usually whether the actions were registered. Rows come only from tool calls: nothing the customer said and nothing the model inferred can add one. Your team builds and calls whatever tools your agent needs. Turning calls into graded evidence stays a LeadLogiX decision — what a field is worth and how much a source deserves to be believed are set on the platform side so they mean the same thing across every workspace. A submitted definition is a proposal: it changes nothing until a LeadLogiX administrator approves it. Only some fact keys move the score — a contact vector, a budget corroboration, a target property, an intent signal; the rest are recorded faithfully and do not. Privacy is decided at registration: a payload cannot mark itself private or public. Measured on one mid-strength lead: with no tool evidence it scored below the pricing floor and was unsellable; one tool-captured budget lifted it to $51.83; adding a target property and an intent signal took it to $59.76. A tool-captured phone matters more still — with no contact vector, reachability is zero and the lead is worth nothing. Tool evidence can also rescue a lead the model judged to have no purchase intent. Nothing about the vocabulary is built in — labels are free text, so the same machinery carries any line of business. An insurer's run_risk_check becomes RISK CHECK (band: preferred); home services' schedule_survey becomes SURVEY BOOKED; legal intake's run_conflict_check becomes CONFLICT CLEARED; staffing's submit_candidate becomes CANDIDATE SUBMITTED; logistics' quote_freight becomes QUOTE ISSUED. If the timeline should read POLICY_QUOTED , that is what gets registered. Distinct actions want distinct tools, so a buyer reads a story rather than one blob. What the Website Journey card shows On the row Where it comes from What it tells the reader The event label The Journey Event Type registered for that tool What kind of action this was, in your business's words The small mono name The tool name exactly as your agent called it Which tool produced the row — the audit trail The detail line The parameters that were mapped, as name: value What was actually asked for or recorded A paired chip A call and its result sent as two back-to-back turns for the same tool The tool's answer was captured too, not just the request A direction chip How the platform decided this was a call or a result Provenance — whether your connector declared it or it was inferred A footer count Calls from tools that are unknown, disabled or out of scope How much evidence is waiting on registration Which of a tool's results count as evidence — and why to send the result side, declare the whole toolset and avoid a catch-all tool — is decided by whoever builds your agent. Point them at Tool calls as evidence → Mapping decisions . Worked example: partner campaigns, start to finish Your Agent team adds the field partnerCampaignId: "PC-4471" on each conversation — and leaves it out when there is no partner rather than sending it empty, which matters for step 4. You add the signal Settings → Custom Signals → Add . Metadata path partnerCampaignId , Label Partner campaign , Value type Text, preset Filterable tag, a colour, the roles who should see it. Save. Check the next lead The badge is on the tab you chose. Nothing there? The field is not arriving under that exact name. Add a Lead Tag so it DOES something Settings → Lead Tags → Add , rule path=sourceMetadata.partnerCampaignId , operator=exists — your Agents' field name, not the signal's. Count them A second signal on the same field, preset Track a metric, aggregation count — under “Your metrics” in the Analytics Hub. Fields suit working context (a preferred contact time, a school district); badges suit the one flag you want noticed; metrics give a quick pulse without a report. Keep procurement-only detail off the agent view, and vice versa. ## Administer ### Billing & Plans URL: /docs/billing Billing & Plans Know what you are charged for and when, pay invoices, and keep the workspace from being suspended. Find it: Organization > Billing & Invoices. admin only — sub-workspaces roll up to the parent Before you start: An administrator role. Sub-workspace charges roll up to the parent, so this is the parent's admin. A payment method, if you want charges collected automatically rather than paying each invoice. #### Paying invoices Anchor: /docs/billing#paying-invoices Paying invoices Add a card once Payment Methods → add a card through Stripe → set it as default. LeadLogiX never sees or stores the raw card number. Invoices are charged automatically on their due date. No card, or auto-pay off? Open the invoice and press Pay now Paying happens inside the invoice, never from the list. Auto-pay itself is an account setting — contact us to change it. Confirm the amount and the card Nothing is charged until you confirm. The invoice shows your confirmation number, the card used, your bank's authorisation code and a link to the payment provider's receipt. Quote the confirmation number in any question about a charge. Invoices move through Paid Open Past due , and email notifications link straight to the invoice. #### Two ways to be billed Anchor: /docs/billing#two-ways-to-be-billed Two ways to be billed Plan How it bills Pay-Per-Lead Invoiced for the leads you procure — suits volume that varies month to month. Unlimited A flat subscription with no per-lead charge — suits steady, higher volume. The only plan on which processor modes 0 and 1 are available. Your plan is set with the platform; if Pay-Per-Lead volume is climbing, Unlimited usually becomes cheaper — ask us to switch. Changes normally take effect at your next billing period, and a lead at the boundary is billed exactly once , on whichever plan was active when you procured it. What a plan change does to the leads you already have Every lead keeps the terms it arrived under, permanently. A plan change decides what happens to future leads only. Moving to Pay-Per-Lead: everything delivered on Unlimited stays fully yours — contact details, CRM export, assistant access and automations — whether or not it was ever worth selling on its own. Leads arriving after the switch need procuring. Moving to Unlimited: everything from the switch onward is yours outright. Leads you declined to buy beforehand stay locked — moving to a subscription is not the same as having bought them. Procure one and it opens immediately. #### What you're charged for Anchor: /docs/billing#what-youre-charged-for What you're charged for Every invoice line traces to a specific lead — or to your subscription. Line Rule Each lead you procure (Pay-Per-Lead) Recorded at the price shown when you bought it, and invoiced exactly once. The flat subscription (Unlimited) One line per period. Every lead consumed is still itemised at $0, so you see what the fee covered. Discounts The rate in force when each lead was procured is stamped on that line. A later change never rewrites an old invoice. Tax Its own labelled line where a rate is configured for your account — never inside a lead's price. Never charged: leads that failed quality gates, sessions too short to become leads, and on Pay-Per-Lead anything you did not procure. Billing starts at your account's start date — no back-billing. All billing is in US dollars, handled through Stripe. #### When you're charged Anchor: /docs/billing#when-youre-charged When you're charged The billing rhythm, start to finish. Your period closes Periods run from your account's billing start date; everything procured (or consumed) in one goes onto one invoice. The invoice is issued by email with its due date You always see the itemised invoice before any charge; procuring a lead charges nothing at that moment. On the due date, auto-pay charges your default card Without a card or with auto-pay off, the invoice waits for Pay now. #### If an invoice goes unpaid Anchor: /docs/billing#if-an-invoice-goes-unpaid If an invoice goes unpaid The same schedule for every workspace, counted from the due date. Past due What happens 7 days First notice: the invoice, the amount, and the date the workspace would be suspended. 23 days Final notice, one week before suspension — always one week before, whatever your payment window. 30 days The workspace is suspended (for the standard 30-day window). A final email confirms it and says how to restore access. Until then the invoice shows Past due and the grace window is yours to settle in. Only a real balance can suspend a workspace — an invoice for $0 never does. These three emails go to your Billing email and to every admin , so a warning still arrives if the billing address is out of date; in Company Profile they show as Always sent rather than a switch. Invoice and refund notifications remain yours to turn off. Accounts settled manually — by invoice, bank transfer or agreement — get the reminder but no suspension date and no shutdown. Already paid outside LeadLogiX, or need a suspension lifted? Email ; we record the payment and restore access. #### If your workspace is suspended Anchor: /docs/billing#if-your-workspace-is-suspended If your workspace is suspended Everything that costs money stops. Nothing that holds your data does. Seeing “This account has been disabled” and unable to sign in, or your API keys suddenly stopped working? A workspace is suspended for one reason — an invoice unpaid past the schedule above. Check Billing for a Past due invoice, or email . Closed How Sign-in Everyone is signed out and cannot sign back in, by password or SSO. Nobody new can be added, including through SSO. API keys Lead capture and the identity endpoints refuse requests with a message naming the suspension. Scheduled jobs CRM sync, reports and automations are skipped — not failed or deleted — and resume when access is restored. Connected apps Cut off without waiting for their access to expire. Their authorisation is preserved, so nothing needs re-approving. Leads, customer profiles, invoices, tags and settings are untouched, and your team's email addresses stay reserved. Restoring access brings back exactly what was switched off — an application you revoked earlier stays revoked. Every sub-workspace is suspended with its parent, and changes take about a minute in both directions. You'll know it worked: The invoice list shows the current cycle with the leads it covers. You are billed for leads that clear the quality gates, never for sessions — a period with many sessions and few leads costing little is the system working, not a billing error. “This account has been disabled” — everyone at once The workspace is suspended over an unpaid invoice. Pay it, or email if you already paid outside LeadLogiX. Nothing is lost while it lasts. A refund is not on my card statement The invoice shows the provider's own status the moment a refund is applied — Refunded or Partially refunded , each with its own confirmation number. Applied is not arrived: a card refund commonly takes a few business days to appear. A sub-workspace admin cannot find Billing By design: sub-workspaces have no bill and no Billing page. Their usage appears as a breakdown on the parent's invoice. An invoice has $0 lead lines You are on Unlimited: each consumed lead is itemised at $0 to show what the flat fee covered. Why a price can never change after the fact A lead's price is sealed when the lead is scored and carries an integrity signature. Billing can read it but never change it, and each lead is invoiced exactly once — so an old invoice always re-adds exactly, even after a discount change. The billing console, and refunds Tab What it holds Plan & Usage Your current plan, this period's usage, and whether a default card is set. Invoices Every invoice with its status. Open one to pay it, read its confirmation and receipt, and — with sub-workspaces — see a per-team breakdown of leads and cost. Payment Methods Add or remove cards (entered securely via Stripe) and set your default. Ledger Every charge, payment, refund, tax line and adjustment, each with its confirmation number — what you reconcile a card statement against. Refunds go back to the original payment method through the card processor, and are issued by the platform. Both the invoice and the Ledger show the refund the moment it is applied. ### Security URL: /docs/connected-apps Security See which outside applications can reach your workspace and cut one off, review what they saved, and take a copy of the security log. Find it: Settings > Connected Apps. admins only — the page is titled Connected applications Before you start: An administrator role — these pages are not visible to other roles. At least one application authorized by somebody in this workspace. Until then the page is empty, and that is the correct state. See what is connected #### Read the list Anchor: /docs/connected-apps#read-the-list Read the list Open Settings → Connected Apps Each row is one authorized application: who allowed it and when, and when it was last used — or never used. Read the badges See the table below. Open It can on any row you are unsure of It lists exactly what the application was granted — see What an app may read. Badge What it means Reviewed by LeadLogiX LeadLogiX has checked who publishes this application. It says nothing about what it may read — that is set separately. Not reviewed Nobody has vouched for it — most applications, including legitimate ones nobody asked us about. Judge it as you would any software you install. Not working Listed but cannot do anything — usually because the person who allowed it was suspended or left. The line underneath says which. Connected is what has access now; Previously Connected is what used to — disconnected applications move there rather than disappearing. Tool Registrations appears only when an application has described its own tools to LeadLogiX; each registration has a Delete button, which stops LeadLogiX reading that tool's calls as evidence on new leads (processed leads keep what it read). A registration still marked Waiting for LeadLogiX review cannot be deleted until it is approved or declined; the application can register it again unless you disconnect it. The search box applies to both application tabs. A connected application, field by field What an app may read #### The “It can” list Anchor: /docs/connected-apps#the-it-can-list The “It can” list Each row's It can section lists what that application was granted, in plain sentences, folded away by default. What stays on the row is the part a decision turns on: whether it reads customer contact details (highlighted, with a lock icon) and how many permissions it holds. An application that grants nothing is never folded. What appears is what was granted , not what was requested — anything beyond what the approver could give was refused at approval and never appears. What you may see What it means Who can approve Read your leads, their scores and their summaries — without contact details. Scores, qualification and summaries. No names, phone numbers or email addresses. Any role. Read your leads INCLUDING customer names, phone numbers and email addresses. Personal data leaves LeadLogiX. Admin only — an agent or procurement user cannot, even by accident. Read merged customer profiles across your leads. A customer's whole history across every linked conversation — without contact details, and with names removed from written summaries. Needs the Customer Profiles API enabled for your workspace. Any role. Read merged customer profiles INCLUDING customer names, phone numbers and email addresses. The same histories with personal data. Needs the Customer Profiles API enabled. A profile spans conversations across groups, so a user restricted to particular tags can never approve it. Admin only. See which workspace you belong to and your role in it. Membership only — for applications that just sign people in. Any role. Check whether a person your AI is speaking to matches someone already in this workspace. Returns pointers to matching conversations — never their details. The only permission where personal data travels TOWARDS LeadLogiX: the application sends what it holds about the person it is talking to and gets back conversation and profile ids, never contact details. Used to recognise a returning customer mid-call. Needs the Identity Resolution API enabled. Admin only. See which colleagues and which team desks can be contacted, and be given the specific email addresses and phone numbers an admin has cleared for AI use. Contact details nobody has cleared stay hidden. Your own team's contacts, not customers' — including the desk each lead tag routes to. Necessary but not sufficient: every address and number is also gated by its AI agent disclosure box, so an app may hold this and be handed nothing. Never granted by default, even to an app that asks for everything. Admin only. Register tool definitions for this workspace and check their review status. Submitted tools stay inactive until a LeadLogiX administrator approves them. The app can describe its own tools so their calls become evidence. It writes only proposals, and can never approve its own. You can see and remove everything it submitted on the Tool Registrations tab. Needs tool submission enabled. Admin only. Run the automations this workspace has published to applications, and read the results they declare. Each automation is switched on individually by a LeadLogiX administrator; none is available by default. The app can make your workspace ACT — post to a channel, call an outside system, send a message, move a lead into a paid line of business. A published automation returns only the fields it was set up to return, never its logs. Needs automation access enabled. See the Automations tab. Admin only. Read back the notes this application has saved for you in this workspace, and search LeadLogiX's own product documentation. It sees only what it saved itself — not notes from other applications, and not notes belonging to your colleagues. Its own notes, scoped to this workspace, this application and you together — two colleagues may not be entitled to the same leads. The documentation search covers the same public pages for every workspace, read-only. Needs application memory enabled. Any role. Save notes and documents for you in this workspace, and delete them again. Only this application can read what it saves, and only for you. Notes expire on their own; documents saved for search stay until they are deleted or the workspace is. It writes, but only its own notes — nothing in your workspace is disclosed or changed by it. Capped per application per person. Unusual for a writing permission, any role can approve it. Keep assistants that must not read each other's notes as separate applications; a grouping label is for tidiness, not separation. Any role. Read back and list the files this application has saved for you in this workspace. It sees only what it saved itself — not files from other applications, and not files belonging to your colleagues. Files are scoped exactly as notes are. Needs application memory enabled. Any role. Save files for you in this workspace and delete them again. Only this application can read what it saves, and only for you. ⚠ Files do not expire on their own — they stay until they are deleted or the workspace is. Separate from saving notes on purpose: jotting things down is not storing arbitrary files. Discloses and changes nothing else. A cap is the only bound — see the Application storage tab. Any role. Nothing — this authorization no longer grants any access. The permissions behind it have gone, usually after a role change. Listed, but can do nothing. — Disconnect one #### Cut an application off Anchor: /docs/connected-apps#cut-an-application-off Cut an application off Open Settings → Connected Apps Admins only. The page is titled Connected applications . Press Disconnect on the row A confirmation appears — Disconnect ? — with Disconnect and Keep it connected . Confirm It can no longer read this workspace's leads, immediately. The row moves to Previously connected, with the date. Disconnecting affects this workspace only — and does not delete what it saved Anything built on the connection stops working until someone authorizes it again. It does not remove the application from LeadLogiX or other workspaces, does not stop someone here authorizing it again tomorrow (tell your team if that is the concern), and does not delete its notes and files — see Application storage. Application storage #### What an application has kept, and removing it Anchor: /docs/connected-apps#what-an-application-has-kept-and-removing-it What an application has kept, and removing it Open Settings → Application Storage One row per application per person. Search by application, person or collection name; filter by store. Read the three allowances Notes , Searchable and Files — capped separately, never added together (notes and collections in characters, files in bytes). Delete what should not be there A confirmation names what is about to go. Everything that application holds for that person — notes, collections and files — is removed. There is no undo The application simply finds its data absent. Notes expire on their own; collections and files do not — only a delete removes them, so a full allowance is why an application suddenly cannot save anything. A row labelled No longer connected names an application that was removed or withdrew its registration after saving something; its data stays listed and deletable here. Reconnecting an application finds its notes and files where it left them. Security log #### The security log Anchor: /docs/connected-apps#the-security-log The security log Who did what, when — and how to take a copy of it. Open Organization → Security Logs Filter by category, severity, person and date range. The list says when it is showing a window rather than the whole log. Export as CSV or JSON The same events either way — CSV for a spreadsheet, JSON for another system. Read the notice at download time: when an export stops at 10,000 events it says so there, because the file cannot carry the warning. One export a minute, shared by both formats Downloading CSV then JSON straight away is refused — they are one export offered two ways. Both buttons dim and count down; take the one you need first and the other a minute later. Recorded: sign-ins, role changes, key generation, connecting or switching a CRM, and each export of the log itself. To reach further back than 10,000 events, narrow the date range and export again. A JSON export's own count describes the file, not the log. When access fails #### It connected, and now nothing works Anchor: /docs/connected-apps#it-connected-and-now-nothing-works It connected, and now nothing works Consent succeeding is not the same as access working Application access is off by default for a workspace, and turning it on is a LeadLogiX administrator's action. Until then approving an application succeeds and every call it makes is refused. If a developer says their integration authorizes cleanly and is then refused on every call, email to have application access enabled. What you see What happened Not working The person who allowed it is suspended or has left. Someone still in the workspace must authorize it again. Nothing — this authorization no longer grants any access. Their role changed and the permissions behind the grant are gone. Re-authorize with an account that has the right role. Everything looks right and calls are refused Application access is probably not enabled for the workspace. Ask LeadLogiX. Apps that run automations #### Letting a connected application run an automation Anchor: /docs/connected-apps#letting-a-connected-application-run-an-automation Letting a connected application run an automation An app can run one of your automations by name only when all three are true: An admin approved Automation runs for that application It is its own permission on the consent screen. Approving it publishes nothing by itself. LeadLogiX has enabled automation access for your workspace Ask us. Until then the permission can be approved and every call is refused. The automation itself is published to applications One at a time, by us; none by default. An automation switched on for your own schedules is not reachable until published separately. This lets an application make your workspace act An automation built to act can send a message, call another system, or put work aside for days later, on your behalf. The automations published for this today are answer-only: they check whether an address is well-formed, whether a phone number is dialable, and whether two names refer to the same person. Each run appears under Settings → Scheduled Jobs → History labelled Application , with the application's name, beside Scheduled, Event and Follow-up runs. What “Reviewed” means #### A badge, not a permission Anchor: /docs/connected-apps#a-badge-not-a-permission A badge, not a permission Reviewed by LeadLogiX means we checked who publishes an application. It does not widen what the application may read, and marking one reviewed grants it nothing — vouching for who somebody is and deciding what they may read are different judgements. Not reviewed means nobody asked us to check: a self-registered application is limited to lead data without contact details until a LeadLogiX administrator deliberately widens it. #### Abandoned registrations Anchor: /docs/connected-apps#abandoned-registrations Abandoned registrations A nightly sweep lists every self-registered application with no activity for 90 days, and removes nothing unless a LeadLogiX administrator has switched removal on. Activity means being used , not being connected — a seasonal integration nobody has called in months counts as inactive, so say so before it lapses. You'll know it worked: Every application someone authorized is listed with who allowed it and when it was last used; a disconnected one moves to Previously connected. If nothing has ever been authorized the page says Nothing is connected — the normal state for a workspace with no outside integration, not an error to fix. Approved, and every call is refused Application access is not enabled for the workspace — see When access fails. A row says Not working The person who allowed it was suspended or left. Someone in the workspace must authorize it again. An application can no longer save anything Its storage allowance is full — collections and files never expire. Delete under Application storage. The log export was refused An export in either format was taken less than a minute ago. Wait for the countdown. ## Start ### Quickstart URL: /docs/overview Developer Reference Quickstart Mint a key, submit one session, and watch it reach a terminal state. The API is small and asynchronous: you notify LeadLogiX that a conversation is ready, then poll until it is done. Everything below can be run against the sandbox, where nothing touches production leads or reporting. #### What are you building? Anchor: /docs/overview#what-are-you-building What are you building? Pick the job. Each row is a complete path, in order. Your job The path What you need Get conversations scored The starting point for everyone. This page, then Ingest a session An ingestion key. POST /api/v1/sessions , then poll. Recognise a returning caller mid-conversation Give an agent memory. Recognise a returning customer Your ingestion key on your own workspace; OAuth as an installed app. Read scored leads into an AI agent MCP → Hosted server OAuth token with leads:read . No polling — the model calls tools. Drive LeadLogiX from Claude Desktop or Cursor MCP → leadval-mcp (npm) Your own ingestion key, running locally over stdio. Push leads into your own system Webhooks & polling No webhook signature exists — read that page before designing around one. Send your own fields alongside a conversation Custom metadata , then Tool calls as evidence Nothing extra — it rides on the session you already send. Let other companies connect their LeadLogiX OAuth 2.1 / OIDC , which sets the build order Open registration; no approval step. Add "Sign in with LeadLogiX" to your product Sign in with LeadLogiX OIDC. openid + workspace:read is enough. #### What you can build today Anchor: /docs/overview#what-you-can-build-today What you can build today Before you read further: one path is not self-service yet. Notify and poll score conversations your AI Agents already write to LeadLogiX — POST /api/v1/sessions, then poll the status Identity and profiles recognise a returning caller and follow the pointers, with the same ingestion key (the workspace must have the API enabled) MCP read leads, customer histories, routing and hours into a model — hosted over OAuth, or locally with leadval-mcp OAuth applications apps other customers install, and Sign in with LeadLogiX — open registration, no approval step A custom agent's write path the record contract is published (Session object) but the transport that writes it is not self-service yet; the supported producers are the AI Agents supplied with LeadLogiX. Building your own agent? Contact support@evas.live and it is arranged with you The one thing to understand before anything else LeadLogiX is notify-then-poll , not request-then-response. You do not upload a conversation and get a score back. The conversation is written to LeadLogiX's storage as it happens; you then tell LeadLogiX a session is finished, and it scores it in the background. POST /api/v1/sessions returns 202 Accepted and an id — never a score. #### How the pieces fit Anchor: /docs/overview#how-the-pieces-fit How the pieces fit Four surfaces, and which one answers which question. Surface Direction Credential Answers Ingestion API /api/v1/sessions You → LeadLogiX Ingestion key val_key__… "Score this conversation." Identity API /api/v1/identity/* You → LeadLogiX Ingestion key "Is this the same person?" and "do we know this caller?" Hosted MCP /mcp An AI agent → LeadLogiX OAuth access token "What do we know about this lead or customer?" Delivery CRM sync, webhooks, automations LeadLogiX → you Configured in the workspace "Tell me when something happens." #### Which credential Anchor: /docs/overview#which-credential Which credential Three of them, and they do not overlap. Which credential: your own servers acting on your own workspace use an ingestion key (X-API-Key: val_key__…); a person signed in to LeadLogiX uses a login session token; an application other LeadLogiX customers install uses an OAuth token — with resource=…/mcp to read data through MCP, or openid to sign people in. Credential Who uses it Where it works val_key__… Your servers and AI Agents The /api/v1 and /api/v2 endpoints — ingestion, identity, profiles. It acts on your own workspace and never expires on its own. Login session token A signed-in person, or your app acting as one Workspace and admin endpoints, including /api/tenant/keys/* , and the sandbox handshake OAuth access token An application a customer authorized The MCP endpoint /mcp , scoped to exactly one workspace They are not interchangeable, and a login session is not a service credential Sending an ingestion key to /mcp , or an OAuth token to /api/v1/sessions , fails — and the error says so. A login session token comes from a person signing in, carries an inactivity timeout and is revoked whenever roles or passwords change: a background job built on one works in testing and fails in production, on a schedule nobody controls. Building something other customers install? Do not ask them for an API key — use OAuth . #### Send your first session Anchor: /docs/overview#send-your-first-session Send your first session Four steps. The first two need no code. Mint an ingestion key Open Settings → API Playground as a workspace admin and click Generate . The key is shown once. Keys start with val_key__ and authenticate your servers — not your users. Keep it in your backend or secret manager, never in a browser or mobile app; rotating means revoking and re-minting. Send one in the Playground first The Playground submits through the same routes as your code and shows the request, the live status timeline and the conformance notes as they arrive. Getting a session through there first separates "my request is wrong" from "my pipeline is wrong", which is most of the debugging. Find it: Settings > API Playground. sandbox is on by default Notify from your own code One POST, two fields. LeadLogiX reads the conversation from your workspace's raw lead storage, so you never push transcript content through the API. Re-sending the same sessionId is safe — it never re-queues. processor names the line of business — 2 real estate, 3 US life insurance, both on every plan; see Processor modes . curl -X POST https:///api/v1/sessions \ -H "X-API-Key: val_key__…" \ -H "Content-Type: application/json" \ -d '{"sessionId":"conv_9F3A21","processor":2}' # 202 Accepted # {"status":"ACCEPTED","sessionId":"conv_9F3A21","processing":{"state":"QUEUED"}} const BASE = "https://"; const res = await fetch(`${BASE}/api/v1/sessions`, { method: "POST", headers: { "X-API-Key": process.env.LEADVAL_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ sessionId: "conv_9F3A21", processor: 2 }), }); console.log(res.status, await res.json()); // 202 { status: "ACCEPTED", … } import os import requests BASE = "https://" HEADERS = {"X-API-Key": os.environ["LEADVAL_API_KEY"]} r = requests.post(f"{BASE}/api/v1/sessions", headers=HEADERS, json={"sessionId": "conv_9F3A21", "processor": 2}, timeout=10) print(r.status_code, r.json()) # 202 {"status": "ACCEPTED", ...} Poll until it is terminal curl https:///api/v1/sessions/conv_9F3A21/status \ -H "X-API-Key: val_key__…" # {"processing":{"state":"PROCESSED"}, # "conformance":{"level":"ACCEPTED_WITH_NOTES","notes":[{"code":"SUMMARY_MISSING", … }]}} const TERMINAL = new Set(["PROCESSED", "DEGRADED", "FAILED"]); for (;;) { const r = await fetch(`${BASE}/api/v1/sessions/conv_9F3A21/status`, { headers: { "X-API-Key": process.env.LEADVAL_API_KEY }, }); if (r.status === 429) { // honour Retry-After, then carry on await new Promise((ok) => setTimeout(ok, Number(r.headers.get("Retry-After") ?? 5) * 1000)); continue; } const doc = await r.json(); if (TERMINAL.has(doc.processing.state)) break; // branch on processing.state, never on conformance await new Promise((ok) => setTimeout(ok, 2000)); } import time TERMINAL = {"PROCESSED", "DEGRADED", "FAILED"} while True: r = requests.get(f"{BASE}/api/v1/sessions/conv_9F3A21/status", headers=HEADERS, timeout=10) if r.status_code == 429: # honour Retry-After, then carry on time.sleep(int(r.headers.get("Retry-After", "5"))) continue r.raise_for_status() doc = r.json() if doc["processing"]["state"] in TERMINAL: # branch on processing.state, never on conformance break time.sleep(2) Terminal means PROCESSED , DEGRADED or FAILED — Session object says what each means. The status document never contains PII, a score or a price, so it is safe to log. Notes are a report on your payload, not a pass/fail: ACCEPTED_WITH_NOTES is the ordinary result for a healthy session. The sandbox is opt-in per request, and needs two credentials Without X-LeadVal-Sandbox: true a test session is a real lead — scored, reported and billed like any other. With the header, you must also send your LeadLogiX login session token as Authorization: Bearer … beside the key, for a person who belongs to the key's workspace; the header alone is refused with 401 UNAUTHENTICATED . Sandbox and live session ids are counted separately. #### Where the results come out Anchor: /docs/overview#where-the-results-come-out Where the results come out Notify, poll, and results out: your code notifies POST /api/v1/sessions and receives 202, polls GET /api/v1/sessions/{sessionId}/status until processing.state is terminal, and the scored lead leaves LeadLogiX through CRM sync, automations and scheduled jobs. The result is never in the ingestion API — the status says when a session finished, not what it scored. There is no signed event you can subscribe to. That is deliberate, and Webhooks & polling explains the three routes out and which to pick. To read a scored lead into a model, use MCP → Hosted server . #### Base URL and format Anchor: /docs/overview#base-url-and-format Base URL and format Item Value Base URL Your LeadLogiX host, e.g. https://app.your-workspace.com Content type application/json , required on POST Errors RFC 9457 application/problem+json — branch on code , never on the message. All of them: Ingestion errors & limits Style Asynchronous — notify, then poll status until terminal SDKs None, by design — generate a client from /openapi.yaml . See AI & tooling #### Processor modes Anchor: /docs/overview#processor-modes Processor modes Chosen per session, in the notify call. Mode Name Kind What runs Plan 2 REALESTATE Line of business Full pipeline: identity, geocoding, quality scoring, pricing. All plans 3 LIFE_INSURANCE_US Line of business Full pipeline, calibrated for US life insurance. No subject geocoding — a policy has no address. All plans 1 IDENTITY_ONLY Pipeline depth Identity resolution only — no scoring or pricing. Unlimited 0 AUTOMATION_ONLY Pipeline depth Runs your automations only — the engine is skipped. Unlimited Send the line of business the conversation belongs to — a wrong one is priced silently The mode selects which calibration prices the lead and which cohort it is compared against, so a life conversation sent as 2 is priced against the wrong market and nothing in the response says so. Modes 0 and 1 require the Unlimited plan and are refused with PLAN_UPGRADE_REQUIRED , never silently downgraded; the plan gate never stands between you and a line of business. The list of lines of business grows A 422 PROCESSOR_UNSUPPORTED enumerates every mode the server accepts, by number and label — it is generated from LeadLogiX's own registry, so it can never tell you a mode does not exist on the day it starts working. Read that response rather than hard-coding this table, and treat an unfamiliar processorLabel as an unknown line of business — never as real estate. ## Ingestion API ### Ingest a session URL: /docs/integration Developer Reference Ingest a session The ingestion path end to end, from minting a key to reading a processed lead's status. Where your AI Agents already write to LeadLogiX, steps 2 and 5 are already done and only 1, 3 and 4 are yours to wire. Mint an ingestion key In Settings → API Playground , a workspace admin generates a key ( val_key__… ). Copy it once and store it securely. This is the credential your AI Agents present when they notify LeadLogiX. Your AI Agent finishes a conversation The Agent writes the transcript and context to LeadLogiX's raw lead storage, keyed by a sessionId you choose. If you use Agents supplied with LeadLogiX this is already done. If you are building your own Agent, this step is yours — and it is the one that decides whether the session is scored at all. The record has a contract, and it is fully specified in Session object : the metadata contract ( sessionId , content , started and embedding are mandatory — their absence rejects the session outright), the transcript grammar your content must follow, the container tags the document must carry (your workspace tag first), and the minimum fingerprint . Read those four before writing the emitter; between them they decide whether a session is scored, silently degraded, or never opened at all. The transport that writes the record is not self-service yet — see What you can build today . Notify LeadLogiX the session is ready POST the sessionId and a processor mode — the line of business this conversation belongs to ( 2 real estate, 3 US life insurance; both on every plan). LeadLogiX queues it and returns 202 ACCEPTED immediately. Re-notifying the same sessionId is safe — it never re-queues. The same call in curl, Node and Python is on the Quickstart . POST /api/v1/sessions Content-Type: application/json X-API-Key: val_key__… { "sessionId": "conv_9F3A21", "processor": 2 } # → 202 Accepted { "status": "ACCEPTED", "sessionId": "conv_9F3A21", "processing": { "state": "QUEUED" } } Notify as soon as the conversation ends — don't wait Write your final turn, then notify. LeadLogiX waits for the record itself and retries while it is still assembling, so you do not need to poll storage first. In particular do not wait for the written summary : it is produced by a separate enrichment pass that runs afterwards and is batched — measured on a live session, the transcript was complete in about 10 seconds and the summary still had not appeared three minutes later. A lead whose summary has not landed yet still processes; it is simply noted and revisited. Waiting for it only delays the lead. Mint ids that stay distinct once punctuation and case are removed — chat-1 and chat_1 are one session, and the second is never scored. The rule . Poll for status until it's terminal Poll every couple of seconds until processing.state is PROCESSED , DEGRADED , or FAILED . The status is safe to expose — it carries conformance and progress, never PII, score, or price. GET /api/v1/sessions/conv_9F3A21/status X-API-Key: val_key__… # → 200 OK { "sessionId": "conv_9F3A21", "ingestedAt": "2026-07-25T14:03:11.000Z", "processorMode": 2, "processorLabel": "REALESTATE", "conformance": { "level": "ACCEPTED_WITH_NOTES", "evaluatedAt": "2026-07-25T14:03:20.000Z", "notes": [ { "code": "SUMMARY_MISSING", "path": "summary", "message": "…" } ] }, "processing": { "state": "PROCESSED", "reprocessingCandidate": false } } Notes are not failures: ACCEPTED_WITH_NOTES is the everyday result, because the level is simply "were there any notes". Branch on processing.state ; read the notes to improve the payload. Every field of this document is on Session object . A session waiting for its metadata is not a rejected one — do not give up on it A document whose content has landed but whose metadata has not is re-read on a backoff rather than rejected, so a session can sit at QUEUED with waitingForSource: true for roughly 200 seconds while processing.awaitingMandatoryKeys names the keys still missing. That is a diagnosis, not a verdict: the keys usually arrive, and a client that gives up abandons a session about to succeed. Both waiting fields and rejection contain missingMandatoryKeys — branch on the field name, never on that shape. Field Tense Read it as processing.awaitingMandatoryKeys Now The keys this session is still waiting for. Present only while it waits. processing.lastAwaitingMandatoryKeys Past The same thing, kept after the session settles, with an observedAt — so it appears on PROCESSED sessions too. Both present: still waiting. Only this one: the wait is over and processing.state says how it ended. Watch it on sessions that succeed : a connector beginning to lose leads shows up here first, as sessions that waited and recovered — your writer is sending metadata later than content, one slow index away from a rejection you cannot appeal. Work the scored lead The lead now lives in your Clearing Pool — scored, tagged and priced. From here it flows to your CRM and chat tools automatically; see Webhooks & polling . #### Choosing a processor mode Anchor: /docs/integration#choosing-a-processor-mode Choosing a processor mode One field, and it decides which pipeline runs. processor is not a formality, and the four values are not degrees of one thing. Two of them are lines of business and two are pipeline depths. 2 and 3 each run the whole product against their own calibration; 1 and 0 run part of it. Choosing between 2 and 3 is a question about what the conversation was about; choosing between 2 and 1 is a question about how much of LeadLogiX you want to run. Mode What runs What you get back What you give up 2 REALESTATE All plans The full pipeline: identity, geocoding, vetting, quality scoring, pricing, tagging. A scored, priced, tagged lead in your Clearing Pool, exportable to a CRM. Nothing. A full line of business, available on every plan. 3 LIFE_INSURANCE_US All plans The same full pipeline, against the US life-insurance calibration and its own fact set: product type, coverage amount, term length, reason for cover, residence state, beneficiary named, ready to apply. A scored, priced, tagged life lead — compared against life leads rather than property leads, and priced by life calibration. Subject geocoding. A policy has no address, so no subject location is looked up and none is claimed. The residence of the person is still resolved: on this line of business it is the licensing gate. 1 IDENTITY_ONLY Unlimited Identity resolution, then stop. A match verdict — is this the person from session X? — and profile stitching. No score, no price, no geocoding, no CRM-ready lead. Nothing enters the paywall. 0 AUTOMATION_ONLY Unlimited Your automations only — the scoring engine is skipped entirely. Whatever your automations do (webhooks, alerts, CRM writes you drive yourself). No identity, no score, no price. LeadLogiX is acting as a trigger, not a scorer. Mode 3 is not a flavour of mode 2 Four differences an integration will notice, all of them deliberate. 1. Money means something else. budget on a mode-3 lead is a monthly premium ; on mode 2 it is a one-off purchase price. The two are orders of magnitude apart — a large policy costs tens of dollars a month — so never sum, average or compare them, and label each from its own line of business rather than printing a bare "Budget". The coverage amount discussed is a separate fact and is not the budget. 2. The roles are disjoint. OWNER , INSURED , BENEFICIARY and PAYER — four roles that can all sit on one contract and are not synonyms — where mode 2 has BUYER and SELLER . Map an unfamiliar role to unknown, not onto the nearest role you already handle. 3. Nothing about the subject is geocoded. The lead's residence is resolved as usual, and on this line of business it matters more than anywhere else — it decides which licensed agents may lawfully contact the person. But a policy itself has no location, so no subject lookup is performed and no proximity result is claimed. Each line of business also declares the markets it serves in its own calibration ( supportedGeocodingCountries ); LIFE_INSURANCE_US serves the United States, so a residence outside it degrades honestly to stateCode: "XX" rather than being re-aimed at a market that cannot contain the answer. 4. Health details are never kept. A life conversation contains them — it is how callers expect to be quoted — and LeadLogiX records none of it, scores none of it and releases none of it. That is a rule, not a gap: an empty health field is not evidence the caller stayed silent, so do not read one as a finding. The plan gate never blocks a line of business Both 2 and 3 work on every plan. Only modes 0 and 1 are refused on a plan that does not carry them, with 403 PLAN_UPGRADE_REQUIRED — named, never silently downgraded, so you find out at integration time instead of discovering months of unscored leads. The mode is per session, not per workspace Nothing stops you sending mode 1 for one conversation and mode 2 for the next. They are different sessions with different ids. #### What each mode returns Anchor: /docs/integration#what-each-mode-returns What each mode returns The status document, mode by mode — and where the actual result comes out. The notify API never returns a result. It returns progress — the diagram . Whatever the mode, a successful poll looks the same shape; what differs is processorLabel , whether conformance is populated, and where you go to collect the outcome. Mode Terminal status you poll conformance Where the result actually is 2 REALESTATE { state: "PROCESSED" } , processorLabel: "REALESTATE" Populated — CONFORMANT or ACCEPTED_WITH_NOTES The scored lead. Out via CRM sync, alerts/automations, the dashboard, or MCP search_leads / get_lead . Not in this response. 3 LIFE_INSURANCE_US { state: "PROCESSED" } , processorLabel: "LIFE_INSURANCE_US" Populated, graded identically to mode 2 — CONFORMANT or ACCEPTED_WITH_NOTES . A full line of business also rejects a source document missing a mandatory metadata key, exactly as mode 2 does; modes 0 and 1 never do. The scored lead, in the same places. Its facts are life facts — product type, coverage amount, term length, residence state — and its budget is a monthly premium. 1 IDENTITY_ONLY { state: "PROCESSED" } , processorLabel: "IDENTITY_ONLY" Populated, and graded like mode 2 — CONFORMANT or ACCEPTED_WITH_NOTES . Mode 1 opens the source document, so the contract can be checked against it. A resolved userId and an identity verdict written onto the lead, feeding Customer Profiles. Not readable through this API — see below. 0 AUTOMATION_ONLY { state: "PROCESSED" } , processorLabel: "AUTOMATION_ONLY" Terminal { level: "NOT_EVALUATED", reason: "AUTOMATION_ONLY_NO_DOCUMENT_FETCH" } — the source is never fetched, so there is nothing to grade Whatever your automations did — a webhook you received, a CRM row you wrote, an alert you were sent. Never poll for conformance — poll for processing.state processing.state is the completion signal. Conformance is an observation about the payload, and on mode 0 there is no payload to observe: the level settles on the terminal value NOT_EVALUATED , with reason naming why and evaluatedAt present and null . Polling will never move it, and it is not a fault — read reason and stop. Only PENDING is worth waiting on, and it now means what it says: still in flight. An integration that waits for CONFORMANT will still wait for ever on a mode-0 session that finished perfectly, because CONFORMANT is a grade and mode 0 never earns one. Mode 0 completes with lead.automation_ready The engine never runs, so no lead.processed is emitted — that would imply a pipeline ran, and would change what every existing rule fires on, since a rule with no lines named runs on all of them. Automation rules that react to a mode-0 session completing subscribe to lead.automation_ready . lead.ingested is arrival, not completion Every mode emits lead.ingested , and it fires while the session is still being accepted — the lead reads QUEUED at that moment, with no processing result on it yet. Subscribe to it when you want to know a session ARRIVED. To act on what a session produced, use the completion event for its line: lead.automation_ready for mode 0, lead.identity_resolved for mode 1, lead.processed for a full vertical. Metadata filters and mode 0 A mode-0 rule cannot use a metadata filter. The metadata lives in the source document, which mode 0 is defined never to fetch, so it never reaches the lead at all. Target a mode-0 rule by its line and workspace, or use a document-watch rule, which filters on the lead document itself. Mode 1 has no such limitation. If your agent needs an identity answer DURING a conversation, mode 1 is the wrong tool Mode 1 is asynchronous and its verdict is not in the status response — it exists to stitch profiles in the background. To ask "is this the person from session X?" and branch on the answer while the visitor is still talking, call POST /api/v1/identity/lookup — the Recognise a returning customer recipe — or POST /api/v1|v2/identity/resolve when you already hold the candidates. Both answer synchronously with a verdict. Reaching for mode 1 and then polling for something that never arrives is the most common wrong turn on this API. #### Reading results back Anchor: /docs/integration#reading-results-back Reading results back Two credentials, two surfaces — and an ingestion key cannot read. An ingestion key ( val_key__… ) writes and asks; it does not browse. It can notify and poll sessions, run the identity calls, and submit and poll a profile merge — but no endpoint lists or searches scored leads for a key. The only lead a key can read is one an identity lookup pointed it at, through GET /api/v1/leads/{leadId}/context under the same paywall and scrub as everywhere else. That is deliberate: a key lives in an Agent runtime, and scored leads carry contact details and price. You want Use Credential To submit a finished conversation for scoring POST /api/v1/sessions Ingestion key ( X-API-Key ) To know whether it finished GET /api/v1/sessions/:id/status Ingestion key A same-person verdict, synchronously POST /api/v1|v2/identity/resolve Ingestion key To ask mid-conversation whether this workspace already knows the caller POST /api/v1/identity/lookup Ingestion key To follow a pointer that lookup returned GET /api/v1/leads/{leadId}/context , GET /api/v1/profiles/{profileId} Ingestion key To READ scored leads back into an agent MCP — search_leads , get_lead , get_lead_brief , list_lead_tags OAuth 2.1 access token with leads:read To ask whether a lead reached the CRM, or who can be given it MCP — get_lead_crm_status , list_crm_agents OAuth 2.1 access token with leads:read To read one customer’s whole history back into an agent MCP — get_customer_profile OAuth 2.1 access token with profiles:read Leads pushed to you without polling CRM sync, a custom webhook job, or an alert automation Configured in the workspace, not by API GET /api/leads is not part of the API It exists, and it will refuse your key. It is the dashboard's own endpoint and requires an admin login session , not an ingestion key. If you are building something that needs to read leads programmatically, that is MCP — see MCP → Hosted server , or the two read-back endpoints in Identity & Profiles API for a lead the identity lookup pointed you at. #### Verifying your integration Anchor: /docs/integration#verifying-your-integration Verifying your integration Four things worth proving before you go live. Prove How The happy path Send a complete session in the Playground with Sandbox on , then poll until processing.state is PROCESSED . That is the pass condition. Read conformance.notes and fix what they name, but do not assert on CONFORMANT — see below. Your rejection handling Deliberately omit started from the conversation record and notify. You should get a terminal FAILED carrying rejection.missingMandatoryKeys: ["started"] — assert on the code , not the message. Your note handling Omit an expected key such as speakers . The session still reaches PROCESSED , with conformance.level: ACCEPTED_WITH_NOTES . If your integration treats that as a failure, it is wrong — notes are advisory. Your retry logic Notify the same sessionId twice. The second call must return 202 with alreadySubmitted: true and must not create a second lead. Do not assert on CONFORMANT — a correct integration usually is not CONFORMANT means zero notes of any kind, and two routine notes are not yours to remove. SUMMARY_MISSING appears whenever the written summary has not landed by the time the pipeline runs — a separate, batched pass, measured still absent three minutes after a transcript was complete — and SPEC_VERSION_MISSING appears on any connector that omits leadvalSpecVersion . A build gated on CONFORMANT goes red for timing rather than for a defect. Gate on processing.state === "PROCESSED" , and review the note code s as a quality report rather than a gate. Do not test by re-sending a rejected session id A sessionId is spent the moment it is accepted, rejected included. Re-notifying returns the stored outcome rather than reprocessing, so a fix you make after a rejection needs a new id to be observable. Remember the id is compared with punctuation removed and case folded — changing conv-1 to conv_1 is not a new id. #### Knowing which of your users exist in the CRM Anchor: /docs/integration#knowing-which-of-your-users-exist-in-the-crm Knowing which of your users exist in the CRM Checked daily by LeadLogiX; read it through the hosted MCP server. A workspace can connect several CRMs at once. Once a day LeadLogiX reads the people in each one — Salesforce User , HubSpot owners — and matches them against your workspace members by normalised email . There is no REST endpoint for the result. An application reads it with the hosted MCP tool list_crm_agents : each member comes back with a crm entry per connected provider carrying its state , canBeAssignedWork and checkedAt , plus assignableInAnyCrm across all of them. With the workspace:read.contact scope each entry also carries the crmUserId to assign work to and, when the match is ambiguous, candidateIds . Admins see the same states as badges under Workspaces & Users . state Meaning Safe to route work to? ACTIVE Matched; the seat is live and can own records. Yes INACTIVE Matched, but deactivated or archived in the CRM. No NOT_OWNABLE Matched and active, but not a staff seat — an integration user, an automated process, or a queue. No AMBIGUOUS Several CRM users share this email. candidateIds lists them; LeadLogiX refuses to pick. No NOT_FOUND No CRM user has this email. No UNKNOWN The check did not complete, or the CRM has more users than LeadLogiX stores. Treat as unknown, not absent Never collapse these to a boolean The three states that are neither ACTIVE nor NOT_FOUND each need a different response, and UNKNOWN in particular is not a statement about the person — it means the last refresh failed, the connection lacks the scope, or the CRM has more users than fit in one stored roster. Code that reads state === "ACTIVE" ? "in CRM" : "not in CRM" will confidently report real staff as missing during an outage. Why matching is exact-email only, and why an outage never empties the list No name fuzzing. Real CRM data contains duplicate addresses — one live org returned the same email on three separate user records — so a duplicate produces AMBIGUOUS rather than a guess. Assigning a lead to the wrong colleague is worse than assigning it to nobody. And a failed refresh never overwrites a good roster: LeadLogiX keeps the previous list and records the failure, so a CRM outage cannot read as "everyone left". Prefer to click than to curl? The API Playground runs all of the above against the real endpoints in sandbox mode, and hands you a ready-made cURL command for each request. ### Session object URL: /docs/schema Developer Reference Session object The two payloads you work with: the notify envelope you send, and the status document you poll. Both are intentionally minimal. You are Read Writing the notify call Notify envelope — two fields, and the sessionId rules are stricter than they look Writing the poller Processing states — which are terminal, or you poll forever on DEGRADED Looking at a rejected session Session metadata contract and Contract rejections Writing a conversation record Transcript grammar and Container tags Building it for the first time Ingest a session , which walks the flow; this page is the field-level detail behind it #### Notify envelope Anchor: /docs/schema#notify-envelope Notify envelope POST /api/v1/sessions — the entire body. Field Type Required Rules sessionId string Required ≤ 128 chars. Most characters are fine — control characters, path traversal (../), percent-encoding and markup are rejected, so ids like sess_2026-08-09.a1 are valid. It must still contain at least one letter or digit after punctuation is removed: an id such as "---" or "___" is refused with 422 ENVELOPE_INCOMPLETE. processor integer Required The line of business (or pipeline depth) this session belongs to — see Processor modes below for the current set. A strict integer: strings and floats are rejected. The accepted set is a registry and grows, so read the 422 PROCESSOR_UNSUPPORTED response, which enumerates every mode this server accepts, rather than hard-coding a list. Idempotent by sessionId The sessionId is the dedup key — read the next callout for exactly how two ids are compared. Re-notifying returns 202 with alreadySubmitted: true and never disturbs the existing lead's state or queue position, so a retry after a timeout is safe. ID normalisation silently merges sessions: demo-1, DEMO_1 and demo.1 are one Dedup does not compare the id you sent. It compares the id with every character that is not a letter or digit removed, and the rest upper-cased. So sess-1 , sess_1 , Sess.1 and SESS1 are one session. The first is queued and scored; every later one returns 202 ACCEPTED and is never fetched and never scored — the status code alone cannot tell you that happened. Make your ids unique after that projection. A random or sequential alphanumeric suffix is safe ( conv_9F3A21 , chat2026081401 ). Distinguishing two sessions only by a separator or by letter case is not — chat-1 versus chat_1 is the usual way this happens. Two signals tell you that you collided. The notify response carries alreadySubmitted: true — treat it as an alarm, not as a benign retry, unless you know you retried. And the status document echoes the id of the session that actually holds the slot, so if GET /api/v1/sessions/SESS1/status comes back with "sessionId": "sess-1" , an earlier conversation owns it. The key is scoped to your workspace, and sandbox and live are counted separately — so your testing cannot collide with your production traffic, and another workspace's ids can never collide with yours. #### Status document Anchor: /docs/schema#status-document Status document GET /api/v1/sessions/:sessionId/status { "sessionId": "conv_9F3A21", "ingestedAt": "2026-07-25T14:03:11.000Z", "processorMode": 2, "processorLabel": "REALESTATE", "conformance": { "level": "ACCEPTED_WITH_NOTES", "evaluatedAt": "2026-07-25T14:03:20.000Z", "notes": [ { "code": "METADATA_SHAPE_INCOMPLETE", "path": "speakers", "message": "…" } ] }, "processing": { "state": "PROCESSED" } } Field What it is Read it as sessionId The id of the session that holds this slot Echoes the session that owns the dedup key — if it differs from the id you asked about, an earlier conversation took it ( ID normalisation ). ingestedAt When the notify was accepted ISO-8601. processorMode , processorLabel The line of business or pipeline depth An unfamiliar label is an unknown line of business — never real estate. processing.state The completion signal — see Processing states The only field to gate on. processing.waitingForSource The record is still being assembled Present with QUEUED . A wait, not a failure. processing.awaitingMandatoryKeys , processing.lastAwaitingMandatoryKeys Which mandatory keys are late — now, and the last time A diagnosis, not a verdict — Ingest a session , step 4. processing.rejection Why a contract violation was refused Only on FAILED ; missingMandatoryKeys names the keys. Branch on its code . processing.errorMessage A sanitised reason for any other failure Read it on FAILED when there is no rejection — a refused document is reported here, not in the notes. conformance.level A report on the payload — see Conformance levels Never the outcome of the session. conformance.notes[] code , path , message Match on the code; the message is prose and may be reworded. conformance.evaluatedAt , conformance.reason When it was graded; why it never will be null with a reason on NOT_EVALUATED — settled, not pending. By design the status document returns no names, phone numbers, scores, prices or vault contents, so it is safe to log or surface in your own dashboards. #### Processing states Anchor: /docs/schema#processing-states Processing states processing.state State Meaning Terminal? QUEUED Accepted, waiting for a worker. May include waitingForSource: true while the conversation is still being assembled. No PROCESSING A worker has claimed the session and is running the pipeline. No PROCESSED Finished successfully. The lead is scored and available. Yes DEGRADED Finished, but a component fell back — the lead is flagged for reprocessing. Yes FAILED Could not be processed. Includes a sanitized errorMessage . Yes #### Session metadata contract Anchor: /docs/schema#session-metadata-contract Session metadata contract What the conversation record must carry before you notify. The notify call is two fields. Everything the pipeline actually reads lives on the conversation record your Agent writes, and that record has a contract. Get a mandatory key wrong and the session is rejected outright — it is never scored, and it never reaches your Clearing Pool. Key Tier What it is sessionId Mandatory The canonical id. Must equal the id you notify with. processor Mandatory The line of business — the SAME integer you send as processor on the notify call, and the two must agree. Lets the statistical machine compare this lead only against leads from the same line. content Mandatory The conversation itself — the turns, as text. started Mandatory When the conversation began. embedding Mandatory Where it happened — the placement/host context object. fingerprint Expected Device/session signals. Absent, LeadLogiX cannot recognise a returning visitor. speakers Expected Who was in the conversation. Drives speaker attribution. agent Expected Which Agent handled it. Drives per-agent analytics. totalDuration Expected Conversation length. Feeds pacing and the sub-twenty-second filter. Eleven further keys are optional and purely additive — consent , ended , endReason , lastDuration , segments , prompt , function , experiment , leadvalSpecVersion and the two emb* context keys. The marketing-attribution fields — pageUrl , referrer , utm , clickIds — are optional too, but they live inside the mandatory embedding object rather than beside it. Omitting any of them costs you the feature it feeds; the session is never rejected for it. One optional key is not free: leadvalSpecVersion Leaving it out never rejects anything, but it always adds a SPEC_VERSION_MISSING note — which is enough to move conformance.level from CONFORMANT to ACCEPTED_WITH_NOTES . Send leadvalSpecVersion: "1.0" and the note goes away. It is also how LeadLogiX knows which contract generation your connector was built against, which is worth more than the note costs. Three ways to fail one key, and all three count A key is unusable if it is absent , present with the wrong type (a JSON object sent as a string that does not unwrap), or present, right type, and empty of the fields that give it meaning. A downstream consumer cannot tell those apart — none of them yields a value — so all three are reported, and for a mandatory key all three reject. Expected is not optional-with-a-nicer-name A missing expected key never rejects the session. It produces a conformance note and silently costs you a capability: no fingerprint means every visit looks like a first visit, so returning-customer recognition and profile stitching cannot run for that session. Structured values are stringified exactly once Metadata values are stored as strings, so a structured value — fingerprint , speakers , embedding — must be the result of exactly one serialisation of the source object. Never re-serialise a value that is already a string, and do not let a transport layer add an escaping pass. The stored string must parse in a single strict pass, with no literal \n or \t escapes between JSON tokens. Multi-escaped payloads are recorded as violations and may fail to parse at all, discarding every signal they carried. #### Transcript grammar Anchor: /docs/schema#transcript-grammar Transcript grammar content — the shape the parser actually reads. You don't write this text — storage assembles it You send structured messages to raw lead storage — each with a role , an optional name , and its content . Storage joins them into the single transcript below, and that is what LeadLogiX reads. This section exists so you can tell what your messages become — the only thing you control directly is the name field, which is where the speaker id and the tool name go. content is a sequence of turns joined by a delimiter: a blank line, a run of hyphens, and a blank line. Each turn is a header line followed by its body, and the header declares the role. Storage always writes exactly three hyphens, but the reader is deliberately wider than that: two or more hyphens count, and spaces or tabs are allowed around them and on the blank lines. Write the three-hyphen form; assume the wider form when you decide what to allow through from a visitor. content [USER (spk:bbc4783ec6876ae80ebf7d7d8c75fb6b "Kendar")] Hello! I'm Kendar. What can you show me here? --- [ASSISTANT (Sarah)] Hi, I'm here to help. What are you looking for? --- [TOOL (open_listing)] {"mlsNumber":"73530260","boardId":68} --- [TOOL (open_listing)] {"status":"ok","data":{"listPrice":749000}} The last two turns are a tool call and its result . They look identical on purpose — storage renders both as [TOOL (name)] , and LeadLogiX tells them apart from the shape of the payload, not the header. Send them as two plain tool messages, adjacent and in order. Role Header carries Why it matters USER spk: and optionally a claimed name in quotes The speaker id is the identity ; a quoted name is only a claim , verified independently. Name-only headers are accepted but leave multi-speaker sessions unresolvable — and a user claiming the agent's name becomes undetectable. ASSISTANT The agent's name MUST equal agent.agentAddressName . That equality is how agent identity is told apart from a user's claim. TOOL The tool name Both the invocation and its result. One JSON object per turn — never a batch. Always set name to the tool: without it the payload falls back to shape-detection and you lose what you already knew. SYSTEM — ⚠ Storage discards system messages — they never reach LeadLogiX. Anything LeadLogiX must see belongs in session metadata instead. Three fields to leave alone — each loses data silently All three were measured on live sessions. Every one returns success and produces a document, and none of them produces a conformance note or any signal on the status endpoint — the checks that run over content only confirm that each turn opens with a valid header, and all three of these mistakes leave the header valid and damage the body. To catch them, read the assembled record's content while you are building the connector and check it against the example above; once you are live, the symptom is a lead that is thinner than the conversation was. 1 · tool_call_id — does not add a correlation id. Storage renders it as an extra [Tool Response: …] line inside the tool body, which stops that body being a single JSON object, so every fact in that payload is dropped. Pairing does not need it: calls and results are matched by order and payload shape. 2 · tool_calls[] on an assistant message — produces no tool turn at all. The invocation is folded into the assistant's own spoken text as -> name({args}) , losing the call and corrupting the language analysis run over what the agent said. Send every tool call as its own tool message. 3 · a timestamp appended to name — there is no timestamp field, and appending one puts it inside the parentheses where it is not read. On an assistant message it also breaks the agent-name match, so your agent stops being recognised as itself. Sanitise a visitor's claimed name before you send it Storage passes ] , ) and " through unescaped, and each one breaks the header. A ] is the worst: the turn loses its speaker id and the name, and the leftover header text spills into the message body as though the visitor had said it. Strip those three characters from any name you did not generate yourself — an empty name is handled cleanly, a half-broken one is not. A visitor's message is rendered verbatim — neutralise the separator and declare every voice Whatever a visitor types is written into content exactly as typed, which means the turn separator and the header line are not reserved characters — they are just text a visitor can produce. Two habits close this, and both are yours to implement because nothing downstream can distinguish a separator you sent from one the visitor typed. 1 · Neutralise the separator sequence in user-authored text. Before you send a user message, break any line that is only hyphens when it sits between blank lines — indent it, prefix it, or collapse the blank line above it. Remember the reader is wider than the writer: two hyphens with padding counts. Left alone, a visitor pasting a markdown horizontal rule ends their turn there; the words after it are folded back into the same turn so nothing is lost, but a visitor who follows the rule with a [USER (spk:… "Name")] or [ASSISTANT (…)] line gets a real, separate turn attributed to someone who never spoke. 2 · Declare every speaker in speakers . This is the defence that actually holds, and it is why speakers is more than analytics. LeadLogiX compares each USER turn's spk: id against the ids you declared, and a turn whose id you never declared is withheld from the participant model entirely — it becomes no participant, no contact vector and no stakeholder, and is recorded as an undeclared speaker instead. Send an id for every genuine voice, including the visitor. Send speakers or you get no filtering at all. With no declared ids there is nothing to compare against, so every speaker in the transcript is admitted — which is correct for older records that never declared any, and is exactly the gap a forged turn walks through. If your emitter sends spk: ids on its turns, it must also declare them. Habit 2 covers USER turns only — a speaker id is a USER concept, so a forged [ASSISTANT (…)] line is not caught by it. That one is caught by habit 1, and by the rule that an assistant header must match agent.agentAddressName . Do both; neither is sufficient alone. Four things that corrupt the record No cross-turn stamps — never repeat a correlation id or transport artifact on turns it does not belong to; it pollutes the utterance text and corrupts linguistic analysis. No processed user text — no spell-correction, translation, redaction or summarising of what a person said. No synthetic turns — never insert a greeting or confirmation that did not happen. No secrets — redact at your boundary and record the redaction visibly ( "apiKey":"[REDACTED]" ) rather than quietly reshaping the payload. #### Container tags Anchor: /docs/schema#container-tags Container tags The label that decides whether your document is ever opened. A container tag decides two things: which workspace may retrieve the document, and which cohort the session joins when baselines are computed. Getting it wrong is the most common way a correct integration produces nothing at all. Position Tier Tag Who sets it You write it? 1st Workspace tag__<8 hex> Minted when your API key is created Always — first 2nd Global pool global_anonymous_pool Platform constant, identical for every workspace Always — after your workspace tag Later Profile cprofile_… Added by LeadLogiX after processing, when a session joins a customer profile Never Order matters: your workspace tag first Write the tags in exactly this order: "containerTags": ["tag_evasglobal_9c4e17ab", "global_anonymous_pool"] The document store makes a document searchable only under its first tag, and LeadLogiX looks up your history — the similar past leads a new lead is compared against, and the earlier sessions that let it recognize a returning visitor — under your workspace tag. A document with any other tag first still processes, but it is never compared with your history, and your later sessions are never compared with it. The pool tag is still required; it simply comes second. A new session whose first tag is not your workspace tag is refused. GET /api/v1/sessions/:id/status returns a terminal { state: "FAILED" } whose errorMessage begins Container tag order violation and names the tag that must come first. Changing the tags afterwards does not help — an update does not reorder them — so rewrite the conversation with the tags in order, then notify again once the refusal has been deleted . A failed session is kept for a short retention period (60 minutes by default, removed within five minutes after that); until then, notifying the same session answers alreadySubmitted: true with the same refusal and nothing is read again. After it, the status call answers 404 and a notification is a new session. A session whose document was already read and admitted is not re-checked if it is reprocessed. You do not construct your workspace tag — you read it out of your API key. The key is a composite, and the tag sits between the workspace id and the final entropy segment. Deriving it means a copy-paste error between two settings cannot happen: // val_key______ // The tag is part of the credential. Derive it; never store it separately. // Take everything BETWEEN the workspace id and the trailing entropy segment: // "__" is legal inside a tag, so splitting and taking [2] can truncate it. const parts = apiKey.split("__"); const containerTag = parts.slice(2, -1).join("__"); // "tag_evasglobal_9c4e17ab" # val_key______ # The tag is part of the credential. Derive it; never store it separately. # Take everything BETWEEN the workspace id and the trailing entropy segment: # "__" is legal inside a tag, so splitting and taking [2] can truncate it. parts = api_key.split("__") container_tag = "__".join(parts[2:-1]) # "tag_evasglobal_9c4e17ab" A document without your tag is, to your key, a document that does not exist Before a single line of transcript is read, LeadLogiX requires the tag bound to your API key to appear in the document's tags, compared by exact string equality — no trimming, no case folding. If it does not, the document is treated as not found : the transcript is never read, no conformance is ever evaluated, and no lead is scored. Retrieval by session id is account-global, so this is the boundary that stops one workspace's key from reading another's conversations — and it answers exactly as for a document that was never written, so a key cannot even learn that someone else's document exists. It is reported, not silent — after the readiness wait. The session record is created when you notify, so the outcome has somewhere to land. While it waits, GET /api/v1/sessions/:id/status shows waitingForSource: true ; after the readiness budget (8 checks, about ten minutes) it returns a terminal { state: "FAILED" } whose errorMessage names your workspace tag and both possible causes. Verbatim, so you can match on it: No document for this session was found in this workspace after 8 readiness checks: it was never written to the archive, or it was written without this workspace's tag ("tag_…"). Write it with that tag first in its containerTags, then notify again. What you will not see is a conformance note. Those checks run on a document LeadLogiX declined to open, so no report exists and conformance.level is the terminal NOT_EVALUATED , with reason: "NO_CONFORMANCE_REPORT_RECORDED" . That is a settled answer, not a "not yet" — read processing.errorMessage on a FAILED session, not the notes. While it waits, a document not written yet and one written without your tag look the same — QUEUED with waitingForSource: true — so if that lasts more than a minute or two, check the document's tags first. Extra tags are harmless — after the first two — and inert Your own tags — a campaign id, an environment marker — may ride along after your workspace tag and the pool tag, and are ignored. Put one first and the session is refused. They also segment nothing: history lookups run under your workspace tag only. Use metadata for dimensions you want to filter on. A document with no tags is refused rather than waved through; an untagged document cannot be attributed to anyone. Writing to the document after you notify can strip its customer-profile link After processing, LeadLogiX may add a cprofile_… tag linking the session to a customer profile. Because writes upsert, a later write that re-sends only your own two tags can strip the tag LeadLogiX added . Finish writing, then notify — never the other way around. Copy the tag; never rebuild it from the workspace name The tag__ shape is a minting convention, not a validated rule. LeadLogiX compares your tag to the key's tag; it parses neither. A workspace provisioned by hand can hold a tag in another shape entirely and work fine — which is exactly why the rule is copy, not reconstruct. #### Minimum fingerprint Anchor: /docs/schema#minimum-fingerprint Minimum fingerprint Enforced only when a fingerprint is present. fingerprint is expected, not required — a session without one is never rejected. But a fingerprint that is present must carry a minimum set, or it is recorded as METADATA_SHAPE_INCOMPLETE . The minimum is deliberately built from fields that exist regardless of incognito mode, browser family or device class : Field Why it is always obtainable fingerprintId The identifier your emitter computes on every capture. staticTraits.browserUserAgent Sent on every HTTP request; no browser can suppress it. staticTraits.timezoneIana Available in every JS runtime. staticTraits.languagePreferences navigator.language is universal. transientMetadata.ipAddress Observed server-side; a client cannot withhold it. capturedAt or timestamp The capture time, under either key. Everything else is explicitly not required Canvas, audio, WebGL, font and plugin hashes; hardware memory, client hints and network info; battery level; local IP; media-device hashes — all optional, because a privacy-respecting browser legitimately withholds them. Requiring any of them would reject users for their browser choice rather than integrations for their mistakes, so the contract never does. Send what you have. #### Contract rejections Anchor: /docs/schema#contract-rejections Contract rejections When a session is missing required context. If the conversation record is missing keys the pipeline requires, the session fails with a structured rejection naming them. This is the only feedback channel for a contract violation — the keys are listed so your Agent can fix exactly what was wrong rather than guess from prose: { "processing": { "state": "FAILED", "rejection": { "code": "INGEST_CONTRACT_VIOLATION", "missingMandatoryKeys": ["started", "embedding"], "rejectedAt": "2026-07-25T14:03:20.000Z", "message": "…" } } } A rejected session is discarded, not retried Re-notifying the same sessionId will not reprocess it — the id is the dedup key and it is already spent. Fix the conversation record, then notify under a new sessionId . #### Conformance levels Anchor: /docs/schema#conformance-levels Conformance levels conformance.level — a report on the payload, not the outcome of the session. Level What it actually means What it does NOT mean CONFORMANT The payload checks produced zero notes. Not a quality verdict, and not a target. A perfectly good session lands on ACCEPTED_WITH_NOTES whenever the written summary has not landed yet — see Verifying your integration . ACCEPTED_WITH_NOTES The payload checks produced at least one note. That is the whole rule — the level is a count of notes, nothing more. Not a statement that the session was accepted , despite the name. A session rejected for a missing mandatory key reports this level too, with the fatal METADATA_KEY_MISSING notes sitting alongside the advisory ones. Read processing.state for the verdict. PENDING Not evaluated yet . The session is still in flight — processing.state beside it is QUEUED or PROCESSING . Keep polling; a grade is coming. Not a grade, and never permanent. It is never returned beside a terminal processing.state — if the session has finished and no report exists, you get NOT_EVALUATED instead. NOT_EVALUATED No grade is coming, ever. The session reached a terminal processing.state and no report was produced. reason names why — AUTOMATION_ONLY_NO_DOCUMENT_FETCH (processor 0 never opens the source document, and the contract is evaluated against that document's metadata) or NO_CONFORMANCE_REPORT_RECORDED (the session finished without one being written, which is what a document refused at fetch looks like). evaluatedAt is present and null : the question was settled, and the answer is that nothing was evaluated. Not a failure and not a rejection. A mode-0 session reporting it alongside { state: "PROCESSED" } ran exactly as designed. Read reason , then stop polling — it will not change. How to read a note Notes describe the payload, never the lead: they never lower a score and never change a price. Match on the code — the message is prose and may be reworded. Almost every code is an advisory improvement for your Agent team. The exception is the three METADATA_* codes — METADATA_KEY_MISSING , METADATA_VALUE_TYPE_INVALID and METADATA_SHAPE_INCOMPLETE — which are advisory on an expected key and fatal on a mandatory one , matching the three-ways-to-fail rule above. When one of them lands on a mandatory key, processing.state is FAILED and processing.rejection.missingMandatoryKeys names the keys. One question, one field Did it finish? processing.state . Is my payload clean? conformance.notes . Why was it refused? processing.rejection , or processing.errorMessage for a failure outside the metadata contract. Deriving any of the three from another is where integrations go wrong. ### Webhooks & polling URL: /docs/webhooks Developer Reference Webhooks & polling How a scored lead leaves LeadLogiX. There is no signed event feed to subscribe to: delivery is configured per destination — CRM sync, automations, scheduled jobs — and the status endpoint is the supported way to observe an individual session. This page covers all three and when each applies. #### Three ways out Anchor: /docs/webhooks#three-ways-out Three ways out CRM sync Scheduled A scheduled job pushes newly scored leads to your CRM, exactly-once (a stable lead id + confirm-then-mark prevents duplicates). See the “Connect a CRM” guide. Chat & SMS scripts Ready-made Ready-made scripts post hot leads to Slack, Teams, or SMS as they qualify. See “Scripts & Automation.” Custom webhook jobs A scheduled CUSTOM_WEBHOOK job POSTs to any HTTPS endpoint you own, with secrets pulled from your vault — for wiring LeadLogiX into a homegrown system. Why no real-time signed webhook? LeadLogiX favours durable, exactly-once delivery over fire-and-forget pushes: scheduled sync and scripts retry, dedup, and record what they've sent, so a lead is never lost or delivered twice. If you need near-real-time, run the sync or script on the 15-minute minimum cadence. #### Customer Profile fields on CRM payloads Anchor: /docs/webhooks#customer-profile-fields-on-crm-payloads Customer Profile fields on CRM payloads When an exported lead belongs to a Customer Profile (the person has had multiple conversations), the payload carries a profile block: a public profile id, a live profile URL , the conversation count, a summary state ( READY / PENDING / UNAVAILABLE ), and a compact summary. HubSpot payloads use leadval_profile_* properties; Salesforce uses LeadVal_Profile_*__c fields (create the summary field as a Long Text Area — it carries up to 500 characters); the RAW payload includes the full snapshot as profile . Leads without a profile carry these fields as null — never fabricated. The URL is the freshness channel The AI-written profile summary builds asynchronously — usually within minutes of a merge. Exports don't wait and don't re-send: the snapshot is honestly labeled point-in-time, and the profile URL always opens the current profile for anyone with LeadLogiX access. #### Reading status programmatically Anchor: /docs/webhooks#reading-status-programmatically Reading status programmatically If you want your own system to react the moment a session finishes, poll GET /api/v1/sessions/:sessionId/status until it's terminal (see Session object ). It's the supported, PII-free signal that a lead is ready to pull from your CRM. #### Where to go next Anchor: /docs/webhooks#where-to-go-next Where to go next If you want to… Go to know exactly what processing.state and conformance mean while polling Session object have an AI agent read leads directly, with no polling at all MCP → Hosted server — the model calls tools instead of you writing a poller send your own fields out with the lead Custom metadata — they ride the same payloads described above configure the CRM push itself Connect a CRM on the Guides surface — this page describes what arrives, that one sets it up ### Identity & Profiles API URL: /docs/identity 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 Anchor: /docs/identity#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 Anchor: /docs/identity#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 Anchor: /docs/identity#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 Anchor: /docs/identity#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 Anchor: /docs/identity#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 Anchor: /docs/identity#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? Anchor: /docs/identity#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. ### Ingestion errors & limits URL: /docs/ingestion-errors Developer Reference Ingestion errors & limits Every refusal the ingestion, identity and profiles endpoints return, and the rate limits behind the 429. Every error is RFC 9457 problem+json with a stable code: branch on the code, never on the sentence. OAuth and MCP refusals use a different dialect and live on their own page. #### Error format Anchor: /docs/ingestion-errors#error-format Error format Every error is RFC 9457 problem+json (RFC 9457 obsoletes RFC 7807 — same media type, current number). 422 response Content-Type: application/problem+json { "type": "https://evas.live/problems/processor-unsupported", "title": "Processor Unsupported", "status": 422, "code": "PROCESSOR_UNSUPPORTED", "detail": "Field 'processor' must be a strict integer, one of: 0 (AUTOMATION_ONLY), 1 (IDENTITY_ONLY), 2 (REALESTATE), 3 (LIFE_INSURANCE_US). Strings and floats are not accepted.", "supportedProcessors": [ { "mode": 0, "label": "AUTOMATION_ONLY" }, { "mode": 1, "label": "IDENTITY_ONLY" }, { "mode": 2, "label": "REALESTATE" }, { "mode": 3, "label": "LIFE_INSURANCE_US" } ], "requestId": "req_… " } Read supportedProcessors, not the sentence On PROCESSOR_REQUIRED and PROCESSOR_UNSUPPORTED the body carries supportedProcessors — every mode this server accepts, as {mode, label} , generated from the same registry the validator uses. Branch on that array and you cannot drift out of step with us when a new line of business registers. The detail sentence above is an example , not a fixture: it is generated per request and grows a new mode the day one is registered. Branch on code , never on detail . The status and the code are the contract; the sentence is written for a human reading a log and is rewritten whenever it can be made clearer. Status code When 401 UNAUTHENTICATED Missing, malformed, or unknown API key. 403 TENANT_SUSPENDED The key or workspace is suspended. 403 PLAN_UPGRADE_REQUIRED Processor 0 or 1 requested without the Unlimited plan. 403 IDENTITY_API_NOT_ENABLED Identity resolution is not switched on for this workspace. 415 UNSUPPORTED_MEDIA_TYPE Body wasn't application/json . 422 ENVELOPE_INCOMPLETE sessionId missing, empty, too long, or has disallowed characters. 422 PROCESSOR_REQUIRED / _UNSUPPORTED processor missing, or not one of the modes this workspace is offered. The 422 body carries supportedProcessors — branch on that, never on this sentence. 429 RATE_LIMITED Too many notifications for this key — honour Retry-After . 404 SESSION_NOT_FOUND Status requested for a sessionId this WORKSPACE hasn't notified — the scope is the workspace, not the key, so any active key can poll any of its sessions. Also returned once a failed session passes its retention window and is deleted, so a 404 is never proof the session was never delivered. 401 KEY_EXPIRED The key was valid and has passed its expiry. 403 TENANT_MISMATCH The key does not belong to the workspace that owns the resource. 400 MALFORMED_BODY The body was not parseable JSON. 413 PAYLOAD_TOO_LARGE The request exceeded the size cap for this endpoint. Identity and Profiles add their own, on top of every row above: Status code When 403 PROFILES_API_NOT_ENABLED The Customer Profiles API is not switched on for this workspace. 404 UNKNOWN_PROFILE The profileId tuning profile does not exist. 404 PROFILE_MERGE_NOT_FOUND Status polled for a merge requestId this WORKSPACE did not create — the scope is the workspace, not the key, so any active key can poll any of its requests. 422 SESSION_ID_MISSING A current or candidates[n] entry has no non-empty sessionId. 400 INSUFFICIENT_EVIDENCE_FOR_LOOKUP Identity lookup only: you sent nothing searchable. An integration bug — not the verdict INSUFFICIENT_EVIDENCE , which is a real answer about a real comparison. 422 MALFORMED_EVIDENCE An evidence field is present with the wrong shape, too long, or carrying control or injection characters — detail names the exact path. 422 CANDIDATES_NOT_ARRAY candidates was not an array. 422 TOO_MANY_CANDIDATES More candidates than the endpoint accepts — 50 on identity v1 , 20 on v2 , 50 on a profile merge. Bounding and recency-ordering the set is the caller's job. Two 403s that look alike and are not PLAN_UPGRADE_REQUIRED means the capability exists and your plan does not carry it — an upgrade fixes it. IDENTITY_API_NOT_ENABLED and PROFILES_API_NOT_ENABLED mean the API is off for your workspace regardless of plan and someone at LeadLogiX has to switch it on. Retrying either is pointless; they are configuration, not capacity. #### Rate limits Anchor: /docs/ingestion-errors#rate-limits Rate limits Per API key, not per workspace — two keys get two budgets. Endpoint Default limit On refusal POST /api/v1/sessions 60 per minute 429 RATE_LIMITED with Retry-After in seconds Identity resolve, v1 and v2 120 per minute 429 RATE_LIMITED with Retry-After POST /api/v1/identity/lookup , GET /api/v1/leads/{leadId}/context and GET /api/v1/profiles/{profileId} 120 per minute, SHARED 429 RATE_LIMITED — one budget across all three, separate from resolve. A returning-customer handshake is a lookup plus one read-back, so plan for 60 handshakes per minute, not 120. POST /mcp 600 per minute Per authorization — see OAuth & MCP errors Polling counts against the same budget as notifying A status poll is a request. Sixty per minute is comfortable for one session polled every couple of seconds, and it is not comfortable for thirty sessions polled that way at once — that is 900 requests a minute. Poll each session on its own backoff, or notify a batch and sweep their statuses on a slower interval. Honour Retry-After rather than retrying immediately. It carries the remaining window in seconds, so a client that waits for it recovers on its first attempt instead of spending its next budget on refusals. ## Recipes ### Recognise a returning customer URL: /docs/returning-customer Developer Guide Recognise a returning customer Your AI agent picks up a conversation. Has this person spoken to you before? This walks the whole flow end to end: ask LeadLogiX in the first seconds, read the answer without over-claiming, and pull the history into the model's context. #### What you are building Anchor: /docs/returning-customer#what-you-are-building What you are building Three steps. Ask with POST /api/v1/identity/lookup on your ingestion key and receive a verdict and pointers; decide from nameUse on the row you act on, with no call; fetch the history by following the pointer — with the same key on your own workspace, or with the get_lead and get_customer_profile MCP tools and an OAuth token when you are an application installed on a customer's workspace. Ask — one HTTP call, ingestion key POST /api/v1/identity/lookup with whatever identifying signals you have. Returns a verdict and pointers — never customer data. Decide — no call, just reading the response The verdict says whether there is a match; nameUse on the row you act on says whether you may greet them by name. Fetch — follow the pointer The customer profile if the person's conversations have been merged, otherwise the lead. This is where the actual content lives. The credential follows whose workspace you are reading, not which step you are on. Your own: the key you already have, for every step. A customer's, because you are building something they install: an OAuth token their admin consented to, for every step. Before you start: An ingestion API key (val_key__…) — the same one you use to notify sessions. Identity access enabled for the workspace. Off by default; ask LeadLogiX. Without it every call returns 403 IDENTITY_API_NOT_ENABLED. Your agent collecting device information at the start of a conversation. Everything else is optional. For step 3, nothing extra if you are reading your own workspace — the same key follows the pointers. For step 3 as an INSTALLED APPLICATION on somebody else's workspace: an OAuth token with leads:read, plus profiles:read for merged histories, plus identity:lookup if you want to ask the question over MCP too. #### Step 1 — ask Anchor: /docs/returning-customer#step-1-ask Step 1 — ask Send as much as you have. A device fingerprint alone is enough to call; an email, phone or name makes the answer much stronger. Send it once, early, and never block on it: on 429 or any error, carry on as though the person were new — a stalled greeting is worse than a generic one. curl -X POST https://YOUR-HOST/api/v1/identity/lookup \ -H "X-API-Key: val_key__..." \ -H "Content-Type: application/json" \ -d '{ "evidence": { "fingerprint": { "staticTraits": { "canvasFingerprintHash": "…", "webglVendorHash": "…", "browserUserAgent": "Mozilla/5.0 (...)", "timezoneIana": "America/New_York" }, "transientMetadata": { "ipAddress": "203.0.113.7" } }, "phones": ["+1 (202) 555-0140"], "emails": ["jordan@example.com"], "names": { "claimedFirst": "Jordan" } } }' import requests evidence = { "fingerprint": { "staticTraits": { "canvasFingerprintHash": "…", "webglVendorHash": "…", "browserUserAgent": "Mozilla/5.0 (...)", "timezoneIana": "America/New_York", }, "transientMetadata": {"ipAddress": "203.0.113.7"}, }, "phones": ["+1 (202) 555-0140"], "emails": ["jordan@example.com"], "names": {"claimedFirst": "Jordan"}, } r = requests.post("https://YOUR-HOST/api/v1/identity/lookup", headers={"X-API-Key": "val_key__..."}, json={"evidence": evidence}, timeout=3) look = r.json() if r.ok else None # on any error, treat the caller as new A session you notified a moment ago is not searchable yet A lead becomes searchable once LeadLogiX has extracted a usable fingerprint, email or phone from it — not when it is scored, so identity-only records ( processorMode: 1 ) are found too. Sandbox runs never are, and failed runs are absent because nothing was assembled to index. When testing, poll GET /api/v1/sessions/:id/status until PROCESSED before you look the person up. #### Step 2 — decide Anchor: /docs/returning-customer#step-2-decide Step 2 — decide verdict What happened What to do EXACT_MATCH Matched, and a name, email, phone or voice signature agreed. Recognised customer. Fetch the history and use it. PROFILE_MERGE_DETECTED Two or more different customers matched. Fetch both for context, but every row returns nameUse: "WITHHOLD" with nameUseReason: "AMBIGUOUS_IDENTITY" — LeadLogiX cannot say which of them this is. Ask a disambiguating question. POSSIBLE_MATCH Matched, but nothing PERSON-LEVEL corroborated it — the same device, or one phone a household shares. Warm the context. Do not use a name you were not told in this conversation. SESSION_FORK The same identity in two places too far apart to be one person. A security signal. Attach no history. INSUFFICIENT_EVIDENCE Candidates existed but none had enough comparable signals. Treat as new. You may call again later once more has surfaced. NEW_PROFILE Nothing matched. Treat as new. Greet by name only when nameUse is PERMITTED on the row you act on Not from the verdict, the band, the highest posterior or corroboratedBy . A phone and a device shared by a household corroborate an address, not a person — the agent that derives permission from anything else greets a stranger by somebody else's name. nameUse is per row, and get_customer_profile returns the same field with the same two values on each conversation, so one rule covers both surfaces. #### Reading the response Anchor: /docs/returning-customer#reading-the-response Reading the response Every field you will branch on, and what it does NOT mean. Field What it is Read it as verdict The engine's decision across every candidate Branch on it with the table above. The VERDICT INSUFFICIENT_EVIDENCE means candidates WERE retrieved and none cleared the evidence floor — a real answer. It is not the ERROR INSUFFICIENT_EVIDENCE_FOR_LOOKUP (400), which means you sent nothing searchable; fix the request for that one. matches[] Candidates scored at or above the bar, best first The rows you act on. Empty on NEW_PROFILE. matches[].leadId A pointer to the lead Always present — there is always something to fetch. matches[].customerProfileId A pointer to the merged customer null more often than not, and that is not an error: a profile exists only after two or more conversations were merged into one person. Fetch the lead instead. matches[].processorMode The line of business of that record 1 is an identity-only record: the device is recognised, but there is no score or narrative behind it. matches[].nameUse , nameUseReason PERMITTED or WITHHOLD , and why Whether you may address the person you are speaking to by a name they have not given you in this conversation. It is not a redaction rule for an operator reading their own records — what an operator may see is decided by the credential's scope ( leads:read.contact , profiles:read.contact ) and the lead's contactAvailability . Applying it to a records question produces answers that state a name and refuse to state it in one breath. matches[].corroboratedBy Which person-level channels agreed Tone, never permission. Device, network, timezone and location never appear in it. [] means the match rests on device signals alone — what a shared or public computer looks like — and a device fingerprint can never produce EXACT_MATCH : the engine clamps it, and you cannot opt out. An agreeing email or voice signature corroborates a person and reaches EXACT_MATCH ; ["contact-phone"] on its own does not. matches[].constraintsFired Why THIS row was held back SHARED_CONTACT_NO_NAME : one phone and one device shared, nobody named — a household. SHARED_SURNAME_NO_FORENAME : a surname matched on a shared device and nobody gave a first name; any first-name agreement lifts it, a nickname included (“Jon” against a stored “Jonathan” reaches EXACT_MATCH ), so ask for a first name, not a surname you already have. matches[].posterior The pair's probability Never a licence to pick one of several people: on PROFILE_MERGE_DETECTED the highest posterior is a coin flip presented as recognition. top-level constraintsFired Every clamp fired by EVERY candidate scored Not about your row: it unions near misses too, so a clean EXACT_MATCH can arrive beside a SHARED_CONTACT_NO_NAME somebody else's row fired. Read the row's own array, or just nameUse . nearMisses[] Candidates retrieved and scored BELOW the bar Never merged into matches . A pair that disagreed, or one with too little to judge — that one carries posterior: null , and the null is meaningful. The list to read before asking “have we spoken before?” rather than asserting it. retrieval.candidatesConsidered How many candidates were compared NEW_PROFILE with 0 does not mean “this person is new”: NOBODY was retrieved — your evidence carried no retrievable trait, or no other indexed lead shares one. retrieval.truncated The candidate set was capped An absent match under truncation is not evidence of absence. Send a stronger identifying signal and ask again. decision.activeGuards The guards this workspace's tuning runs Without SHARED_CONTACT_NO_NAME , household confusion was not screened for , so an unclamped match means less. Let your caution follow the rules in force. #### Step 3 — fetch the history Anchor: /docs/returning-customer#step-3-fetch-the-history Step 3 — fetch the history You hold Follow a leadId with Follow a customerProfileId with An ingestion key — the same one step 1 used GET /api/v1/leads/ {leadId} /context GET /api/v1/profiles/ {profileId} An OAuth token , because you are a third party on somebody else’s workspace the get_lead tool the get_customer_profile tool Both doors compose the same answer through the same functions: the pay-per-lead paywall, the role filter and the personal-data scrub behave identically whichever credential arrived, so an integration that starts on a key and later ships as an OAuth application does not re-learn what a lead looks like. A key cannot be exchanged for an OAuth token — there is no client-credentials grant. The tools themselves are on MCP → Hosted server . In what you fetch Read it as contactAvailability Four values, four different things to say out loud. NOT_RECORDED is the only one meaning you do not have their details. WITHHELD_UNPURCHASED : the workspace HAS them and has not bought this lead — offer to unlock it rather than telling a returning customer you have no record. WITHHELD_NO_PERMISSION : they exist and this authorization does not include them. AVAILABLE : the block is populated. Without it a masked lead and an anonymous one are byte-identical. verticalRollups[] A profile spanning two lines of business refuses identity.role , identity.budget and identity.financingStatus — the vocabularies are disjoint. Read the rollup, which quotes per line. Its budget and financingStatus are served only when exactly one conversation in that line could have produced them, and are null otherwise; role always survives. signals.verticalSignals[] The per-line read. Entries may carry recommendedChannel and followUpWindowHours , which appear on no other surface. signals.nonVerticalConversations[] Conversations that contributed no line-of-business signal — modes 0 and 1 among them, which also cannot join a profile. NOT_FOUND after a positive lookup is normal on the OAuth path — do not retry The lookup answers on your API key; MCP answers on the OAuth user, who may be restricted to particular tags, so a lead the lookup can see is not automatically one that user may read. On the API-key path the boundary is the workspace: another workspace's lead is 403 , one that does not exist is 404 . #### Doing the same thing as an installed application Anchor: /docs/returning-customer#doing-the-same-thing-as-an-installed-application Doing the same thing as an installed application Same question, same answer, different door. If you are building something other LeadLogiX customers install, you never hold their key. The identity_lookup tool on the hosted MCP endpoint asks the identical question and returns the identical answer: send email , phone , firstName , lastName or fingerprint — any one is enough, as flat singulars (sending the wire name emails is refused by name rather than silently dropped). It is the one permission where personal data travels towards LeadLogiX, which is why the consent screen flags it as personal data although the answer contains none. It needs the identity:lookup scope AND the workspace grant — without either, the tool is simply absent With either missing the tool is absent from tools/list rather than present and refusing, so “I cannot see the tool” is the expected symptom, not a bug. Revoking the workspace grant reaches tokens already issued. #### The whole thing, in code Anchor: /docs/returning-customer#the-whole-thing-in-code The whole thing, in code // 1 ── ask, in the first seconds of the conversation const res = await fetch(`${LEADVAL}/api/v1/identity/lookup`, { method: "POST", headers: { "X-API-Key": INGEST_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ evidence: { fingerprint, phones, emails } }), }); // Never block the conversation on this. if (!res.ok) return null; const look = await res.json(); // 2 ── decide if (look.verdict === "NEW_PROFILE" || look.verdict === "INSUFFICIENT_EVIDENCE" || look.verdict === "SESSION_FORK") return null; // SESSION_FORK: security signal const top = look.matches[0]; // ⚠ ONE FIELD, ON THE ROW YOU ARE ACTING ON. Do not derive this from the // verdict, the band or corroboratedBy — and do NOT read the top-level // look.constraintsFired as being about THIS row: that array unions EVERY // candidate scored, near misses included, so it can name a clamp that a // different person's row fired. Each row carries its own constraintsFired. const greetByName = top.nameUse === "PERMITTED"; // 3 ── fetch, with the same key (your own workspace). As an installed OAuth app, // call the get_customer_profile / get_lead MCP tools instead. const path = top.customerProfileId ? `/api/v1/profiles/${top.customerProfileId}` : `/api/v1/leads/${top.leadId}/context`; const history = await (await fetch(`${LEADVAL}${path}`, { headers: { "X-API-Key": INGEST_KEY }, })).json(); return { history, greetByName, // For tone when you may NOT use a name: "SHARED_CONTACT_NO_NAME" is a // household, "INSUFFICIENT_EVIDENCE" means the comparison never happened. reason: top.nameUseReason, }; import requests def recognise(fingerprint, phones, emails): # 1 ── ask, in the first seconds of the conversation try: res = requests.post(f"{LEADVAL}/api/v1/identity/lookup", headers={"X-API-Key": INGEST_KEY}, json={"evidence": {"fingerprint": fingerprint, "phones": phones, "emails": emails}}, timeout=3) except requests.RequestException: return None # Never block the conversation on this. if not res.ok: return None look = res.json() # 2 ── decide if look["verdict"] in ("NEW_PROFILE", "INSUFFICIENT_EVIDENCE", "SESSION_FORK"): return None # SESSION_FORK: security signal top = look["matches"][0] # ⚠ ONE FIELD, ON THE ROW YOU ARE ACTING ON. Never derive it from the verdict, # the band or corroboratedBy, and never read the top-level constraintsFired # as being about THIS row — it unions every candidate scored. greet_by_name = top["nameUse"] == "PERMITTED" # 3 ── fetch, with the same key (your own workspace). As an installed OAuth # app, call the get_customer_profile / get_lead MCP tools instead. if top.get("customerProfileId"): path = f"/api/v1/profiles/{top['customerProfileId']}" else: path = f"/api/v1/leads/{top['leadId']}/context" history = requests.get(f"{LEADVAL}{path}", headers={"X-API-Key": INGEST_KEY}, timeout=5).json() # For tone when you may NOT use a name: "SHARED_CONTACT_NO_NAME" is a # household, "INSUFFICIENT_EVIDENCE" means the comparison never happened. return {"history": history, "greet_by_name": greet_by_name, "reason": top.get("nameUseReason")} #### Test your integration Anchor: /docs/returning-customer#test-your-integration Test your integration A device you have never sent Expect 200 , NEW_PROFILE , matches: [] . A 400 means your fingerprint is not reaching evidence.fingerprint . A session you have put through the pipeline Ingest, wait for PROCESSED , then look up the same fingerprint. Expect POSSIBLE_MATCH , that lead's id, and corroboratedBy: [] . Add the EMAIL from that conversation Expect EXACT_MATCH with corroboratedBy: ["contact-email"] . Now try the PHONE instead, still with no name Expect POSSIBLE_MATCH , corroboratedBy: ["contact-phone"] , the row’s own constraintsFired: ["SHARED_CONTACT_NO_NAME"] , and nameUse: "WITHHOLD" with nameUseReason: "SHARED_CONTACT_NO_NAME" . That is correct, not a wiring bug — a phone plus a device with nobody named is a household. The real wiring symptom is corroboratedBy staying EMPTY : the channel never arrived. Then check the row, not the response With more than one candidate indexed, confirm you read matches[0].constraintsFired , not the top-level array — a clean EXACT_MATCH beside a top-level SHARED_CONTACT_NO_NAME is somebody else's row, not a contradiction. Prove nothing personal came back Search the whole response for the customer's name, email and phone. All three must be absent — this endpoint returns pointers only. Errors: 400 INSUFFICIENT_EVIDENCE_FOR_LOOKUP is an integration bug (nothing searchable was sent), 403 IDENTITY_API_NOT_ENABLED means stop calling and ask LeadLogiX, and 429 means honour Retry-After and carry on as though the caller were new. A handshake is a lookup plus one read-back from a shared budget, so plan for half the lookup limit in handshakes. The rest: Ingestion errors & limits . You'll know it worked: A lookup answers in well under a second and your agent greets a returning customer with context it did not have to ask for — without ever naming someone it was not told about. ### Custom metadata URL: /docs/custom-metadata Developer Reference Custom metadata Send fields beyond the standard contract and a workspace can put them on their leads, total them, or use them to route. Optional, additive, and it never affects a score or a price. #### Do you need this? Anchor: /docs/custom-metadata#do-you-need-this Do you need this? Probably not, and that is a fine answer. A connector that sends the standard contract is complete — nothing here can cause a rejection, a conformance note or a degraded lead. Skip the page and nothing breaks. Add it when the answer to one question is yes: is there something this customer's business cares about that LeadLogiX does not already extract? A partner id, a qualifying answer your tooling already computed, a preference their team routes on. Your agent is the only thing in the conversation, so a fact that is not in the transcript and not in the metadata does not exist downstream — this is how it survives. It's a per-customer call The same agent serving two workspaces may send extra keys that only one of them maps. Sending a key nobody maps costs nothing — it is retained and left uninterpreted. Custom fields ride the session you already send — Ingest a session covers that envelope. For your AGENT’S ACTIONS rather than your own fields, see Tool calls as evidence , which is the higher-leverage of the two. #### How it works, end to end Anchor: /docs/custom-metadata#how-it-works-end-to-end How it works, end to end You add a key to the session metadata No new endpoint and no extra call — the same metadata you already send, with one more key. Name it yourself. The workspace admin maps it, or doesn't In Settings → Custom Signals they point a signal at your key and choose how it appears. In Settings → Lead Tags they can separately write a rule against the same key. Both are their decisions, made without you. It shows up, or it acts A signal renders on the lead (field, badge, or a total in their Analytics Hub). A tag rule drives alerts, CRM sync, automations and who can see the lead. One key, both uses The two features read your metadata independently — neither is built on the other, and a signal does not have to exist for a tag rule to work. A rule names your key with a sourceMetadata. prefix, never a signal's name. #### What to send Anchor: /docs/custom-metadata#what-to-send What to send The same envelope, one more key. Values are stringified exactly once, as everywhere else. The standard contract keys ( sessionId , started , embedding , …) as usual, plus yours: metadata { "sessionId": "conv_9F3A21", "started": "2026-07-25T14:01:02.000Z", "partnerCampaignId": "PC-4471", "preferences": "{\"contactTime\":\"evenings\",\"tier\":\"vip\"}" } Constraint Limit Why it matters to you Path depth 5 segments preferences.tier is 2. A value buried 6 deep cannot be reached. Path length 128 characters Long key names spend that budget. Value types text · number · yes/no An object or array cannot be mapped directly — send its leaf fields. Per workspace 25 signals Yours is one of a shared allowance. Send what is useful, not everything. Stringification exactly once A doubly-encoded object cannot be walked into. #### Four rules that decide whether it actually works Anchor: /docs/custom-metadata#four-rules-that-decide-whether-it-actually-works Four rules that decide whether it actually works Send leaves, not trees {"tier":"vip"} stringified once is ideal — the admin points at preferences.tier . A deep tree is not reachable. Keep values matchable A tag rule matches with equals . "PC-4471" works; "campaign PC-4471 (spring)" does not. Keep names and types stable Mapping is by path, so renaming a key silently stops a configured signal from finding anything. A key that is a string on some sessions and an object on others populates intermittently. Omit optional keys — don't blank them A rule can test presence alone. An empty string still counts as present , so a key you always send but sometimes leave blank matches every lead. Omit it or send null . #### What it cannot do Anchor: /docs/custom-metadata#what-it-cannot-do What it cannot do Extra keys are never scored, never priced and never used for identity. Extraction runs as a separate pass after scoring, so nothing you add here can move a lead's quality score or its price — by design, and worth saying to a customer who asks whether sending more will make their leads worth more. It will not. The AI that assigns tags reads the conversation , not your metadata. A fact that must be reasoned about belongs in the transcript; a fact that must be matched exactly belongs in metadata. Send it in both places when both matter. PII belongs where PII belongs Do not use a custom key as a side channel for personal data. It is copied onto the lead where the workspace's own role rules apply to it, and the same privacy rules govern metadata as everywhere else in the contract. You'll know it worked: The customer sees the field on their next lead — not on older ones, because signals are read when a conversation is processed. If a new lead shows nothing, the key is not arriving under the name they mapped, which is a question for you rather than a setting on their side. ### Tool calls as evidence URL: /docs/tool-evidence Developer Reference Tool calls as evidence Every tool your agent calls is a recorded action, not a claim. LeadLogiX turns those calls into graded facts and rebuilds the customer's journey from them — which makes your tool design one of the biggest levers you have on lead quality. This is not an API you call — it is how you design the tools your agent already has. Tool calls ride the conversation you already send ( Ingest a session ); your own extra fields are Custom metadata . This page is the developer's half; what a workspace admin sees and approves is Custom Signals & Tool Evidence on the Guides surface. #### Tool calls outrank the transcript Anchor: /docs/tool-evidence#tool-calls-outrank-the-transcript Tool calls outrank the transcript A conversation tells you what someone said . A tool call tells you what they did — searched, viewed, booked, applied. LeadLogiX treats those differently on purpose: an action taken in a structured field is verifiable, a sentence is not. The same session can therefore produce a thin lead or a rich one depending entirely on whether your agent recorded its actions as tool calls. This page is about getting the second outcome. Nothing here can hurt you Unrecognised tools are never dropped. A call LeadLogiX does not know is retained verbatim and surfaced for registration — you ship first, it gets mapped after. #### What you control — and what you don't Anchor: /docs/tool-evidence#what-you-control-and-what-you-dont What you control — and what you don't The split is deliberate, and it is why tool evidence is trusted. You decide LeadLogiX decides Which tools exist, and what parameters they take Whether a tool is recognised, and what its parameters mean When to call them, and what values to send How strongly the source is believed (its evidence tier) Joining a call to its response Whether a value is private and withheld before purchase Declaring your full toolset up front Which values feed scoring, and which are recorded only Your job is to emit faithful, well-shaped observations. Deciding what they are worth is the platform's job — which is exactly why a buyer can trust them. Sending a field called tier or pii in your payload changes nothing; those are read from the registry, never from you. #### The Website Journey, and how it is built Anchor: /docs/tool-evidence#the-website-journey-and-how-it-is-built The Website Journey, and how it is built Everyone working the lead sees a Website Journey timeline, rebuilt deterministically from your tool turns in transcript order — one row per tool turn, labelled with the event registered for that tool, its detail line composed from the mapped parameters as name: value pairs. Because rows come only from tool calls, the timeline cannot be inflated by anything a customer or a model merely said. What the card looks like, and why it reads the same for any industry, is on Custom Signals & Tool Evidence . What you emit What the journey shows Two consecutive turns naming the SAME tool, each one JSON object, nothing in between One exchange: the call, then the result — both rows marked paired A call with no result after it “Response not recorded”, honestly A tool turn LeadLogiX does not recognise, has disabled, or is outside the workspace No row; the card footer counts it as kept for review #### Where your response actually shows up Anchor: /docs/tool-evidence#where-your-response-actually-shows-up Where your response actually shows up Four places, and one where it deliberately does not. Sending the result half is the single highest-value thing you can do, and it is worth knowing exactly what it buys. A conformant response reaches four surfaces: Surface What your response contributes The Website Journey Its own row , with the event label, tool name and a direction chip. Both halves of the exchange are marked paired . The call keeps its own row — an exchange is not collapsed into one. Tool-Provided Facts Its values, graded at the tier registered for the response side — typically a ground-truth tier, which outranks the call's. This is the surface most people miss. Scoring, where wired Where a fact key is wired, your response feeds the same hooks a call does — so it can move the lead's quality index and its price. The evidence vault / API evidence.toolEvidence.facts[] , each carrying direction: "RESPONSE" , its tier, strength, tool name and turn index. A refused response gets NO journey row at all This is the one place a response can be less visible than you expect. A body that is a top-level array — or any JSON that is not a single object — is retained whole under unregistered with a NON_CONFORMANT_PAYLOAD violation, contributes no facts, and produces no timeline row . Result sets are commonly arrays, so this is the most likely way a response silently disappears from the journey. Wrap it — {"results":[…]} — or send one object per turn. What to map a response field to — and the two mappings that quietly corrupt a lead — is under Mapping decisions . #### Mapping decisions Anchor: /docs/tool-evidence#mapping-decisions Mapping decisions Which side to map, what to map it to, and what it moves. Send the result, not just the request. The call shows what was asked; the result shows what your system knew — a price, an availability, a risk band. Sent as two back-to-back turns naming the same tool, the pair is marked paired , and the result's values can be graded at a higher tier than the request's. A result reaches facts and scoring only when its registration maps a result side; one whose registration maps none appears on the timeline and nowhere else — so ask for the result side to be mapped. Map a result field to… What happens Something only your system knows — a price, an availability, a risk band or score, a qualification outcome Correct. The strongest evidence on the platform, and the reason to register a result side. Something about the customer's intent — a budget, a requested property Dangerous. It asserts the customer stated it, and where that fact key is wired it changes the lead's price. Never map a result that echoes the request's filters — the call already recorded them. A phone or email belonging to your agent or office Wrong, and it will not look wrong: the number becomes a contact channel on the lead, raising reachability and handing the buyer someone else's number. A tier asserting the customer authored the value Refused and recorded as RESPONSE_TIER_REFUSED — a response is system-produced by construction — producing nothing at all. Declare the full toolset in metadata.function.tools — every tool available to the agent, so the platform can refuse tool turns your agent never made (below). And give distinct actions distinct tools: a single catch-all log_event(name, payload) cannot be registered usefully. How much a tool-captured fact moves a lead Only some fact keys move the score — a contact vector, a budget corroboration, a target property, an intent signal; the rest are recorded faithfully and do not. Privacy is decided at registration: a payload cannot mark itself private or public. Measured on one mid-strength lead: with no tool evidence it scored below the pricing floor and was unsellable; one tool-captured budget lifted it to $51.83; adding a target property and an intent signal took it to $59.76. A tool-captured phone matters more still — with no contact vector, reachability is zero and the lead is worth nothing. Tool evidence can also rescue a lead the AI judged to have no purchase intent. One lead, one measurement — an illustration of direction, not a price list. #### Designing tools that produce strong evidence Anchor: /docs/tool-evidence#designing-tools-that-produce-strong-evidence Designing tools that produce strong evidence Ordered by how much they change a buyer's decision. Invest here Because Tools that capture structured values A phone number from a form field beats the same number transcribed from speech. Tools that record a commitment Booking, applying, requesting a callback — an action taken outranks an intention stated. Tools whose response carries your system's truth The call shows what was asked; the response shows what your system knew — price, availability, a qualification result. Distinct tools for distinct actions Separate events turn into a legible arc: searched, viewed, qualified, booked. The one anti-pattern to avoid Do not funnel everything through a single generic log_event(name, payload) tool. One registration cannot describe many different shapes, so either the mapping fits nothing or every action lands under one meaningless label. Distinct actions want distinct tools. #### Rules that materially change the result Anchor: /docs/tool-evidence#rules-that-materially-change-the-result Rules that materially change the result Rule What happens if you do not Emit both sides, back to back, as two turns naming the same tool The more credible half of the exchange is lost, and the timeline says “response not recorded”. Do not try to join them with a correlation id — see below. Send raw values — 450000 , not "$450k" Formatted strings are lossy; the value may be kept as text and cannot be totalled. Declare enum for closed value sets Statuses arrive as unmapped observations nobody can act on until a human registers them. Keep values out of arrays Arrays are not traversable. Promote what matters to a named field, or send one call per item. Keep tool names exactly stable Matching is case-sensitive: a rename or a casing change silently ends the evidence, with no error. Declare your full toolset in metadata.function.tools Any tool turn in the transcript is graded — including one a visitor typed. See below; this is the security control, not housekeeping. Casing is the one that bites search_listings and Search_Listings are two different tools. A casing change after a refactor is not an error and loses no data — the calls are still retained — but the facts quietly stop arriving. If a tool is known by more than one name, ask for the alternates to be registered as aliases. Do not try to pair with a correlation id There is no field that survives the trip. The document store emits no correlation annotation on a turn header, so the id never arrives — and tool_call_id is worse than useless: it renders as a [Tool Response: …] line inside the tool body. The payload is still read, but you have corrupted the raw body kept for audit and gained nothing. Pairing is adjacency. Emit the call and its result as two consecutive turns naming the same tool, with nothing in between. #### Declare your toolset — this is a security control Anchor: /docs/tool-evidence#declare-your-toolset-this-is-a-security-control Declare your toolset — this is a security control It is the only thing that makes a tool turn trustworthy. A [TOOL (name)] turn asserts that your agent called that tool . Nothing in the transcript proves it. Message content is stored verbatim, so a visitor who types a turn separator followed by a tool header produces a turn that is byte-identical to one you emitted — the parser cannot tell them apart, and no rule at that layer can. What a visitor cannot reach is metadata, which travels out of band on the document envelope. So the toolset you declare in metadata.function.tools is the whole defence. Declare every tool available to the agent , not only the ones it used. Your declaration What happens to a tool turn Names the tool Graded normally. Does not name it Refused — no facts, no journey row. The payload is kept under unregistered with an UNDECLARED_TOOL_TURN note, so the refusal is visible rather than a silent gap. Absent or empty No filtering at all. Every tool turn is graded, forged or not. Declaring nothing cannot mean “refuse everything” — legacy sessions never declared a toolset — so it fails open. An entry with no readable name The gate switches off for the whole session and records UNREADABLE_TOOL_DECLARATION . Only you can cause this; a visitor cannot reach metadata. Send it in whatever shape your framework already produces — names are found structurally, not matched against a list of vendors: metadata.function.tools // all of these declare the tool "search_listings" [{ "type": "function", "function": { "name": "search_listings" } }] // OpenAI chat [{ "type": "function", "name": "search_listings" }] // OpenAI Responses [{ "name": "search_listings", "input_schema": {} }] // Anthropic [{ "toolSpec": { "name": "search_listings" } }] // Bedrock [{ "functionDeclarations": [{ "name": "search_listings" }] }] // Gemini ["search_listings"] // a bare name Confirm it is actually switched on Send one session that calls a tool you deliberately leave out of your declaration. The lead should show an UNDECLARED_TOOL_TURN note and that tool’s payload under unregistered , with no facts from it. If it grades facts instead, your declaration is not being read and the gate is off. #### Getting your tools registered Anchor: /docs/tool-evidence#getting-your-tools-registered Getting your tools registered Two routes. Both end in a LeadLogiX review. Your application… Route Where connects to the customer's workspace over POST /mcp with tools:write Submit them yourself, per workspace Below is your own pipeline, or is not connected over MCP Hand the schema to the platform Custom Signals & Tool Evidence — email the JSON Schema plus one real call and one real response to Either way, step one is just shipping: calls to an unregistered tool are retained verbatim and listed for review, so nothing is lost while you wait. #### Submitting your own tools over MCP Anchor: /docs/tool-evidence#submitting-your-own-tools-over-mcp Submitting your own tools over MCP Self-serve, per workspace, with a review at the end. If your application connects to a customer’s workspace through the POST /mcp endpoint and they approved the tools:write scope, you can register your own tools without anyone emailing anyone. This is the route to use when you are building something other LeadLogiX customers install: it works per workspace, and you never hold their credentials. Three tools cover the whole cycle: submit_tool registers a definition, list_tools tells you where each of your registrations stands, and verify_tool answers whether the tool you are shipping today still matches what was approved. You will use submit_tool once per tool and verify_tool on every release — a schema that drifts after approval is the failure that costs you evidence silently, because the calls keep arriving and stop being understood. submit_tool Send the tool’s name, its parameter schema, the journey event its calls represent, and a mapping for each parameter you want captured — which LeadLogiX fact it becomes, at which evidence tier, and whether it carries personal data. You get back a submission id and status: "PENDING" . Re-sending an unchanged definition is free: submission is idempotent on the contract. list_tools, until it is decided Poll it, or just call it when your agent starts. Every registration you have made in that workspace comes back with where it stands — and if one was declined, the reviewer’s note explaining why. That note is the only thing you receive, so it is written to be acted on. verify_tool, from then on Once approved, call it at start-up and after any schema change. It answers whether the tool you are shipping still matches what was approved and, if not, exactly which field names and types differ — never a value. Drift is silent otherwise: the calls keep arriving and the facts quietly stop. Read compared_against, not just matches verify_tool answers about whichever contract exists. On a tool still awaiting review there is no approved contract, so it compares against what you submitted and returns compared_against: "SUBMITTED" with approved_contract_hash: null . That is a useful answer — the code you are shipping is the code you sent — but matches: true on its own does not mean the tool is live. A start-up check that reads only matches will report healthy for a registration nobody has approved. Treat compared_against: "APPROVED" as the only value that means in force. What a mapping may claim Each mapping points at an existing fact key — you cannot invent one; list a key you need in proposedNewFactKeys and the reviewer decides, which turns a refusal into a request. Evidence tiers are capped: a workspace tool may grade its output at USER_SUBMITTED_STRUCTURED or below, because the tiers above it assert that LeadLogiX itself produced the value. Mark a mapping pii: true whenever the field carries somebody's personal details — see the warning at the end of this page. Names are matched exactly, so they have a shape A tool name must start with a letter or digit and use only letters, digits and _ . : - , up to 64 characters — and the same applies to every alias. Matching against the transcript is exact and case-sensitive, so a name outside that shape could never match a call; it is refused at submission rather than approved and left silently grading nothing. A tool with no mappings is still worth registering mappings may be empty. The journey event is taken from journeyEventType on its own, so a tool that captures no parameters still puts a step on the customer's Website Journey — an appointment booked, a transfer made. Register it for the timeline even when there is nothing to grade. A submission is a request, not a change A registration sits at PENDING and is not loaded by the pipeline at all, so its calls keep landing as unregistered observations until a LeadLogiX administrator reviews it. You cannot approve your own — approving one decides how every future lead in that workspace is read into evidence, and that stays a LeadLogiX decision. Approval applies only to leads processed afterwards ; a lead already scored is never re-read. If submit_tool is not offered The tool only appears in tools/list when the token carries tools:write and the workspace has been granted tool submissions by LeadLogiX. Both are deliberate, and neither is something your application can turn on — ask the workspace admin to request it. The pii flag is trusted — a value you declare as not personal is shown to buyers before purchase Mark every parameter that carries a name, email, phone number or address, on the mapping you submit or in the email you send. Flagged values are withheld until a lead is purchased, including in the journey's detail line; it is checked at review and is what a reviewer looks at hardest. ## Apps & OAuth ### OAuth 2.1 / OIDC URL: /docs/oauth Developer Reference OAuth 2.1 / OIDC LeadLogiX is an OAuth 2.1 authorization server. If you are building software that other LeadLogiX customers install, this is how it gets access: the customer approves it on a consent screen, your application receives a token scoped to exactly one workspace, and the customer can withdraw it at any time without involving you. #### Build it in this order Anchor: /docs/oauth#build-it-in-this-order Build it in this order Five steps, one journey. Each links to the page that owns it. Decide you need OAuth at all If you are writing code against your own workspace, stop here and use an ingestion key — this whole journey is for software other people install . The table below settles it. Get a client_id Register a client . Open registration — no approval, no account with us, no waiting. Work out what you may ask for Scopes & limits , before you write the authorize URL. A requested scope is not a granted one: three independent ceilings apply. Implement the flow The authorization code flow below — PKCE, S256 only — and the complete client at the end of this page. Call something with the token MCP → Hosted server is the data plane; there is no separate REST API for reading leads with an OAuth token. Only signing people in? Go to Sign in with LeadLogiX instead — openid plus workspace:read is enough and no workspace grant is involved. Building your product on that identity? Build on LeadLogiX identity . #### When to use this instead of an API key Anchor: /docs/oauth#when-to-use-this-instead-of-an-api-key When to use this instead of an API key Your situation Use Code you run against your own workspace An ingestion key ( val_key__… ) An application other customers install OAuth — this page An AI agent that should read a customer's leads OAuth + the MCP endpoint Signing people in to your own product OAuth + OpenID Connect The difference that matters is who holds the credential. An API key is the customer's, shared with you, valid for everything that key can do until someone remembers to rotate it. An OAuth token is yours , limited to the permissions the customer approved, bound to one workspace, and revocable from a screen in their own settings. The whole decision, as a picture, is on the Quickstart . #### Two planes Anchor: /docs/oauth#two-planes Two planes One switch — the resource parameter — and the single most common integration mistake against this server. One authorization server issues tokens for two planes, and the resource parameter decides which. Ask for openid with no resource and the token is for the identity plane (/idp/userinfo); send resource=https:///mcp without openid and it is for the data plane (POST /mcp). A token minted for one plane is refused by the other. Your request The token is for Use it against scope includes openid , no resource The identity plane /idp/userinfo and the ID token no openid , plus resource=https:///mcp The data plane POST /mcp A token minted for one plane is refused by the other — and the refusal does not say “plane” You get 401 invalid_token with error_reason: token_wrong_audience . If you need both, run two authorizations: one with openid for identity, one with an explicit resource for data. Ask for the resource exactly as you will call it — a trailing slash is a different resource. #### Workspace switch Anchor: /docs/oauth#workspace-switch Workspace switch Consent succeeds, every call 403s — the failure that wastes an afternoon. Authorized, and still refused: application access is a per-workspace switch Connected-application access is off until a LeadLogiX administrator turns it on for that workspace , and it is not checked during authorization — only on requests. So the sign-in works, the customer approves you, your application appears in their Connected Apps list, the token introspects as valid, and every call to /mcp returns 403 with error_reason: mcp_not_enabled . Retrying and re-authorizing will not fix it. Treat that code as “ask the customer's administrator to have connected applications enabled for this workspace”, never as a broken credential. #### Refresh rotation Anchor: /docs/oauth#refresh-rotation Refresh rotation Refresh tokens rotate with no grace window. Read this before you write any retry logic. Presenting a refresh token twice revokes the whole authorization Every refresh returns a new refresh token and invalidates the old one immediately. Replaying a consumed one is treated as theft: the entire grant is revoked, every token issued under it stops working, and the customer must authorize your application again. Redeeming an authorization code twice does the same. Do Never Persist the new refresh token BEFORE you use the access token it arrived with Retry a refresh with the old value after a timeout or a dropped response Treat invalid_grant on refresh as “start a new authorization” Treat invalid_grant as “try again” Serialise refreshes per grant — one in flight at a time Let two workers refresh the same grant concurrently A retry-on-timeout around the refresh call converts one dropped response into a lost integration. This is the one part of an OAuth client worth writing by hand rather than accepting from a code generator — assistants routinely produce exactly that retry loop. #### Discovery Anchor: /docs/oauth#discovery Discovery Read the endpoints from metadata; never copy them from a page. Document Path Authorization server metadata (RFC 8414) /.well-known/oauth-authorization-server OpenID configuration /.well-known/openid-configuration Protected resource metadata (RFC 9728) /.well-known/oauth-protected-resource/mcp The endpoints those documents name Endpoint Path Purpose Authorization /idp/authorize Where you send the customer Token /idp/token Code exchange and refresh Registration /idp/register Dynamic client registration Revocation /idp/revoke Hand a token back when you are done with it Introspection /idp/introspect Check one of your own tokens; never another client's UserInfo /idp/userinfo Claims for the approver, with an openid token Pushed authorization request /idp/par Optional. Takes the authorization parameters and returns a request_uri , keeping a long URL out of browser history, referrers and logs End session /idp/session/end Sign the customer out of LeadLogiX — not a disconnect, see below JWKS /idp/jwks Public signing keys for ID tokens The protected-resource document names the canonical resource identifier you send as resource — a stable identity, not whatever host you happened to call: GET /.well-known/oauth-protected-resource/mcp { "resource": "https://leadval.evas.live/mcp", "authorization_servers": ["https://leadval.evas.live"], "bearer_methods_supported": ["header"], "scopes_supported": ["automations:run", "files:read", "files:write", "identity:lookup", "leads:read", "leads:read.contact", "memory:read", "memory:write", "profiles:read", "profiles:read.contact", "tools:write", "workspace:read", "workspace:read.contact"] } Signing out is not disconnecting /idp/session/end ends the customer's sign-in session; it deliberately does not revoke the access they granted you, so a nightly sync survives the admin who set it up signing out. To give access back, call /idp/revoke ; the customer takes it away from Connected Apps. A post_logout_redirect_uri you did not register is refused rather than followed. #### What this server implements Anchor: /docs/oauth#what-this-server-implements What this server implements Every row was checked against the running server, not against an intention. Specification Status What you can rely on OAuth 2.1 (draft) Implemented Authorization code only. response_types_supported is ["code"] and grant_types_supported is ["authorization_code","refresh_token"] — no implicit, no password, no client credentials. RFC 6749 · OAuth 2.0 core Implemented The framework the above profiles. Error responses use its dialect at every /idp endpoint. RFC 6750 · Bearer tokens Header only Presented in Authorization: Bearer ; never read from the query string or the body. Access tokens are opaque , not JWTs (RFC 9068 is not used) — do not parse one; its meaning is re-derived at every request, which is what lets a revocation or a demotion take effect immediately. RFC 7636 · PKCE Required code_challenge_methods_supported is ["S256"] . There is no plain method. Required for every public client; a LeadLogiX-curated confidential client may omit it only if it sends nonce , which RFC 9700 §2.1.1 names as the alternative. RFC 8414 · AS metadata Implemented Served at the bare origin. issuer is https://leadval.evas.live — the endpoints live under /idp/* but the issuer does not. OpenID Connect Core & Discovery Implemented subject_types_supported: ["public"] , ID tokens signed RS256 , keys at /idp/jwks . Claims for openid , email , profile . RFC 9728 · Protected resource metadata Implemented Describes /mcp and points back at this server, so an MCP client discovers where to authorize instead of being configured with it. RFC 8707 · Resource indicators Implemented The resource parameter selects the audience. It is what separates the identity plane from the data plane. RFC 9207 · Issuer identification Implemented Authorization responses carry iss , including error redirects. A client with more than one provider can tell who answered. RFC 9126 · Pushed authorization requests Optional /idp/par . Every rule on this page applies identically either way. RFC 7591 · Dynamic client registration Open No initial access token. Registering yourself grants no more than any other route would. RFC 7592 · Registration management Not enabled Deliberate. GET on your registration works with its access token; PUT and DELETE return 404 — see Register a client . RFC 7009 · Token revocation Implemented /idp/revoke . Revoking a refresh token ends the authorization. RFC 7662 · Token introspection Implemented /idp/introspect , own tokens only. The fastest way to see the granted scope and audience of a token you hold. RFC 8252 · Native apps Implemented §7.3 loopback redirects only: http://127.0.0.1 and http://[::1] . localhost is refused. OIDC RP-Initiated Logout Implemented end_session_endpoint at /idp/session/end . OIDC Back-Channel Logout Implemented Register a backchannel_logout_uri to be told when a session ends. backchannel_logout_session_supported is true, so tokens carry sid . Model Context Protocol authorization 2025-11-25 A compliant MCP client needs only the endpoint URL; the 401 challenge leads it to everything else. RFC 9700 · OAuth security BCP Followed Exact redirect matching, no plain-HTTP redirect outside loopback, refresh rotation, and §2.1.1's code-injection defence — PKCE for public clients, nonce for a curated confidential one. Failed client authentication at /idp/token is rate limited. RFC 9449 · DPoP Off Deliberately not advertised. The resource server accepts Bearer only, and advertising DPoP would instruct a correct client to send proofs this endpoint would reject. If your library enables DPoP whenever a server seems to offer it, leave it off here. Implicit, hybrid, password and client-credentials grants Refused Every token traces to a person who approved it. Unattended machine-to-machine access uses an ingestion key. Conformance is asserted on every build The table above is not maintained by hand. A test reads the live discovery documents and fails if any advertised value stops matching what this page claims, so a configuration change cannot quietly turn a documented guarantee into a false one. #### Choices a specification left open Anchor: /docs/oauth#choices-a-specification-left-open Choices a specification left open The four that change how a client behaves. Decision What we chose Why it matters to you Client authentication none , client_secret_basic and client_secret_post Public clients are first-class — PKCE is the protection, not a secret embedded in a native app or an SPA. Confidential clients exist for identity products that cannot do PKCE, but LeadLogiX mints those; there is no self-service path to one. Refresh token rotation On, with no grace window Replaying a consumed refresh token revokes the whole authorization — the rule . Audience binding Decided by resource , and by whether openid was asked for A token minted for one plane is refused by the other — two planes . Scope granting Intersection, never a failure Asking for a scope you cannot have completes the flow without it. Read the granted scope from the token response — Scopes & limits . #### Verify it yourself Anchor: /docs/oauth#verify-it-yourself Verify it yourself Everything above is observable without an account. # Authorization server metadata (RFC 8414) — issuer, endpoints, algorithms curl -s https://leadval.evas.live/.well-known/oauth-authorization-server | jq # OpenID Provider metadata — claims, subject types, logout endpoints curl -s https://leadval.evas.live/.well-known/openid-configuration | jq # Protected resource metadata (RFC 9728) — what /mcp is and who authorizes for it curl -s https://leadval.evas.live/.well-known/oauth-protected-resource | jq # Signing keys (RFC 7517) curl -s https://leadval.evas.live/idp/jwks | jq # Registration management is off (RFC 7592) — both return 404 curl -s -o /dev/null -w '%{http_code}\n' -X PUT https://leadval.evas.live/idp/register/any curl -s -o /dev/null -w '%{http_code}\n' -X DELETE https://leadval.evas.live/idp/register/any import requests BASE = "https://leadval.evas.live" meta = requests.get(f"{BASE}/.well-known/oauth-authorization-server", timeout=10).json() print(meta["code_challenge_methods_supported"]) # ["S256"] print(meta["grant_types_supported"]) # ["authorization_code", "refresh_token"] print("dpop_signing_alg_values_supported" in meta) # False — Bearer only # Registration management is off (RFC 7592) — both return 404 for method in ("PUT", "DELETE"): print(method, requests.request(method, f"{BASE}/idp/register/any", timeout=10).status_code) #### The authorization code flow Anchor: /docs/oauth#the-authorization-code-flow The authorization code flow PKCE is mandatory; resource decides the plane. Send the customer to the authorization endpoint GET /idp/authorize ?response_type=code &client_id= &redirect_uri= &scope=leads:read%20offline_access &state= &code_challenge= &code_challenge_method=S256 &resource=https://leadval.evas.live/mcp # data plane shown. For sign-in: add openid to scope and send no resource. They sign in and approve The consent screen names your application, says whether LeadLogiX has reviewed it, states which workspace the token will act on, and lists in plain words what it will be able to read. If they cannot grant everything you asked for, the flow still completes with the rest. Check state and iss, then exchange the code curl -X POST https://leadval.evas.live/idp/token \ -d grant_type=authorization_code \ -d code= \ -d redirect_uri= \ -d client_id= \ -d code_verifier= \ -d resource=https://leadval.evas.live/mcp # {"access_token":"…","refresh_token":"…","token_type":"Bearer","expires_in":3600,"scope":"leads:read"} # Read the GRANTED scope from here — it is not necessarily what you asked for. import requests r = requests.post("https://leadval.evas.live/idp/token", data={ "grant_type": "authorization_code", "code": code, "redirect_uri": REDIRECT_URI, # the same one, exactly "client_id": CLIENT_ID, "code_verifier": verifier, "resource": "https://leadval.evas.live/mcp", }, timeout=10) r.raise_for_status() tok = r.json() granted = set(tok["scope"].split()) # the GRANTED scope, not your request Call the API with the token Authorization: Bearer — header only. Calling the MCP endpoint has the request. redirect_uri is matched exactly, and a mismatch never redirects After URL normalisation the value must equal one you registered — a trailing slash, an extra query parameter or a different path is a different URI. An unregistered one is refused without redirecting, so the mistake shows as an error page rather than a callback. Check the state you sent and the iss that answered (RFC 9207) before you exchange the code. #### Token lifetimes Anchor: /docs/oauth#token-lifetimes Token lifetimes And what ends each one early. Token Lifetime Notes Authorization code 60 seconds Single use — a second redemption revokes the grant Access token 1 hour Opaque. Stops working at once on a disconnect, not at expiry ID token 5 minutes A JWT — verify it on receipt; do not hold it Refresh token 30 days Rotated on every use — no grace window Grant 30 days of inactivity Refreshing extends it Customers see every connected application under Settings → Connected Apps and can disconnect it at any moment without telling you. Handle a sudden 401 as a normal state — stop calling and prompt to reconnect rather than retrying — and revoke your own tokens when a customer uninstalls you: curl -X POST https://leadval.evas.live/idp/revoke \ -d token= \ -d client_id= import requests requests.post("https://leadval.evas.live/idp/revoke", data={"token": refresh_token, "client_id": CLIENT_ID}, timeout=10) #### Test against the live server Anchor: /docs/oauth#test-against-the-live-server Test against the live server No account of ours, and nothing to ask us for. The authorization server is public and self-service. Register a client with a loopback redirect URI such as http://127.0.0.1:8976/callback ( http://localhost is refused), open the authorization URL in a real browser, approve as a LeadLogiX user, and inspect what comes back. A flow that never reaches consent is almost always a redirect-URI or PKCE mismatch, and the error page names which. curl -s -X POST https://leadval.evas.live/idp/introspect \ -d token= \ -d client_id= # Check scope, aud and exp before writing more code. import requests info = requests.post("https://leadval.evas.live/idp/introspect", data={"token": access_token, "client_id": CLIENT_ID}, timeout=10).json() print(info.get("active"), info.get("scope"), info.get("aud"), info.get("exp")) Before you ship, check Why it bites later Endpoints read from discovery, not copied A moved endpoint breaks every deployed copy of your client at once. A fresh code_verifier per authorization, stored per browser session Reusing one makes the code interceptable — the attack PKCE exists to stop. You store the rotated refresh_token before using the response Rotation means the old one is dead. Keeping it logs the customer out on the next refresh. A 401 triggers a refresh, not a re-authorization Sending the customer back to consent on every expiry is the commonest cause of “this app keeps asking me to sign in”. Your resource matches what you call A token minted for one plane is rejected by the other. You tested a DEMOTION, not only a grant Authority is re-derived per request; a client that caches a role keeps acting on one the person lost. #### A complete authorization Anchor: /docs/oauth#a-complete-authorization A complete authorization No framework. Every endpoint read from discovery. Two things are worth copying exactly: the endpoints come from the discovery document rather than from this page, and the PKCE verifier is generated per authorization and stored against the browser session, not in a module-level variable two concurrent sign-ins would share. This is the identity flow; for data, drop openid and add resource . import { createHash, randomBytes } from "node:crypto"; const ISSUER = "https://leadval.evas.live"; const CLIENT_ID = process.env.LEADVAL_CLIENT_ID; const REDIRECT_URI = "https://app.example.com/auth/leadval/callback"; // Cache this. It is stable, and one fetch per process is enough. const meta = await (await fetch(`${ISSUER}/.well-known/openid-configuration`)).json(); export function beginLogin(session) { const verifier = randomBytes(32).toString("base64url"); session.pkce = verifier; // per browser session session.state = randomBytes(16).toString("base64url"); session.nonce = randomBytes(16).toString("base64url"); const url = new URL(meta.authorization_endpoint); url.searchParams.set("client_id", CLIENT_ID); url.searchParams.set("redirect_uri", REDIRECT_URI); url.searchParams.set("response_type", "code"); url.searchParams.set("scope", "openid email profile workspace:read offline_access"); url.searchParams.set("code_challenge", createHash("sha256").update(verifier).digest("base64url")); url.searchParams.set("code_challenge_method", "S256"); url.searchParams.set("state", session.state); url.searchParams.set("nonce", session.nonce); // NOTE: no `resource` — that is what makes this an identity-plane request. return url.toString(); } export async function handleCallback(session, query) { if (query.state !== session.state) throw new Error("state mismatch"); if (query.iss !== meta.issuer) throw new Error("unexpected issuer"); // RFC 9207 const res = await fetch(meta.token_endpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code: query.code, redirect_uri: REDIRECT_URI, client_id: CLIENT_ID, code_verifier: session.pkce, }), }); if (!res.ok) throw new Error(`token endpoint ${res.status}: ${await res.text()}`); const tok = await res.json(); await persist(tok); // store the refresh token FIRST — see refresh-rotation // 1. Verify the ID token against meta.jwks_uri — RS256, iss === meta.issuer, // aud === CLIENT_ID, exp in the future, nonce === session.nonce. // Use a library (jose: jwtVerify + createRemoteJWKSet). Do not hand-roll it. // 2. Read the GRANTED scope, which is not necessarily what you asked for. const granted = new Set(tok.scope.split(" ")); // 3. Only now fetch the profile. const me = await (await fetch(meta.userinfo_endpoint, { headers: { Authorization: `Bearer ${tok.access_token}` }, })).json(); return { tok, granted, me }; } export async function refresh(stored) { const r = await fetch(meta.token_endpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: stored.refresh_token, client_id: CLIENT_ID, }), }); const body = await r.json(); if (!r.ok) { // invalid_grant is TERMINAL. The rotation was consumed — by you, a moment // ago, or by a retry. Send the customer back through authorization. if (body.error === "invalid_grant") throw new ReauthorizeRequired(); throw new Error(`refresh failed: ${body.error}`); // do NOT retry with the old token } await persist(body); // BEFORE using the access token it came with return body; } import base64, hashlib, os, secrets from urllib.parse import urlencode import requests ISSUER = "https://leadval.evas.live" CLIENT_ID = os.environ["LEADVAL_CLIENT_ID"] REDIRECT_URI = "https://app.example.com/auth/leadval/callback" # Cache this. It is stable, and one fetch per process is enough. meta = requests.get(f"{ISSUER}/.well-known/openid-configuration", timeout=10).json() def b64url(raw: bytes) -> str: return base64.urlsafe_b64encode(raw).rstrip(b"=").decode() def begin_login(session: dict) -> str: verifier = b64url(secrets.token_bytes(32)) session["pkce"] = verifier # per browser session session["state"] = b64url(secrets.token_bytes(16)) session["nonce"] = b64url(secrets.token_bytes(16)) params = { "client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI, "response_type": "code", "scope": "openid email profile workspace:read offline_access", "code_challenge": b64url(hashlib.sha256(verifier.encode()).digest()), "code_challenge_method": "S256", "state": session["state"], "nonce": session["nonce"], # NOTE: no "resource" — that is what makes this an identity-plane request. } return f"{meta['authorization_endpoint']}?{urlencode(params)}" def handle_callback(session: dict, query: dict) -> dict: if query.get("state") != session["state"]: raise ValueError("state mismatch") if query.get("iss") != meta["issuer"]: # RFC 9207 raise ValueError("unexpected issuer") r = requests.post(meta["token_endpoint"], data={ "grant_type": "authorization_code", "code": query["code"], "redirect_uri": REDIRECT_URI, "client_id": CLIENT_ID, "code_verifier": session["pkce"], }, timeout=10) r.raise_for_status() tok = r.json() persist(tok) # store the refresh token FIRST — see refresh-rotation # Verify tok["id_token"] with a JOSE library (e.g. PyJWT + PyJWKClient on # meta["jwks_uri"]): RS256, iss, aud == CLIENT_ID, exp, nonce. Do not hand-roll it. granted = set(tok["scope"].split()) # the GRANTED scope me = requests.get(meta["userinfo_endpoint"], headers={"Authorization": f"Bearer {tok['access_token']}"}, timeout=10).json() return {"tok": tok, "granted": granted, "me": me} def refresh(stored: dict) -> dict: r = requests.post(meta["token_endpoint"], data={ "grant_type": "refresh_token", "refresh_token": stored["refresh_token"], "client_id": CLIENT_ID, }, timeout=10) body = r.json() if not r.ok: # invalid_grant is TERMINAL. The rotation was consumed — by you, a moment # ago, or by a retry. Send the customer back through authorization. if body.get("error") == "invalid_grant": raise ReauthorizeRequired() raise RuntimeError(f"refresh failed: {body.get('error')}") # never retry with the old token persist(body) # BEFORE using the access token it came with return body UserInfo returns the standard claims, plus the workspace and role when workspace:read was granted — the claim table, the role mapping and the email_verified rule are on Sign in with LeadLogiX . #### Getting your application reviewed Anchor: /docs/oauth#getting-your-application-reviewed Getting your application reviewed Every application starts unreviewed , and the consent screen says so. Review changes that label and adds your logo; it grants nothing — see Review is a badge, not a permission . To start one, contact support@evas.live . ### Register a client URL: /docs/register-client Developer Reference Register a client Three ways to get a client_id, and one set of rules about where LeadLogiX will send an authorization code. Registration is open — you do not need an account with us, an initial access token, or an approval, and none of those would give your client more access than registering yourself does. Step 2 of the OAuth journey . Next: Scopes & limits , before you write the authorize URL — a requested scope and a granted scope are different things. #### Pick a registration method Anchor: /docs/register-client#pick-a-registration-method Pick a registration method Method Use it when What you get Dynamic registration Your stack can POST once at install or build time. The normal choice. A client_id immediately, no approval step Client ID Metadata Document You would rather publish a document than store a credential. Your metadata URL is the client_id Preregistration Your platform cannot do either — some marketplaces require a fixed id up front. An id we create for you, on request #### Dynamic registration Anchor: /docs/register-client#dynamic-registration Dynamic registration RFC 7591. Open, unauthenticated, immediate. curl -X POST https://leadval.evas.live/idp/register \ -H "Content-Type: application/json" \ -d '{ "client_name": "Acme Pipeline", "redirect_uris": ["https://acme.example/oauth/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none", "scope": "openid email leads:read" }' # 201 Created # { "client_id": "…", "registration_access_token": "…", "allowedScopes": ["leads:read"], … } const res = await fetch("https://leadval.evas.live/idp/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ client_name: "Acme Pipeline", redirect_uris: ["https://acme.example/oauth/callback"], grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], token_endpoint_auth_method: "none", // public client: PKCE is the proof scope: "openid email leads:read", }), }); const { client_id, registration_access_token } = await res.json(); import requests r = requests.post("https://leadval.evas.live/idp/register", json={ "client_name": "Acme Pipeline", "redirect_uris": ["https://acme.example/oauth/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none", # public client: PKCE is the proof "scope": "openid email leads:read", }, timeout=10) r.raise_for_status() client = r.json() # client_id, registration_access_token, allowedScopes, … In the response Read it as client_id Your client. Embed it; it is not a secret. registration_access_token Re-reads the registration with GET . It cannot update or delete it — registration management is off, so register again if you need different metadata. allowedScopes The capability part of your requested scope , moved to a ceiling on your client; the OIDC part ( openid , email , profile ) stays in scope . Expected — request scopes at the authorization endpoint as normal. client_secret Never issued here. A self-registered client is public , and a secret you send is ignored: a secret shipped to an SPA or a native app is a secret in the world. Confidential clients ( client_secret_basic / client_secret_post ) exist for identity products that cannot send a code_challenge ; LeadLogiX mints those by hand. The scope you send back is a declaration, not an entitlement Registration echoes your requested scope verbatim. That is what the protocol says to do, and it means the value in your client record proves nothing about what you can obtain. What a token actually carries is decided at consent, against three separate limits — see Scopes & limits . Read the granted scope from the token response and behave accordingly. #### Client ID Metadata Document Anchor: /docs/register-client#client-id-metadata-document Client ID Metadata Document Your client_id is an https URL we fetch. Publish a JSON document with the same fields you would have POSTed, and use its URL as your client_id . There is nothing to store on your side and nothing to rotate. LeadLogiX fetches the document the first time it sees the id and caches it. The whole document, with every field that matters: GET https://acme.example.com/leadval-client.json { "client_id": "https://acme.example.com/leadval-client.json", "client_name": "Acme Sales Copilot", "redirect_uris": ["https://acme.example.com/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none", "scope": "openid leads:read", "application_type": "web" } client_id must equal the URL byte for byte Not equivalent — identical. A trailing slash, http where the URL says https , or the document served from a redirect target while the id names the redirect source, and authorization fails with invalid_client . This is the one mistake almost everybody makes first, and it is worth checking before anything else. Relatedly: the fetch does not follow redirects, so the id must be the URL that serves the JSON, not one that points at it. Refused in the document Why client_secret or client_secret_expires_at A document anyone can fetch cannot carry a secret. Its presence is refused rather than ignored. token_endpoint_auth_method of client_secret_basic , client_secret_post or client_secret_jwt Same reason. Use none — every client registered this way is public and authenticates with PKCE. Confidential clients exist, but LeadLogiX mints them; there is no self-service path to one. A URI-valued field that is relative, or not https Every URL in the document must be absolute and https, including the redirect URIs, under the same rules as the table below. Anything served with a non-200 status, or not as JSON The fetch is strict on purpose: a 301 to your real document, or an HTML error page returned with a 200, both fail closed. Nothing about your client is stored here — the document is the registration This is the practical difference from dynamic registration, and it cuts both ways. There is no record of you on our side to expire, to be swept as dormant, or to drift out of step with your own metadata: we re-fetch and re-validate the document instead of keeping a copy. So editing it changes your client — add a redirect URI and it works once the cache expires, no call to us. Equally, unpublishing it stops every new authorization immediately, which is how you retire a CIMD client. Tokens already issued are separate; those are revoked from Connected apps , by the person who granted them. The cache window is yours to set: we honour the Cache-Control: max-age (or Expires ) on your document, clamped to between 30 seconds and 24 hours, and use 30 seconds if you send neither. Serve a short max-age while you are iterating and a long one in production. A client that reads discovery finds client_id_metadata_document_supported: true in the authorization-server metadata, so an MCP client can register itself this way with no credential and no approval — nobody at LeadLogiX has to let it in. The fetch is rate limited, and that is not aimed at you Anyone who can reach the authorization endpoint can make this server fetch a URL of their choosing. The limits are per calling address and per target host, and LeadLogiX refuses to fetch itself on any hostname it answers on. A legitimate client fetches once and never notices. #### Redirect URI rules Anchor: /docs/register-client#redirect-uri-rules Redirect URI rules Where an authorization code may be delivered. Accepted Refused https://… — any host http://… on any hostname, including localhost http://127.0.0.1:PORT/… Anything that does not parse as a URL http://[::1]:PORT/… Wildcards or patterns — the match is exact http://localhost is refused; http://127.0.0.1 is accepted This surprises people, so it is worth the sentence: the carve-out for plain HTTP exists because a native app listening on the loopback interface has no network hop to intercept (RFC 8252 §7.3). localhost is a hostname, resolved by whatever the machine's resolver says — which is not a guarantee that the code stays on the machine that asked for it. Use the address literal. A registration containing a refused URI fails with invalid_client_metadata and the message names the offending entries, so you are never left guessing which one. #### Removing a registration Anchor: /docs/register-client#removing-a-registration Removing a registration You cannot delete your own; here is what does. The registration endpoint answers GET with your registration access token and nothing else — PUT and DELETE return 404 because registration management is switched off. That is deliberate: enabling DELETE also enables PUT , and a token that can rewrite its own redirect_uris is a larger risk than a registration nobody removes. What happens to an abandoned registration Detail A nightly sweep reports it Self-registered clients with no activity for 90 days are listed on every run of the platform's retention job. Removal is off until an operator turns it on The sweep reports and deletes nothing by default, so the first weeks produce a list somebody reads. Being called keeps it alive; merely being authorized does not Activity is the last time an authorization was used . A client somebody still calls is never reaped; one holding an authorization nobody has exercised for 90 days is. None of this applies to a metadata document There is no stored registration to abandon or reap — unpublish the document and no new authorization can start. Existing tokens are revoked from Connected apps . Ask and it goes sooner Contact support@evas.live — an operator deletion revokes every grant and purges the artifacts with it. #### What every client must do Anchor: /docs/register-client#what-every-client-must-do What every client must do Requirement Detail PKCE Mandatory for public clients , S256 only. There is no plain challenge method. A confidential client that LeadLogiX has curated may omit it — but only if it sends nonce , which is the countermeasure that replaces it (RFC 9700 §2.1.1). Exact redirect match The URI in the authorization request must be one of the registered strings, character for character. An unregistered one is refused without redirecting anywhere. Discovery Read endpoints from /.well-known/oauth-authorization-server rather than copying them from documentation. Refresh handling Rotation with no grace window. Store the new token before you use it — refresh rotation . #### Preregistration Anchor: /docs/register-client#preregistration Preregistration When neither of the above is possible. Some marketplaces require a client id before any code runs. Contact support@evas.live with your application name, redirect URIs and the scopes you need. A preregistered client is registered by us with the scopes you were granted — not capped at a default — and appears to a customer exactly like any other application on the consent screen. ### Scopes & limits URL: /docs/scopes Developer Reference Scopes & limits What an application can ask for, what decides whether it gets it, and why asking for more than you can have is not an error. A token's scope is the intersection of three independent limits — the client's, the approving person's, and the workspace's — and any of the three can be the one that removed something. Step 3 of the OAuth journey — read it before you write the authorize URL. The most expensive mistake here is assuming a requested scope is a granted one, finding out after launch, and going back to every customer for re-consent. Next: the authorization code flow , then MCP → Hosted server . #### The catalogue Anchor: /docs/scopes#the-catalogue The catalogue Scope What it grants Who can approve it leads:read Scored leads, their scores and summaries — without contact details. Anyone who can read leads: admin, procurement, or an agent for their assigned tags. leads:read.contact The same leads including names, phone numbers and email addresses . Admin only. Deliberately stricter than the console — an agent can see a phone number on screen but cannot hand it to a third party. profiles:read Merged customer profiles across leads — without contact details, and with names redacted from free text. Admin or procurement, and the workspace must have the Customer Profiles API grant enabled. profiles:read.contact The same profiles including names, phone numbers and email addresses . Admin only, and the Customer Profiles API grant. Deliberately narrower than leads:read.contact : a profile spans conversations across groups, so an agent scoped to particular tags can never be entitled to the whole of one. workspace:read Which workspace this authorization acts on, and the person's role in it. Any recognised role, including agent — it discloses nothing the approver cannot already see about themselves. workspace:read.contact Your colleagues' work email addresses and phone numbers , the CRM seat id each one maps to, the notification addresses this workspace routes tagged leads to, and — because both describe an individual rather than the organisation — that person's own working hours and CRM time zone . Admin only. This is the scope that lets an application reach the person it decided should take a lead — without it list_crm_agents can name somebody and give you no way to contact them. The tag addresses are frequently a desk or distribution list rather than an individual, which is what makes them survive a staff change. ⚠ Note the split: a DESK's hours and the WORKSPACE's hours are organisational facts and need no scope at all, but a named person's hours and time zone sit behind this one — without it, people[].availability reports WITHHELD_NO_PERMISSION , which says nothing about whether hours exist. tools:write Describe your own tools, so LeadLogiX reads their calls as structured evidence on this workspace's leads — and check where each submission stands. Admin only, and the workspace must have the Tool submissions grant enabled. It writes into a review queue: a submitted tool stays inactive until LeadLogiX approves it, so this grants the right to ask , never the right to change how leads are scored. automations:run Run the automations this workspace has published to applications , and read the results each one declares. Admin only, and the workspace must have the Automation runs grant enabled. ⚠ This is a write, despite reading like a query. An automation can post to a chat channel, call an outside system, send a message on this workspace's behalf, or move a lead into a paid line of business — so approving it means letting an application make the workspace act , not just look. Two things bound it: each automation is published to applications individually and none is published by default, and a published automation hands back only the specific fields it declares — never its logs, and never anything it happened to read along the way. memory:read Read back the notes this application has saved here itself , and search LeadLogiX's own product documentation . Any role, and the workspace must have the Application memory grant enabled. Notes are keyed to three things together — this workspace, this application, and the person who approved this screen — so an application never sees another application's notes, and never a colleague's. That last part is deliberate: two people at the same company may not be entitled to see the same leads, and notes everybody shared would quietly hand one person's data to the other. The documentation half discloses nothing about anybody: it is the same product manual published on the public site, identical for every workspace, and it is searched rather than changed — there is no way for any application to edit what other workspaces read. memory:write Save notes and searchable documents here for later, and delete them again. Any role, and the workspace must have the Application memory grant enabled. ⚠ This is a write — but the only one any role may approve, because what it touches is the application's own saved notes and nothing else. Nothing this workspace knows is disclosed through it. Notes expire on their own ; documents saved so they can be found by meaning do not — a quota is what bounds those, and the workspace can see and delete every one. How much one application may keep for one person is capped. If you run several assistants and they must not read each other's notes, connect them as separate applications : the grouping label a caller passes is for tidiness, not for keeping things apart. files:read List and read back the files this application has saved here itself. Any role, and the workspace must have the Application memory grant enabled. Files are keyed to the same three things notes are — this workspace, this application, and the person who approved this screen — so an application never sees another application's files, and never a colleague's. files:write Save files here, and delete them again. Any role, and the workspace must have the Application memory grant enabled. ⚠ This is a write , and it is deliberately separate from the notes one: agreeing that an application may keep notes for you is not the same as agreeing it may store arbitrary files, and a single checkbox covering both would be asking you to approve something this screen had not described. ⚠ Files do not expire — a quota is the only thing bounding them, so the workspace's own Application storage page is where you see what is being held, and where you can delete it. identity:lookup Whether the person your application is speaking to matches somebody already in this workspace . You send the identifying signals you have — an email address, a phone number, a device fingerprint — and receive pointers to matching conversations. Never a name, an address or a phone number. Admin only, and the workspace must have the Identity Resolution API enabled. ⚠ This is the one permission where personal data travels towards LeadLogiX rather than away from it: approving it means allowing an application to transmit identified people's contact details here, and to learn which of this workspace's customers they are. That is why it is flagged as personal data even though the answer contains none. OIDC scope Grants Notes openid A stable user identifier, sub Makes the token an identity-plane token — two planes email , profile The approving person's own email and name Never workspace data — the claims are on Sign in with LeadLogiX offline_access Not required A refresh token is issued regardless. Ask for the least you need — a .contact scope costs you consent leads:read.contact and profiles:read.contact send real customers' personal details to your systems, and are shown to the approver on their own line, marked, with a warning. Requesting one you do not need costs you approvals. Without it, free text is redacted before it leaves LeadLogiX — not merely stripped of structured fields. #### The three ceilings Anchor: /docs/scopes#the-three-ceilings The three ceilings A scope must survive all of them. 1 · The client Registered What this application may ever request. A curated limit set by LeadLogiX, or — for a self-registered client with no limit — whatever it declared, intersected with the public default. 2 · The approver Role A token can never exceed the person who authorized it. An agent approving a request for contact details grants everything else and not that. 3 · The workspace Grant Some data is off unless a LeadLogiX administrator has enabled it for that workspace — profiles:read is the current example. The public default, and why it is small A client nobody has vetted can ask for leads:read and workspace:read . It cannot ask for contact details: that would put a user in front of a consent screen approving personal-data egress to an application registered two seconds earlier. Widening a specific client's ceiling is a deliberate act by a LeadLogiX administrator. #### Asking for more than you can have Anchor: /docs/scopes#asking-for-more-than-you-can-have Asking for more than you can have Not an error. The authorization request succeeds, the consent screen shows the scopes it cannot offer as unavailable, and the token comes back without them. This is deliberate — failing the whole request because one scope was out of reach would break sign-in for every workspace that has not enabled an optional feature. So read the granted scope, every time The scope in the token response is the truth. A client that assumes it received what it requested will call an endpoint it has no authority for and read the resulting refusal as an outage. Introspection ( POST /idp/introspect ) tells you the same thing for a token you already hold. #### Review is a badge, not a permission Anchor: /docs/scopes#review-is-a-badge-not-a-permission Review is a badge, not a permission LeadLogiX can mark an application reviewed. That changes what every future consent screen tells a user about it — "Reviewed by LeadLogiX" instead of "Not reviewed" — and it changes nothing about what the application may be granted. The two controls are separate on purpose: vouching for who somebody is and deciding what they may read are different judgements, and collapsing them would mean every trust decision silently widened access. If you need a wider ceiling, ask for the limit — not for the badge Contact support@evas.live describing the data your application needs and why. Being marked reviewed will not give it to you. Every application starts unreviewed, and the consent screen says so — “Anyone can register an application with this name.” That warning protects customers from something impersonating you, so nothing you send removes it; review replaces it with a reviewed badge and your logo. #### Scopes can shrink under a live token Anchor: /docs/scopes#scopes-can-shrink-under-a-live-token Scopes can shrink under a live token Authority is re-derived on every request, not frozen at issue. If the person who authorized your application is demoted, loses a capability, leaves the workspace, or the workspace turns a feature off, the token keeps working for whatever remains — and refuses what no longer holds. When nothing remains you receive insufficient_scope with error_reason: no_effective_scope . The inverse is also true and less obvious: widening a grant does not reach an existing token. A customer who gains a capability must authorize your application again before it can use it. ### Sign in with LeadLogiX URL: /docs/sign-in-with-leadval Developer Reference Sign in with LeadLogiX The identity half of the authorization server. Ask for openid, exchange the code, and call UserInfo to learn who signed in, which workspace they belong to and what they may do there — enough to create an account in your own product without asking them to type anything. #### Add it in four steps Anchor: /docs/sign-in-with-leadval#add-it-in-four-steps Add it in four steps Identity only. Shorter than the full OAuth journey. Register a client Register a client . Dynamic registration, with your callback in redirect_uris . No approval step. Send them to authorize with the right scopes openid gets you an identity. Add workspace:read if you want to know which workspace they belong to and their role in it — and almost every product does. Send no resource : openid is what makes this an identity-plane token — two planes . Read the claims, not the name sub is the stable user identifier. Map roles with the table below, and check name_verified before trusting a workspace name for anything that matters. Decide what happens on first sign-in Link to an existing account or provision a new one — the last section covers both, including the trap of matching on an unverified email. You can stop here Nothing on this path needs a workspace grant, and none of it reads customer data — you can skip most of Scopes & limits . The runnable client is A complete authorization ; what to build around it is Build on LeadLogiX identity . Want their leads too? That is a second, data-plane authorization and MCP → Hosted server . #### Claims Anchor: /docs/sign-in-with-leadval#claims Claims Released at UserInfo, per granted scope. Scope Claims Notes openid sub , workspace , role , role_rank The last three additionally require workspace:read to have been granted — see below. email email , email_verified email_verified is omitted if we could not check it. Absent is not false. profile name , given_name , family_name , updated_at Standard OIDC profile claims about the person, not their membership. Declaring a claim is not releasing it workspace , role and role_rank are declared under openid because that is the only place UserInfo will look — it filters to OIDC scopes before resolving claims. They are released only when workspace:read was actually granted and the authorization is still live. Ask for both. #### Mapping LeadLogiX roles onto yours Anchor: /docs/sign-in-with-leadval#mapping-leadlogix-roles-onto-yours Mapping LeadLogiX roles onto yours role role_rank What it means admin 50 Runs the workspace: billing, members, integrations, configuration. procurement 10 Buys leads and works the clearing pool. agent 10 Works assigned leads. Sees a subset, scoped by tag. Treat the vocabulary as open — map anything you do not recognise to your lowest tier These are the roles a workspace assigns. A token can carry a value that is not among them, and the safe response is always the same: grant your least privileged tier and carry on, rather than guessing from role_rank or failing the sign-in. Never treat an unrecognised role as an administrator because its rank is high. Rank is an ordering, not a hierarchy procurement and agent are both rank 10 and neither manages the other. A role_rank >= 10 test admits both — which is fine when that is what you meant, and a silent privilege grant when you meant "procurement". Compare role when the distinction matters. #### Linking to an account you already have Anchor: /docs/sign-in-with-leadval#linking-to-an-account-you-already-have Linking to an account you already have Key on sub, not on email sub is stable and belongs to LeadLogiX. An email address can be changed by its owner, and reassigned by an administrator. Require email_verified === true before linking by email If you must match an existing account by email, the claim must be present AND true. Treating an absent claim as false is safe; treating it as true is an account-takeover path. Re-check membership rather than caching it Role and workspace change. Call UserInfo again — or the MCP get_workspace tool, which re-derives on every call — rather than storing a role at signup and trusting it a year later. Identify a workspace by workspace.id , never by its name — typed by whoever registered it and unchecked unless name_verified is true. Provisioning, sessions and staying current: Build on LeadLogiX identity . #### Choosing which LeadLogiX account gets connected Anchor: /docs/sign-in-with-leadval#choosing-which-leadlogix-account-gets-connected Choosing which LeadLogiX account gets connected One browser can hold two LeadLogiX identities. Ask, before you store a subject. LeadLogiX keeps one sign-in session per browser for 14 days . An authorization request that needs no interaction completes against whichever account that session holds — no screen, no chooser. That is ordinary single sign-on, and for a plain sign-in button it is what you want. It stops being what you want the moment you store the subject A person who holds two LeadLogiX accounts in one browser — anybody testing an integration, and any consultant who belongs to two workspaces — gets whichever one is live. If your product then binds that sub to one of your own user records, the wrong identity can be attached permanently, with no error on either side : a subject you have never seen looks exactly like a first connection. Only the person can tell, and only if you ask them. Send prompt=select_account on the authorization request. The consent screen then names the account that is about to be connected and offers Use a different account , which signs them out and returns them to the same pending request. prompt What LeadLogiX does select_account Always shows the screen, names the account being connected, and offers to sign out and come back as somebody else. Use this before you store a subject against one of your users. login The same screen, framed as “confirm it is you”. Use it when you want the person re-checked rather than offered a choice. consent Always shows the screen, even for an application this person already approved. none Never shows a screen. Answers login_required , interaction_required or consent_required at your redirect URI instead of prompting. Must be sent on its own. Read the supported list, do not probe for it The authorization-server metadata publishes prompt_values_supported . Any value outside it is refused with invalid_request and unsupported prompt value requested , delivered to your redirect URI — which is easy to mistake for a problem with the rest of your request. A forced consent supersedes the last one — and revokes its tokens select_account and consent show the screen every time, and approving creates a new authorization that replaces the previous one. Any access or refresh token issued under the old one stops working: the next call answers 401 with error_reason: grant_missing , and a refresh answers invalid_grant . For an interactive sign-in that is harmless — you have just been given a newer token. It is not harmless if the same client_id also holds a long-lived token doing background work: asking a person to confirm their identity would cut that off. Use a separate client for the flow that asks, or send no prompt on the flow that must not be interrupted. ⚠ And the supersede stands even if your own flow then fails. Once the person approves, the new authorization exists — whatever your application does next. If you take the code and your own account-linking step rejects it (because that identity is already attached to a different user of yours, say), the previous authorization is still gone. The visible outcome is a link that failed and a background token that stopped working, with nothing in between to connect them. An account switch adds a hop to the redirect chain When somebody approves as a different account from the one the browser session holds, LeadLogiX ends the first sign-in before completing the authorization. A browser follows that automatically. A server-side harness that stops at the first non-redirect response will see it as a dead end — follow it, and the code arrives at your redirect URI as usual. ### Build on LeadLogiX identity URL: /docs/build-on-identity Developer Reference Build on LeadLogiX identity If your product serves LeadLogiX customers and nobody else, you do not need an accounts system. LeadLogiX already knows who the person is, which workspace they belong to and what they may do there — and re-derives all three on every request rather than trusting a snapshot. Consume that instead of maintaining a second copy that drifts. #### What you can stop building Anchor: /docs/build-on-identity#what-you-can-stop-building What you can stop building What you would normally build What replaces it A users table, password reset, MFA The authorization code flow. sub is your user key. An organisations table and a membership join workspace.id from UserInfo. It is your tenant key. A roles model and an admin screen to manage it role and role_rank . The customer manages membership in LeadLogiX. Invitations and seat management The customer invites in LeadLogiX; a new member can sign in to your product immediately. Deprovisioning when someone leaves Their next call fails — see Staying current below. What this does not remove You still own your own data, and it still needs a key. Store it against (workspace.id, sub) — never against an email address, which the owner can change, and never against the workspace NAME. The flow itself is on Sign in with LeadLogiX ; this page is what to build around it. #### Sessions Anchor: /docs/build-on-identity#sessions Sessions Two designs, and the trade-off between them. Derive per request Short local session How it works Call UserInfo (or the MCP get_workspace tool) on every request that needs authority. After the code exchange, mint your own cookie holding sub, workspace id, role and a short expiry. Freshness Exact. A demotion takes effect on the next request. Up to the cookie's lifetime stale. Keep it to minutes, not days. Cost A network call per request. None after sign-in. Use it when The decision is consequential — money, deletion, cross-workspace reads. The decision is ordinary page rendering. Most products end up with both: a short cookie for rendering, and a live re-derivation before anything that matters. What neither should do is store a role at sign-up and trust it a year later — that is the copy that drifts. Your session must not outlive the grant A local session is derived state. If the customer disconnects your application, LeadLogiX stops honouring the token but your cookie knows nothing about it. Keep the grant's refresh token with the session and destroy the session the moment a call returns grant_revoked . #### Authorization decisions Anchor: /docs/build-on-identity#authorization-decisions Authorization decisions Map the role once, at your boundary. // Your capabilities, expressed once, in terms of LeadLogiX's roles. const CAN = { "view_dashboard": ["admin", "procurement", "agent"], "export_data": ["admin", "procurement"], "change_settings": ["admin"], }; // An unlisted role gets nothing. That is the correct default for a value you do // not recognise — deny, do not infer from role_rank. export const can = (claims, capability) => CAN[capability].includes(claims.role); // ⚠ Compare the ROLE, not the rank, when the distinction matters. // procurement and agent are both rank 10 and neither manages the other, so // `role_rank >= 10` admits both — fine when that is what you meant, and a // silent privilege grant when you meant procurement. # Your capabilities, expressed once, in terms of LeadLogiX's roles. CAN = { "view_dashboard": {"admin", "procurement", "agent"}, "export_data": {"admin", "procurement"}, "change_settings": {"admin"}, } def can(claims: dict, capability: str) -> bool: # An unlisted role gets nothing. That is the correct default for a value you # do not recognise — deny, do not infer from role_rank. return claims.get("role") in CAN[capability] # ⚠ Compare the ROLE, not the rank, when the distinction matters: # procurement and agent are both rank 10 and neither manages the other. Do not invent a second role model on top. A per-user override table in your product is a permission LeadLogiX's administrator cannot see, cannot revoke, and will not remove when they remove the person. Treat any role you do not recognise as your least privileged tier. #### Staying current Anchor: /docs/build-on-identity#staying-current Staying current Three signals, and none of them is a poll. What happened How you find out What to do The customer disconnected your application Any call → grant_revoked Destroy the session. Re-authorize only if the person asks. Their role changed, they were suspended, deleted, their password was reset, or the workspace was suspended A back-channel logout token POSTed to your registered backchannel_logout_uri End the local session for that sub. The next sign-in picks up the new authority. The workspace is suspended or being deleted Calls → tenant_inoperable Stop calling. Do not re-authorize — it will not help. Register a backchannel_logout_uri (https, accepting an unauthenticated POST from LeadLogiX) to receive the second row. Without it you learn about a role change at your next token refresh, which is long enough to matter for a demotion. The logout token is a JWT signed with the same keys as the ID token: check iss , aud , the events claim, and that it carries sub or sid — then end only that session, never the whole workspace. #### Provisioning on first sign-in Anchor: /docs/build-on-identity#provisioning-on-first-sign-in Provisioning on first sign-in The callback carries the workspace, the subject and the role. const { me } = await handleCallback(session, query); // The workspace you have never seen before IS your signup event. There is no // separate onboarding API to call, and no webhook to wait for. let org = await db.orgs.findByWorkspaceId(me.workspace.id); if (!org) { org = await db.orgs.create({ workspaceId: me.workspace.id, // the key. immutable, server-generated. displayName: me.workspace.name, // a label. see the warning below. createdBy: me.sub, }); } // The first person from a workspace is not necessarily its administrator — // read the role, do not assume it. await db.members.upsert({ orgId: org.id, sub: me.sub, role: me.role }); me = handle_callback(session, query)["me"] # The workspace you have never seen before IS your signup event. There is no # separate onboarding API to call, and no webhook to wait for. org = db.orgs.find_by_workspace_id(me["workspace"]["id"]) if org is None: org = db.orgs.create( workspace_id=me["workspace"]["id"], # the key. immutable, server-generated. display_name=me["workspace"]["name"], # a label. see the warning below. created_by=me["sub"], ) # The first person from a workspace is not necessarily its administrator — # read the role, do not assume it. db.members.upsert(org_id=org.id, sub=me["sub"], role=me["role"]) UserInfo { "sub": "6nQ2…", "email": "dana@northwind.example", "email_verified": true, "name": "Dana Fry", "workspace": { "id": "t_78d6b253d55b6c32", "name": "Northwind Roofing", "name_verified": false, "is_sub_workspace": false, "parent_id": null }, "role": "procurement", "role_rank": 10 } A workspace name is not an identity workspace.name is typed by whoever registered the workspace and is checked by nobody unless name_verified is true. Display it if you like; never use it to decide who someone is, to match against your own customer records, or to grant anything. The id is the identity. is_sub_workspace and parent_id are always present. If you bill or report per organisation, decide early whether a sub-workspace is its own customer or part of its parent — parent_id is the field to key that decision on. #### What you still have to get right Anchor: /docs/build-on-identity#what-you-still-have-to-get-right What you still have to get right Concern Why it is still yours Storing the refresh token A long-lived credential for one person in one workspace. Encrypt it at rest and scope access to the service that refreshes. Your own audit trail LeadLogiX records what happened in LeadLogiX. What happened in your product is yours to record, and sub is the actor id to record it under. Rate limits and retries Your traffic is your own — see OAuth & MCP errors → Rate limits . Data you derive Anything you compute from a customer's leads is yours to delete when they disconnect. Nothing revokes it for you. ### OAuth & MCP errors & limits URL: /docs/oauth-errors Developer Reference OAuth & MCP errors & limits Every refusal from the authorization server and the MCP endpoint, and the rate limits behind a 429. Each carries a stable machine-readable reason alongside the human sentence: branch on the reason. The sentences are rewritten whenever they can be made clearer, so anything that parses them breaks on an improvement. #### Two dialects, and which one you get Anchor: /docs/oauth-errors#two-dialects-and-which-one-you-get Two dialects, and which one you get Where it failed Shape The authorization server, /idp/* OAuth 2.0 errors — error plus error_description , in the redirect query string or the token response body (e.g. {"error":"invalid_grant"} ). The resource server, POST /mcp , not authorized HTTP 401/403, a WWW-Authenticate header, and a JSON body carrying error , error_description and error_reason . A 401 carries resource_metadata , which is how a client that has never seen LeadLogiX discovers where to authorize. A tool failing at /mcp A JSON-RPC result with isError: true , inside HTTP 200 — not a transport failure. The ingestion API, /api/v1 RFC 9457 application/problem+json with a code — a different page: Ingestion errors & limits . A failed tool call is not retryable A tool that refuses answers HTTP 200 with isError: true — deliberately, because a 4xx would look like a broken transport and invite a retry. Retry only genuine transport errors and 429 . #### Three refusals that are not what they look like Anchor: /docs/oauth-errors#three-refusals-that-are-not-what-they-look-like Three refusals that are not what they look like You see It is usually Fix 403 mcp_not_enabled on a token that introspects as valid Application access is off for that workspace An administrator's switch, not your credential — the workspace switch 403 on a call that used to work A scope that shrank under a live token (a role change, a feature turned off) Scopes can shrink ; no_effective_scope when nothing remains invalid_grant on refresh The rotation was already consumed Re-authorize; never retry — refresh rotation #### error_reason — the full list Anchor: /docs/oauth-errors#error-reason-the-full-list error_reason — the full list Stable tokens. This is what to branch on. error_reason HTTP What to do no_token 401 Send the bearer token. Header only — LeadLogiX does not read it from the query string or the body. token_no_subject 401 The token names nobody. Re-authorize; do not retry. token_expired 401 Refresh, then retry once. token_no_audience 401 The token is not bound to a resource. Send resource on the token request. token_wrong_audience 401 You are calling the other plane — two planes . grant_missing 401 The authorization is gone — revoked, or superseded by a newer consent for the same client. Re-authorize. grant_revoked 401 Re-authorize the user. Somebody disconnected your application deliberately — do not retry, and do not treat it as an outage. grant_suspended 403 Wait. The workspace is suspended. Retrying will not help and re-authorizing will not either. grant_mismatch 401 Token and authorization disagree. Re-authorize. tenant_inoperable 403 The workspace is suspended or scheduled for deletion. Stop calling until it returns. user_missing 403 The account that authorized you no longer exists. Ask someone else to authorize. user_suspended 403 That account is suspended. Another member must authorize. role_unresolved 403 The authorizing account has no recognised role. An administrator must fix it. user_left_tenant 403 That person left the workspace. Somebody still in it must authorize. mcp_not_enabled 403 An operator, not a retry. The workspace has not enabled application access — Connected Apps is the admin's side. no_effective_scope 403 The authorization no longer grants anything, usually after a role change. Re-authorize. grant_revoked and grant_suspended need opposite responses Both arrive as refusals and look identical to a client that only reads error . One means the customer withdrew your access and you must ask again; the other means their workspace is paused and asking again does nothing. A client that treats them alike either spams a consent screen at a suspended account, or quietly stops working after a revocation it never told anyone about. #### invalid_grant on refresh means re-authorize Anchor: /docs/oauth-errors#invalid-grant-on-refresh-means-re-authorize invalid_grant on refresh means re-authorize It is terminal: the refresh token was already used, and replaying it has revoked the authorization. Start a new authorization; never loop on the refresh. The rotation rule is the one part of an OAuth client worth writing by hand. #### Rate limits Anchor: /docs/oauth-errors#rate-limits Rate limits Separate budgets, not one ceiling. Each is counted per authorization — one application, for one person, in one workspace. Calls Limit Note /mcp — reads (leads, tags, CRM, profiles, workspace) 600 requests / minute Sized for an agent working normally — one question commonly becomes twenty calls. Reaching it usually means a loop rather than load. identity_lookup 60 / minute Its own, tighter budget: it is an existence check over somebody else's customer list. automation__ 60 / minute Separate from the reads. submit_tool 20 / hour Per authorization. Reading notes — memory_get , memory_list , memory_usage 1,200 a minute Writing notes — memory_put , memory_delete 300 a minute Reading files — file_get , file_list 120 a minute Writing files — file_put , file_delete 60 a minute Search and collection writes — knowledge_search , knowledge_write , knowledge_delete 60 a minute and 3,000 an hour Two clocks, either can refuse you. /idp/register 10 / hour per source Plus a platform-wide ceiling. /idp/token — failed client auth only 120 / 5 min per client per source; 600 / 5 min per client Only invalid_client (401) counts. A stale code, a wrong code_verifier , and every successful exchange or refresh cost nothing — a 429 here means a credential problem, not a traffic problem. Going over returns 429 with Retry-After . Honour it rather than retrying through it — the budgets are per minute, so a client that backs off correctly never notices one. These are the defaults a workspace starts with; LeadLogiX can raise them for a customer, and memory_usage reports the storage limits your connection is actually measured against. Search is the one with two ceilings, and the second is hourly A per-minute budget stops a runaway loop; it cannot stop an application that searches steadily all afternoon. If you consult a collection on every turn of every conversation, consult it once and keep what you learned in a note — notes are the cheapest calls on the surface. Reads and writes fail in opposite directions, on purpose If the limiter itself is unavailable, a read is let through and logged — one extra bounded read by a caller already authorized costs little. A write is refused with RATE_LIMIT_UNAVAILABLE , because a bad minute in the limiter would otherwise admit unbounded permanent writes. Retry it after a pause; it is not a rejection of what you sent. Sizing for a busy workspace These assume the customer connected your application once, under one operations account, and runs everything through it — so the whole business shares one allowance. If you expect more than a few hundred conversations an hour through one connection, say so before you launch: raising a ceiling is a change on our side, far easier to arrange in advance than during a first busy week. ## MCP ### Hosted server URL: /docs/mcp-hosted Developer Reference MCP — hosted server LeadLogiX runs a Model Context Protocol server at /mcp. An AI application authorizes through OAuth, receives a token scoped to one workspace, and reads scored leads, customer profiles and routing through twenty-four tools — plus, where the workspace publishes them, its own automations as callable tools. Nothing to install, and no credential the customer has to copy anywhere. #### There are two MCP servers. This is the one you probably want. Anchor: /docs/mcp-hosted#there-are-two-mcp-servers-this-is-the-one-you-probably-want There are two MCP servers. This is the one you probably want. Hosted server (this page) leadval-mcp (npm) Who runs it LeadLogiX. Nothing to install. You do — it runs beside your agent, over stdio. How it authenticates OAuth 2.1 — the customer approves scopes once, and can revoke them. Your own ingestion API key ( val_key__… ). Whose data Any workspace that authorizes your application Your own workspace What it does READS scored leads, customer profiles, routing and hours; with the matching scopes it also keeps notes and files, registers your tools for review, and runs the workspace's published automations. ASKS and TELLS — notify a session, poll it, and look up whether a caller is known. Tools The catalogue below, filtered to the scopes you were granted leadval_notify_session , leadval_get_session_status , leadval_process_and_wait , leadval_identity_lookup — leadval-mcp Reach for it when You are building an app other companies connect to LeadLogiX, or you want a customer's own data in a model. You are wiring your own agent to your own workspace — Claude Desktop, Cursor, a custom runtime. Most teams use both: leadval-mcp on the conversational side tells LeadLogiX a conversation finished and asks whether the caller is known; this server pulls the scored lead or the merged history back into a model. Recognise a returning customer uses one of each. Neither is the OpenAPI spec or llms.txt , which are for a coding assistant writing your integration — AI & tooling . A token for this endpoint comes from the OAuth journey: OAuth 2.1 / OIDC , Register a client , Scopes & limits . #### Connecting Anchor: /docs/mcp-hosted#connecting Connecting Item Value Endpoint POST /mcp Transport Streamable HTTP, stateless — every request stands alone; no Mcp-Session-Id is issued or needed, so requests may run concurrently and in any order. Authorization An OAuth access token in Authorization: Bearer … . Header only. Resource Request the token with resource=https:///mcp and without openid — two planes . Discovery /.well-known/oauth-protected-resource — an unauthenticated 401 also returns it in WWW-Authenticate , so a compliant MCP client needs nothing but the URL. Before it works The workspace must have application access switched on, or every call is 403 mcp_not_enabled — the workspace switch . Send both Accept types, or every call returns 406 Accept: application/json, text/event-stream — both . A request that accepts only JSON, or sends no Accept header, is refused with 406 however valid its token. This is the MCP Streamable HTTP contract, so a compliant client already does it; hand-rolled clients and HTTP tools are where it bites. If you send You get GET /mcp 405 with Allow: POST . There is no SSE stream to open and no session to resume. A JSON-RPC batch (an array of requests) Refused. The protocol version advertised here removed batching; send one request per call. A token bound to a different resource, or a URL that differs by a trailing slash 401 . Ask for the token with resource=https:///mcp exactly as you will call it. #### Calling it Anchor: /docs/mcp-hosted#calling-it Calling it Plain JSON-RPC over HTTP; any MCP client library does the same. curl -X POST https://leadval.evas.live/mcp \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call", "params":{"name":"search_leads","arguments":{"limit":25}}}' const rpc = async (method, params) => { const r = await fetch("https://leadval.evas.live/mcp", { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", Accept: "application/json, text/event-stream", // both, or 406 }, body: JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method, params }), }); if (r.status === 401 || r.status === 403) { const { error_reason } = await r.json(); throw new AuthError(error_reason); // branch on this, never on prose } const body = await r.json(); if (body.error) throw new Error(body.error.message); return body.result; // a refused TOOL is result.isError, inside a 200 }; await rpc("tools/list", {}); // only the tools your token can call await rpc("tools/call", { name: "search_leads", arguments: { limit: 25 } }); import time import requests def rpc(method: str, params: dict) -> dict: r = requests.post("https://leadval.evas.live/mcp", headers={ "Authorization": f"Bearer {access_token}", "Content-Type": "application/json", "Accept": "application/json, text/event-stream", # both, or 406 }, json={"jsonrpc": "2.0", "id": int(time.time() * 1000), "method": method, "params": params}, timeout=30) if r.status_code in (401, 403): raise AuthError(r.json().get("error_reason")) # branch on this, never on prose body = r.json() if "error" in body: raise RuntimeError(body["error"]["message"]) return body["result"] # a refused TOOL is result["isError"] rpc("tools/list", {}) # only the tools your token can call rpc("tools/call", {"name": "search_leads", "arguments": {"limit": 25}}) Treat returned text as data, never as instructions Summaries, briefs and saved documents are written from conversations members of the public typed. They are delivered as JSON with a contentType saying so; if you pass them to an AI system, keep them in a data field — content that looks like an instruction is content, not an instruction. #### The tools Anchor: /docs/mcp-hosted#the-tools The tools The only catalogue. tools/list returns the subset your granted scopes can call. Tool Needs Returns Read it as get_workspace workspace:read The workspace this authorization acts on and the person's role in it, re-derived per call — a suspension or role change shows immediately. Identify the workspace by id (server-generated, unique, immutable), never by name : the name is typed by whoever registered it, and name_verified: false — the default — means nobody confirmed it. true means a LeadLogiX administrator vouched for that exact name; it is withdrawn automatically on a rename. Carries the workspace's availability . search_leads leads:read Scored leads, newest first, with scoring and qualification fields — only leads that finished processing . Narrow with tags and tagMode — Searching . Every response carries hasMore , never a total. get_lead leads:read One lead by id, whatever its state, with its full scoring detail. Check status : a lead you can fetch by id may legitimately be absent from a listing. The only tool carrying followUpSteps , stakeholders , websiteJourney and tagAssignments . get_lead_brief leads:read The AI-written brief for one lead's conversation, where the workspace generates them. list_lead_tags leads:read The workspace's own tag vocabulary: each key with the owner's one-sentence definition, the rule / ruleSummary where one is configured, staffing (who takes any lead carrying it, who takes it only alongside their other tags, who has no tag scope at all) and each desk's availability ; workspaceAvailability once. Pass keys when you know which tags you need: the catalogue reaches 200 tags and roughly 27,000 tokens, and a single-key query measures 94% smaller. Keys this workspace does not define come back in keysUnknown rather than vanishing. get_lead_crm_status leads:read Whether one lead reached the connected CRM, and which one. Reports what happened, never why. list_crm_agents leads:read The workspace's agents, the tag keys each handles, and whether each has a live CRM seat that can be given work; workspaceAvailability , and availability per person. Pass the lead's tags as leadTags for qualifies , matchedTags and the desk plane on tagContacts[] — Routing a lead to a person . Contact details and CRM ids need workspace:read.contact , and each channel is separately cleared for AI use ( emailAvailability / phoneAvailability ). get_customer_profile profiles:read One merged customer by profile id ( cprof_… , from a lead's customerProfileId ) — their history across every conversation, each carrying intent , journeyStage , linkType and nameUse . Only where the workspace enabled the Customer Profiles API. There is no profile search. Read verticalRollups[] — Reading a profile . identity_lookup identity:lookup Whether this workspace has spoken to this person before, asked mid-conversation : send the identifying signals you have, receive pointers — a lead id, and a profile id where conversations were merged — never customer data. The one tool that takes personal data in . Read nameUse ( PERMITTED / WITHHOLD , with nameUseReason ) on the row you act on — Reading the response . Only where the Identity Resolution API is enabled. submit_tool tools:write · writes Registers one of your tools with this workspace, so LeadLogiX reads its calls as structured evidence. Returns a submission id and status: "PENDING" . A proposal : nothing changes until a LeadLogiX administrator approves it, and you cannot approve your own. Idempotent on the contract — Submitting your own tools . verify_tool tools:write Whether the tool you are shipping still matches what was approved, and which field names and types differ if not — never a value. Read compared_against : on a tool still awaiting review it compares against what you submitted, so matches: true alone does not mean live. list_tools tools:write Your registrations for this workspace and where each one stands, with the reviewer's note on anything declined. memory_get memory:read One note this application saved earlier, under a key it chose. A key never written is { found: false } — an answer, not an error; do not retry it. Notes are private to this application and the person who authorized it. memory_list memory:read What this application has saved in one group — keys and sizes by default, contents on request. complete says whether that is everything; a full page is not the end of the list. memory_usage memory:read How much this application is holding for this person , by group, and the limits it is measured against. Call it when a save is refused for space. Never a workspace total. knowledge_search memory:read The nearest passages to a question in words — from LeadLogiX's product documentation, or from a collection this application wrote — each with a distance and a citable link. It always returns its nearest match, so the usual failure is an answer merely close : read distance before relying on one. A fault is a coded refusal, never an empty list. memory_put memory:write · writes Save one JSON value under a key. The same key again replaces it, so a retry is safe. Everything expires — ttlSeconds chooses when. Only where Application memory is enabled. memory_delete memory:write · writes Remove one note. Not there is { removed: false } , an answer rather than an error. knowledge_write memory:write · writes Save a document into a collection of this application's own, to be found later by meaning rather than by key. Long documents are split, not shortened; the same id again replaces every part at once. ⚠ Does not expire — a quota bounds it. LeadLogiX's documentation is read-only. knowledge_delete memory:write · writes Remove one saved document, every part of it. Not there is { removed: false } . file_list files:read What files this application saved for you in one collection — names, sizes and types. complete says whether that is everything. file_get files:read One file back, whole. Text comes back as text and anything else as base64, decided by the stored type rather than how it was sent. Never written is found: false . file_put files:write · writes Save one file. The same name again replaces it. ⚠ Files do not expire: delete what is no longer needed, because waiting does not free space. file_delete files:write · writes Remove one file — every stored version of it. Already gone is removed: false . A tool you cannot call is not advertised — and the schema is the contract tools/list is filtered by granted scope: a token holding only workspace:read sees exactly one tool, because an advertised tool that always refuses teaches a model to keep retrying it. Each tool arrives with its own JSON schema and description carrying arguments and response fields no page repeats — build against what tools/list returns from your own connection, and enumerate at runtime rather than hard-coding this table. Whether lead results include names, phones and emails depends on leads:read.contact ; each tool's description says which you are getting. #### Searching and filtering leads Anchor: /docs/mcp-hosted#searching-and-filtering-leads Searching and filtering leads Tags are per workspace, and an empty page is not an answer. Argument or field Read it as tags Tag keys , never display names — a name is a label an admin can rename; the key cannot change. Each workspace invents its own: reroof_now means what that workspace decided, and may not exist in the next one. Read list_lead_tags once per workspace first. A key the workspace has not defined matches nothing rather than erroring, so a stale key looks like “no leads”. tagMode ANY (default) or ALL . ALL is applied after the window is read, so it can return an empty page while matching leads sit just past the edge — the response then says so with hasMore: true and a note ; widen limit or send fewer tags. hasMore true : the list is a page, not a census — do not total it, average it, or answer “none match” from it. Only hasMore: false is complete for the filter you sent. A filter that cannot be applied Refused, not ignored: tags that is not an array of non-empty strings (a bare "urgent" is the usual slip), a tagMode other than ANY / ALL , or more than 30 keys is an error — a silently dropped filter would look exactly like one that matched the whole workspace. A lead missing from the listing search_leads leaves out leads that failed to process (no score, no verdict, no summary); get_lead returns any lead by id with its status . #### Routing a lead to a person Anchor: /docs/mcp-hosted#routing-a-lead-to-a-person Routing a lead to a person Who covers it, whether they can be given it, and whether anyone is there. Offering a visitor a real person is a join of two tools and one field. Find the area's tag with list_lead_tags ; ask list_crm_agents the narrow question with { leadTags: [""] } and keep those who qualify and are assignable; then check availability before you put anyone through. The three fail differently: a person whose CRM seat is disabled cannot be given the lead, and one with a live seat may still be asleep — the first two checks say nothing about the hour. Field Read it as leadTags (argument) Prefer it over tag : it answers qualifies directly instead of leaving you to apply tagMatchMode , and it is the argument that returns tagContacts[] — the desk plane, with that desk's hours. With tag you get neither. qualifies Whether this person covers this lead, by the same rule that governs which leads they can see. It answers WHO, never whether anyone is there. matchedTags Which of the lead's tags this person covers. tagMatchMode ANY — the usual setting: a rep tagged miami and tampa covers both. ALL : only leads carrying every one of their tags, so the same rep covers neither city alone. Matching on the array without the mode silently inverts the rule. unrestricted: true The person has no tags and handles everything — never evidence of territory coverage. assignableInAnyCrm Governs assigning a lead to own , not whether you may reach someone: a workspace with no CRM connected reports false for everyone and may be perfectly reachable by phone. crmChecked true : compared against the CRM, and no live seat is a real finding. false : not looked up , which is not “no seat”; uncheckedPeople counts them. connectedCrms , complete An empty connectedCrms means no CRM user list is held — not connected, or not pulled yet. complete: false means the response was truncated, so absence proves nothing. In all three cases say the check is incomplete rather than that nobody can help. availability On tagContacts[] for the desk, falling back to workspaceAvailability . CLOSED : do not transfer — offer a callback and, if effective.opensAt is present, say when they reopen. A no-answer state: claim nothing either way. Hours never withhold a contact. All eight states: Availability & hours . autoAssignByLlm: true with no rule A tag applied by AI reading the conversation — a judgement, not a tested fact. Say so when a promise rests on it: “it sounded like the Tampa area” is honest, “you are in our Tampa territory” is not. #### What a lead actually carries Anchor: /docs/mcp-hosted#what-a-lead-actually-carries What a lead actually carries Every key on the wire, with what it does NOT mean. A tool description carries only what changes behaviour; this is the reference. Field What it is Read it as id The lead id Rows carry id , never leadId . conversationId The conversation it came from Correlates with a session your agent may still be inside. score , qualityLabel The calibrated Lead Quality Index, and its grade suppressed Failed a hard integrity gate true is not a live opportunity : LeadLogiX neither sells nor bills it. Do not route it, offer a callback on it, or count it as pipeline. role Their part in the transaction The vocabulary is per line of business — buyer/seller/renter is real estate; life insurance uses a different set. Never enum it across lines. journeyStage Where they are in the journey Lower-case on the wire ( awareness / consideration / decision ); leads written before the vocabulary settled carry UPPER-CASE values that were not migrated — fold case before comparing. intentSentiment How they feel about transacting budgetAmount Stated budget Means whatever the line of business means by money — a purchase price on real estate, a monthly premium on life insurance. Read it with the processor mode; never sum or compare across lines. Paired with budgetCurrency . city , stateCode Resolved location stateCode: "XX" is “not resolved”, not a state. statisticalGrounding The cohort grounding Always null on search_leads , whatever the lead's real grounding — never report a listing row as ungrounded; call get_lead when it matters. tags Tag keys Join them to list_lead_tags for what each means in this workspace. customerProfileId The merged customer, when there is one Present on search_leads and get_lead when the authorization also holds profiles:read . Absence is normal — a first conversation has no profile — and it is also withheld for a profile that was superseded or lost every member, because get_customer_profile would refuse it. financing.* Financing position status (e.g. PRE_APPROVED ), mortgagePreApproved , payingCash — each boolean or null. contactability.* How reachable, and on what basis phoneStatus , emailStatus , carrierIdentityMatch , recordingDisclosed , and the consent fields. contactability.consentGranted Recorded consent null means unknown, not denied — decide from your own policy rather than reading it as a refusal. businessSummary The written summary of the conversation Free text: names removed without leads:read.contact , regulated content removed always — see below. targetProperties[] What they are looking at status , city , stateCode ; address needs leads:read.contact . Real-estate shaped; another line may carry none. contact.* First name, last name, email, phone Only with leads:read.contact . A scope-gated key you were not granted is absent , never an empty array — empty would claim “nobody configured one”. followUpSteps[] get_lead only The next actions, written from the conversation. Absent from a listing row, not empty. stakeholders[] get_lead only Everyone else who took part, with role and relationship ; only with leads:read.contact . websiteJourney[] get_lead only What they searched for and did before speaking to anyone, rebuilt from your agent's tool calls: eventType , toolName , turnIndex , direction , summary . tagAssignments[] get_lead only key , assignedAt , a rationale quoting the conversation, and source : DETERMINISTIC (a workspace rule was evaluated and matched — a tested fact), LLM (the AI judged it; confidence 0–1, and anything below the workspace's floor was never applied), MANUAL / AUTOMATION . Where a rule and its description differ, the rule is what happened. Redacted text reads [withheld] — strip it before a person sees it Free-text fields pass two independent controls. Personal names are removed unless your token carries the matching .contact scope; regulated content declared by the line of business — health information on life insurance, for example — is removed unconditionally , whatever scopes you hold. Where something was removed the literal token [withheld] is left in its place: a marker, not content. Strip it, or drop the sentence containing it. A whole field may instead come back null with a note, or as "omitted" — both mean “not available to you”, never “the lead has none”. #### How much you may store Anchor: /docs/mcp-hosted#how-much-you-may-store How much you may store Published numbers, so you can size an integration before you build it rather than meet a ceiling in production. Every allowance below belongs to one application, for one person, in one workspace . That is the unit a customer can revoke, so it is the unit everything is counted against — and it means another application in the same workspace, or the same application acting for a colleague, has its own separate allowance that yours cannot spend. What you store How much How long Notes — memory_put , a JSON value under a key you choose 64,000 characters each · 10,000 notes and 40,000,000 characters in total 30 days by default, up to a year if you ask for it Documents you search by meaning — knowledge_write 60,000 characters per document · 5,000 sections and 10,000,000 characters in total, across at most 25 collections ⚠ nothing expires — knowledge_delete is the only thing that frees space Files — file_put 128 KiB each · 1,000 files and 125 MiB in total ⚠ nothing expires — file_delete is the only thing that frees space Reaching one of these is a refusal you can read and act on, never a quietly shortened answer. The two that never expire are the ones worth watching: waiting does not free space, so an application that writes and never deletes will eventually stop being able to write. Ask rather than assume — a workspace can be given more These are the figures a workspace starts with, and LeadLogiX can raise them for a particular customer. memory_usage returns the limits your connection is actually measured against, alongside what you are holding, broken down by collection for all three. Read them from there rather than hard-coding what you see here, and a workspace that was tuned will never surprise you. #### How fast you may call Anchor: /docs/mcp-hosted#how-fast-you-may-call How fast you may call Every tool family has its own budget, counted per application, per person, per workspace — so staying under the read limit does not keep you under the others. Going over is 429 with Retry-After ; honour it. The numbers are on OAuth & MCP errors → Rate limits . #### Running the workspace's own automations Anchor: /docs/mcp-hosted#running-the-workspaces-own-automations Running the workspace's own automations The only part of the catalogue that differs between workspaces. Every tool above is the same for everyone. Automation tools are not: they are built from the workspace's own records at the moment you call tools/list , so two applications holding identical grants on different workspaces legitimately see different tools. Discover them, never hard-code them. Scope automations:run — and the workspace must have automation access enabled by LeadLogiX. Tool name automation__ . The namespace is reserved, so no built-in tool can occupy it. Description Written for a model, by the people who built the automation — it says what the automation does and when to call it. This is what your model should select on. Arguments Published as the tool's own inputSchema . Validated strictly : a number outside its bounds is rejected rather than clamped, an unknown key is rejected rather than ignored, and a required text field will not accept blanks. Result { ok: true, executionId, result? } . The result carries only the fields that automation declares — never its logs, and never anything it read along the way. An automation that declares no result returns none. Budget 60 automation calls per minute, separate from the 600 reads. Exhausting it is an HTTP 429 with Retry-After, not a tool error. Nothing is published by default, and a setting you configured always wins A workspace admin approving automations:run publishes nothing on its own — each automation is published to applications individually. And where an argument and a workspace setting name the same thing, the setting wins: an argument can never redirect an automation the workspace configured, which is why an address is not something you can pass. Every refusal carries a code and a retryable flag in structuredContent . Branch on the code; the sentence beside it is for a person reading a log. Code Retry? What happened INVALID_ARGUMENTS No Your call is wrong. The message names the field and the rule it broke. AUTOMATION_NOT_CALLABLE No Not published to applications. Nothing you send will change that — the workspace has to ask us to publish it. AUTOMATION_NOT_SHARED No Published, but switched off for this workspace. AUTOMATION_UNAVAILABLE Yes A gate could not be READ. A transient fault refusing safely — not a decision about you. WORKSPACE_SUSPENDED No The workspace is suspended or being deleted. CONCURRENCY_LIMIT Yes The workspace is already running its share. ⚠ The run never started — wait retryAfterSeconds and send it again. TIMEOUT Yes The run exceeded the platform ceiling. It may not next time. AUTOMATION_FAILED No It ran and threw. The same input will throw again. RESULT_TOO_LARGE No ⚠ The automation ran . Only its answer could not be returned, so retrying does the work twice. A budget refusal is not in that table Exhausting the 60-per-minute automation allowance is answered by the transport before any tool runs — HTTP 429 with Retry-After , the same answer as every other budget here. There is no tool-level duplicate of it. Some automations can act — post to a channel, call another system, send a message. That is switched off per automation and off by default: while it is off the run can read and answer but cannot reach anything outside itself. An automation that is allowed to act may also promise to do something later , and that is worth understanding before you call one. What an automation may do while it answers you What you see Search the product documentation by meaning, so the answer is grounded in the manual rather than recalled Whatever it declares as its result — typically the answer plus the section it came from. Keep a short transcript under an id it chooses, usually one you pass in Nothing directly. Later calls sharing that id can build on the earlier ones instead of starting cold. Queue work to be carried out later — a follow-up minutes or days from now The call returns as soon as the run finishes. The follow-up is not in the response and nothing calls you back; it appears on the workspace's own Automations history when it happens. You cannot cancel it. What the workspace ships for this surface today Seven, in two groups that behave very differently. Always discover the real list from tools/list — it is built per workspace and this is only what the platform publishes to everyone. Answers — they read nothing, store nothing and send nothing What it tells you automation__script_global_check_email Is this address well formed, and is its domain one that can never receive mail. ⚠ Nothing contacts a mail server, so a well-formed address is not a mailbox that exists. automation__script_global_check_phone Is this number dialable, its +country form, and what kind of line. ⚠ FIXED_LINE_OR_MOBILE means that country's plan does not separate them — not that a text will arrive. automation__script_global_check_name Do two names refer to one person — the same graded answer LeadLogiX's own identity resolution gives. ⚠ NEAR is evidence, never a decision. Acts — these send messages or spend money What it does …notify_slack , …notify_teams , …notify_webhook Sends the workspace's new high-scoring leads to the channel or endpoint it configured, now, instead of waiting for the next scheduled run. They take no arguments at all : which leads qualify and where they go are the workspace's settings, so you cannot aim one. Each lead is sent at most once ever, so a second call normally reports sent: 0 — that means nothing was new, not that it failed. …promote_for_valuation Sends one record that was captured without a valuation through the full engine, so it comes back with a score, a price and tags. You name the record with leadId ; you do not choose the line of business, because that is the billing decision and the workspace owns it. The second group costs the customer something A promotion is a full engine run and is billed on a pay-per-lead workspace; a notification is a message real people read. Both are ordinary, both are what the customer switched them on for — but do not call one to find out what it does, and do not retry one on a timeout without checking first. A record can be promoted once, and a refusal saying so is an answer, not a failure. #### What an automation may keep, and for how long Anchor: /docs/mcp-hosted#what-an-automation-may-keep-and-for-how-long What an automation may keep, and for how long Not a database you can reach — but what an automation keeps is still the workspace's data. None of this is a tool you can call. There is no way to read or write a workspace's stored values over this API, and no tool exposes one — the only things you can call are the automations themselves. It matters for a different reason: an automation you invoke may keep something, and the customer whose workspace it is has a right to know what, and for how long. What it may keep How long Ceiling A stored value — a cursor, a cache, something remembered between runs 7 days by default, 30 at most 64,000 characters per value; 1,000 values and 4,000,000 characters per workspace A conversation's turns — kept under an id, usually one you passed in 24 hours by default, 7 days at most 16,000 characters per turn; 1,000 turns per conversation; 500 conversations per workspace A promise to do something later — a follow-up minutes or days out runs up to 90 days out; gives up after 30 days by default, 180 at most 8,000 characters of detail; 500 open promises per workspace Three things worth knowing before you pass anything in An id you choose is an id you share. If you pass a conversation id, what that automation keeps is filed under it, and a later call using the same id can build on it. Choose one that belongs to the conversation, not to a person. It expires on its own, and you cannot cancel it. Nothing here has a delete you can call. A stored value falls away on its own clock, and a promised follow-up will happen — it is not in your response, nothing calls you back, and it appears on the workspace's own Automations history when it does. It is the workspace's, not yours. Everything an automation keeps belongs to that workspace, is reachable only by that workspace's own automations, and goes with it when the workspace is deleted. These are the ceilings a workspace starts with, and a LeadLogiX administrator can change them per workspace. An automation that reaches one is refused rather than truncated, so what you see is an error and never a quietly shortened answer. ### leadval-mcp (npm) URL: /docs/mcp-package Developer Reference MCP — leadval-mcp (npm) A local MCP server that runs beside your own agent, authenticated with your ingestion key. It does the conversational half of the job: tell LeadLogiX a conversation finished, poll it, and ask whether the caller is someone you already know. The hosted server does the other half — reading scored leads and customer histories back. #### How it differs from the hosted server Anchor: /docs/mcp-package#how-it-differs-from-the-hosted-server How it differs from the hosted server It runs on your machine over stdio with your own ingestion key and acts on your own workspace; the hosted server runs at LeadLogiX and reads a customer's workspace over OAuth. The full comparison is on MCP → Hosted server . They are two halves of one flow: a returning-customer agent asks leadval_identity_lookup here, then reads the history back with the hosted server's get_customer_profile or get_lead — Recognise a returning customer . Install it in one line npx -y leadval-mcp resolves — the package is on npm and the block below uses it. Nothing to clone and nothing to build. If you would rather run a local checkout, point "command": "node" at the absolute path of packages/leadval-mcp/dist/index.js instead. Do not ship this to your customers It authenticates with an ingestion key, which is a workspace-wide credential. Asking a customer to paste one into their AI client hands you — and their client — more access than any OAuth grant would, with no consent screen and no way for them to see it later. For anything other people install, use the hosted server. #### Configure it Anchor: /docs/mcp-package#configure-it Configure it Two environment variables: LEADVAL_BASE_URL (your LeadLogiX host) and LEADVAL_API_KEY . Both are required; the server reports a clear configuration error rather than failing per call. claude_desktop_config.json { "mcpServers": { "leadval": { "command": "npx", "args": ["-y", "leadval-mcp"], "env": { "LEADVAL_BASE_URL": "https://your-leadval-host", "LEADVAL_API_KEY": "val_key__…" } } } } That block goes in your MCP client's configuration file — Claude Desktop's claude_desktop_config.json , or the equivalent in Cursor and other clients that speak stdio MCP. #### The four tools Anchor: /docs/mcp-package#the-four-tools The four tools Tool What it does leadval_notify_session Tells LeadLogiX a finished conversation is ready to score. Idempotent — re-notifying the same sessionId never re-queues. leadval_get_session_status Polls conformance and processing state. Never returns PII, score or price, so it is safe to log. leadval_process_and_wait Notifies, then polls until the session reaches a terminal state or the timeout expires. The convenience path while you are developing. leadval_identity_lookup Asks, mid-conversation , whether this workspace has spoken to this person before. Returns pointers — a lead id, and a customer profile id where their conversations have been merged — never customer data. ⚠ Read nameUse on the row you act on: PERMITTED or WITHHOLD , with nameUseReason . It is the same field get_customer_profile returns per conversation, so one rule covers both. corroboratedBy is evidence and tone, never permission. It notifies; it does not upload The conversation must already be in your workspace's raw lead storage under that sessionId , written by your AI Agent. These tools are the same notify-then-poll flow as the Quickstart , wrapped so an assistant can drive it. It submits real sessions: sandbox runs need a login handshake, so test in the API Playground . ## Reference & tools ### API reference URL: /docs/api Developer Reference API Reference Every endpoint the API publishes, generated from the OpenAPI specification so this page and the spec cannot disagree. Ingestion endpoints authenticate with an X-API-Key; key management is admin-only and uses your LeadLogiX login session. #### Every endpoint Anchor: /docs/api#every-endpoint Every endpoint Generated from the spec, like everything on this page. Method Path What it does Auth Source: openapi.yaml ( ) — the same file your codegen reads. There are no SDKs; generate a client from it ( how ). Every status code and code these endpoints return, and their rate limits, are on Ingestion errors & limits . #### Which group do you need? Anchor: /docs/api#which-group-do-you-need Which group do you need? Five groups. Most integrations touch one. Group Reach for it when The guide that explains it Ingestion You want conversations scored. This is the one almost everybody needs — notify, then poll. Ingest a session Identity You want to know whether a caller is already known, or whether two sessions are the same person — and to follow the pointers back. Recognise a returning customer for the live case; Identity & Profiles API for the reference Profiles You want to assert that conversations belong to one person for a reason LeadLogiX cannot see. Rarely needed — profiles are built automatically. Identity & Profiles API Keys Managing ingestion keys from your own tooling. Admin login session, not an API key. API Playground for the manual path Notices Contract changes announced ahead of time, machine-readable. Poll it in CI. Changelog Reading scored leads is not REST: an OAuth application uses MCP → Hosted server . GET /api/leads , if you find it, is the dashboard's own endpoint and refuses an ingestion key. #### Ingestion Anchor: /docs/api#ingestion Ingestion Notify LeadLogiX a session is ready, then poll its status. POST /api/v1/sessions — Notify that a session is ready to score. Queues a finished session for processing. Returns 202 immediately. Re-notifying the same sessionId is idempotent — it never re-queues. Add `X-LeadVal-Sandbox: true` (plus your login token as a Bearer) to run in the isolated sandbox namespace. ⚠ BEFORE YOU NOTIFY: WRITE THE CONVERSATION TO THE DOCUMENT STORE **ONCE**, NOT IN CHUNKS AS THE SESSION RUNS. LeadLogiX reads the assembled document after this call, so anything the store did not keep is simply absent from the lead — and the store drops the messages of a write that arrives while it is still indexing the previous one, while still answering 200. Measured on synthetic documents: one write carrying 8 messages keeps 8; one write every 12 s keeps 8; one every 3 s keeps 4–5; writes back-to-back keep 1–2 — present on every run, varying with load. A connector cannot detect the loss from any response, so buffer the transcript, send it in a single write with the complete metadata block, and then notify. If you must stream, leave at least 12 seconds between writes and treat that as a floor with no margin. ⚠ TWO RELATED STORE BEHAVIOURS WITH THE SAME REMEDY. A write carrying only metadata and no messages is refused with HTTP 400, so a final "end of session" write that carries no speech stores nothing. And a metadata key written after the document reports `done` becomes visible and is then discarded once re-indexing settles — so waiting for a late key is waiting for something that may be taken back. The single write at session end avoids all three. ⚠ THE 202 IS A QUEUE HAND-OFF, NOT AN ACCEPTANCE OF THE DOCUMENT. Conformance is judged later, against the document as assembled. Poll the status endpoint; a session missing a required metadata key surfaces it there within seconds as `processing.awaitingMandatoryKeys` long before any verdict exists. X-LeadVal-Sandbox (header) Set `true` to run in the isolated sandbox namespace. ⚠ SANDBOX NEEDS **TWO** CREDENTIALS, WHICH IS UNUSUAL AND EASY TO MISS. The request must carry the ingestion key in `X-API-Key` **and** a signed-in person's LeadLogiX login session token in `Authorization: Bearer `. The token's user must belong to the same workspace as the key. Without the token the call is `401 UNAUTHENTICATED`; with a token from another workspace it is `403 TENANT_MISMATCH`. That is deliberate: the sandbox executes the real pipeline, so it is gated on a human session rather than on a key alone. Unattended automation therefore cannot use sandbox mode — omit the header and run for real, or drive the sandbox from the API Playground. { "sessionId": "conv_9F3A21", "processor": 2 } { "status": "ACCEPTED", "sessionId": "string", "alreadySubmitted": false, "processing": "…" } GET /api/v1/sessions/{sessionId}/status — Poll processing status and conformance. Returns processing state and conformance notes. Never returns PII, score, or price — safe to log or surface. Poll until processing.state is terminal (PROCESSED, DEGRADED, or FAILED). ⚠ `processing.awaitingMandatoryKeys` IS A DIAGNOSIS, NOT A VERDICT. While the source conversation is still being assembled, a document whose content has been written but whose metadata has not yet landed publishes the missing required keys here, under `missingMandatoryKeys` and `mandatoryKeys` — the SAME two key names the terminal `rejection` object uses. A client that pattern-matches on `missingMandatoryKeys` without checking WHICH object it is in will abandon a session that is about to succeed. The session is still QUEUED; keep polling. The keys usually arrive. ⚠ AND THAT WAIT IS BOUNDED IN THE HUNDREDS OF SECONDS, NOT IN SECONDS. A session whose required metadata never appears is held for roughly 200 seconds across four re-reads before it is rejected, so a genuinely non-conformant session now reaches its terminal state minutes after notify rather than immediately. `awaitingMandatoryKeys` appears within seconds and names the key, which is why it exists: it is the fast half of a slow verdict. ⚠ `processing.deficiencies` NAMES WHAT FAILED, ON DEGRADED AND FAILED. A sentence such as "Lead degraded processing failed after 5 reprocessing attempt(s)" does not say whether the document was deficient or our pipeline was, so the stage codes are reported structurally: CLEARING_NARRATIVE_EXTRACTION_FAILED, IDENTITY_RESOLUTION_FAILED, COREFERENCE_TRANSCRIPT_TURN_LOSS and their siblings. Branch on the codes, never on the sentence. An empty array means the failure was not attributable to a named stage — it does not mean nothing was wrong. ⚠ A FAILED SESSION IS RETAINED FOR MINUTES AND THEN DELETED, AFTER WHICH THIS ENDPOINT ANSWERS 404. A failure record is diagnostic, so it carries a short deletion deadline (platform setting `analyticsBbnSettings.purgeRetentionFailureMinutes`, seeded 60) rather than the multi-year retention a processed lead gets, and a cleanup pass that runs every five minutes deletes it once that deadline passes. From then on the status call returns `404 SESSION_NOT_FOUND` — the identical code to "this workspace never notified that session", because nothing remains to tell them apart. So a 404 is NOT proof of non-delivery, and a rejection you did not read inside the window cannot be re-derived here later. Record `rejection` when you first see it. That retention is a live setting an operator can change with no deploy; changes are announced on `GET /api/v1/notices`. sessionId (path, required) X-LeadVal-Sandbox (header) Set `true` to run in the isolated sandbox namespace. ⚠ SANDBOX NEEDS **TWO** CREDENTIALS, WHICH IS UNUSUAL AND EASY TO MISS. The request must carry the ingestion key in `X-API-Key` **and** a signed-in person's LeadLogiX login session token in `Authorization: Bearer `. The token's user must belong to the same workspace as the key. Without the token the call is `401 UNAUTHENTICATED`; with a token from another workspace it is `403 TENANT_MISMATCH`. That is deliberate: the sandbox executes the real pipeline, so it is gated on a human session rather than on a key alone. Unattended automation therefore cannot use sandbox mode — omit the header and run for real, or drive the sandbox from the API Playground. { "sessionId": "string", "ingestedAt": [ "string", "null" ], "processorMode": [ "integer", "null" ], "processorLabel": "REALESTATE", "conformance": { "level": "string", "reason": "string", "evaluatedAt": [ "string", "null" ], "notes": [ { "code": [ "string", "null" ], "path": [ "string", "null" ], "message": [ "string", "null" ] } ] }, "processing": { "state": "string", "reprocessingCandidate": false, "waitingForSource": false, "awaitingMandatoryKeys": { "missingMandatoryKeys": [ "string" ], "mandatoryKeys": [ "string" ], "message": "string" }, "lastAwaitingMandatoryKeys": { "missingMandatoryKeys": [ "string" ], "mandatoryKeys": [ "string" ], "observedAt": [ "string", "null" ], "message": "string" }, "errorMessage": [ "string", "null" ], "deficiencies": [ "string" ], "rejection": { "code": "INGEST_CONTRACT_VIOLATION", "missingMandatoryKeys": [ "string" ], "mandatoryKeys": [ "string" ], "rejectedAt": [ "string", "null" ], "message": [ "string", "null" ] } } } #### Identity Anchor: /docs/api#identity Identity Advanced — decide whether a session belongs to a returning visitor. POST /api/v1/identity/lookup — Does this workspace already know this person?. For an AI agent in the first seconds of a live conversation. Send whatever identifying signals you have — at minimum a device fingerprint, plus an email, phone or name if the conversation has surfaced one — and LeadLogiX searches your own processed leads for the same person. Unlike the two `resolve` endpoints, you do NOT supply candidates: this searches for them. It returns POINTERS, never customer data — a lead id per match, and a customer profile id where one exists. Follow them with `GET /api/v1/leads/{leadId}/context` and `GET /api/v1/profiles/{profileId}`, with the same ingestion key — an API key is not an OAuth token and cannot be exchanged for one, so the MCP tools are not reachable with it. If you DO hold an OAuth token, the `get_lead` and `get_customer_profile` tools are the same composition behind a different door. A preliminary answer by design. Device signals alone can never produce `EXACT_MATCH`: without a matching name, email, phone or voice signature the verdict is clamped to `POSSIBLE_MATCH`. ⚠ Read `nameUse` on the match row you are acting on — `PERMITTED` or `WITHHOLD` — rather than deriving permission from `verdict`, `band` or `corroboratedBy`. `nameUseReason` says why it was withheld. The same field with the same two values appears on every conversation returned by the `get_customer_profile` tool, so one rule covers both surfaces. ⚠ The TOP-LEVEL `constraintsFired` unions every candidate scored, near misses included, so it can name a clamp that a DIFFERENT person's row fired — a clean `EXACT_MATCH` returned beside `SHARED_CONTACT_NO_NAME` is that, not a contradiction. Each match row carries its own `constraintsFired`. `decision.activeGuards` lists the protections the workspace's tuning can fire at all. `customerProfileId` is commonly `null`: profiles exist only once two or more conversations have been merged, so a first-time or never-merged customer has none. The `leadId` is always present, so there is always something to fetch. Access is OFF by default and is enabled by LeadLogiX per workspace on request — until then, calls return `403 IDENTITY_API_NOT_ENABLED`. Included in your plan; not separately billed. { "verdict": "string", "confidence": [ "number", "null" ], "constraintsFired": [ "string" ], "catalogVersion": [ "string", "null" ], "resolutionProfileId": [ "string", "null" ], "candidatesConsidered": 0, "truncated": false, "matches": [ { "leadId": "string", "conversationId": "string", "customerProfileId": [ "string", "null" ], "processorMode": [ "integer", "null" ], "band": [ "string", "null" ], "posterior": [ "number", "null" ], "lastSeenAt": [ "string", "null" ], "corroboratedBy": [ "string" ], "nameUse": "string", "nameUseReason": [ "string", "null" ], "constraintsFired": [ "string" ], "channels": [ { "id": "string", "result": "string" } ] } ], "nearMisses": [ { "leadId": "string", "conversationId": "string", "customerProfileId": [ "string", "null" ], "processorMode": [ "integer", "null" ], "band": [ "string", "null" ], "posterior": [ "number", "null" ], "lastSeenAt": [ "string", "null" ], "corroboratedBy": [ "string" ], "nameUse": "string", "nameUseReason": [ "string", "null" ], "constraintsFired": [ "string" ], "channels": [ { "id": "string", "result": "string" } ] } ], "nearMissesTruncated": false, "decision": { "band": [ "string", "null" ], "sufficiency": [ "object", "null" ], "clamps": [ "string" ], "resolutionProfile": [ "string", "null" ] }, "evidenceParsed": [ "string" ], "retrieval": { "tokensSupplied": 0, "tokensQueried": 0, "tokensCapped": false, "candidatesConsidered": 0, "truncated": false } } GET /api/v1/leads/{leadId}/context — Follow a lookup pointer to the lead behind it. The return leg of `identity/lookup`. That endpoint answers with POINTERS — a `leadId`, sometimes a `customerProfileId` — and this is how a caller holding an ingestion key dereferences one. An OAuth caller uses the `get_lead` MCP tool instead; both compose through the same functions, so the pay-per-lead paywall, the role filter and the personal-data scrub behave identically whichever credential you hold. Returns only YOUR workspace's leads: the workspace comes from the key and is re-checked against the document, so a lead id from anywhere else is refused rather than read. Gated on the same Identity Resolution grant that let you obtain the pointer. ⚠ Read `contactAvailability` before saying anything about the person. `NOT_RECORDED` is the only value that means "we do not have their details". `WITHHELD_UNPURCHASED` means this workspace HAS them and has not bought this lead yet — offer to unlock it rather than denying knowledge of a returning customer. `AVAILABLE` means the contact block is populated. leadId (path, required) { "lead": "object" } GET /api/v1/profiles/{profileId} — Follow a lookup pointer to the merged customer behind it. The profile half of the return leg. Takes the `customerProfileId` an `identity/lookup` match carried — always `cprof_…` — and returns that person's history across every conversation LeadLogiX has linked to them. ⚠ Three different things get called a "profile id" and passing the wrong one is answered by name rather than as an ordinary not-found: `cprof_…` is the merged CUSTOMER and the only one this takes; `cprofile_…` is an internal tag no API returns; and a resolution profile (`forensic`, `preliminary`) is a scoring catalog, not a person. A bare 404 for those would read as "this customer has no profile", which is the opposite conclusion. A profile whose every member conversation has been deleted is `404` rather than an empty success: it exists and has nothing left to say. A profile that was MERGED into another is not served either. The answer is `404` with code `PROFILE_MERGED` and a `mergedInto` field carrying the `cprof_…` id of the profile it was merged into — the same person, whose history now lives there; ask again with that id. Any other profile that is not currently available answers exactly like an unknown id. profileId (path, required) { "profile": "object" } POST /api/v1/identity/resolve — Lightweight returning-visitor check. Compares a current session against candidate prior sessions and returns a match verdict (same person?), a confidence, and a decision band. Stateless — you supply the candidates. The detailed `evidence` schema is provided by EVAS on request (support@evas.live). Most integrations don't need this — the standard pipeline already recognizes returning visitors. Access is OFF by default and is enabled by LeadLogiX per workspace on request — until then, calls return `403 IDENTITY_API_NOT_ENABLED`. { "advisory": "string" } POST /api/v2/identity/resolve — Full returning-visitor resolution (up to 20 candidates). A more thorough resolution than v1. Supply the current session plus up to 20 candidates. Same auth and error model as v1. Evidence schema via EVAS. Needs the same per-workspace access grant as v1 (`403 IDENTITY_API_NOT_ENABLED` until LeadLogiX enables it). #### Profiles Anchor: /docs/api#profiles Profiles Advanced — merge conversations into a customer profile (async, off by default). POST /api/v1/profiles/merge — Merge conversations into a customer profile (async). Submits a new conversation plus the earlier conversations you believe belong to the same person. Zero-trust: you pass only conversation IDs (never internal tags or an asserted result) — the identity engine re-fetches and re-verifies every pair, merging only what the evidence confirms. Returns `202` with a `requestId` and a `statusUrl` to poll. Most integrations don't need this — the standard pipeline builds Customer Profiles on its own. Access is OFF by default and is enabled by LeadLogiX per workspace on request — until then, calls return `403 PROFILES_API_NOT_ENABLED`. Only conversations that were scored for a line of business can join a profile. A conversation that was processed for identity only, or for automation only, produces no line-of-business record and is refused. ⚠ WHICH conversation is ineligible decides what you get back, and this paragraph used to describe only one of the two. A **candidate** is refused softly: the request is accepted, and that conversation is reported in the status response with `action: NOT_ELIGIBLE` and `reason: PIPELINE_DEPTH_MODE`. The **anchor** — the `conversationId` the merge is founded on — cannot be refused softly, because with no eligible anchor there is no profile to found and therefore no status document to report into: the call answers `400 PROFILE_ANCHOR_INELIGIBLE` and says so. Before 2026-09-10 it answered `400 VALIDATION_ERROR` with a message about the candidate array, which was the wrong array to go looking at. { "requestId": "string", "status": "QUEUED", "statusUrl": "string" } GET /api/v1/profiles/merge/{requestId}/status — Poll a profile-merge outcome. Returns the request's status (`QUEUED` → `RUNNING` → `DONE`, or `REJECTED`). On completion it returns a safe profile reference — a public profile id and a live dashboard link — plus per-conversation outcomes. `DONE` means the merge completed; the AI-written summary builds asynchronously, so `summaryState` tells you whether it's `READY` yet and the link always shows the current state. A request that doesn't exist or belongs to another workspace returns the same `404` (no cross-workspace enumeration). requestId (path, required) { "requestId": "string", "status": "DONE", "profile": [ "object", "null" ], "memberOutcomes": [ { "conversationId": "string", "action": "string", "linkType": [ "string", "null" ], "posterior": [ "number", "null" ], "reason": [ "string", "null" ] } ] } #### Keys Anchor: /docs/api#keys Keys Mint and manage ingestion keys (admin login, not an API key). POST /api/tenant/keys/generate — Mint a new ingestion API key (admin). Authenticated with your admin **login session** token (Bearer), NOT an ingestion key — you don't need a key to create one. Easiest in Settings → API Playground. The value is shown once. { "generatedApiKey": "val_key__…" } DELETE /api/tenant/keys/{keyId} — Revoke a key immediately (admin). `keyId` is the key's id (its hash) as shown in the Playground — not the cleartext value. keyId (path, required) POST /api/tenant/keys/{keyId}/status — Activate or deactivate a key (admin). keyId (path, required) #### Notices Anchor: /docs/api#notices Notices What changed in this contract, and when. No authentication. GET /api/v1/notices — Contract change notices, newest first. Every change that alters what your code must send, or what it can expect back. ⚠ NO AUTHENTICATION. It carries strictly less than `openapi.yaml`, which is also public, and requiring a credential to answer "did anything change" is backwards for the question you most need to answer before you have one. ⚠ READ `revisionStatus`, NOT `revision`. Marking a fix with a deployed revision id is our answer to "shipped is not deployed" — but the changes that hurt most are DATA: a mandatory ingestion key added to the live `sla_signals` registry, a calibration enforcement flip. Neither has a revision to cite, and both change the contract. "No deploy required" is a reason a change needs MORE announcement, not less. This endpoint exists for that class — which is exactly why `revision: null` could not be the field to read: it meant BOTH "there is no revision and there never will be" and "this is code that has not shipped yet", and a caller polling "is it live?" must wait on the second and never on the first. `revisionStatus` separates them, in four values: · `DEPLOYED` — `revision` names the revision this shipped on. · `PENDING` — merged, not yet deployed. A later entry will name the revision. ⚠ **`PENDING` CAN LAG REALITY BY ONE DEPLOY, STRUCTURALLY.** An entry naming a revision can only be written once that revision exists, so it always ships in the NEXT one — which means a change can be LIVE while its entry still reads `PENDING`. Resolve it without asking us: compare `servingRevision` against the revision named in the most recent `DEPLOYED` entry, and if `servingRevision` is newer, anything still marked `PENDING` between them may already be live. A property of an append-only log, not a lapse. · `NOT_APPLICABLE` — a DATA change. There is no revision and there never will be. Stop asking. · `UNDECLARED` — this log did not say. Treat it as UNKNOWN, never as `NOT_APPLICABLE`. Entries written before the field existed carry it. ⚠ `NOT_APPLICABLE` and `UNDECLARED` are trivially confusable and mean opposite things to a poller: "never, stop waiting" versus "unknown, keep asking". If you branch on one value only, branch on `DEPLOYED`. ⚠ `servingRevision` on the envelope is the revision ACTUALLY ANSWERING THIS REQUEST. Compare it against a notice's own `revision` to tell "merged" from "live" yourself, rather than taking our word for it. It is reported on the 503 branch too, because a build fault is only actionable if you know which build. ⚠ `notices[0]` IS NOT THE NEWEST ENTRY. Undated standing sections lead the list, so index 0 is normally the standing-commitments preamble, whose `date`, `class`, `actionRequired` and `revision` are all null. Select on `date != null` before taking a first element. ⚠ A MISSING ARTIFACT ANSWERS 503, NEVER AN EMPTY LIST. "No notices" and "the build did not produce the file" are different answers, and a CI job diffing this must not read a build fault as "nothing changed". Cached for 300 seconds. The human-readable original is `docs/INTEGRATOR_NOTICES.md`, which is NOT served — that is why this endpoint exists. { "success": true, "count": 49, "source": "docs/INTEGRATOR_NOTICES.md", "servingRevision": "leadval-00284-9fh", "notices": [ { "date": [ "string", "null" ], "title": "string", "class": "additive, non-breaking", "actionRequired": "none", "revision": "leadval-00276-tzh", "revisionStatus": "DEPLOYED", "body": "string" } ] } ### Availability & hours URL: /docs/availability Developer Reference Availability & hours Knowing who handles a lead is only half a routing decision: qualifies tells you who covers it, and says nothing about whether anybody is at their desk. Every workspace, desk and person therefore carries an availability block — on get_workspace, list_lead_tags and list_crm_agents. Three planes, eight states, and one rule that surprises people. #### The availability block Anchor: /docs/availability#the-availability-block The availability block The same shape on all three planes: workspace, desk, person. The shape is the same on all three planes. sources is always present and never empty; effective is a convenience derived from it, and is absent — not null — when no source reached a reading, because inventing one would look like a measurement. Key Meaning effective.state OPEN or CLOSED . Absent block means nothing concluded effective.basis Which sources entry it came from — always one that is present effective.timezone The zone the instants below belong to. ⚠ Render with this — “reopens at 13:00Z” is not a sentence you can say to a caller effective.opensAt When the next opening begins. ⚠ Can be null even on a real reading — no opening within eight days effective.closesAt When the current opening ends. null when the desk never closes sources[].source WHAT KIND of answer: WORKSPACE_HOURS , DESK_HOURS , BUSINESS_HOURS , OPERATING_HOURS , WORKING_HOURS , LIVE_STATUS sources[].origin WHOSE SYSTEM said so: LEADVAL , SALESFORCE , HUBSPOT . You need both halves to judge an answer sources[].timezone The zone that entry was computed in. null on a no-answer sources[].asOf When the underlying data was read. null for hours held in LeadLogiX, which are read live sources[].connectionId WHICH connection answered. Two orgs of one product are two entries you can tell apart — source and origin alone do not separate them. null for LEADVAL sources[].connectionLabel A name for that connection you can say out loud, where the id is only for telling two apart sources[].describes WHICH SETTING this entry is, in words. Always present, never null — including on a real reading, whose note is null. ⚠ Choosing which source to act on is your decision; this is how you know what you are choosing between. It names the screen for a LeadLogiX setting (“set in LeadLogiX on Company Profile”) and the connection’s own label for a CRM (“set in HubSpot”). Two pairs are invisible to source or origin alone: WORKING_HOURS arrives from BOTH LEADVAL and HUBSPOT , and BUSINESS_HOURS vs OPERATING_HOURS are two different records inside one Salesforce org sources[].inheritedFrom The parent workspace id when a sub-workspace inherited this. Inheritance is one hop sources[].note A sentence you can relay to a caller. Present on every no-answer workspaceAvailability The workspace plane, once per response, on list_lead_tags and list_crm_agents Hours inform. They never gate — a closed desk returns the same contact details A closed desk returns byte-identical contact details to an open one, and that is deliberate. One address serves two different jobs: transferring a live caller (wrong at nine in the evening) and sending a message that will be read when the desk next opens (exactly right at nine in the evening). Which one fits is your decision, so we never make it for you by withholding the address. If hours could hide a contact, the after-hours enquiry — the one nobody was there to take — is the one your agent would silently drop. #### The eight states Anchor: /docs/availability#the-eight-states The eight states Two carry a reading. Five mean nobody can say — and each names a different fix. State Means What to do OPEN Answered now Transfer, or read the number out. Use the email freely CLOSED Not answered now Do not transfer. Offer a callback and, if opensAt is present, say when they reopen. ⚠ Still send the email NOT_CONFIGURED Nobody has set hours here No claim either way. ⚠ Never say “they are closed” NO_DATA We hold no hours from that system As above. Connect it, or schedule the pull RECONNECT_REQUIRED Connected, but not permitted to read hours As above. The workspace must reconnect that CRM NOT_SUPPORTED That system cannot answer at all As above, and stop asking it STALE What we hold is not usable — a failed read, or a stored schedule that will not parse Treat presence as unknown: offer, do not promise. note says which WITHHELD_NO_PERMISSION Person plane only: you lack workspace:read.contact Says nothing about whether hours exist. Request the scope A no-answer is never a CLOSED A workspace that never filled in its opening hours is not shut. Five of the eight states mean “we cannot say”, and they are separate because each has a different remedy — collapsing them into “closed” tells a caller something nobody claimed. #### Two rules you must not re-derive Anchor: /docs/availability#two-rules-you-must-not-re-derive Two rules you must not re-derive Both are legal, and both look like mistakes. State is computed server-side in the source’s own zone, daylight saving included. If you are ever tempted to recompute it from raw times: an opening time equal to its closing time means open twenty-four hours , not a zero-length window — it is how Salesforce expresses its 24×7 default. And a closing time earlier than its opening time crosses midnight : a 22:00–06:00 desk is open all night, and the carry-over belongs to the previous day. #### Which sources are emitted Anchor: /docs/availability#which-sources-are-emitted Which sources are emitted What is emitted today Emitted today: WORKSPACE_HOURS , DESK_HOURS , WORKING_HOURS , BUSINESS_HOURS , OPERATING_HOURS , LIVE_STATUS . Every source in the vocabulary now carries a real reading, so there is nothing left to wait for. By origin: WORKSPACE_HOURS and DESK_HOURS from LEADVAL ; WORKING_HOURS from both LEADVAL and HUBSPOT ; BUSINESS_HOURS and OPERATING_HOURS from SALESFORCE ; LIVE_STATUS from HUBSPOT . ⚠ BUSINESS_HOURS and OPERATING_HOURS are both SALESFORCE and are not the same thing — the first is the organisation’s default support hours and can never describe one person, the second is that person’s own Field Service schedule. ⚠ One source can arrive from more than one origin — WORKING_HOURS now does — so branch on the PAIR, never on source alone. ⚠ LIVE_STATUS is a REPORTED flag rather than a schedule: OPEN when the person is marked available for assignment, CLOSED when they have marked themselves away. Its timezone is null because no zone was involved in working it out, so weigh it by asOf — an hour-old “available” may simply be stale. It appears only on the person plane, only for a person that system knows, and only for a caller holding workspace:read.contact . ⚠ Several systems may answer and LeadLogiX does NOT reconcile them — two entries disagreeing is a normal payload, and which to trust is your judgement. effective is LeadLogiX’s own reading only; basis never names a CRM entry, so its absence means we hold no hours rather than that nobody knows. sources[] order is stable presentation, not a ranking. And a CRM’s untouched factory default reports NOT_CONFIGURED , never OPEN . ### AI & tooling URL: /docs/ai-tooling Developer Reference AI & tooling Machine-readable descriptions of this API, for codegen and for the AI that writes your integration. There are no SDKs, by design: the OpenAPI document is the client contract, and you generate from it. #### Machine-readable API (OpenAPI) Anchor: /docs/ai-tooling#machine-readable-api-openapi Machine-readable API (OpenAPI) Point your codegen or coding assistant at this. Resource What it is Use it for /openapi.yaml OpenAPI 3.1 — every REST endpoint, request and response schema, auth and the problem+json error model Client generation, Postman, contract tests /llms.txt A curated map of the docs (the llms.txt standard): links and one-line descriptions An AI finding the right page fast /llms-full.txt The full documentation as one clean plain-text file Everything an AI needs to build an integration, in a single fetch /.well-known/oauth-authorization-server The authorization server's own metadata Every OAuth endpoint — read it at runtime; see Discovery Two surfaces are deliberately not in the OpenAPI document, and the document says so: the OAuth authorization server ( /idp/* ), which is described by its discovery documents, and the MCP endpoint ( POST /mcp ), which is JSON-RPC rather than REST and lists its own tools. #### No SDKs, by design Anchor: /docs/ai-tooling#no-sdks-by-design No SDKs, by design Generate a client from the spec; it is the contract we test against. LeadLogiX publishes no client libraries, and that is a decision rather than a gap. The REST surface is small, the spec is generated from the same source the server is checked against on every build, and a hand-written SDK in five languages would be five more copies of the contract to drift. A generated client is exactly as current as the spec you generate it from — regenerate when the Changelog says something changed. Generate # TypeScript: types for every path, operation and schema npx openapi-typescript https:///openapi.yaml -o leadlogix.d.ts npm install openapi-fetch # Python: a typed client package pipx run openapi-python-client generate --url https:///openapi.yaml import createClient from "openapi-fetch"; import type { paths } from "./leadlogix"; // generated above const api = createClient({ baseUrl: "https://", headers: { "X-API-Key": process.env.LEADVAL_API_KEY }, }); const { data, error, response } = await api.POST("/api/v1/sessions", { body: { sessionId: "conv_9F3A21", processor: 2 }, // typed from the spec }); if (error) { // problem+json: branch on the stable code, never on the sentence console.error(response.status, error.code); } else { console.log(data.processing.state); // "QUEUED" } What a generated client does not give you Where it is The notify-then-poll loop — when to stop polling, and what each state means Session object → Processing states Retry rules: which codes to retry, and honouring Retry-After Ingestion errors & limits OAuth — use a standard OAuth 2.1 library against the discovery documents OAuth 2.1 / OIDC MCP — use any MCP client library; the tool list is served per token MCP → Hosted server #### MCP is for runtime, not for writing code Anchor: /docs/ai-tooling#mcp-is-for-runtime-not-for-writing-code MCP is for runtime, not for writing code The two Model Context Protocol servers are for an agent calling LeadLogiX while it works , not for a coding assistant writing your integration. The hosted server reads a customer's workspace over OAuth; the local leadval-mcp package notifies and looks up with your own key. How they differ, and the only tool catalogue, are on MCP → Hosted server ; installing the package is on leadval-mcp (npm) . Routing an agent by opening hours is on Availability & hours . #### Building an integration with an AI assistant Anchor: /docs/ai-tooling#building-an-integration-with-an-ai-assistant Building an integration with an AI assistant The fastest path Give your AI coding assistant the OpenAPI spec ( /openapi.yaml ) and /llms-full.txt , and ask it to build the notify-then-poll flow. Both are grounded in the shipping API, so the generated code targets real endpoints, real auth, and the real error model. Need the Identity Resolution or Customer Profiles API schema, or a hand? Contact EVAS at support@evas.live . Building an OAuth client? Write the refresh logic yourself Assistants routinely generate retry-on-failure around a token refresh, and here that revokes the customer's whole grant — the rotation rule . ### API Playground URL: /docs/playground Developer Reference The API Playground A browser client for the ingestion API. Mint and revoke keys, and submit real requests against the live endpoints in sandbox mode, with the status timeline and conformance notes shown as they arrive. It issues the same calls your code will, which makes it the shortest way to tell a malformed request from a misconfigured pipeline. Find it: Settings > API Playground. admins mint keys; anyone with a key can test #### Use it to answer three questions Anchor: /docs/playground#use-it-to-answer-three-questions Use it to answer three questions Each one is faster here than in your own code. Question How the Playground answers it “Is my request malformed, or is the pipeline unhappy?” It issues the same call your code does. If it succeeds here and fails in your code, the difference is in your request — not in LeadLogiX. “Why was my session rejected?” Conformance notes arrive in the timeline naming the missing keys. Compare them against the metadata contract on Session object . “What does a finished lead actually look like?” Watch the status poll to a terminal state, then open the lead in the Clearing Pool to see what the pipeline produced from your payload. Sandbox first, and it is a real switch Sandbox runs exercise the whole pipeline but never touch production leads, reporting or billing. Switching to Live needs a payment method on the workspace. When you are ready to write code, Quickstart is the same flow in curl, Node and Python. #### What it does Anchor: /docs/playground#what-it-does What it does Two panels, side by side. Manage keys Admin Generate ingestion keys, give each a label (e.g. “Voice Bot — West Team”), and activate, deactivate, or revoke them. New keys are shown once — copy them right away. Test ingestion Live Paste a key, pick a processor mode, and send a real POST /api/v1/sessions — then watch the status poll update live until the lead is processed. #### Mint a key Anchor: /docs/playground#mint-a-key Mint a key Name it Give the key a label that says where it'll be used. The workspace's identity is attached automatically. Generate & copy The full val_key__… value appears once. Copy it into your backend or secret manager now — you won't see it again. Manage it later Toggle a key inactive to pause it, or revoke it to kill it immediately. Both take effect at once. #### Test a session safely Anchor: /docs/playground#test-a-session-safely Test a session safely Sandbox is on by default — nothing touches production. Paste your key and keep Sandbox on Sandbox writes the test lead to an isolated namespace, so your real Clearing Pool and reports stay clean. The toggle clearly shows sandbox_* vs lead_* . Pick a Session ID and processor mode Use any id (there's a “generate random” button), then pick a processor mode. The picker offers 2 (REALESTATE), plus 1 and 0 on the Unlimited plan. Other lines of business — 3 (LIFE_INSURANCE_US) — are sent by posting to /api/v1/sessions directly; the Playground's copy-ready cURL is the fastest way to do that, with the processor value changed. Send — and watch it flow The Playground shows the exact JSON and a copy-ready cURL, fires the request, then streams the live pipeline timeline: accepted → queued → processing → processed, plus any conformance notes. It's the same code your Agents hit The Playground calls the real endpoints exactly as an AI Agent would — so if it works here in sandbox, it works in production. Flip Sandbox off only when you deliberately want a real lead. ### Changelog URL: /docs/changelog Developer Reference Changelog Changes to the public API, the authorization server and the MCP endpoint, dated by the day they reached production. Anything a client can observe from outside is listed here; internal work is not. #### API status & versions Anchor: /docs/changelog#api-status-versions API status & versions Four live paths. None is deprecated. Current Live and current: /api/v1 (ingestion, session status, identity lookup and resolution, profiles), /api/v2 (second-generation identity resolution — v1 identity stays available), /idp/* (the OAuth 2.1 / OIDC authorization server) and /mcp (the OAuth-authorized Model Context Protocol endpoint). When a version is deprecated it will be announced here first, with a date, and the endpoint will keep answering until that date passes. Contract changes are also published machine-readably at GET /api/v1/notices . #### What counts as a breaking change Anchor: /docs/changelog#what-counts-as-a-breaking-change What counts as a breaking change Treated as breaking Not breaking Removing an endpoint, a field, or an accepted value Adding an optional field to a response Narrowing what an endpoint accepts Adding an endpoint Changing the meaning of an existing field Adding a value to a field documented as open Changing a success status code Rewording an error message — parse the code, never the sentence Read your endpoints from discovery, not from this page Every OAuth URL is published in /.well-known/oauth-authorization-server . A client that reads them at startup survives a move; one that hard-codes them from documentation does not, and finds out in production. #### 2026 Anchor: /docs/changelog#2026 2026 2026-09-28 A missing workspace tag answers like a missing document A conversation record that does not carry your workspace tag is now reported exactly like one that was never written: the session waits the same readiness budget with waitingForSource: true , then fails with the same message, which names your workspace tag and both possible causes. Read processing.errorMessage on the FAILED session. 2026-09-27 Your workspace tag must come first A new session whose first container tag is not your workspace tag is refused with a terminal FAILED whose errorMessage begins Container tag order violation . Write containerTags with your workspace tag first and global_anonymous_pool second. 2026-09-14 Applications can run a workspace's own automations A new scope, automations:run , and with it the first tools on this endpoint that are not the same for every workspace . Tools named automation__ are built from that workspace’s own records when you call tools/list , so two applications holding identical grants on different workspaces legitimately see different tools. Discover them; never hard-code them. Each automation publishes its arguments as the tool’s own inputSchema and is validated strictly: a number outside its bounds is rejected rather than clamped, an unknown key is rejected rather than dropped, and a required text field refuses blanks. A success is { ok: true, executionId, result? } , where result carries only the fields that automation declares — never its logs, and never anything it read along the way. This scope is a write, despite reading like a query. An automation can post to a channel, call another system or send a message on the workspace’s behalf — and one that is allowed to act may also queue work the platform carries out minutes or days after your call returns . That follow-up is not in your response, nothing calls you back, and you cannot cancel it. Approving the scope publishes nothing by itself: each automation is published to applications individually, and none is by default. Refusals carry a code and a retryable flag. CONCURRENCY_LIMIT and TIMEOUT are retryable — on the first, the run never started. INVALID_ARGUMENTS and AUTOMATION_FAILED are not. RESULT_TOO_LARGE means the automation ran and only its answer could not be returned, so retrying does the work twice. Exhausting the separate 60-per-minute automation budget is an HTTP 429 with Retry-After , not a tool error. 2026-08-31 Processor refusals list every accepted mode 422 PROCESSOR_REQUIRED and PROCESSOR_UNSUPPORTED carry supportedProcessors , every mode the server accepts as {mode, label} . Branch on that array rather than on a list copied from documentation. Documentation: the API reference's example error sentence still listed three processor modes after the fourth was accepted. The example is now marked as generated per request. 2026-08-28 US life insurance, and a terminal answer for ungraded sessions Processor 3 , LIFE_INSURANCE_US , on every plan: a full line of business with its own calibration, roles and fact set. Its budget is a monthly premium. conformance.level gained NOT_EVALUATED , with a reason and evaluatedAt: null , for a session that finished without a grade (processor 0 never opens its source document). PENDING now only ever means "still in flight", and processor 1 sessions are graded like any full run. 2026-08-21 Following identity pointers with an ingestion key, and one field for names GET /api/v1/leads/{leadId}/context and GET /api/v1/profiles/{profileId} dereference the pointers POST /api/v1/identity/lookup returns, with the same ingestion key. They compose the same answer as the get_lead and get_customer_profile MCP tools, and share the lookup's rate budget. nameUse ( PERMITTED / WITHHOLD ) and nameUseReason on every lookup row, and on each conversation of a customer profile: whether an agent may use a name the person has not given in this conversation. It replaces deriving that answer from the verdict, the band or corroboratedBy . Documentation, Recognise a returning customer : the guide said step 3 always goes through OAuth, and sent callers holding only an ingestion key to MCP, which a key cannot reach. It now sends them to the two endpoints above. Documentation, Recognise a returning customer : the guide treated corroboratedBy: ["contact-phone"] as enough to greet a caller by name — exactly what a household sharing a phone returns. Greet by name only on nameUse: "PERMITTED" . Its test step for a phone-only lookup likewise called the correct POSSIBLE_MATCH a wiring fault. Documentation: a lead becomes searchable by the lookup once a usable fingerprint, email or phone has been extracted from it — not once it is scored, as the guide said. Identity-only records are found too. 2026-08-16 Applications can register their own tools A new scope, tools:write , and three MCP tools behind it: submit_tool registers one of your tools with a workspace so LeadLogiX reads its calls as graded evidence on that workspace’s leads; list_tools reports where each of your registrations stands, carrying the reviewer’s note on anything declined; and verify_tool tells you whether the tool you are shipping still matches what was approved, naming which field names and types differ — never a value. submit_tool is idempotent on the tool’s contract, so re-sending an unchanged definition returns the original submission rather than a second one. A retry after a network timeout costs nothing. The MCP endpoint is no longer read-only. It is still read-only for any token without tools:write , and what the new tools write is a proposal : a submission sits at PENDING , is not loaded by the pipeline at all, and changes nothing until a LeadLogiX administrator approves it. You cannot approve your own. Approval applies only to leads processed afterwards — a lead already scored is never re-read. scopes_supported now includes tools:write . It is absent from the defaults a self-registered client may request, and requires both a workspace grant from LeadLogiX and an approving person who administers the workspace. If the three tools do not appear in tools/list , one of those is missing — read the granted scope from the token response rather than assuming you received what you asked for. Documentation, llms.txt : it said LeadLogiX has no token endpoint. It has had one since the authorization server shipped — /idp/token , with refresh tokens. What does not exist is a client-credentials grant. 2026-08-08 Authorization hardening A client registered ahead of time by an administrator could only obtain openid , whatever scopes it had been configured with — every other scope was dropped in silence. Preregistered clients now receive the scopes they were given. A consent link opened in a browser that was not yet signed in failed after the sign-in with a session error. The interaction now survives the round trip. http://localhost/… is refused as a redirect URI. Plain HTTP is still accepted for loopback, but the host must be a literal loopback address — http://127.0.0.1:PORT/… or http://[::1]:PORT/… . A hostname resolves through whatever the machine's resolver says, which is not a guarantee that the authorization code stays on the machine that asked for it. Granted scopes are capped at consent by three independent limits: what the client is allowed, what the approving person's role permits, and what the workspace has enabled. Asking for more is not an error — the extra scopes are shown as unavailable on the consent screen and are absent from the token. Read the granted scope from the token response rather than assuming you received what you requested. Self-registered clients with no activity for 90 days can be removed. A registration is judged on its own age and on when its authorizations were last used, so a client that is still being called is never reaped — one that holds an authorization nobody has used in 90 days is. Removal is a LeadLogiX platform setting and is off by default; the sweep reports first. 2026-08-07 Standard OpenID Connect An access token obtained with openid and no resource parameter was audience-bound to the MCP endpoint, so /idp/userinfo rejected every token it was ever given. Asking for identity now returns a token that works against UserInfo. Clients that send resource — which the MCP specification requires — are unaffected. Standard OIDC claims on /idp/userinfo and in the ID token, for the profile and email scopes. 2026-08-06 OAuth 2.1 / OIDC authorization server LeadLogiX issues its own tokens: authorization code with PKCE ( S256 required), refresh with rotation, and a consent screen the customer controls and can withdraw. Discovery at /.well-known/oauth-authorization-server and /.well-known/openid-configuration . Dynamic client registration (RFC 7591), token revocation (RFC 7009) and token introspection (RFC 7662). The MCP endpoint at /mcp , authorized by an OAuth token and scoped to exactly one workspace. Protected-resource metadata at /.well-known/oauth-protected-resource lets an MCP client discover the authorization server instead of being configured with it. Nothing older is listed Entries begin where the public developer surface did. For anything earlier, the API reference describes what is live now, which is the only thing a new integration needs.