# Lensmor API Documentation This file is generated from the public Mintlify MDX sources for LLM and agent consumption. It intentionally uses plain Markdown code fences without Mintlify-specific metadata. ## Lensmor API Documentation Source: /index Build event and attendee intelligence workflows with the Lensmor API. Use it to discover trade shows, evaluate event fit, inspect exhibitors and attendee source types, access registered Visitor data when available, and unlock contact details when your team is ready to act. Use this documentation to authenticate, make your first request, and integrate Lensmor event, exhibitor, personnel, contact, and recommendation resources into your own workflows. Current documentation version: `v0.26.0` ## Base URL `https://platform.lensmor.com` All endpoint paths in this documentation are relative to this base URL. For example, `GET /external/events/list` resolves to `GET https://platform.lensmor.com/external/events/list`. ## Get an API key Create an account at [app.lensmor.com](https://app.lensmor.com), upgrade to a paid subscription plan, then create a user API key from **Settings → API Keys**. Use the key with the API base URL above: ```http Authorization: Bearer sk_your_api_key ``` ## What you can build Search, filter, and rank trade shows by geography, category, quality, and profile fit. Match target accounts or buyer profiles against exhibitor data and related event presence. Distinguish Exhibitor, Social Signals, and registered Visitor records while preserving people with multiple source labels. Move from preview results to base event coverage, registered Visitor access, or both in one atomic action. Start asynchronous email unlock jobs and poll task status until contact data is ready. ## Which endpoint should I use? | If you have... | Start with | Why | | --- | --- | --- | | A keyword, geography, or date range | [Events list](/api-reference/events/list-events) | Broad event catalog search. | | A company website or buyer profile | [Apply profile and get recommended events](/api-reference/profile-matching/apply-profile-and-get-recommended-events) | Returns ranked events from profile context. | | A known event ID | [Exhibitors list](/api-reference/exhibitors/list-event-exhibitors) or [Personnel list](/api-reference/personnel/list-event-personnel) | Shows event-scoped companies and people; personnel records can include Exhibitor, Social Signals, and Visitor source labels. | | An event that needs both base and visitor access | [Unlock full event access](/api-reference/events/unlock-full-event-access) | Prechecks and atomically unlocks missing access layers for up to `5000` credits. | | A company name and you want companies | [Exhibitor company search](/api-reference/exhibitors/search-exhibitors-by-company-name) | Precision-first company lookup. Non-empty results cost `50` credits. | | A company name and you want events | [Exhibitor event search](/api-reference/exhibitors/search-events-by-exhibitor-company-name) | Finds events associated with that company. Non-empty results cost `50` credits. | | Personnel IDs and you need emails | [Unlock contact emails](/api-reference/contacts/unlock-contact-emails) | Starts an async email unlock task. | ## Recommended integration path Create a user API key and send it as `Authorization: Bearer sk_your_api_key`. Call `GET /external/credits/balance` and the relevant action precheck before event, visitor, email, or reverse-search workflows. Use Events, Exhibitors, Personnel, Contacts, or Profile Matching endpoints depending on your workflow. Treat `sourceType` as a multi-value array and keep event-scoped labels separate from source labels aggregated across a person's related events. Read `semantics` metadata, handle `402 Payment Required`, and back off on `429 Too Many Requests`. ## First request ```bash curl "https://platform.lensmor.com/external/events/list?page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` If the request succeeds, continue with the [Quickstart guide](/guides/quickstart). If it fails, check [Authentication](/authentication) and [Error conventions](/concepts/errors). ## Available resources - **Credits** — inspect the API key owner's current credit balance. - **Events** — browse, score, rank, inspect event details, and unlock base or visitor access. - **Exhibitors** — list exhibitors, search by company context, inspect profiles, and fetch related events. - **Personnel** — list people, interpret attendee source labels, inspect profiles, and fetch related events. - **Contacts** — search contacts, unlock emails, and poll unlock tasks. - **Profile Matching** — apply profile inputs and retrieve recommended events or exhibitors. ## Shared conventions - [Authentication](/authentication) - [Error conventions](/concepts/errors) - [Pagination conventions](/concepts/pagination) - [Identifiers](/concepts/identifiers) - [Attendee source types](/concepts/attendee-source-types) - [Credits and access](/concepts/credits-and-access) - [Rate limits](/concepts/rate-limits) ## Stay current Review the [Changelog](/changelog) for newly documented endpoints, behavior clarifications, and machine-readable resource updates. If your integration needs a workflow that is not covered here, contact Lensmor support. --- ## Authentication Source: /authentication Lensmor API requests require a user API key. ## Getting your API key 1. Sign in or create an account at [Lensmor App](https://app.lensmor.com). 2. Upgrade the account to a paid subscription plan. 3. Open **Settings → API Keys**. 4. Click **Create API Key**, give it a label, and copy the key immediately — it is only shown once. Your key starts with `sk_` and looks like this: ``` sk_0123456789abcdef_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef ``` Store it in an environment variable before making requests: ```bash export LENSMOR_API_KEY="sk_your_api_key" ``` API keys are available to subscribed accounts. If you do not see the API Keys section after upgrading, contact Lensmor support. ## Base URL `https://platform.lensmor.com` Combine this base URL with the endpoint paths documented in the API reference. For example, `GET /external/events/list` becomes `GET https://platform.lensmor.com/external/events/list`. ## Header format ```http Authorization: Bearer sk_your_api_key ``` ## Example request ```bash curl "https://platform.lensmor.com/external/events/list?page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## How it works - Send a Bearer token in the `Authorization` header. - Bearer scheme matching is case-insensitive at runtime. - Use the examples on each endpoint page for the expected request path, parameters, and response shape. ## Before you call the API - Use a valid user API key. - Send the header on every request. - Expect `401 Unauthorized` when the key is missing, malformed, revoked, or otherwise invalid. ## Security recommendations - Store API keys in a secret manager or server-side environment variable. - Do not expose API keys in browser JavaScript, mobile app bundles, public repos, or analytics payloads. - Rotate keys if they are accidentally logged or shared. - Avoid logging full `Authorization` headers. - Use your own server as a proxy when building browser-based experiences. Treat user API keys like credentials. They can access Lensmor data and initiate workflows that use the same credits as the Lensmor SaaS app. ## Test and production usage Lensmor does not currently provide a separate sandbox environment. All API calls go to the production base URL. To test your integration safely: - Use read-only endpoints (`events/list`, `events/detail`, `exhibitors/list`, `personnel/list`, `credits/balance`) freely — they do not consume credits. - Avoid calling credit-consuming endpoints (`events/:id/unlock`, `events/:id/visitors/unlock`, `events/:id/full-access/unlock`, `contacts/unlock`, and paid exhibitor company lookups) until you are ready to use real data. - Check your shared SaaS/API balance with `GET /external/credits/balance` before running paid workflows. - API usage and Lensmor SaaS usage draw from the same credit pool for the subscribed user account. For integration development, start with the free read-only endpoints and only call credit-consuming endpoints when your workflow is validated. ## Notes - Use the full `sk_...` key value from **Settings -> API Keys**. The visible key prefix alone is not enough for authentication. - Invalid or missing keys return the shared API error format described in [Error conventions](/concepts/errors). --- ## Changelog Source: /changelog Track documentation updates, newly documented API capabilities, and behavior clarifications that may affect integrations. ## v0.26.0 Released August 26, 2026. ### Fixed - Reconciled all `31` published customer-facing routes against Event Business `origin/master` commit `ce54e3e6e18c756ce154f1ba8800bfc89d7ac193`. - Corrected phone, LinkedIn activity, and outreach task identifiers to decimal numeric strings and documented nullable child-task IDs. - Added the inherited profile-matching request fields and a concrete recommended-event response schema with recommendation evidence. - Documented HTTP `200` subscription and concurrency business-error envelopes, missing validation/conflict responses, and the default rate-limit configuration. - Corrected multi-value personnel `sourceType` filtering, response-source notes, and examples where internal `id` and public `eventId` can differ. - Documented that outreach generation currently requires internal event `id` when `event_id` is supplied. ### Changed - Updated OpenAPI metadata to version `0.26.0` and regenerated the machine-readable documentation resources. ## v0.25.0 Released August 10, 2026. ### Added - Documented `GET /external/personnel/events/by-name` for reverse-looking up events from an exact personnel full name. - Documented `person_name`, optional event-start date bounds, event pagination, the fixed `50`-person candidate window, active-paid-user access, credit behavior, and the per-user concurrency limit of `10`. - Added `search_personnel_events_by_name` to the Actions precheck contract. ### Changed - Updated OpenAPI metadata to version `0.25.0` and regenerated the machine-readable documentation resources. ## v0.24.3 Released August 6, 2026. ### Changed - Refreshed the current documentation version after the July attendee-source and visitor-availability documentation update. - Regenerated machine-readable documentation resources so `openapi.json` and `llms-full.txt` report the current documentation version. - Updated OpenAPI metadata to version `0.24.3`. ## v0.24.2 Released July 30, 2026. ### Added - Documented the `sourceType` query parameter on `GET /external/personnel/list` for filtering event personnel by `exhibitor`, `social`, or `visitors`. - Documented the `hasVisitors` response field on public event list and detail records. ### Clarified - Clarified that `hasVisitors` is a Visitor-data availability signal and does not prove the caller has unlocked Visitor access. - Clarified that the public event list documentation does not expose a `has_visitors` query filter yet. - Updated OpenAPI metadata to version `0.24.2`. ## v0.24.1 Released July 24, 2026. ### Added - Added the Attendee source taxonomy that maps product labels to API values: Exhibitor (`exhibitor`), Social Signals (`social`), and Visitor (`visitors`). - Added an attendee-intelligence guide for field marketing, account-based event planning, sales engagement, and CRM enrichment workflows. - Added Simplified Chinese navigation and translations for the core onboarding, attendee, access, and contact-unlock documentation. ### Changed - Clarified that `sourceType` is a non-exclusive array and that personnel profile and contact search can aggregate labels across associated events. - Clarified that Visitor represents registered attendees obtained through organizer or data-provider partnerships and remains a separate access layer from base event access. - Added attendee source labels to Personnel List, Personnel Profile, and Contact Search OpenAPI examples. - Updated OpenAPI metadata to version `0.24.1`. ## v0.24.0 Released July 24, 2026. ### Added - Added `POST /external/events/{id}/visitors/unlock` for the `3000`-credit visitor data layer after base event access is unlocked. - Added `POST /external/events/{id}/full-access/unlock` for atomic base event plus visitor access, with dynamic `0`–`5000` credit usage. - Added `unlock_event_visitors` and `unlock_event_full_access` action prechecks with access-state, visitor-availability, subscription, and expected-credit details. - Added `addonAmount`, `addonBalance`, and `addonExpireAt` to the credit-balance contract. ### Changed - Changed personnel and contact `sourceType` from a nullable string to a normalized array containing `exhibitor`, `social`, and/or `visitors`. - Clarified that exhibitor company search and exhibitor event search consume `50` credits only when the result is non-empty; empty results consume no credits. - Documented phone unlock at `150` credits per chargeable contact. - Added `sponsor_match_starred` to the exhibitor event-search request and documented `sponsorMatchStarred` plus the canonical `Lensmor` event data source. - Added `linkedin_message_types` and typed LinkedIn InMail, connection-note, and connected-message response variants. ### Fixed - Added phone, phone-unlock, event-count, and outreach status fields to the shared contact response contract. - Updated email and phone unlock submission responses to cover both asynchronous `accepted` and no-work `success` outcomes, including skipped personnel details. - Updated OpenAPI metadata to version `0.24.0`. ## v0.23.1 Released July 19, 2026. ### Fixed - Standardized current API-key guidance on the Business `sk_` format across human and LLM-facing documentation. - Defined the event fit score as a `0`–`10` value with the exact `profile_match`, `matched_exhibitor_density`, and `event_scale` breakdown fields. - Added the dedicated recommended-exhibitor response contract, including `recommendationProcessing`, fallback `code`, `show_refresh_hint`, and the item-level `reason` field. ### Changed - Updated OpenAPI metadata to version `0.23.1`. ## v0.23.0 Released June 29, 2026. ### Changed - Clarified task-source behavior for `x-call-source` on LinkedIn activity unlock and outreach message generation workflows. - Updated OpenAPI metadata to version `0.23.0`. ### Fixed - Added missing `taskCenterId` metadata to the outreach message generation response schema. - Added missing `status`, `create_time`, and `update_time` fields to the outreach message detail response schema. ## v0.22.0 Released June 18, 2026. ### Added - Added documentation for phone number unlock workflow: - `POST /external/contacts/unlock-phone` — start an asynchronous phone number unlock job. - `GET /external/contacts/unlock-phone-tasks/{taskId}` — poll phone unlock task status and retrieve results. - Added documentation for AI-powered outreach message generation: - `POST /external/personnel/generate-outreach-message` — generate personalized outreach messages for personnel. - `GET /external/personnel/outreach` — retrieve generated outreach message content. - Converted existing endpoint pages (contacts unlock, unlock task, LinkedIn activity unlock) to OpenAPI-driven format with interactive playground support. - Updated OpenAPI metadata to version `0.22.0`. ## v0.21.0 Released June 12, 2026. ### Added - Added exhibitor buying-signal fields to supported exhibitor responses: - `buyingSignals` - `buyingSignalTags` - `buyingSignalStatus` - Added LinkedIn activity fields to personnel list responses: - `linkedinActivity` - `linkedinActivityStatus` - Added documentation for the LinkedIn activity unlock workflow. - Added documentation for action precheck requests. - Added `api-catalog.json` as the machine-readable API catalog. ### Changed - Updated API key guidance to direct users to [app.lensmor.com](https://app.lensmor.com), where subscribed accounts can create keys from **Settings -> API Keys**. - Clarified that API calls use `https://platform.lensmor.com` as the base URL. - Clarified that API usage and Lensmor SaaS usage share the same credit balance. - Updated OpenAPI metadata to version `0.21.0`. ### Fixed - Fixed event unlock examples so `balanceAfter` matches the production response shape. - Fixed pagination guidance for locked event preview pages. - Fixed machine-readable documentation links for `openapi.json`, `api-catalog.json`, `llms.txt`, and `llms-full.txt`. - Removed internal implementation wording from public documentation copy. ### Verified - Verified all documented endpoints against the production API. - Verified `openapi.json`, `api-catalog.json`, `llms.txt`, and `llms-full.txt` as machine-readable documentation resources. --- ## Quickstart Source: /guides/quickstart Make your first Lensmor API request and understand the core objects you will use in most integrations. The Lensmor API is designed around a typical event-intelligence workflow: 1. Start with a company profile, buyer profile, keyword, or target market. 2. Discover relevant trade shows and event records. 3. Inspect exhibitors and people connected to those events, including their Exhibitor, Social Signals, and Visitor source labels. 4. Unlock base event access, visitor access, full event access, or contact fields only when the data is actionable. ## Before you begin You need a valid user API key and an environment capable of sending HTTPS requests. Create an account at [app.lensmor.com](https://app.lensmor.com), upgrade to a paid subscription plan, then create an API key from **Settings → API Keys**. ```bash export LENSMOR_API_KEY="sk_your_api_key" ``` Lensmor API keys use the `sk_` prefix. Copy the full key value from **Settings -> API Keys**; the visible prefix alone is not enough for authentication. ## 1. Check your credit balance Credit-aware workflows return clear billing semantics, but most integrations should still check balance before running unlock operations. ```bash curl "https://platform.lensmor.com/external/credits/balance" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` The response includes subscription credits, gift credits, the total balance, and the next reset timestamp when available. This is the same credit pool used by the Lensmor SaaS app. ## Choose the right starting point | User goal | First endpoint | Credit note | | --- | --- | --- | | Search for events by keyword, country, city, or date | `GET /external/events/list` | Read-only | | Rank events from a company profile or audience description | `POST /external/profile-matching/actions/apply-recommended-events/paged` | Read-only | | Browse companies inside a selected event | `GET /external/exhibitors/list` | Read-only unless the user unlocks event access | | Browse people inside a selected event | `GET /external/personnel/list` | Read-only; returned `sourceType` labels identify accessible Exhibitor, Social Signals, and Visitor records | | Unlock complete exhibitor and personnel coverage | `POST /external/events/:id/unlock` | `2000` credits when base event access is still locked | | Unlock visitor records after base event access | `POST /external/events/:id/visitors/unlock` | `3000` credits when visitor access is still locked | | Unlock missing base and visitor layers together | `POST /external/events/:id/full-access/unlock` | Dynamic cost from `0` to `5000` credits | | Find exhibitor records from a company name | `POST /external/exhibitors/search-by-company-name` | Can consume `50` credits | | Find events associated with a company name | `POST /external/exhibitors/search-events` | Can consume `50` credits | | Unlock selected contact emails | `POST /external/contacts/unlock` | Can consume `15` credits per chargeable contact | ## 2. Search the event catalog Use the events list endpoint when you need a broad catalog query. ```bash curl "https://platform.lensmor.com/external/events/list?keyword=retail&country=United%20States&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` The event list is paginated. Use the `eventId` field from responses when calling other endpoints: - `id` and `eventId` are separate identifiers and can differ; prefer `eventId` for external storage and calls. - Use `eventId` when passing to endpoints that accept `event_id`. - Many event-scoped endpoints accept either value. ## 3. Inspect a specific event After selecting an event, fetch the event detail record. ```bash curl "https://platform.lensmor.com/external/events/139574" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` Use the detail response to confirm event dates, venue, geography, source URL, and available count fields before deciding whether to explore exhibitors or personnel. ## 4. Fetch exhibitors or personnel Event-scoped lists may return preview results if the event is locked. ```bash curl "https://platform.lensmor.com/external/exhibitors/list?event_id=139574&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` Personnel items can include a multi-value `sourceType` array: ```json { "id": "789", "fullName": "Jane Smith", "companyName": "Acme Retail Systems", "sourceType": ["exhibitor", "social"] } ``` The values map to the product's **Exhibitor**, **Social Signals**, and **Visitor** labels. A person can have multiple labels, and the API uses `"visitors"` for the product's singular Visitor label. See [Attendee source types](/concepts/attendee-source-types). When the response contains `semantics.accessMode: "preview"`, read `semantics.counts.visibleTotal` and `semantics.unlock.requiredForMoreResults` before calling an unlock endpoint. These fields describe base event access and tell you whether more base personnel coverage is available. Visitor is a separate registered-attendee data layer. For Visitor or combined full access, call `POST /external/actions/precheck` with `unlock_event_visitors` or `unlock_event_full_access` before execution. Precheck is read-only and returns current access state, Visitor availability, eligibility, and expected credits. ## 5. Handle errors and limits Production integrations should handle these responses explicitly: - `401 Unauthorized` means the API key is missing or invalid. - `402 Payment Required` means the requested credit-consuming workflow cannot proceed with the current balance or access state. - `404 Not Found` means the requested event, exhibitor, person, or task does not exist. - `429 Too Many Requests` means your integration should back off and retry later. See [Error conventions](/concepts/errors), [Credits and access](/concepts/credits-and-access), and [Rate limits](/concepts/rate-limits) for shared behavior across endpoints. ## Next steps Segment Exhibitor, Social Signals, and Visitor records without losing multi-source context. Learn how preview, base event, visitor, and atomic full access work. Start an email unlock task and poll until the result is ready. --- ## Build attendee intelligence Source: /guides/build-attendee-intelligence Use this guide to build field-marketing, account-based event planning, and sales-engagement workflows around the people connected to an event. Lensmor Attendees include three source labels: - **Exhibitor** (`exhibitor`) — people associated with exhibiting companies. - **Social Signals** (`social`) — people identified from LinkedIn signals connected to the event. - **Visitor** (`visitors`) — registered attendees obtained through organizer or data-provider partnerships. A person can have multiple labels. See [Attendee source types](/concepts/attendee-source-types) for the complete mapping and scope rules. ## Recommended workflow Search the event catalog, rank events, or apply profile matching. Fetch event detail before using its `eventId` in attendee requests. Call `GET /external/personnel/list` and read both each person's `sourceType` array and the response-level `semantics` object. Use the Visitor or full-access action precheck. Visitor data is available only for selected events. Unlock base event access for broader exhibitor/personnel coverage, Visitor access for registered attendee records, or full access for both missing layers. Filter or segment accessible records by source, score the people relevant to your workflow, and unlock email or phone fields only for selected personnel IDs. ## 1. List people for an event ```bash curl "https://platform.lensmor.com/external/personnel/list?event_id=139574&page=1&pageSize=50" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` Each item can include a multi-value `sourceType`: ```json { "id": "789", "fullName": "Jane Smith", "title": "VP of Partnerships", "companyName": "Acme Retail Systems", "sourceType": ["exhibitor", "social"], "contactUnlockStatus": "locked" } ``` Use the event-scoped list when you need source meaning for one selected event. Personnel profile and contact search can aggregate labels across multiple associated events. ## 2. Filter or segment the accessible audience Use `sourceType` when you want the API to return one event-scoped source segment: ```bash curl "https://platform.lensmor.com/external/personnel/list?event_id=139574&sourceType=visitors&page=1&pageSize=50" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` Supported values are `exhibitor`, `social`, and `visitors`. Preserve overlapping labels because a matching person can still include additional `sourceType` values. When you fetch all accessible records, segment the returned items in your application: ```js const bySource = { exhibitor: items.filter((person) => person.sourceType?.includes("exhibitor")), social: items.filter((person) => person.sourceType?.includes("social")), visitors: items.filter((person) => person.sourceType?.includes("visitors")), }; ``` Do not add the three segment counts together to calculate unique people because the same person can appear in multiple segments. ## 3. Check Visitor availability and access Visitor data is a separate layer. Precheck before showing an unlock confirmation: ```bash curl -X POST "https://platform.lensmor.com/external/actions/precheck" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action_type": "unlock_event_visitors", "call_source": "api", "params": { "event_id": "139574" } }' ``` Inspect `allowed`, `credits`, `reason`, and `detail.visitor_data_available`. If base event access is still locked, use the full-access precheck or unlock base event access first. `semantics.accessMode: "full"` on the personnel list refers to base event access. Always use Visitor precheck state when your workflow specifically requires registered Visitor records. ## 4. Enrich only selected people After prioritizing attendees, collect their personnel IDs and call the contact email or phone unlock workflow. Both workflows can consume credits and run asynchronously, so show the user the selected scope, check balance, and poll the returned task ID. ## Example use cases ### Field marketing Compare Exhibitor, Social Signals, and Visitor records to understand the accessible event audience, then prioritize registered Visitors or relevant exhibiting-company contacts for campaign planning. ### Account-based event planning Combine `GET /external/exhibitors/list` with event personnel. Identify target accounts exhibiting at the event, then inspect the source labels and roles of people connected to those companies. ### Sales engagement Use source labels, title, department, and seniority to shortlist relevant attendees. Unlock contact fields only after the user confirms the final outreach list. ### CRM enrichment Store the person ID, event ID, full `sourceType` array, and unlock state in your own integration. Preserve the event context so aggregated profile labels are not mistaken for event-specific registration. ## Current limitations - Event list and event detail expose `hasVisitors` as a Visitor-data availability signal. - Event list accepts `has_visitors=1` or `0` to filter by Visitor-data availability. - Visitor coverage varies by event and must not be inferred from event category, size, or location. --- ## Build event recommendations Source: /guides/build-event-recommendations Use this guide when your product needs to recommend trade shows from a company website, ICP description, target audience, or existing event shortlist. Lensmor exposes more than one event-discovery endpoint because recommendation workflows differ by starting point. The main design choice is whether you already know the candidate event set. ## Choose the right endpoint | Starting point | Recommended endpoint | Why | | --- | --- | --- | | User wants to browse events by keyword, country, city, or date | [Events list](/api-reference/events/list-events) | Returns a neutral paginated catalog. | | User provides a company website or target audience and wants recommended events | [Apply profile and get recommended events](/api-reference/profile-matching/apply-profile-and-get-recommended-events) | Applies profile context and returns scored event recommendations. | | User already has one event and wants a compact fit result | [Events fit score](/api-reference/events/score-one-event) | Scores one event without returning a full recommendation page. | | User already has several candidate event IDs and wants them sorted | [Events rank](/api-reference/events/rank-events) | Ranks a known shortlist. | | User knows an exhibitor company name and wants related events | [Exhibitor event search](/api-reference/exhibitors/search-events-by-exhibitor-company-name) | Reverse-lookups events from company name context. | ## Recommendation-first workflow Ask for a company website, target audience description, or both. Better input context usually produces better recommendations. Call `POST /external/profile-matching/actions/apply-recommended-events/paged` with filters such as country, category, date range, or attendee count. Use `match_score`, `matched_exhibitor_count`, geography, dates, and event size fields to decide what to show first. Fetch event detail, preview exhibitors/personnel, and unlock event coverage only when needed. ## Example: recommend events from a company website ```bash curl -X POST "https://platform.lensmor.com/external/profile-matching/actions/apply-recommended-events/paged" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "company_url": "https://acme.example", "country": "United States", "future": 1, "page": 1, "pageSize": 20 }' ``` ## Example: rank a shortlist Use ranking after your product has already narrowed the event universe. ```bash curl -X POST "https://platform.lensmor.com/external/events/rank" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "event_ids": ["139574", "139575", "139576"] }' ``` ## Interpreting recommendation data - `match_score` is useful for ordering and visual ranking. Avoid treating small score differences as exact mathematical truth. - `matched_exhibitor_count` indicates whether an event has account overlap with the profile context. - `unlocked` tells you whether full event-scoped coverage is already available. - `profile_version` and `active_result_version` help clients understand result freshness. ## UX recommendations - Let users edit profile inputs and rerun matching. - Show enough event metadata to make the recommendation explainable: location, date, scale, and matched exhibitor count. - Avoid spending credits during the recommendation step. Use unlock flows only after the user selects an event. - Keep pagination controls visible. Recommendation results can span multiple pages. ## Related concepts Understand `id`, `eventId`, and event-scoped identifiers. Learn which follow-up workflows can consume credits. --- ## Find and unlock an event Source: /guides/find-and-unlock-event Use this guide when your integration needs to move from discovery to base event coverage, registered Visitor access, or both. Event unlocks are intentionally explicit. Lensmor lets you preview exhibitor and personnel data first, then choose the access layer the user actually needs. Visitor records are registered event attendees obtained through organizer or data-provider partnerships and are available only for selected events. ## Recommended workflow Search the event catalog, rank events, or apply profile matching to identify a shortlist. Fetch the event detail and confirm the event identity, date, venue, source URL, and available data counts. Call event-scoped list endpoints. If the event is locked, read the `semantics` object to understand preview limits. Use base event unlock for exhibitor and personnel coverage, visitor unlock after base access, or full-access unlock for both layers. Precheck before each paid action. ## 1. Search events ```bash curl "https://platform.lensmor.com/external/events/list?keyword=retail&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` For fit-based discovery, use [Get event fit score](/api-reference/events/score-one-event), [Rank events](/api-reference/events/rank-events), or [Apply recommended events](/api-reference/profile-matching/apply-profile-and-get-recommended-events). ## 2. Fetch event detail ```bash curl "https://platform.lensmor.com/external/events/139574" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` Confirm that the event is the one your team expects before unlocking. For example, check `name`, `dateStart`, `dateEnd`, `venue`, `country`, `url`, and any count fields returned by the API. ## 3. Preview event-scoped data ```bash curl "https://platform.lensmor.com/external/personnel/list?event_id=139574&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` Locked events can return a response like this: ```json { "items": [ { "id": "789", "fullName": "Jane Doe", "title": "VP of Partnerships", "companyName": "Example Retail Co", "sourceType": ["exhibitor", "social"], "email": null, "contactUnlockStatus": "locked" } ], "total": 120, "page": 1, "pageSize": 20, "hasMore": true, "semantics": { "accessMode": "preview", "previewLimit": 50, "counts": { "actualTotal": 120, "visibleTotal": 20, "remainingLockedCount": 100 }, "pageState": { "requestedPage": 1, "accessible": true, "maxAccessiblePage": 1 }, "unlock": { "requiredForMoreResults": true, "actionType": "unlock_event_personnel", "credits": 2000 }, "guidance": { "code": "preview_results_truncated", "message": "This event is locked. Unlock the event to access the remaining matching results." } } } ``` Use `semantics` to decide whether to unlock base event coverage. `semantics.accessMode` does not describe the separate Visitor layer, so do not infer Visitor access from pagination or `"full"` base access alone. ## 4. Unlock base event access ```bash curl -X POST "https://platform.lensmor.com/external/events/139574/unlock" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "x-call-source: api" ``` The response tells you whether credits were used: ```json { "success": true, "alreadyUnlocked": false, "creditsUsed": 2000, "balanceAfter": { "subscriptionBalance": 0, "permanentBalance": 451021, "totalBalance": 451021, "unlimited": false }, "event": { "id": "8458", "eventId": "26855", "name": "CES 2025" } } ``` If `alreadyUnlocked` is `true`, the event was already available and `creditsUsed` is `0`. ## 5. Add visitor access or unlock both layers Visitor access is separate from base event access: - `POST /external/events/:id/visitors/unlock` costs `3000` credits and requires base event access, an active subscription, and registered Visitor data for the event. - `POST /external/events/:id/full-access/unlock` atomically charges only for missing layers: `2000` for base event access plus `3000` for visitor access. Precheck the intended action before execution: ```bash curl -X POST "https://platform.lensmor.com/external/actions/precheck" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action_type": "unlock_event_full_access", "call_source": "api", "params": { "event_id": "139574" } }' ``` Continue only when `allowed` is `true`, show the returned `credits` to the user, and refresh the balance after execution. Read `detail.visitor_data_available` instead of inferring availability from the event category or list counts. If full-access precheck returns `no_contacts_available` because Visitor data is unavailable, use base event unlock when exhibitor and personnel coverage is still needed. ## Common mistakes - Unlocking before checking preview results. Preview first to verify the event has relevant records. - Using only the event name as your local key. Store `id` or `eventId` so follow-up calls are deterministic. - Treating `402 Payment Required` as a generic failure. It usually means your integration needs to pause, notify the user, or ask them to add credits. - Ignoring `semantics`. It tells you whether a list is in preview mode or has full access. - Calling visitor unlock before base event access. Precheck returns `state_conflict` in that state. - Treating base event access and visitor access as the same layer. They have separate access records and credit costs. - Treating Social Signals as official registration evidence. Only the Visitor label represents registered attendees in the current source taxonomy. ## Related endpoints Browse events with filters and pagination. Spend credits to access full exhibitor and personnel coverage. Add visitor records after base event access. Atomically unlock missing base and visitor layers. Fetch exhibitors for a selected event. Fetch people associated with a selected event. Map product attendee labels to API `sourceType` values. --- ## Unlock contact emails Source: /guides/unlock-contact-emails Use this guide when your integration has identified relevant personnel records and needs email addresses for outreach, CRM enrichment, or sales workflows. Email unlocks run asynchronously. The create request validates the batch and starts a task. Your integration then polls the task endpoint until the result is ready. ## When to use this workflow Use contact email unlock after you already know which people are worth enriching. Typical inputs come from: - [List event personnel](/api-reference/personnel/list-event-personnel) - [Get personnel profile](/api-reference/personnel/get-personnel-profile) - [Search contacts](/api-reference/contacts/search-contacts) Do not call email unlock as a blind bulk export. Batch only the people your user has selected or your scoring workflow has marked as relevant. ## 1. Collect personnel IDs Most personnel responses include `id`, name, title, company context, and `contactUnlockStatus`. ```json { "id": "789", "fullName": "Jane Doe", "title": "VP of Partnerships", "companyName": "Example Retail Co", "sourceType": ["exhibitor", "visitors"], "email": null, "contactUnlockStatus": "locked" } ``` Preserve the full `sourceType` array when moving a selected attendee into your enrichment workflow. Only send records that are still locked. Already unlocked contacts are not charged again, but filtering them out keeps your task easier to audit. ## 2. Start the unlock task ```bash curl -X POST "https://platform.lensmor.com/external/contacts/unlock" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -H "x-call-source: api" \ -d '{ "event_id": "139574", "personnel_ids": ["789", "790"] }' ``` The API accepts up to `2000` personnel IDs per request. `event_id` is optional; omitting it skips event membership validation. ```json { "status": "accepted", "task_id": "321", "job_id": "321" } ``` Store `task_id`. It is the stable identifier you need for polling. ## 3. Poll the task ```bash curl "https://platform.lensmor.com/external/contacts/unlock-tasks/321" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` Poll with backoff rather than a tight loop. A practical pattern is to wait a few seconds between attempts, then increase the interval for longer jobs. ## 4. Handle task states Your integration should account for these outcomes: - `accepted` or in-progress state: continue polling. - completed state: inspect item-level results. Count a contact as delivered only when its item status is unlocked and an email is present. - failed state: show the task error and decide whether the user should retry. - `404 Not Found`: the task ID is invalid or not visible to the API key owner. See [Get contact unlock task](/api-reference/contacts/get-contact-unlock-task) for the exact response shape. Completed tasks can contain mixed item outcomes. Show failed, ineligible, or unresolved contacts separately instead of folding them into the unlocked count. ## Credit behavior Contact email unlock currently costs `15` credits per chargeable contact. - Already unlocked contacts are not charged again. - Batches larger than `2000` personnel IDs return `422 Unprocessable Entity`. - Insufficient balance returns `402 Payment Required`. - The response and your commercial agreement are the source of truth for billing. ## Recommended integration behavior - Show the estimated number of locked contacts before creating the task. - Check `GET /external/credits/balance` before large batches. - Store the task ID and make polling resumable. - Back off on `429 Too Many Requests`. - Re-fetch `GET /external/credits/balance` after terminal task states when you need final credit reconciliation. - Re-fetch personnel or contact records after completion if your UI needs the latest unlock status or email visibility. ## Related endpoints Find people by name, company, event, or LinkedIn context. Start an asynchronous email unlock task. Poll task status and retrieve task results. Understand credit costs, balances, and access semantics. --- ## Production readiness Source: /guides/production-readiness Use this checklist before putting a Lensmor API integration in front of customers or internal sales teams. Production integrations should handle authentication, pagination, credit-aware actions, asynchronous jobs, and rate limits explicitly. This keeps workflows predictable and prevents accidental credit spend. ## Request checklist Include `Authorization: Bearer sk_your_api_key`. Treat `401 Unauthorized` as a key or account configuration issue. Always pass `page` and `pageSize` for list endpoints. Do not assume all records fit in the first response. Use `GET /external/credits/balance` before base event, visitor, full-access, contact, or paid exhibitor company workflows. Use `POST /external/actions/precheck` before workflows where you need to know whether an action is allowed, whether it should charge credits, or which unlock path applies. Store `task_id` from contact unlock responses so polling can continue after page refreshes, worker restarts, or network failures. Treat a terminal task as complete for the job, then inspect each item result before counting delivered emails or failed contacts. Respect `Retry-After` for `429 Too Many Requests` and use `X-RateLimit-*` headers for proactive throttling. ## Error handling matrix | Status | Meaning | Recommended behavior | | --- | --- | --- | | `400 Bad Request` | Request shape or validation failed. | Fix client-side input and show a validation message. | | `401 Unauthorized` | API key is missing, invalid, revoked, or not visible to the caller. | Ask the user to reconnect or rotate the key. | | `402 Payment Required` | The operation needs credits or access that is not currently available. | Pause the workflow, show credit context, and let the user decide. | | `404 Not Found` | The requested event, exhibitor, person, or task was not found. | Re-check stored identifiers and avoid retry loops. | | `409 Conflict` | The request conflicts with current profile, task, or workflow state. | Refresh current state before retrying. | | `422 Unprocessable Entity` | A selected personnel batch exceeds the runtime limit of `2000`. | Split the selection into smaller batches. | | `429 Too Many Requests` | Rate limit exceeded. | Retry only after `Retry-After`; use exponential backoff. | ## Credit-safe UI pattern For credit-consuming actions, use a two-step confirmation pattern: 1. Show what the user is about to unlock or search. 2. Show the expected credit cost when known. 3. Check current balance. 4. Ask the user to confirm. 5. Execute the API call. 6. Refresh balance and result state. 7. Reconcile final billing against the post-action balance and task or endpoint response. This pattern is especially important for [Unlock event](/api-reference/events/unlock-event-access), [Unlock event visitor access](/api-reference/events/unlock-event-visitor-access), [Unlock full event access](/api-reference/events/unlock-full-event-access), [Unlock contact emails](/api-reference/contacts/unlock-contact-emails), and the paid exhibitor company lookup endpoints. ## Credit-consuming actions | Action | Endpoint | Current behavior | | --- | --- | --- | | Event unlock | `POST /external/events/:id/unlock` | Charges `2000` credits when the event was not already unlocked. Repeating the same unlock is idempotent and returns `creditsUsed: 0`. | | Event visitor unlock | `POST /external/events/:id/visitors/unlock` | Charges `3000` credits when visitor access is still locked. Requires base event access, visitor data, and an active subscription. | | Full event access | `POST /external/events/:id/full-access/unlock` | Atomically charges only missing access layers: `2000` for base event access plus `3000` for visitor access. | | Contact email unlock | `POST /external/contacts/unlock` | Starts an async task and charges `15` credits per chargeable contact. A completed task can contain mixed item-level outcomes. | | Contact phone unlock | `POST /external/contacts/unlock-phone` | Starts an async task and charges `150` credits per chargeable contact. | | Exhibitor company search | `POST /external/exhibitors/search-by-company-name` | Charges `50` credits only when the result is non-empty. | | Exhibitor event search | `POST /external/exhibitors/search-events` | Charges `50` credits only when the result is non-empty. | Other read-only list, profile, heuristic-search, and precheck endpoints do not spend credits by themselves. Treat the two company-name lookup endpoints above as explicit paid exceptions. ## Rate-limit friendly polling For contact unlock tasks, avoid tight polling loops. ```text Create task -> wait 3s -> poll -> wait 5s -> poll -> wait 10s -> poll ``` Stop polling when the task is completed or failed. If the user leaves the page, store the task ID and resume later. For completed contact unlock tasks, inspect item-level results before updating CRM fields. Count only records that include an unlocked email, surface item-level failures separately, and refresh credit balance after terminal states when users need a spend audit. ## Logging and support Log enough context to investigate issues without storing sensitive data: - endpoint path and method - response status - `traceId` from error responses when available - `X-Request-ID` if your client sends one - task ID for asynchronous jobs - event or personnel identifiers involved in the workflow Do not log API keys or unlocked email addresses in plaintext application logs. ## Related concepts How to authenticate API requests. Shared error response shape and tracing fields. Standard pagination fields and page-size limits. Headers, retry behavior, and integration guidance. --- ## Credits balance Source: /api-reference/credits/get-credits-balance Get the current credit balance for the API key owner. Use this endpoint before credit-consuming workflows such as event unlock, visitor unlock, contact email unlock, or exhibitor event search. ## When to use this endpoint Call `GET /external/credits/balance` before any workflow that can spend credits: - [Unlock event](/api-reference/events/unlock-event-access) - [Unlock event visitor access](/api-reference/events/unlock-event-visitor-access) - [Unlock full event access](/api-reference/events/unlock-full-event-access) - [Unlock contact emails](/api-reference/contacts/unlock-contact-emails) - [Search exhibitor events](/api-reference/exhibitors/search-events-by-exhibitor-company-name) For user-facing integrations, show the balance before a paid action and refresh it after the action completes. ## Endpoint `GET /external/credits/balance` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Request example ```bash curl "https://platform.lensmor.com/external/credits/balance" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "totalAmount": 45050, "totalBalance": 38050, "subscriptionAmount": 35000, "subscriptionBalance": 30000, "addonAmount": 10000, "addonBalance": 8000, "addonExpireAt": 1784851200000, "giftAmount": 50, "giftBalance": 50, "resetAt": 1714521600000 } ``` ## Response fields | Field | Description | | --- | --- | | `totalAmount` | Total credits granted across active subscription, add-on, and gift buckets. | | `totalBalance` | Total currently available credits. Use this for simple "can this user spend credits?" checks. | | `subscriptionAmount` | Subscription credits granted for the active credit period. | | `subscriptionBalance` | Remaining subscription credits. | | `addonAmount` | Add-on credits granted in currently active add-on buckets. | | `addonBalance` | Remaining add-on credits. | | `addonExpireAt` | Latest active add-on credit expiration timestamp in milliseconds, or `null`. | | `giftAmount` | Gift credits granted to the user. | | `giftBalance` | Remaining gift credits. | | `resetAt` | Earliest active subscription credit expiration timestamp in milliseconds, or `null`. | ## Integration guidance - Use `totalBalance` for high-level balance checks. - Use bucket-specific fields when your UI needs to explain subscription, add-on, and gift credit usage. - Refresh the balance after credit-consuming operations because `balanceAfter` may differ from a previously cached value. - Treat `resetAt` and `addonExpireAt` as nullable. Some accounts may not have an active expiring bucket of that type. ## Error responses - `401 Unauthorized` - `429 Too Many Requests` ## Notes - `resetAt` is a timestamp in milliseconds for the earliest active subscription credit expiration, or `null`. - `addonExpireAt` is a timestamp in milliseconds for the latest active add-on credit expiration, or `null`. - See [Credits and access](/concepts/credits-and-access) for shared credit behavior. --- ## Actions precheck Source: /api-reference/actions/precheck-an-external-action Precheck an external API action before executing a workflow that may depend on event access, contact availability, credits, or action-specific policy. Use this endpoint when your integration needs a credit-safe confirmation step before calling an unlock or charged execution endpoint. ## Endpoint `POST /external/actions/precheck` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Request body | Name | Required | Type | Notes | | --- | --- | --- | --- | | `action_type` | Yes | string | Explicitly handled values include `query_event_personnel`, `unlock_event_contacts`, `unlock_event_visitors`, `unlock_event_full_access`, `unlock_contact_emails`, `unlock_contact_phones`, `search_exhibitor_events`, `search_personnel_events_by_name`, `integration_status`, `integration_export_contacts`, `integration_export_exhibitors`, and `others`. Other 1–100 character strings are accepted but return `allowed: false` with `reason_code: "unsupported_action"`. | | `params` | Yes | object | Action-specific inputs, such as `{ "event_id": "26855" }`. | | `call_source` | No | string | Source label such as `api` or `agent`. | | `locale` | No | string | Optional locale, such as `en-US`. | | `trace_id` | No | string | Caller-provided trace id for support/debug correlation. | | `conversation_id` | No | string | Optional conversation id for agent workflows. | | `channel_message_id` | No | string | Optional message id for agent workflows. | ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/actions/precheck" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action_type": "query_event_personnel", "call_source": "api", "params": { "event_id": "26855" } }' ``` ## Response example ```json { "ok": true, "action_type": "query_event_personnel", "allowed": true, "should_charge": false, "credits": 0, "reason_code": "ok", "biz_code": "", "detail": { "event_id": "26855", "access_mode": "preview", "preview_limit": 50, "unlock_action_type": "unlock_event_contacts", "unlock_credits": 2000 } } ``` ## Response fields | Field | Description | | --- | --- | | `ok` | Always `true` for a handled precheck envelope. | | `action_type` | Normalized action type that was evaluated. | | `allowed` | Whether the action can proceed under current account, access, and balance state. | | `should_charge` | Whether executing the related action is expected to consume credits. | | `credits` | Expected credit amount for the action. | | `reason_code` | Machine-readable decision reason. Examples include `ok`, `already_unlocked`, `insufficient_balance`, `not_found`, and `unsupported_action`. | | `biz_code` | Billing/business code used by the execution path when applicable. | | `detail` | Action-specific metadata. Shape varies by `action_type`. | ## Visitor unlock example ```json { "ok": true, "action_type": "unlock_event_visitors", "allowed": true, "should_charge": true, "credits": 3000, "reason_code": "ok", "biz_code": "Visitor Unlock - API", "detail": { "event_id": "26855", "event_unlocked": true, "visitor_unlocked": false, "visitor_data_available": true, "visitor_access_credits": 3000, "available_balance": 8000 } } ``` ## Notes - Precheck is read-only. It does not unlock records, create tasks, or consume credits. - `action_type` is validated as a non-empty string rather than a closed enum. Branch on `allowed` and `reason_code`; do not assume every accepted string names a supported action. - `query_event_personnel` is useful before calling [Personnel list](/api-reference/personnel/list-event-personnel). - `search_personnel_events_by_name` is useful before calling [Personnel events by name](/api-reference/personnel/search-personnel-related-events-by-name). Put `person_name` and optional `date_start_from` and `date_start_to` values in `params`. A valid-name precheck quotes `50` credits; execution charges only when the lookup returns events. - `unlock_event_visitors` requires base event access first. It can report `3000` expected credits, `state_conflict` when the event is still locked, `no_contacts_available` when visitor data is unavailable, or `forbidden` when the account has no active subscription. - `unlock_event_full_access` calculates only the missing access layers: `2000` for base event access plus `3000` for visitor access. It can therefore report `0`, `2000`, `3000`, or `5000` credits. - For full-access precheck, `detail` includes `event_unlocked`, `visitor_unlocked`, `visitor_data_available`, `event_access_credits`, `visitor_access_credits`, and `full_access_credits`. - Once a list endpoint returns `semantics`, treat that response as the authoritative explanation for the actual list result. - Precheck decisions can change as credits, event access, or underlying data changes. Recheck before executing a paid action. --- ## Events list Source: /api-reference/events/list-events Browse events currently available through the Lensmor API. Use this endpoint when you need a paginated event catalog with basic filters such as keyword, country, city, and date range. ## When to use this endpoint Use `GET /external/events/list` for broad discovery and browsing experiences: - Build an event search or browsing page. - Let users filter events by geography or date. - Retrieve stable event identifiers before calling detail, exhibitor, personnel, or unlock endpoints. - Create an initial shortlist before using profile matching or ranking endpoints. If you already have a company profile and want relevance-ranked results, start with [Apply profile and get recommended events](/api-reference/profile-matching/apply-profile-and-get-recommended-events) instead. ## Endpoint `GET /external/events/list` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `20`; maximum `100`. | | `keyword` | No | string | Event name or keyword search. | | `country` | No | string | Country filter. | | `city` | No | string | City filter. | | `date_start_from` | No | ISO date string | Inclusive lower bound for event date filtering. | | `date_start_to` | No | ISO date string | Inclusive upper bound for event date filtering. | | `event_type` | No | string[] | Event type filter. Comma-separated input is supported. Known values include `In-person`, `conference_led`, `expo_led`, `hybrid`. | | `quality` | No | string[] | Quality label filter. Comma-separated input is supported. Known values include `Verified`, `Standard`. | | `sponsor_match_starred` | No | integer | `1` returns starred Sponsor Match events; `0` returns non-starred events. | | `has_visitors` | No | integer | `1` returns events with registered Visitor data; `0` returns events without it. | ## Request example ```bash curl "https://platform.lensmor.com/external/events/list?keyword=retail&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ### Filter by geography and date ```bash curl "https://platform.lensmor.com/external/events/list?country=United%20States&city=New%20York&date_start_from=2026-01-01&date_start_to=2026-03-31&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ### Filter by multi-value fields `event_type` and `quality` accept array-style inputs from clients and comma-separated inputs in query strings. ```bash curl "https://platform.lensmor.com/external/events/list?event_type=conference_led,expo_led&quality=Verified,Standard&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ### Filter by Sponsor Match or Visitor availability ```bash curl "https://platform.lensmor.com/external/events/list?sponsor_match_starred=1&has_visitors=1&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` Both filters accept only `0` or `1`. ## Response example ```json { "items": [ { "id": "8458", "eventId": "26855", "name": "CES 2025", "nickname": "Consumer Electronics Show 2025", "description": "CES 2025, taking place in Las Vegas, NV from January 7-10...", "url": "https://www.ces.tech/", "dateStart": "2025-01-07", "dateEnd": "2025-01-10", "venue": "Las Vegas Convention Center", "city": "Las Vegas", "region": "Nevada", "country": "United States", "exhibitorCount": 3259, "hasVisitors": true, "sponsorMatchStarred": 0, "image": "https://example.com/ces-image.png", "dataSource": "Lensmor" } ], "total": 42, "page": 1, "pageSize": 20, "totalPages": 3, "hasMore": true } ``` ## Response fields | Field | Description | | --- | --- | | `items` | Event records for the requested page. | | `id` | Internal Lensmor event row identifier returned as a string. | | `eventId` | Public event identifier. Use this value when passing to other endpoints that accept `event_id`. It can differ from `id`. | | `name` | Canonical event name. | | `nickname` | Optional short name or common alias. | | `description` | Short event description when available. | | `url` | Source or official event URL when available. | | `dateStart`, `dateEnd` | Event date range. | | `venue`, `city`, `region`, `country` | Location metadata. | | `exhibitorCount` | Known exhibitor count when available. | | `hasVisitors` | `true` when the event currently has registered Visitor data available. This does not mean the caller has unlocked Visitor access. | | `sponsorMatchStarred` | Sponsor Match starred state (`0` or `1`). | | `dataSource` | Always `Lensmor` for public event records. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | ## Error responses - `401 Unauthorized` - `429 Too Many Requests` ## Notes - This endpoint returns a paginated event catalog rather than recommendation-specific results. - Pagination fields follow the shared conventions described in [Pagination conventions](/concepts/pagination). - Results are not guaranteed to be relevance-ranked unless a keyword or filter strongly narrows the result set. - `id` and `eventId` are separate values and can differ. Use `eventId` when passing to event-scoped endpoints. - `hasVisitors` is returned as an availability signal and can be filtered with `has_visitors`. It still does not mean Visitor access is unlocked; use the Visitor or Full Access action precheck before an unlock workflow. - `sponsorMatchStarred` can be filtered with `sponsor_match_starred`. - `event_type` and `quality` filters must match actual values in the database (e.g. `In-person`, `conference_led` for event type; `Verified`, `Standard` for quality). Invalid values return an empty result set rather than an error. - Filter values are applied to the event catalog, but summary list items may still omit or return `null` for some classification fields. Fetch event detail when you need the full classification shape. --- ## Event detail Source: /api-reference/events/get-event-detail Get detailed information for a single event. Use this endpoint after event discovery when you need venue, category, topic, attendee, and event-type metadata for a specific event. ## When to use this endpoint Use `GET /external/events/:id` when the user has selected an event and your integration needs a canonical event profile before taking follow-up action. Good follow-up actions include: - showing a detailed event page - deciding whether to preview exhibitors or personnel - confirming event identity before an unlock - syncing selected event metadata into a CRM or internal planning tool ## Endpoint `GET /external/events/:id` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Path parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `id` | Yes | string | Supports event `id` or `eventId` returned by event responses. | ## Request example ```bash curl "https://platform.lensmor.com/external/events/139574" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "event": { "id": "8458", "eventId": "26855", "name": "CES 2025", "nickname": "Consumer Electronics Show 2025", "description": "CES 2025, taking place in Las Vegas...", "url": "https://www.ces.tech/", "dateStart": "2025-01-07", "dateEnd": "2025-01-10", "venue": "Las Vegas Convention Center", "city": "Las Vegas", "region": "Nevada", "country": "United States", "latitude": "36.1313238", "longitude": "-115.1503622", "attendeeCount": 7769, "priceLower": "0", "priceUpper": "0", "eventType": "In-person", "sponsorMatchStarred": 0, "categories": [ { "id": 11, "code": "electric_electronics", "name": "Electric & Electronics", "description": null, "confidence": "1" }, { "id": 19, "code": "it_technology", "name": "IT & Technology", "description": null, "confidence": "1" } ], "topics": [], "topicsCount": 697, "verified": 0, "future": 0, "historic": 1, "historicEvent": "CES", "image": "https://example.com/ces-image.png", "dataSource": "Lensmor", "exhibitorCount": 3259, "hasVisitors": true, "personnelCount": null, "eventTypes": [] } } ``` ## Response fields | Field | Description | | --- | --- | | `event` | Event detail object. | | `id` | Internal Lensmor event identifier. | | `eventId` | Public event identifier accepted by many event-scoped endpoints. | | `name`, `nickname`, `description` | Human-readable event identity and description fields. | | `url` | Source or official event URL when available. | | `dateStart`, `dateEnd` | Event date range. | | `venue`, `city`, `region`, `country`, `latitude`, `longitude` | Location metadata. Coordinates are string values (e.g. `"36.1313238"`) or `null`. | | `attendeeCount`, `exhibitorCount`, `personnelCount` | Known summary scale and coverage fields when available. These fields can be `null` or differ from current event-scoped list totals. | | `hasVisitors` | `true` when the event currently has registered Visitor data available. This is separate from the caller's Visitor access state. | | `priceLower`, `priceUpper` | Price range as string values (e.g. `"1295"`) or `null`. | | `eventType`, `eventTypes`, `categories`, `topics`, `topicsCount` | Classification metadata. `categories` is an array of objects with `id`, `code`, `name`, `description`, `confidence` fields. | | `verified`, `future`, `historic`, `historicEvent` | Status flags and historical relationship metadata. | | `sponsorMatchStarred` | Sponsor Match starred state (`0` or `1`). | | `image` | Event image URL when available. | | `dataSource` | Always `Lensmor` for public event records. | ## Count fields Treat `exhibitorCount` and `personnelCount` on event detail as summary metadata for display and planning. They are not the authoritative count for the current caller's access state, filters, or page visibility. When you need current coverage numbers, call the event-scoped list endpoint and use its pagination metadata: - [List event exhibitors](/api-reference/exhibitors/list-event-exhibitors) for exhibitor totals. - [List event personnel](/api-reference/personnel/list-event-personnel) for personnel totals. - `semantics.counts` when present for visible, locked, and matched access context. ## Error responses - `401 Unauthorized` - `404 Not Found` - `429 Too Many Requests` ## Notes - For event identifiers, see [Identifiers](/concepts/identifiers). `id` and `eventId` are separate values and can differ. - Some fields can be `null` when the underlying source does not provide that data. - String-typed numeric fields (`latitude`, `longitude`, `priceLower`, `priceUpper`) require client-side parsing when used for calculations. - `dataSource` is always `"Lensmor"` for public event records. - `hasVisitors` indicates Visitor data availability only. Use the Visitor or Full Access action precheck to confirm unlock eligibility, expected credits, and current access state. - `categories` is an array of objects. Each object contains `id` (integer), `code` (string), `name` (string), `description` (string or null), and `confidence` (string). - This endpoint does not unlock event-scoped exhibitor or personnel access by itself. --- ## Event brief details Source: /api-reference/events/get-event-brief Fetch a lightweight summary for a single event. Use this endpoint when you need a compact event snapshot instead of a list response or a ranking result. ## When to use this endpoint Use `GET /external/events/brief` for lightweight UI cards, quick previews, or background refreshes where full detail metadata is not necessary. Prefer [Event detail](/api-reference/events/get-event-detail) when your product needs category, topic, pricing, coordinate, or historical metadata. ## Endpoint `GET /external/events/brief` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `event_id` | Yes | string | Event identifier. | ## Request example ```bash curl "https://platform.lensmor.com/external/events/brief?event_id=139574" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "event": { "id": "123", "eventId": "139574", "name": "NRF 2026", "nickname": null, "description": "Retail industry event", "url": "https://example.com/events/nrf-2026", "dateStart": "2026-01-12", "dateEnd": "2026-01-15", "venue": "Javits Center", "city": "New York", "region": "NY", "country": "United States", "exhibitorCount": 950, "sponsorMatchStarred": 0, "image": null, "dataSource": "Lensmor" }, "summary": { "attendeeCount": 40000, "exhibitorCount": 950, "topCategories": [], "dataFreshness": "database_snapshot" } } ``` ## Response fields | Field | Description | | --- | --- | | `event` | Compact event identity and location object. | | `event.id`, `event.eventId` | Event identifiers returned with the event. | | `event.name`, `event.description`, `event.url` | Basic display metadata. | | `event.dateStart`, `event.dateEnd` | Event date range. | | `event.venue`, `event.city`, `event.region`, `event.country` | Location fields. | | `event.exhibitorCount` | Known exhibitor count when available. | | `summary.attendeeCount` | Attendee count when available. | | `summary.exhibitorCount` | Exhibitor count repeated in the summary block for compact display. | | `summary.topCategories` | Top categories when available. | | `summary.dataFreshness` | Freshness/source label for the snapshot. | ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `429 Too Many Requests` ## Notes - This is a lightweight snapshot endpoint. - `topCategories` is currently returned as an empty array by the live service. - The endpoint is read-only and does not consume credits. --- ## Events fit score Source: /api-reference/events/score-one-event Score a single event against the authenticated caller's current profile context. Use this endpoint when you already know the target event and want a compact compatibility result instead of a full ranked list. ## When to use this endpoint Use `POST /external/events/fit-score` when your UI needs to answer a focused question: "Is this event a good fit for the current profile?" It works best after an event has already been selected from search, profile recommendations, or a saved shortlist. If you need to rank multiple candidates, use [Events rank](/api-reference/events/rank-events). If you need a full recommendation page from profile inputs, use [Apply profile and get recommended events](/api-reference/profile-matching/apply-profile-and-get-recommended-events). ## Endpoint `POST /external/events/fit-score` ## Authentication See [Authentication](/authentication) ## Success status code `201 Created` ## Request body | Name | Required | Type | Notes | | --- | --- | --- | --- | | `event_id` | Yes | string | Event identifier. | ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/events/fit-score" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"event_id":"139574"}' ``` ## Response example ```json { "event": { "id": "123", "eventId": "139574", "name": "NRF 2026", "nickname": null, "description": "Retail industry event", "url": "https://example.com/events/nrf-2026", "dateStart": "2026-01-12", "dateEnd": "2026-01-15", "venue": "Javits Center", "city": "New York", "region": "NY", "country": "United States", "exhibitorCount": 950, "sponsorMatchStarred": 0, "image": null, "dataSource": "Lensmor" }, "score": 8.1, "recommendation": "recommended", "breakdown": { "profile_match": 8.1, "matched_exhibitor_density": 1.9, "event_scale": 9.5 } } ``` ## Response fields | Field | Description | | --- | --- | | `event` | Compact event object for the scored event. | | `score` | Overall compatibility score on a `0`–`10` scale. | | `recommendation` | Exact decision enum: `recommended`, `consider`, or `not_recommended`. | | `breakdown.profile_match` | Profile-context compatibility component on a `0`–`10` scale. | | `breakdown.matched_exhibitor_density` | Matched-exhibitor density component on a `0`–`10` scale. This is not verified buyer or attendee density. | | `breakdown.event_scale` | Component derived from the Lensmor exhibitor count and capped at `10`. It does not provide a qualitative size benchmark by itself. | ## Interpretation guidance - Use `score` for display and ordering, but avoid treating small differences as exact truth. - Treat `7`–`10` as `recommended`, `4` to below `7` as `consider`, and below `4` as `not_recommended`. - Preserve the three returned breakdown fields exactly. Do not invent additional dimensions. - A zero score does not identify the cause. Do not infer missing profile configuration or missing coverage unless another API field or error states that explicitly. ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `409 Conflict` - `429 Too Many Requests` ## Notes - This endpoint uses `POST` to compute a profile-dependent result for the supplied `event_id`. It does not create a persistent resource. - The current implementation returns `event`, `score`, `recommendation`, and `breakdown` only. - Older draft fields such as `breakdown_details` are not part of the live response. --- ## Events rank Source: /api-reference/events/rank-events Rank multiple events for the authenticated caller. Use this endpoint when you already have a candidate set of events and want them returned in preference order. ## When to use this endpoint Use `POST /external/events/rank` when another part of your workflow has already produced a shortlist and you need Lensmor to sort it. Typical sources of a shortlist: - events selected by a user - events found from [Events list](/api-reference/events/list-events) - events related to an exhibitor or contact - events imported from a CRM or spreadsheet If you do not have a shortlist yet, start with [Apply profile and get recommended events](/api-reference/profile-matching/apply-profile-and-get-recommended-events) or [Events list](/api-reference/events/list-events). ## Endpoint `POST /external/events/rank` ## Authentication See [Authentication](/authentication) ## Success status code `201 Created` ## Request body | Name | Required | Type | Notes | | --- | --- | --- | --- | | `event_ids` | Yes | string[] | List of event identifiers to rank. Runtime validation permits an empty array. | ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/events/rank" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"event_ids":["139574","139575"]}' ``` ## Response example ```json { "items": [ { "event_id": "139574", "name": "NRF 2026", "rank": 1, "match_score": 0.86, "reasons": [] }, { "event_id": "139575", "name": "Shoptalk 2026", "rank": 2, "match_score": 0.79, "reasons": [] } ] } ``` ## Response fields | Field | Description | | --- | --- | | `items` | Ranked event results ordered from best to lowest fit. | | `event_id` | Event identifier from the submitted shortlist. | | `name` | Event name when available. | | `rank` | One-based rank position in the result set. | | `match_score` | Fit score used for ranking. | | `reasons` | Explanation list when available. Currently may be empty. | ## Integration guidance - Preserve the original submitted event IDs in your client if you need to reconcile ranked results with local records. - Display `rank` and `match_score` together when showing ordered recommendations. - Treat the endpoint as shortlist ordering, not a complete recommendation report by itself. - Live responses can return low or zero `match_score` values and empty `reasons`. Combine returned order with event metadata, matched counts, and follow-up detail or list calls before making a final business recommendation. - If `reasons` is empty, use event metadata and evidence from follow-up calls rather than showing a blank explanation block. ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `409 Conflict` - `429 Too Many Requests` ## Notes - A successful request with an empty `event_ids` array returns an empty `items` array. - This endpoint uses `POST` because the request body contains an array of event IDs that does not fit cleanly in query parameters. It does not create a persistent resource. - The current service returns an ordered `items` array with `rank` and `match_score`. - `reasons` is currently emitted as an empty array by the live service. --- ## Unlock event Source: /api-reference/events/unlock-event-access Unlock base event access for complete exhibitor and personnel results. This endpoint does not unlock the separate visitor data layer. Use this endpoint when preview list responses indicate that more event-scoped records require an event unlock. ## When to use this endpoint Call `POST /external/events/:id/unlock` only after you have confirmed that an event is relevant and the preview response indicates more records are available. Typical sequence: 1. Find the event with [Events list](/api-reference/events/list-events), [Rank events](/api-reference/events/rank-events), or profile matching. 2. Preview exhibitors or personnel for that event. 3. Read `semantics.accessMode`, `semantics.counts`, and `semantics.unlock`. 4. Unlock the event if the user wants full event-scoped coverage. Event unlocks can consume credits. Preview event-scoped results first so users understand what they are unlocking. ## Endpoint `POST /external/events/:id/unlock` ## Authentication See [Authentication](/authentication) ## Success status code `201 Created` ## Path parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `id` | Yes | string | Supports event `id` or `eventId` returned by event responses. | ## Headers | Name | Required | Type | Notes | | --- | --- | --- | --- | | `x-call-source` | No | string | Optional usage source. Use `api` or `agent`; defaults to `api`. | ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/events/139574/unlock" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "x-call-source: api" ``` ## Response example ```json { "success": true, "alreadyUnlocked": false, "creditsUsed": 2000, "balanceAfter": { "subscriptionBalance": 0, "permanentBalance": 451021, "totalBalance": 451021, "unlimited": false }, "event": { "id": "8458", "eventId": "26855", "name": "CES 2025" } } ``` ## Response fields | Field | Description | | --- | --- | | `success` | Whether the unlock request completed successfully. | | `alreadyUnlocked` | `true` when the API key owner already had base event access. | | `creditsUsed` | Number of credits charged by this request. `0` when the event was already unlocked. | | `balanceAfter` | Remaining balance snapshot after a paid unlock. `null` when no credits were charged, such as an already-unlocked event. | | `balanceAfter.subscriptionBalance` | Remaining subscription credits. | | `balanceAfter.permanentBalance` | Remaining gift/permanent credits. | | `balanceAfter.totalBalance` | Total remaining credits. | | `balanceAfter.unlimited` | Whether the account has unlimited access semantics. | | `event` | Minimal event identity for confirmation and audit logs. | ## Idempotency behavior Unlocking the same event again does not spend credits again. The API returns `alreadyUnlocked: true` and `creditsUsed: 0` when the event is already available to the API key owner. Your integration can use this behavior to safely retry after a network failure, but should still avoid repeated unlock calls in the UI. ## Access after unlock After a successful unlock, call event-scoped exhibitor or personnel list endpoints again. The list response should move from preview semantics to full-access semantics for that event. To add visitor records, call [Unlock event visitor access](/api-reference/events/unlock-event-visitor-access) after base access, or use [Unlock full event access](/api-reference/events/unlock-full-event-access) to unlock missing layers atomically. ## Error responses - `400 Bad Request` — invalid input or no chargeable event contacts are available. - `401 Unauthorized` - `402 Payment Required` - `404 Not Found` - `409 Conflict` — another credit operation is in progress. - `429 Too Many Requests` ## Notes - Event unlock currently costs `2000` credits when the event is not already unlocked. - If the event is already unlocked, `alreadyUnlocked` is `true` and `creditsUsed` is `0`. - See [Credits and access](/concepts/credits-and-access) for shared credit behavior. --- ## Unlock event visitor access Source: /api-reference/events/unlock-event-visitor-access Unlock the visitor data layer for an event that already has base event access. Use this endpoint when the caller needs registered Visitor records in addition to the exhibitor and personnel coverage unlocked by [Unlock event](/api-reference/events/unlock-event-access). Visitor records are obtained through organizer or data-provider partnerships and are available only for selected events. ## Prerequisites Before executing this paid action: 1. Confirm base event access is already unlocked. 2. Call [Actions precheck](/api-reference/actions/precheck-an-external-action) with `action_type: "unlock_event_visitors"` and the selected `event_id`. 3. Continue only when precheck returns `allowed: true`. Visitor access requires an active subscription, available Visitor data for the event, and enough credits. The product label is **Visitor**, while personnel responses use the API source value `"visitors"`. See [Attendee source types](/concepts/attendee-source-types). The first successful visitor unlock consumes `3000` credits. Precheck is read-only and should be used before the paid call. ## Endpoint `POST /external/events/:id/visitors/unlock` ## Authentication See [Authentication](/authentication). ## Success status code `201 Created` ## Path parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `id` | Yes | string | Supports the event `id` or `eventId` returned by event responses. | ## Headers | Name | Required | Type | Notes | | --- | --- | --- | --- | | `x-call-source` | No | string | Optional usage source. Use `api` or `agent`; defaults to `api`. | ## Precheck example ```bash curl -X POST "https://platform.lensmor.com/external/actions/precheck" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action_type": "unlock_event_visitors", "call_source": "api", "params": { "event_id": "26855" } }' ``` ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/events/26855/visitors/unlock" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "x-call-source: api" ``` ## Response example ```json { "success": true, "alreadyUnlocked": false, "creditsSpent": 3000, "balanceAfter": { "totalAmount": 45050, "totalBalance": 35050, "subscriptionAmount": 35000, "subscriptionBalance": 27000, "addonAmount": 10000, "addonBalance": 8000, "addonExpireAt": 1784851200000, "giftAmount": 50, "giftBalance": 50, "resetAt": 1785542400000 }, "event": { "id": "8458", "eventId": "26855", "name": "CES 2025" } } ``` ## Response fields | Field | Description | | --- | --- | | `success` | Whether the request completed successfully. | | `alreadyUnlocked` | `true` when visitor access was already available before this request. | | `creditsSpent` | Credits charged by this request. It is `3000` for a first successful unlock and `0` for an idempotent repeat. | | `balanceAfter` | Full credit-balance snapshot after a paid unlock, or `null` when no credits were charged. | | `event` | Minimal event identity for confirmation and audit logs. | ## Idempotency Repeating the unlock for the same account and event does not charge again. An idempotent repeat returns `alreadyUnlocked: true`, `creditsSpent: 0`, and `balanceAfter: null`. ## Error responses - `200 OK` business error — free users receive body `code: 400` and `errorKey: "USER_HAS_NO_FEATURE"`; treat the action as failed. - `400 Bad Request` — base event access is still locked, no visitor data is available, or another request input is invalid. - `401 Unauthorized` - `402 Payment Required` - `404 Not Found` - `409 Conflict` — another credit operation is in progress. - `429 Too Many Requests` The corresponding `errorKey` can include `NO_ACTIVE_SUBSCRIPTION`, `EVENT_NOT_UNLOCKED`, `VISITOR_DATA_NOT_AVAILABLE`, or `CREDIT_OPERATION_IN_PROGRESS`. ## Related endpoints - [Unlock event](/api-reference/events/unlock-event-access) - [Unlock full event access](/api-reference/events/unlock-full-event-access) - [Credits balance](/api-reference/credits/get-credits-balance) - [Actions precheck](/api-reference/actions/precheck-an-external-action) --- ## Unlock full event access Source: /api-reference/events/unlock-full-event-access Atomically unlock base event access and visitor access for one event. This endpoint is useful when the user wants all available event data in one confirmed action. The API charges only for access layers that are not already unlocked. ## Credit matrix | State before the call | Result | Credits | | --- | --- | --- | | Event locked, visitor locked, visitor data available | Unlock both layers | `5000` | | Event unlocked, visitor locked, visitor data available | Unlock visitor layer | `3000` | | Event locked, visitor already unlocked | Unlock base event layer | `2000` | | Both layers already unlocked | No new unlock | `0` | | Event locked, visitor data unavailable | Direct execution can unlock the event and set `visitorSkipped: true` | `2000` | Base event access costs `2000` credits. Visitor access costs `3000` credits and requires an active subscription when visitor data is available. Call [Actions precheck](/api-reference/actions/precheck-an-external-action) with `action_type: "unlock_event_full_access"` before execution. Current precheck returns `no_contacts_available` when visitor data is unavailable; use [Unlock event](/api-reference/events/unlock-event-access) when only base event coverage is needed. ## Endpoint `POST /external/events/:id/full-access/unlock` ## Authentication See [Authentication](/authentication). ## Success status code `201 Created` ## Path parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `id` | Yes | string | Supports the event `id` or `eventId` returned by event responses. | ## Headers | Name | Required | Type | Notes | | --- | --- | --- | --- | | `x-call-source` | No | string | Optional usage source. Use `api` or `agent`; defaults to `api`. | ## Precheck example ```bash curl -X POST "https://platform.lensmor.com/external/actions/precheck" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action_type": "unlock_event_full_access", "call_source": "api", "params": { "event_id": "26855" } }' ``` ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/events/26855/full-access/unlock" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "x-call-source: api" ``` ## Response example ```json { "success": true, "alreadyUnlocked": false, "eventUnlocked": true, "visitorUnlocked": true, "visitorSkipped": false, "totalCreditsUsed": 5000, "balanceAfter": { "subscriptionBalance": 25000, "permanentBalance": 50, "totalBalance": 33050, "unlimited": false }, "event": { "id": "8458", "eventId": "26855", "name": "CES 2025" } } ``` ## Response fields | Field | Description | | --- | --- | | `success` | Whether the atomic unlock completed successfully. | | `alreadyUnlocked` | `true` only when both access layers were already unlocked before this request. | | `eventUnlocked` | Whether this request newly unlocked base event access. | | `visitorUnlocked` | Whether this request newly unlocked visitor access. | | `visitorSkipped` | Whether visitor access was skipped because no visitor data was available. | | `totalCreditsUsed` | Total credits charged by this request: `0`, `2000`, `3000`, or `5000`. | | `balanceAfter` | Remaining balance after a paid unlock, or `null` when both layers were already unlocked. | | `event` | Minimal event identity for confirmation and audit logs. | ## Atomicity and idempotency The base event and visitor unlocks run in one transaction. If the paid operation fails, neither access layer should be partially committed. Repeating the call charges only for any layer that is still missing. ## Error responses - `200 OK` business error — free users receive body `code: 400` and `errorKey: "USER_HAS_NO_FEATURE"`; treat the action as failed. - `400 Bad Request` — no chargeable base-event contacts or another request input is invalid. - `401 Unauthorized` - `402 Payment Required` - `404 Not Found` - `409 Conflict` — another credit operation is in progress. - `429 Too Many Requests` ## Related endpoints - [Unlock event](/api-reference/events/unlock-event-access) - [Unlock event visitor access](/api-reference/events/unlock-event-visitor-access) - [Credits balance](/api-reference/credits/get-credits-balance) - [Actions precheck](/api-reference/actions/precheck-an-external-action) --- ## Exhibitors list Source: /api-reference/exhibitors/list-event-exhibitors List exhibitors for a specific event. Use this endpoint when you want a paginated exhibitor catalog scoped to one event, with optional company and personnel filters. ## When to use this endpoint Use `GET /external/exhibitors/list` after an event has been selected. This is the primary endpoint for browsing companies inside one event. Common use cases: - build an event exhibitor directory - filter exhibitors by industry, category, geography, or company keyword - request a best-effort nested personnel sample with `personnelLimit` - decide whether an event is worth unlocking for full coverage ## Endpoint `GET /external/exhibitors/list` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `event_id` | Yes | string | Event identifier used to scope the exhibitor list. | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `20`; maximum `100`. | | `keyword` | No | string | Exhibitor keyword search. | | `country` | No | string | Country filter. | | `category` | No | string[] | Exhibitor category filter. Repeated query parameters are supported. | | `industry` | No | string[] | Exhibitor industry filter. Repeated query parameters are supported. | | `jobTitle` | No | string[] | Personnel job-title filter. Repeated query parameters are supported. | | `managementLevel` | No | string[] | Personnel management-level filter, such as `vp` or `c_suite`. | | `department` | No | string[] | Personnel department filter, such as `marketing` or `sales`. | | `personnelLimit` | No | integer | Maximum nested personnel rows per exhibitor when available. Use `0` or omit for none. Treat nested personnel as best-effort; use `GET /external/personnel/list` with `event_id` and `exhibitor_id` when contacts are required. | ## Request example ```bash curl "https://platform.lensmor.com/external/exhibitors/list?event_id=139574&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ### Filter by company and personnel attributes ```bash curl "https://platform.lensmor.com/external/exhibitors/list?event_id=139574&industry=Retail%20Technology&department=sales&managementLevel=vp&personnelLimit=3&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "items": [ { "id": "456", "companyName": "Acme Retail Systems", "domain": "acme.example", "description": "Retail analytics platform", "website": "https://acme.example", "industry": "Retail Technology", "employeeCount": 220, "country": "United States", "logo": null, "dataSource": "local_import", "linkedinUrl": null, "fundingRound": "Series B", "matched_event_ids": ["139574"], "isRecommended": false, "recommendationRank": null, "matchStatus": null, "matchScore": null, "matchTier": null, "matchReason": null, "categories": ["Retail", "POS", "Analytics"], "techStacks": ["Cloudflare", "Nginx"], "buyingSignalTags": ["funding_round", "new_hire"], "buyingSignalStatus": "ready", "buyingSignals": [ { "signalTag": "funding_round", "signalDate": "2026-03-01", "sourceType": "news", "sourceTitle": "Acme raises $50M Series B", "sourceUrl": "https://techcrunch.com/...", "evidenceSummary": "Acme completed a $50M Series B round.", "relevanceToIcp": "Strong signal — company expanding headcount post-funding.", "recommendedPlay": "Reach out with a congratulatory opening.", "signalScore": 85, "confidence": "high", "directOrInferred": "direct", "analyzedAt": "1704067200000" } ] } ], "total": 87, "page": 1, "pageSize": 20, "totalPages": 5, "hasMore": true, "recommendationProcessing": false, "recommendationProcessingFeature": "none", "semantics": { "accessMode": "preview", "previewLimit": 50, "counts": { "actualTotal": 87, "visibleTotal": 50, "remainingLockedCount": 37 }, "pageState": { "requestedPage": 1, "accessible": true, "maxAccessiblePage": 1 }, "unlock": { "requiredForMoreResults": true, "actionType": "unlock_event_exhibitors", "credits": 2000 }, "guidance": { "code": "preview_results_truncated", "message": "This event is locked. Only the first 50 matching exhibitors are currently accessible. Unlock the event to access the remaining matching results." } } } ``` ## Response fields | Field | Description | | --- | --- | | `items` | Exhibitor records for the selected event and filters. | | `id` | Exhibitor identifier. Use it for exhibitor profile and related-event lookups. | | `companyName`, `domain`, `website` | Company identity and web presence fields. | | `description`, `industry`, `employeeCount`, `country` | Company enrichment metadata. | | `categories` | Exhibitor category labels as a string array (e.g. `["Artificial Intelligence", "Digital Health"]`). `[]` when none. | | `linkedinUrl`, `fundingRound`, `techStacks` | Additional enrichment signals when available. | | `buyingSignalTags` | Deduped buying-signal labels from the latest analysis batch. `[]` when none. | | `buyingSignalStatus` | Latest buying-signal batch state: `ready`, `processing`, `pending`, or `null`. | | `buyingSignals` | Full latest-batch buying-signal objects, each with `signalTag`, `signalDate`, `sourceType`, `sourceTitle`, `sourceUrl`, `evidenceSummary`, `relevanceToIcp`, `recommendedPlay`, `signalScore` (0–100), `confidence` (`high`/`medium`/`low`), `directOrInferred` (`direct`/`inferred`), `analyzedAt` (unix ms). `[]` when none. | | `matched_event_ids` | Event identifiers connected to the result. For event-scoped list calls, this reflects the requested event scope. | | `isRecommended`, `recommendationRank`, `matchStatus`, `matchScore`, `matchTier`, `matchReason` | Recommendation metadata when available. | | `recommendationProcessing` | Whether recommendation metadata is still being prepared. | | `recommendationProcessingFeature` | Which recommendation feature is processing. `"none"` when idle. | | `semantics` | Preview/full access metadata for the selected event. | ## Personnel samples `personnelLimit` asks the API to include a small personnel sample for each exhibitor when the service has a suitable sample for the current event, filters, and access state. It is not a guarantee that every exhibitor item will contain nested personnel rows. When your workflow needs buyer contacts for a specific company, call [Personnel list](/api-reference/personnel/list-event-personnel) with both `event_id` and `exhibitor_id`. Treat that event-scoped personnel response as the source of truth for people coverage and contact unlock state. ## Access semantics When an event is locked, this endpoint can return a preview slice instead of full results. Use `semantics` to explain the state to users: - `accessMode` indicates preview or full access. - `counts.visibleTotal` is the number of records currently visible. - `counts.remainingLockedCount` is the number of matching records hidden behind event unlock. - `pageState.accessible` indicates whether the requested page is visible under the current access state. - `unlock.requiredForMoreResults` tells you whether [Unlock event](/api-reference/events/unlock-event-access) is the next action. ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `429 Too Many Requests` ## Notes - This endpoint is event-scoped and requires `event_id` on every request. - `matched_event_ids` reflects the requested event scope. - `techStacks` is always returned as an array; when no data is available, the API returns `techStacks: []`. - Locked events can return preview results. Use `semantics.unlock` to decide whether to call [Unlock event](/api-reference/events/unlock-event-access). - After event unlock, request the target page again and confirm `semantics.accessMode` moved to `full` before assuming all matching exhibitors are available. --- ## Exhibitors search Source: /api-reference/exhibitors/search-exhibitors-by-company-context Search exhibitors using company context. Use this endpoint when you want exhibitor results derived from company inputs such as a public company URL or target audience description. ## When to use this endpoint Use `POST /external/exhibitors/search` when your integration starts from a company profile or buyer profile rather than a known event. Common use cases include: - Finding exhibitors similar to a target company. - Exploring companies that match a market or buyer description. - Creating a shortlist before checking which events those companies attend. - Powering a prospecting workflow where the user provides a website and asks "who should we look at?" If you already know the exact company name and want a precision-first lookup, use [Search exhibitors by company name](/api-reference/exhibitors/search-exhibitors-by-company-name). If you need events directly from a company name, use [Search exhibitor events](/api-reference/exhibitors/search-events-by-exhibitor-company-name). ## Endpoint `POST /external/exhibitors/search` ## Authentication See [Authentication](/authentication) ## Success status code `201 Created` ## Request body | Name | Required | Type | Notes | | --- | --- | --- | --- | | `company_url` | Conditionally | string | Company website URL used as an input hint. Provide at least one of `company_url` or `target_audience`. | | `target_audience` | Conditionally | string | Free-form audience description. Provide at least one of `company_url` or `target_audience`. | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `20`; maximum `100`. | ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/exhibitors/search" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"company_url":"https://acme.example","page":1,"pageSize":20}' ``` ### Search by audience description ```bash curl -X POST "https://platform.lensmor.com/external/exhibitors/search" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target_audience": "Retail operations leaders evaluating in-store analytics and workforce automation", "page": 1, "pageSize": 20 }' ``` ## Response example ```json { "items": [ { "id": "456", "companyName": "Acme Retail Systems", "domain": "acme.example", "description": "Retail analytics platform", "website": "https://acme.example", "industry": "Retail Technology", "employeeCount": 220, "country": "United States", "logo": null, "dataSource": "database", "linkedinUrl": null, "fundingRound": "Series B", "techStacks": ["Cloudflare", "Nginx"], "matched_event_ids": ["139574"], "buyingSignalTags": ["funding_round"], "buyingSignalStatus": null, "buyingSignals": [ { "signalTag": "funding_round", "signalDate": "2026-03-01", "sourceType": "news", "sourceTitle": "Acme raises $50M Series B", "sourceUrl": "https://techcrunch.com/...", "evidenceSummary": "Acme completed a $50M Series B round.", "relevanceToIcp": "Strong signal — company expanding headcount post-funding.", "recommendedPlay": "Reach out with a congratulatory opening.", "signalScore": 85, "confidence": "high", "directOrInferred": "direct", "analyzedAt": "1704067200000" } ] } ], "total": 87, "page": 1, "pageSize": 20, "totalPages": 5, "hasMore": true } ``` ## Response fields | Field | Description | | --- | --- | | `items` | Matching exhibitor records for the requested page. | | `id` | Lensmor exhibitor identifier. | | `companyName` | Company or exhibitor display name. | | `domain`, `website` | Company domain and website URL when available. | | `description` | Short company description. | | `industry`, `employeeCount`, `country` | Enrichment fields that may be missing for some records. | | `linkedinUrl` | Company LinkedIn URL when available. | | `fundingRound` | Funding stage or funding metadata when available. | | `techStacks` | Technology stack signals. Always returned as an array. | | `matched_event_ids` | Related event identifiers from the search context. | | `buyingSignalTags` | Deduped buying-signal labels from the latest analysis batch. `[]` when none. | | `buyingSignalStatus` | Always `null` on this endpoint (no event-scoped task state). | | `buyingSignals` | Full latest-batch buying-signal objects (see [Exhibitors list](/api-reference/exhibitors/list-event-exhibitors) for the object shape). `[]` when none. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | ## Matching behavior This endpoint is profile-style search. It can return useful results even when the input is a company website or natural-language audience description rather than an exact exhibitor name. No matches are represented as a successful empty paginated response: ```json { "items": [], "total": 0, "page": 1, "pageSize": 20, "totalPages": 0, "hasMore": false } ``` ## Error responses - `400 Bad Request` - `401 Unauthorized` - `429 Too Many Requests` ## Notes - This endpoint uses `POST` because the request body contains structured profile inputs (URLs, audience descriptions) that do not fit cleanly in query parameters. It does not create a persistent resource. - This endpoint accepts profile-style search inputs rather than a simple keyword-only query. - The live service returns `matched_event_ids` as an empty array for this heuristic search. - `techStacks` is always returned as an array; when no data is available, the API returns `techStacks: []`. - `buyingSignalStatus` is always `null` on this endpoint; `buyingSignalTags` and `buyingSignals` still reflect the latest analysis batch and are `[]` when none. - No matches return an empty paginated success response rather than `404 Not Found`. - If you need deduplicated event results directly from a company name, use `POST /external/exhibitors/search-events`. --- ## Exhibitor company search Source: /api-reference/exhibitors/search-exhibitors-by-company-name Search exhibitors by company name using a precision-first matcher. Use this endpoint when you have a company name and want matching exhibitor records, not event results. ## When to use this endpoint Use `POST /external/exhibitors/search-by-company-name` when the user enters a company name and expects company records back. Choose this endpoint when: - the input is a company name, not a website or ICP description - you need exhibitor IDs for follow-up profile calls - you want an empty result instead of broad fuzzy matches when the name is ambiguous Use [Exhibitor event search](/api-reference/exhibitors/search-events-by-exhibitor-company-name) instead when the desired output is events related to a company name. This endpoint consumes `50` credits only when the response contains at least one exhibitor. An empty result does not consume credits. Check [Credits balance](/api-reference/credits/get-credits-balance) and confirm with the user before calling it in a credit-safe workflow. ## Endpoint `POST /external/exhibitors/search-by-company-name` ## Authentication See [Authentication](/authentication) ## Success status code `201 Created` ## Request body | Name | Required | Type | Notes | | --- | --- | --- | --- | | `company_name` | Yes | string | Company name input, length `1` to `200`. | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `20`; maximum `100`. | ## Headers | Name | Required | Type | Notes | | --- | --- | --- | --- | | `x-call-source` | No | string | Optional usage source. Use `api` or `agent`; defaults to `api`. | ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/exhibitors/search-by-company-name" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -H "x-call-source: api" \ -d '{"company_name":"Acme","page":1,"pageSize":20}' ``` ## Response example ```json { "items": [ { "id": "456", "companyName": "Acme Retail Systems", "domain": "acme.example", "description": "Retail analytics platform", "website": "https://acme.example", "industry": "Retail Technology", "employeeCount": 220, "country": "United States", "logo": null, "dataSource": "database", "linkedinUrl": null, "fundingRound": "Series B", "matched_event_ids": [], "isRecommended": false, "recommendationRank": null, "matchStatus": null, "matchScore": null, "matchTier": null, "matchReason": null, "techStacks": ["Cloudflare", "Nginx"], "buyingSignalTags": [], "buyingSignalStatus": null, "buyingSignals": [] } ], "total": 1, "page": 1, "pageSize": 20, "totalPages": 1, "hasMore": false } ``` ## Response fields | Field | Description | | --- | --- | | `items` | Matching exhibitor records. | | `id` | Exhibitor identifier. | | `companyName`, `domain`, `website` | Company identity fields. | | `description`, `industry`, `employeeCount`, `country` | Company metadata. | | `linkedinUrl`, `fundingRound`, `techStacks` | Optional enrichment fields. | | `matched_event_ids` | Related event identifiers when available. | | `isRecommended`, `recommendationRank`, `matchStatus`, `matchScore`, `matchTier`, `matchReason` | Recommendation metadata when available. | | `buyingSignalTags` | Deduped buying-signal labels from the latest analysis batch. `[]` when none. | | `buyingSignalStatus` | Always `null` on this endpoint (no event-scoped task state). | | `buyingSignals` | Full latest-batch buying-signal objects (see [Exhibitors list](/api-reference/exhibitors/list-event-exhibitors) for the object shape). `[]` when none. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | ## Credit behavior This endpoint consumes `50` credits after the company-name matcher finds at least one exhibitor. A successful empty response consumes `0` credits. If a non-empty search cannot spend the required credits, the API returns `402 Payment Required` instead of returning the matched records. Refresh [Credits balance](/api-reference/credits/get-credits-balance) after successful searches when your UI or audit log needs final credit reconciliation. ## Matching behavior This endpoint is precision-first. It is designed to avoid surprising broad matches for short or ambiguous company names. If no company passes matching rules, the API returns a successful empty paginated response. ## Error responses - `400 Bad Request` - `401 Unauthorized` - `402 Payment Required` - `429 Too Many Requests` ## Notes - This endpoint uses `POST` because company name matching may evolve to accept additional body parameters. It does not create a persistent resource. - No matches return an empty paginated success response rather than `404 Not Found`, and do not consume credits. - If you need event results for a company name, use `POST /external/exhibitors/search-events`. --- ## Exhibitor event search Source: /api-reference/exhibitors/search-events-by-exhibitor-company-name Search events directly from an exhibitor company name using a precision-first matcher. Use this endpoint when you have a company name, possibly incomplete, and want deduplicated event results without resolving `exhibitor_id` first. ## When to use this endpoint Use `POST /external/exhibitors/search-events` when your user asks "which events is this company associated with?" This endpoint returns events, not exhibitor records. It is useful for: - account planning from a company name - finding events where a target account appears as an exhibitor - enriching CRM accounts with event participation - starting an event workflow without first calling exhibitor search This endpoint can consume credits. Check [Credits balance](/api-reference/credits/get-credits-balance) before running it in bulk, and use a confirmation step when a user-facing workflow will spend credits. ## Endpoint `POST /external/exhibitors/search-events` ## Authentication See [Authentication](/authentication) ## Success status code `201 Created` ## Request body | Name | Required | Type | Notes | | --- | --- | --- | --- | | `company_name` | Yes | string | Company name input. Incomplete names are allowed, but matching stays precision-first. | | `sponsor_match_starred` | No | integer | `1` returns starred Sponsor Match events, `0` returns non-starred events; omit to include both. | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `20`; maximum `100`. | ## Headers | Name | Required | Type | Notes | | --- | --- | --- | --- | | `x-call-source` | No | string | Optional usage source. Use `api` or `agent`; defaults to `api`. | ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/exhibitors/search-events" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -H "x-call-source: api" \ -d '{"company_name":"Acme","sponsor_match_starred":1,"page":1,"pageSize":20}' ``` ## Response example ```json { "items": [ { "id": "123", "eventId": "139574", "name": "Shoptalk 2026", "nickname": null, "description": null, "url": null, "dateStart": "2026-03-15", "dateEnd": "2026-03-18", "venue": null, "city": "Las Vegas", "region": "Nevada", "country": "United States", "exhibitorCount": 100, "sponsorMatchStarred": 1, "image": null, "dataSource": "Lensmor", "matchedExhibitors": [ { "id": "456", "companyName": "Shoptalk" } ] } ], "total": 1, "page": 1, "pageSize": 20, "totalPages": 1, "hasMore": false } ``` ## Response fields | Field | Description | | --- | --- | | `items` | Events associated with matched exhibitors. | | `id`, `eventId` | Event identifiers returned with each event. | | `name`, `nickname`, `description`, `url` | Event display and source fields. | | `dateStart`, `dateEnd` | Event date range. | | `venue`, `city`, `region`, `country` | Event location fields. Values can be `null` or empty strings when the event source does not provide normalized location metadata. | | `exhibitorCount` | Exhibitor coverage count when available. | | `sponsorMatchStarred` | Sponsor Match starred state (`0` or `1`). | | `dataSource` | Always `Lensmor` for public event records. | | `matchedExhibitors` | Matched company records that connect the input to the returned event. Treat this as the primary evidence that the company-name input matched the returned event. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | ## Credit behavior This endpoint consumes `50` credits after the company-name matcher finds at least one event. A successful empty response consumes `0` credits. If a non-empty search cannot spend the required credits, the API returns `402 Payment Required` instead of returning the matched records. Refresh [Credits balance](/api-reference/credits/get-credits-balance) after successful searches when your UI or audit log needs final credit reconciliation. Use the returned event count, page count, and `matchedExhibitors` as the main value evidence for the search result. ## Concurrency limit Only one call to this endpoint can run concurrently for the same user. When the slot is occupied, the service returns HTTP `200` with `code: 429` and `errorKey: "USER_CONCURRENCY_LIMIT_EXCEEDED"`. Inspect the response body even though the normal successful search uses `201 Created`, and retry only after the in-flight call finishes. ## Error responses - `400 Bad Request` - `401 Unauthorized` - `402 Payment Required` - `429 Too Many Requests` ## Notes - This endpoint is separate from `GET /external/exhibitors/events`, which requires a known `exhibitor_id`. - This endpoint consumes `50` credits only for a non-empty result. See [Credits and access](/concepts/credits-and-access). - Compact-normalized queries shorter than `3` characters use exact-only matching after case, whitespace, and connector-punctuation normalization. - Longer queries use strict token or prefix admission instead of permissive `contains` matching. - No matches return an empty paginated success response rather than `404 Not Found`, and do not consume credits. - `matchedExhibitors` contains only the admitted matched exhibitors that actually participate in the returned event, and event results are returned in deterministic recency order. --- ## Exhibitor profile details Source: /api-reference/exhibitors/get-exhibitor-profile Fetch the profile for a single exhibitor. Use this endpoint when you already know the exhibitor identifier and need a profile-style response with related event references. ## When to use this endpoint Use `GET /external/exhibitors/profile` after an exhibitor has been selected from an event list, company search, or recommendation result. This endpoint is best for showing a company profile card and retrieving the compact event references embedded on that profile. Use [Exhibitor related events](/api-reference/exhibitors/list-exhibitor-related-events) if you need a paginated event list. ## Endpoint `GET /external/exhibitors/profile` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `exhibitor_id` | Yes | string | Numeric-string exhibitor identifier. | ## Request example ```bash curl "https://platform.lensmor.com/external/exhibitors/profile?exhibitor_id=456" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "id": "456", "companyName": "Acme Retail Systems", "domain": "acme.example", "description": "Retail analytics platform", "website": "https://acme.example", "industry": "Retail Technology", "employeeCount": 220, "country": "United States", "logo": null, "dataSource": "database", "linkedinUrl": null, "fundingRound": "Series B", "matched_event_ids": [], "events": [ { "id": "123", "eventId": "139574", "name": "NRF 2026" } ] } ``` ## Response fields | Field | Description | | --- | --- | | `id` | Exhibitor identifier. | | `companyName`, `domain`, `website` | Company identity and web presence fields. | | `description`, `industry`, `employeeCount`, `country` | Company metadata. | | `logo`, `linkedinUrl`, `fundingRound` | Optional enrichment fields. | | `matched_event_ids` | Event identifiers connected to the exhibitor when available. | | `events` | Compact related event references. | | `events[].eventId` | Public event identifier to use when another endpoint asks for `event_id`. | ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `429 Too Many Requests` ## Notes - This endpoint currently supports `exhibitor_id` lookup. - In embedded event objects, the event identifier is returned as `eventId`. Use that value when another endpoint asks for `event_id`. - Older examples that imply `company_name` lookup should not be treated as current behavior. --- ## Exhibitor related events Source: /api-reference/exhibitors/list-exhibitor-related-events List events related to a single exhibitor. Use this endpoint when you want the event history or current event associations for one exhibitor profile. ## When to use this endpoint Use `GET /external/exhibitors/events` when you already have an `exhibitor_id` and need a paginated event list for that company. If you only have a company name, use [Exhibitor event search](/api-reference/exhibitors/search-events-by-exhibitor-company-name). If you need the company profile first, use [Exhibitor profile details](/api-reference/exhibitors/get-exhibitor-profile). ## Endpoint `GET /external/exhibitors/events` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `exhibitor_id` | Yes | string | Numeric-string exhibitor identifier. | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `20`; maximum `100`. | ## Request example ```bash curl "https://platform.lensmor.com/external/exhibitors/events?exhibitor_id=456&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "items": [ { "id": "123", "eventId": "139574", "name": "NRF 2026", "nickname": null, "description": "Retail industry event", "url": "https://example.com/events/nrf-2026", "dateStart": "2026-01-12", "dateEnd": "2026-01-15", "venue": "Javits Center", "city": "New York", "region": "NY", "country": "United States", "exhibitorCount": 950, "sponsorMatchStarred": 0, "image": null, "dataSource": "Lensmor" } ], "total": 12, "page": 1, "pageSize": 20, "totalPages": 1, "hasMore": false } ``` ## Response fields | Field | Description | | --- | --- | | `items` | Events associated with the exhibitor. | | `id`, `eventId` | Event identifiers returned with each event. | | `name`, `nickname`, `description`, `url` | Event display and source fields. | | `dateStart`, `dateEnd` | Event date range. | | `venue`, `city`, `region`, `country` | Location metadata. | | `exhibitorCount` | Known exhibitor count when available. | | `sponsorMatchStarred` | Sponsor Match starred state (`0` or `1`). | | `image` | Event image URL when available. | | `dataSource` | Always `Lensmor` for public event records. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `429 Too Many Requests` ## Notes - Event items reuse the standard event-item response shape. - Pagination behavior follows the shared [Pagination conventions](/concepts/pagination). - This endpoint requires `exhibitor_id`; if you only have `company_name`, use `POST /external/exhibitors/search-events`. --- ## Personnel list Source: /api-reference/personnel/list-event-personnel List personnel records for a specific event. Use this endpoint when you want a paginated people directory for one event, with optional exhibitor, department, level, source-type, and search filters. Each item can include event-scoped Exhibitor, Social Signals, and Visitor source labels. ## When to use this endpoint Use `GET /external/personnel/list` after selecting an event and, optionally, an exhibitor. It is the main endpoint for building attendee or exhibitor-personnel exploration workflows. Common use cases: - browse people associated with an event - filter by department or seniority - filter by one or more event-scoped `sourceType` labels - find contacts for a selected exhibitor - segment accessible attendees by their event-scoped `sourceType` labels - collect personnel IDs before calling [Unlock contact emails](/api-reference/contacts/unlock-contact-emails) `sourceType` filtering is event-scoped. Use one or more of `exhibitor`, `social`, or `visitors`, either comma-separated or as repeated query parameters. Labels are still non-exclusive on returned records. See [Attendee source types](/concepts/attendee-source-types). ## Endpoint `GET /external/personnel/list` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `event_id` | Yes | string | Event identifier. | | `exhibitor_id` | No | string | Filter by a specific exhibitor. | | `department` | No | string | Department filter. | | `level` | No | string | Seniority-level filter. | | `search_query` | No | string | Name or job-title search. | | `sourceType` | No | string | One or more source labels. Supported values: `exhibitor`, `social`, `visitors`; send comma-separated values or repeat the parameter. | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `50`; maximum `50`. | ## Request example ```bash curl "https://platform.lensmor.com/external/personnel/list?event_id=139574&page=1&pageSize=50" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ### Filter by role context ```bash curl "https://platform.lensmor.com/external/personnel/list?event_id=139574&department=marketing&level=vp&search_query=partnership&page=1&pageSize=50" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ### Filter by attendee source ```bash curl "https://platform.lensmor.com/external/personnel/list?event_id=139574&sourceType=visitors&page=1&pageSize=50" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` To match either Social Signals or Visitor records in one request, use `sourceType=social,visitors`. Repeated `sourceType` query parameters are normalized the same way. ## Response example ```json { "items": [ { "id": "789", "fullName": "Jane Smith", "title": "VP of Retail Technology", "department": "Technology", "seniorityLevel": "vp", "linkedinUrl": "https://linkedin.com/in/jane-smith", "companyName": "Acme Retail Systems", "sourceType": ["exhibitor", "social"], "email": null, "phone": null, "contactUnlockStatus": "locked", "phoneUnlockStatus": null, "linkedinActivity": { "activityLevel": "ACTIVE", "isActive": true, "latestPostAt": "1747123200000" }, "linkedinActivityStatus": "ready", "eventCount": 5, "outreachMessageStatus": "ready", "outreachMessageChannels": ["email", "linkedin_message"] } ], "total": 3, "page": 1, "pageSize": 50, "totalPages": 1, "hasMore": false, "semantics": { "accessMode": "preview", "previewLimit": 50, "counts": { "actualTotal": 3, "visibleTotal": 3, "remainingLockedCount": 0 }, "pageState": { "requestedPage": 1, "accessible": true, "maxAccessiblePage": 1 }, "unlock": { "requiredForMoreResults": false, "actionType": null, "credits": null }, "guidance": { "code": "preview_complete_for_query", "message": "This event is locked, but all matching personnel for the current query are already accessible." } } } ``` ## Response fields | Field | Description | | --- | --- | | `items` | Personnel records for the requested event and filters. | | `id` | Personnel identifier. Use this for profile fetches and email unlocks. | | `fullName`, `title` | Person display name and job title. | | `department`, `seniorityLevel` | Normalized role metadata when available. | | `linkedinUrl` | LinkedIn profile URL when available. | | `companyName` | Associated company or exhibitor name. | | `sourceType` | Event-scoped, non-exclusive source labels. Product mapping: Exhibitor = `exhibitor`, Social Signals = `social`, Visitor = `visitors`. An empty array means no supported source label is available. | | `email` | Email address if already unlocked for the caller; otherwise `null`. | | `phone` | Phone number if already unlocked for the caller; otherwise `null`. | | `contactUnlockStatus` | Contact access state, such as `locked` or unlocked states. | | `phoneUnlockStatus` | `ready`, `processing`, `failed`, or `null`. | | `linkedinActivity` | LinkedIn activity object (`activityLevel` `ACTIVE`/`WARM`/`DORMANT`/`""`, `isActive`, `latestPostAt` unix ms or `null`) when the caller has unlocked it and analysis is complete; otherwise `null`. Independent of `contactUnlockStatus`. | | `linkedinActivityStatus` | `ready` (data available), `processing` (unlock/analysis running), or `null` (not unlocked / no record). | | `eventCount` | Number of events associated with the personnel record. | | `outreachMessageStatus` | Current outreach generation status when a record exists. | | `outreachMessageChannels` | Channels associated with the latest outreach record. | | `semantics` | Access metadata for preview/full event coverage. | ## Access semantics Locked events can still return preview personnel results. Read `semantics` before deciding whether to unlock the event: - `accessMode` tells you whether base event coverage is preview or full access. It does not describe the separate Visitor access layer. - `counts.remainingLockedCount` tells you whether more records are hidden. - `unlock.requiredForMoreResults` tells you whether event unlock is useful for this query. - `guidance.message` provides a user-facing explanation. ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `429 Too Many Requests` ## Notes - Personnel list responses intentionally use the lightweight contact-style response shape. - A person can have multiple source labels. Preserve every value rather than assigning one exclusive category. - Visitor records are registered attendees obtained through organizer or data-provider partnerships. Visitor availability and access must be checked separately. - Emails are returned only when the contact is already unlocked for the caller. - Locked events can return preview results. Use `semantics.unlock` to decide whether to call [Unlock event](/api-reference/events/unlock-event-access). - Pagination behavior follows the shared [Pagination conventions](/concepts/pagination). --- ## Personnel profile details Source: /api-reference/personnel/get-personnel-profile Fetch the profile for a single person. Use this endpoint when you already know the personnel identifier and need profile details for that person. ## When to use this endpoint Use `GET /external/personnel/profile` after a user selects a person from a personnel list, contact search result, or LinkedIn-based event lookup. This endpoint is intentionally lightweight. Use it to confirm the person's identity, aggregated source labels, and current email unlock state before starting a contact unlock workflow. ## Endpoint `GET /external/personnel/profile` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `personnel_id` | Yes | string | Numeric-string personnel identifier. | ## Request example ```bash curl "https://platform.lensmor.com/external/personnel/profile?personnel_id=789" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "id": "789", "fullName": "Jane Smith", "title": "VP of Retail Technology", "department": "Technology", "seniorityLevel": "vp", "linkedinUrl": "https://linkedin.com/in/jane-smith", "companyName": "Acme Retail Systems", "sourceType": ["exhibitor", "visitors"], "email": null, "phone": null, "contactUnlockStatus": "locked", "phoneUnlockStatus": null, "eventCount": 5 } ``` ## Response fields | Field | Description | | --- | --- | | `id` | Personnel identifier. | | `fullName`, `title` | Person display fields. | | `department`, `seniorityLevel` | Normalized role metadata when available. | | `linkedinUrl` | Public LinkedIn profile URL when available. | | `companyName` | Associated company or exhibitor. | | `sourceType` | Non-exclusive source labels aggregated across the person's associated events. Product mapping: Exhibitor = `exhibitor`, Social Signals = `social`, Visitor = `visitors`. | | `email` | Email address if already unlocked for the caller; otherwise `null`. | | `phone` | Phone number if already unlocked for the caller; otherwise `null`. | | `contactUnlockStatus` | Current contact access state for this API key owner. | | `phoneUnlockStatus` | `ready`, `processing`, `failed`, or `null`. | | `eventCount` | Number of associated events. | ## Email access An empty `email` value does not necessarily mean Lensmor has no email data. It can also mean the contact has not been unlocked for the caller. Use `contactUnlockStatus` to decide whether to offer an email unlock action. ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `429 Too Many Requests` ## Notes - This response stays intentionally lightweight. - Use [Personnel list](/api-reference/personnel/list-event-personnel) when you need source labels for one specific event. Profile labels can reflect multiple associated events. - See [Attendee source types](/concepts/attendee-source-types) for label meanings and Visitor access behavior. - Emails are returned only when the contact is already unlocked for the caller. - The response includes `linkedinActivity` and `linkedinActivityStatus` for shape consistency, but they are always `null` on this endpoint. Only [Personnel list](/api-reference/personnel/list-event-personnel) populates LinkedIn activity data. --- ## Personnel related events Source: /api-reference/personnel/list-personnel-related-events List events related to a single person. Use this endpoint when you want the event associations for one personnel profile in a paginated event-item response. ## When to use this endpoint Use `GET /external/personnel/events` when you already have a `personnel_id` and want to understand which events are associated with that person. If your starting point is a LinkedIn URL rather than a Lensmor personnel ID, use [Personnel events by LinkedIn](/api-reference/personnel/list-personnel-related-events-by-linkedin-url). ## Endpoint `GET /external/personnel/events` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `personnel_id` | Yes | string | Numeric-string personnel identifier. | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `20`; maximum `100`. | ## Request example ```bash curl "https://platform.lensmor.com/external/personnel/events?personnel_id=789&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "items": [ { "id": "123", "eventId": "139574", "name": "NRF 2026", "nickname": null, "description": "Retail industry event", "url": "https://example.com/events/nrf-2026", "dateStart": "2026-01-12", "dateEnd": "2026-01-15", "venue": "Javits Center", "city": "New York", "region": "NY", "country": "United States", "exhibitorCount": 950, "sponsorMatchStarred": 0, "image": null, "dataSource": "Lensmor" } ], "total": 5, "page": 1, "pageSize": 20, "totalPages": 1, "hasMore": false } ``` ## Response fields | Field | Description | | --- | --- | | `items` | Events associated with the selected person. | | `id`, `eventId` | Event identifiers returned with each event. | | `name`, `nickname`, `description`, `url` | Event display and source fields. | | `dateStart`, `dateEnd` | Event date range. | | `venue`, `city`, `region`, `country` | Location metadata. | | `exhibitorCount` | Known exhibitor count when available. | | `sponsorMatchStarred` | Sponsor Match starred state (`0` or `1`). | | `image` | Event image URL when available. | | `dataSource` | Always `Lensmor` for public event records. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `429 Too Many Requests` ## Notes - Event items reuse the standard event-item response shape. - Pagination behavior follows the shared [Pagination conventions](/concepts/pagination). --- ## Personnel events by LinkedIn Source: /api-reference/personnel/list-personnel-related-events-by-linkedin-url Find a person by LinkedIn URL and list related events. Use this endpoint when your workflow starts from a LinkedIn profile URL instead of a Lensmor `personnel_id`. ## When to use this endpoint Use `GET /external/personnel/events/by-linkedin` when the only stable identifier you have is a LinkedIn profile URL. Common use cases: - browser extension workflow from a LinkedIn profile - CRM enrichment from stored LinkedIn URLs - discovering events connected to a person before deciding whether to unlock contact details - resolving a person to Lensmor `personnel.id` ## Endpoint `GET /external/personnel/events/by-linkedin` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `linkedin_url` | Yes | string | LinkedIn profile URL used for normalized matching. | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `20`; maximum `100`. | ## Request example ```bash curl "https://platform.lensmor.com/external/personnel/events/by-linkedin?linkedin_url=https%3A%2F%2Fwww.linkedin.com%2Fin%2Fjane-smith%2F" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "personnel": { "id": "789", "fullName": "Jane Smith", "title": "VP of Retail Technology", "department": "Technology", "seniorityLevel": "vp", "linkedinUrl": "https://www.linkedin.com/in/jane-smith/", "companyName": "Acme Retail Systems", "sourceType": ["exhibitor"], "email": null, "phone": null, "contactUnlockStatus": "locked", "phoneUnlockStatus": null, "eventCount": 5 }, "events": { "items": [ { "id": "123", "eventId": "139574", "name": "NRF 2026", "nickname": null, "description": "Retail industry event", "url": "https://example.com/events/nrf-2026", "dateStart": "2026-01-12", "dateEnd": "2026-01-15", "venue": "Javits Center", "city": "New York", "region": "NY", "country": "United States", "exhibitorCount": 950, "sponsorMatchStarred": 0, "image": null, "dataSource": "Lensmor" } ], "total": 1, "page": 1, "pageSize": 20, "totalPages": 1, "hasMore": false } } ``` ## Response fields | Field | Description | | --- | --- | | `personnel` | Matched person record using the standard contact-style response shape. | | `personnel.id` | Personnel identifier for profile and email unlock workflows. | | `personnel.email` | Email if already unlocked for the caller; otherwise `null`. | | `personnel.phone` | Phone number if already unlocked for the caller; otherwise `null`. | | `personnel.contactUnlockStatus` | Current contact access state. | | `personnel.phoneUnlockStatus` | `ready`, `processing`, `failed`, or `null`. | | `personnel.eventCount` | Number of associated events. | | `events` | Paginated event results connected to the matched person. | | `events.items[].eventId` | Public event identifier for event-scoped follow-up calls. | | `events.items[].sponsorMatchStarred` | Sponsor Match starred state (`0` or `1`). | | `events.items[].dataSource` | Always `Lensmor` for public event records. | | `events.total`, `events.page`, `events.pageSize`, `events.totalPages`, `events.hasMore` | Pagination metadata. | ## URL normalization Send the full LinkedIn URL and URL-encode it in the query string. The API performs normalized matching internally, but clients should avoid sending partial names or non-LinkedIn URLs. ## Latency and fallback LinkedIn URL matching can take longer than direct Lensmor ID lookups because the API normalizes the URL and resolves the person before listing events. Recommended production behavior: - Use a request timeout that keeps the user workflow responsive. - If you already have `personnel.id`, prefer [Personnel events](/api-reference/personnel/list-personnel-related-events) with `personnel_id`. - If URL lookup times out or returns no match, keep the workflow usable by continuing with event-scoped personnel search, a selected `personnel_id`, or a retry action. - Do not block a whole lead-prioritization workflow on one slow LinkedIn URL lookup. Mark that person as unresolved and continue ranking the rest of the batch. ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `429 Too Many Requests` ## Notes - The returned `personnel` object uses the same contact-style response shape as personnel profile responses. - Emails are returned only when the contact is already unlocked for the caller. - The `personnel` object includes `linkedinActivity` and `linkedinActivityStatus` for shape consistency, but they are always `null` on this endpoint. Only [Personnel list](/api-reference/personnel/list-event-personnel) populates LinkedIn activity data. --- ## Personnel events by name Source: /api-reference/personnel/search-personnel-related-events-by-name Find events associated with a person when you know their full name but do not have a Lensmor `personnel_id` or LinkedIn URL. This endpoint returns the combined, deduplicated events for exact-name personnel matches. The response is a paginated event list; it does not include a separate `personnel` object. ## Endpoint `GET /external/personnel/events/by-name` ## Authentication and access See [Authentication](/authentication). The API-key owner must have an active paid subscription. ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `person_name` | Yes | string | Exact full name, length `1` to `200`. Matching is case-insensitive and leading/trailing whitespace is ignored. Partial and fuzzy matching are not used. | | `date_start_from` | No | date | Inclusive lower bound on event start date. Defaults to the current date, so the default result contains upcoming events. | | `date_start_to` | No | date | Inclusive upper bound on event start date. Omit for no upper bound. | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `20`; maximum `100`. This paginates events, not personnel candidates. | | `x-call-source` | No | header | Usage source, `api` or `agent`; defaults to `api`. | ## Request example ```bash curl "https://platform.lensmor.com/external/personnel/events/by-name?person_name=Jane%20Smith&date_start_from=2026-08-10&date_start_to=2026-12-31&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "x-call-source: api" ``` ## Response example ```json { "items": [ { "id": "501", "eventId": "139574", "name": "Retail Technology Expo 2026", "nickname": null, "description": "Retail technology event", "url": "https://example.com/events/retail-technology-expo-2026", "dateStart": "2026-10-01", "dateEnd": "2026-10-03", "venue": "Las Vegas Convention Center", "city": "Las Vegas", "region": "NV", "country": "United States", "exhibitorCount": 950, "hasVisitors": true, "sponsorMatchStarred": 0, "image": null, "dataSource": "Lensmor" } ], "total": 1, "page": 1, "pageSize": 20, "totalPages": 1, "hasMore": false } ``` ## Matching and result behavior - The server considers up to `50` personnel records with the exact full name. This candidate window is fixed and independent of event `pageSize`. - Events from those exact-name records are combined and deduplicated, then ordered by `dateStart` ascending. - Both date bounds apply inclusively to `dateStart`. Set an earlier `date_start_from` to include historical events. - No personnel match or no qualifying event returns `200 OK` with `items: []`, `total: 0`, `totalPages: 0`, and `hasMore: false`. ## Credits and precheck A non-empty event result consumes `50` credits. An empty result consumes `0` credits. If the result is non-empty but the balance is insufficient, the API returns `402 Payment Required` instead of the event page. Call [Actions precheck](/api-reference/actions/precheck-an-external-action) first with `action_type: "search_personnel_events_by_name"`. Put `person_name` and any date bounds in `params`. Precheck quotes `50` credits for a valid name because it does not execute the lookup; the execution endpoint charges only for a non-empty result. ## Concurrency limit Each user can run up to `10` calls to this endpoint concurrently. When that limit is exceeded, the current Business API contract returns HTTP `200` with a business-error body: ```json { "code": 429, "message": "Too many concurrent requests for this endpoint", "errorKey": "USER_CONCURRENCY_LIMIT_EXCEEDED", "data": null, "details": null, "traceId": "trace-123" } ``` Treat this body as an error and retry after one or more in-flight requests finish. This differs from the ordinary HTTP `429 Too Many Requests` rate-limit response. ## Paid-feature business error For a free user, the normal guard returns HTTP `200` with `code: 400` and `errorKey: "USER_HAS_NO_FEATURE"`. Treat it as a failed lookup and prompt for an eligible subscription. A `403` `NO_ACTIVE_SUBSCRIPTION` response remains possible from the API-key guard fallback path. ## Error responses - `400 Bad Request` for a missing, blank, overlong, or invalid query value - `401 Unauthorized` - `402 Payment Required` for insufficient credits on a non-empty lookup - `403 Forbidden` from the API-key subscription fallback path - `429 Too Many Requests` for ordinary rate limiting ## Notes - For the most precise identity lookup, use [Personnel related events](/api-reference/personnel/list-personnel-related-events) when you already have a `personnel_id`, or [Personnel events by LinkedIn](/api-reference/personnel/list-personnel-related-events-by-linkedin-url) when you have a LinkedIn URL. - Event items use the standard public event shape. `dataSource` is Always `Lensmor`; `sponsorMatchStarred` is `0` or `1`; `hasVisitors` signals Visitor-data availability and does not prove access is unlocked. --- ## Unlock LinkedIn activity Source: /api-reference/personnel/unlock-linkedin-activity Unlock LinkedIn activity visibility for one or more personnel records, optionally in an event context. Use this endpoint when your integration already has personnel IDs and wants to expose LinkedIn activity metadata such as `linkedinActivity` and `linkedinActivityStatus`. ## Endpoint `POST /external/personnel/unlock-linkedin-activity` ## Authentication See [Authentication](/authentication) ## Success status code `201 Created` ## Request body | Name | Required | Type | Notes | | --- | --- | --- | --- | | `personnel_ids` | Yes | string[] | One to `2000` personnel IDs. | | `event_id` | No | string | Optional event identifier. When omitted, event membership validation is skipped. | ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/personnel/unlock-linkedin-activity" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "event_id": "26855", "personnel_ids": ["789", "790"] }' ``` ## Response example ```json { "items": [ { "personnel_id": "789", "success": true, "alreadyUnlocked": false, "taskId": null, "error": null }, { "personnel_id": "790", "success": false, "alreadyUnlocked": false, "taskId": null, "error": "Personnel has no LinkedIn URL" } ] } ``` ## Response fields | Field | Description | | --- | --- | | `items` | Per-personnel unlock or task result. | | `personnel_id` | The personnel ID from the request. | | `success` | Whether this personnel item was accepted or already available. | | `alreadyUnlocked` | `true` when the caller already has an active or completed LinkedIn activity unlock for this personnel record. | | `taskId` | Decimal numeric child-task ID for existing in-progress work. A newly accepted item commonly returns `null` while child-task dispatch is queued; it is also `null` when data already exists or the item failed. | | `error` | Item-level failure message. `null` on success. | ## Notes - This endpoint is idempotent for personnel records with active or completed LinkedIn activity unlocks. - If LinkedIn activity data already exists, the API can mark the record as unlocked without creating a new task. - If activity data does not exist, the API reserves asynchronous work. The initial accepted item can return `taskId: null`; observe completion through refreshed personnel data rather than requiring an immediate child-task ID. - When `event_id` is omitted, the service processes valid personnel IDs without checking their membership in a specific event. - This endpoint does not unlock contact emails. Use [Unlock contact emails](/api-reference/contacts/unlock-contact-emails) for email unlock workflows. - Poll [Personnel list](/api-reference/personnel/list-event-personnel) or [Personnel profile](/api-reference/personnel/get-personnel-profile) to observe `linkedinActivityStatus` and `linkedinActivity` after processing. ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `422 Unprocessable Entity` - `429 Too Many Requests` --- ## Generate outreach messages Source: /api-reference/personnel/generate-outreach-messages Generate AI-powered personalized outreach messages for one or more personnel, optionally using event context. Use this endpoint when your integration needs AI-generated cold outreach content tailored to each contact's role, company, and event participation. ## When to use this endpoint Use `POST /external/personnel/generate-outreach-message` when you want to: - Generate personalized cold emails for event contacts - Create LinkedIn connection messages referencing shared event context - Batch-produce outreach content for multiple contacts at once This endpoint is asynchronous. Each personnel item may create a background task. Use [Get outreach message detail](/api-reference/personnel/get-outreach-message-detail) to retrieve the generated content once processing completes. Channels must be one or more of: `email`, `linkedin_message`. When requesting LinkedIn output, use `linkedin_message_types` to choose `inmail`, `connection_note`, and/or `connected_message`. When `event_id` is supplied, this endpoint currently requires the numeric internal event `id` field, not public `eventId`. The service converts it directly to `BigInt` and does not run the usual event identifier resolver. ## Request body | Name | Required | Type | Notes | | --- | --- | --- | --- | | `personnel_ids` | Yes | string[] | Maximum `2000` personnel IDs. Runtime validation permits an empty array, but useful generation requires at least one ID. | | `event_id` | No | string | Optional numeric internal event `id`. Do not pass public `eventId`. When omitted, generation does not validate event membership or use event context. | | `channels` | Yes | string[] | One or both of `email`, `linkedin_message`. Runtime validation permits an empty array, but it creates no useful output. | | `linkedin_message_types` | No | string[] | LinkedIn variants: `inmail`, `connection_note`, `connected_message`. | | `outreach_purpose` | No | string | Purpose or offer context for personalization. | ```json { "personnel_ids": ["789"], "event_id": "501", "channels": ["email", "linkedin_message"], "linkedin_message_types": ["inmail", "connection_note"], "outreach_purpose": "Invite the contact to a product demo" } ``` ## Credit behavior Outreach message generation does not deduct credits in the current service implementation. Created batch task-center records use `creditsCost: 0`. - Maximum `2000` personnel IDs per request. - Requests above the limit return `422 Unprocessable Entity`. ## Response example ```json { "items": [ { "personnel_id": "789", "success": true, "taskId": null, "error": null } ], "taskCenterId": "321" } ``` ## Response handling - Process each `items` entry independently; one failed person does not invalidate successful submissions. - `taskCenterId` is the decimal numeric batch identifier when new work was reserved. New items normally have `taskId: null` until child dispatch; an existing in-progress item can return a decimal numeric child-task ID. - When `event_id` is provided, store the internal event `id` with `personnel_id`. Do not require an item `taskId` before polling the outreach detail endpoint. - Use [Get outreach message detail](/api-reference/personnel/get-outreach-message-detail) to retrieve the most recent generated content. - Generating a draft does not send an email or LinkedIn message. ## Error responses | Status | Meaning | Recommended handling | | --- | --- | --- | | `400 Bad Request` | Invalid channels, LinkedIn variants, or identifiers. | Fix the request before retrying. | | `401 Unauthorized` | Missing or invalid API key. | Reconnect or rotate the key. | | `409 Conflict` | Another outreach batch is being prepared for the same user and event context. | Retry after the active batch preparation finishes. | | `422 Unprocessable Entity` | More than `2000` personnel IDs were submitted. | Split the request into smaller selected batches. | | `429 Too Many Requests` | Rate limit exceeded. | Honor `Retry-After` and retry with backoff. | ## Notes - [Get outreach message detail](/api-reference/personnel/get-outreach-message-detail) requires `personnel_id` and `event_id`. Provide the internal event `id` during generation when the draft must be retrieved through that endpoint; the lookup endpoint itself can later resolve either `id` or `eventId`. - Messages are generated using AI based on the contact's profile, company context, and event participation data. --- ## Get outreach message detail Source: /api-reference/personnel/get-outreach-message-detail Retrieve the generated outreach message for a specific personnel record in an event context. Use this endpoint after [Generate outreach messages](/api-reference/personnel/generate-outreach-messages) to fetch the AI-generated content. ## When to use this endpoint Use `GET /external/personnel/outreach` when: - Polling for outreach generation completion after calling the generate endpoint - Retrieving previously generated messages for display or export - Checking whether outreach content already exists before triggering a new generation ## Query parameters | Name | Required | Type | Description | Example | | --- | --- | --- | --- | --- | | `personnel_id` | Yes | string | Personnel ID used in the generation request. | `789` | | `event_id` | Yes | string | Event identifier for lookup. This endpoint resolves either internal `id` or public `eventId`; use the same internal `id` supplied during generation when possible. | `501` | ## Request example ```bash curl --get "https://platform.lensmor.com/external/personnel/outreach" \ --data-urlencode "personnel_id=789" \ --data-urlencode "event_id=501" \ --header "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "found": true, "channels": ["email", "linkedin_message"], "messages": { "email": "Hi Jane, I would like to compare notes before Retail Technology Expo.", "linkedin": { "inmail": { "subject": "Retail Technology Expo", "body": "Hi Jane, I would like to compare notes before the event." }, "connection_note": { "body": "Hi Jane, would be glad to connect before the event." } } }, "outreach_purpose": "Book a product demo", "tone": "professional", "status": "ready" } ``` ## No content yet `found: false` is a valid lookup result when no message has been generated, the latest generation is not ready, or the event cannot be resolved. Do not treat it as sent outreach or invent fallback copy. ## Error responses | Status | Meaning | Recommended handling | | --- | --- | --- | | `400 Bad Request` | A personnel or event identifier is not a valid numeric string. | Fix the identifier before retrying. | | `401 Unauthorized` | Missing or invalid API key. | Reconnect or rotate the key. | | `429 Too Many Requests` | Polling exceeded the active rate limit. | Wait for `Retry-After` before retrying. | ## Notes - Returns the most recent outreach generation result for the given personnel and event combination. - An unknown but well-formed event does not return `404`; it produces the normal `200 OK` response with `found: false`. - `messages.linkedin` can contain separate `inmail`, `connection_note`, and `connected_message` objects. InMail can include `subject` and `body`; the other variants contain `body`. - `messages.linkedin_message` remains available as the legacy single-message field when present. - Re-generating outreach for the same personnel + event will overwrite previous content. --- ## Contacts search Source: /api-reference/contacts/search-contacts Search Lensmor contact records within a required company scope. Start with `company_name`, then add `role`, `person_name`, or both to narrow the people returned. This is a read-only discovery endpoint. It helps you find candidate people and collect personnel IDs, but it does not unlock email addresses or phone numbers. ## Use this endpoint to - find potential buyers or decision-makers at a target account; - narrow a company's people by job role or known person name; - check whether Lensmor has candidate contacts before spending credits; - collect personnel IDs for profile, email-unlock, phone-unlock, or outreach workflows. This is a company-scoped contact search, not a database-wide people search. Every request requires `company_name`. The endpoint does not currently accept geography, industry, seniority, department, or attendee-source filters. ## How the filters work `company_name` always defines the company scope. The optional fields narrow the people inside that scope. | Goal | Parameters to use | | --- | --- | | Browse candidate people at a company | `company_name` | | Find people in a particular role | `company_name` + `role` | | Find a known person at a company | `company_name` + `person_name` | | Find a known person in a particular role | `company_name` + `role` + `person_name` | The public contract treats `company_name`, `role`, and `person_name` as search terms. It does not expose an exact-match mode or a relevance score, so clients should not depend on either behavior. ## Endpoint `GET /external/contacts/search` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Default | Description | Example | | --- | --- | --- | --- | --- | --- | | `company_name` | Yes | string | — | Company-name query that scopes the search. Length: `1`–`200` characters. | `Acme` | | `role` | No | string | — | Job-role or title query used to narrow people at the company. Blank values are ignored. Maximum length: `200`. | `VP Marketing` | | `person_name` | No | string | — | Person-name query used to narrow people at the company. Blank values are ignored. Maximum length: `200`. | `Jane` | | `page` | No | integer | `1` | One-based page number. | `1` | | `pageSize` | No | integer | `20` | Maximum records requested per page. Range: `1`–`100`. | `20` | ## Request examples ### Browse contacts at a company ```bash curl --get "https://platform.lensmor.com/external/contacts/search" \ --data-urlencode "company_name=Acme" \ --data-urlencode "page=1" \ --data-urlencode "pageSize=20" \ --header "Authorization: Bearer $LENSMOR_API_KEY" ``` ### Search by role ```bash curl --get "https://platform.lensmor.com/external/contacts/search" \ --data-urlencode "company_name=Acme" \ --data-urlencode "role=VP Marketing" \ --data-urlencode "page=1" \ --data-urlencode "pageSize=20" \ --header "Authorization: Bearer $LENSMOR_API_KEY" ``` ### Search by person name ```bash curl --get "https://platform.lensmor.com/external/contacts/search" \ --data-urlencode "company_name=Acme" \ --data-urlencode "person_name=Jane" \ --data-urlencode "page=1" \ --data-urlencode "pageSize=20" \ --header "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "items": [ { "id": "101", "fullName": "Jane Doe", "title": "VP Marketing", "department": "marketing", "seniorityLevel": "vp", "linkedinUrl": "https://linkedin.com/in/jane", "companyName": "Acme", "sourceType": ["social", "visitors"], "email": null, "phone": null, "contactUnlockStatus": "locked", "phoneUnlockStatus": null, "linkedinActivity": null, "linkedinActivityStatus": null, "eventCount": 3 } ], "total": 1, "page": 1, "pageSize": 20, "totalPages": 1, "hasMore": false } ``` ## Response fields ### Contact record | Field | Type | Description | | --- | --- | --- | | `id` | string | Personnel identifier used in profile, unlock, event, and outreach workflows. | | `fullName` | string or `null` | Person's display name when available. | | `title` | string or `null` | Current job title when available. | | `department` | string or `null` | Normalized department label when available. | | `seniorityLevel` | string or `null` | Normalized seniority label when available. | | `linkedinUrl` | string or `null` | LinkedIn profile URL when available. | | `companyName` | string or `null` | Company associated with the person. | | `sourceType` | string[] | Non-exclusive attendee-source labels aggregated across the person's associated events. | | `email` | string or `null` | Email when already accessible to the caller; otherwise `null`. This endpoint does not unlock it. | | `phone` | string or `null` | Phone when already accessible to the caller; otherwise `null`. This endpoint does not unlock it. | | `contactUnlockStatus` | string | Current email/contact access state for the authenticated API key owner. | | `phoneUnlockStatus` | string or `null` | `ready`, `processing`, `failed`, or `null` when no phone-unlock state is available. | | `linkedinActivity` | object or `null` | Always `null` on contact search. Use [Personnel list](/api-reference/personnel/list-event-personnel) for populated activity data. | | `linkedinActivityStatus` | string or `null` | Always `null` on contact search. | | `eventCount` | integer or `null` | Number of associated Lensmor events when available. | ### Pagination | Field | Type | Description | | --- | --- | --- | | `items` | object[] | Contact records returned on the current page. | | `total` | integer | Total matching contacts across all pages. | | `page` | integer | One-based page number returned. | | `pageSize` | integer | Requested maximum records per page. | | `totalPages` | integer | Total pages for the current search. | | `hasMore` | boolean | Whether another page is available. | ## Understand attendee-source labels `sourceType` is an array because one person can have more than one source label. | API value | Product label | Meaning | | --- | --- | --- | | `exhibitor` | Exhibitor | The person is associated with an exhibiting company. | | `social` | Social Signals | Lensmor identified event-related social activity; this does not prove official registration. | | `visitors` | Visitor | Registered-attendee data obtained through organizer or data-provider partnerships. | Contact search can aggregate these labels across multiple associated events. When you need labels for one specific event, use [Personnel list](/api-reference/personnel/list-event-personnel) with `event_id`. ## No results No matching contacts is a valid search result. The API returns `200 OK` with an empty page rather than an error: ```json { "items": [], "total": 0, "page": 1, "pageSize": 20, "totalPages": 0, "hasMore": false } ``` Do not retry the same request as an error. Let the user broaden the company, role, or person-name query instead. ## Errors | Status | Meaning | Recommended handling | | --- | --- | --- | | `400 Bad Request` | A required parameter is missing or a value fails validation. | Fix the request before retrying. | | `401 Unauthorized` | The API key is missing, malformed, revoked, or invalid. | Reconnect or rotate the key. | | `429 Too Many Requests` | The active rate limit was exceeded. | Wait for `Retry-After`, then retry with backoff. | Errors use the shared response shape: ```json { "code": 401, "message": "Invalid API key", "errorKey": "INVALID_API_KEY", "traceId": "2be6d7f4-3b2d-4d11-8e2d-7a2e4c3e9a10" } ``` See [Error conventions](/concepts/errors) and [Rate limits](/concepts/rate-limits) for shared handling rules. ## Continue the workflow Use this endpoint to collect the `id` values for people you want to act on. Call [Personnel profile](/api-reference/personnel/get-personnel-profile) for a selected personnel ID. Submit selected IDs to [Unlock contact emails](/api-reference/contacts/unlock-contact-emails) or [Unlock contact phone numbers](/api-reference/contacts/unlock-contact-phone-numbers). Store the returned task ID and poll the matching task endpoint until it reaches a terminal state. Contact search is read-only. Check [Credits and access](/concepts/credits-and-access) before starting an email or phone unlock that can consume credits. --- ## Unlock contact emails Source: /api-reference/contacts/unlock-contact-emails Start an asynchronous job to unlock email addresses for one or more personnel records. Use this endpoint after finding contacts from personnel or contact search results. ## When to use this endpoint Use `POST /external/contacts/unlock` when your integration already has selected personnel records and needs email addresses. This endpoint is intentionally asynchronous because email unlock can involve multiple records and credit checks. The create call returns a task identifier; your integration should poll [Get contact unlock task](/api-reference/contacts/get-contact-unlock-task) until the job reaches a terminal state. For best user experience, show the number of locked contacts and available credit balance before creating the unlock task. A `201 Created` response means the request was processed. `status: "accepted"` means a task was created; `status: "success"` means there was no chargeable work to enqueue. Neither state guarantees that every submitted personnel record will return an email. ## Endpoint `POST /external/contacts/unlock` ## Authentication See [Authentication](/authentication) ## Success status code `201 Created` ## Request body | Name | Required | Type | Notes | | --- | --- | --- | --- | | `personnel_ids` | Yes | string[] | One or more personnel identifiers. Maximum `2000` per request. | | `event_id` | No | string | Optional event `id` or `eventId`. When omitted, event membership validation is skipped. | ## Headers | Name | Required | Type | Notes | | --- | --- | --- | --- | | `x-call-source` | No | string | Optional usage source. Use `api` or `agent`; defaults to `api`. | ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/contacts/unlock" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -H "x-call-source: api" \ -d '{"event_id":"139574","personnel_ids":["789","790"]}' ``` ## Response example ```json { "status": "accepted", "task_id": "321", "job_id": "321" } ``` ## Response fields | Field | Description | | --- | --- | | `status` | `accepted` when an asynchronous task was created, or `success` when there was no chargeable work to enqueue. | | `task_id` | Identifier used to poll task status. Present only when `status` is `accepted`. | | `job_id` | Alias for `task_id`. Present only when `status` is `accepted`. | | `skipped_personnel_ids` | Personnel IDs omitted from the unlock task, when applicable. | | `skipped_detail` | Optional grouped reasons for skipped IDs, including invalid, missing, unlinked, already unlocked, or currently unlocking records. | ## Polling pattern After receiving `task_id`, poll the task endpoint: ```bash curl "https://platform.lensmor.com/external/contacts/unlock-tasks/321" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` Use backoff instead of a tight polling loop. A practical pattern is: 1. Wait a few seconds after task creation. 2. Poll every few seconds for short jobs. 3. Increase the interval if the job remains in progress. 4. Stop polling when the task completes or fails. 5. Re-fetch personnel or contact records if your UI needs the latest `email` and `contactUnlockStatus` values. ## Credit behavior Contact email unlock currently costs `15` credits per chargeable contact. - Already unlocked contacts are not charged again. - Failed or ineligible items may not produce an email. Treat the task result and refreshed balance as the source of truth for actual delivery and billing. - The API rejects batches larger than `2000` personnel IDs with `422 Unprocessable Entity`. - Insufficient balance returns `402 Payment Required`. - Retry behavior should be tied to the task state, not only the create response. ## Error responses - `400 Bad Request` - `401 Unauthorized` - `402 Payment Required` - `404 Not Found` - `409 Conflict` - `422 Unprocessable Entity` - `429 Too Many Requests` ## Notes - This endpoint creates an asynchronous unlock job. Poll [Get contact unlock task](/api-reference/contacts/get-contact-unlock-task) with `task_id`. - Do not create duplicate unlock tasks while a prior task for the same selected contacts is still pending or processing. - See [Credits and access](/concepts/credits-and-access) for shared credit behavior. --- ## Get contact unlock task Source: /api-reference/contacts/get-contact-unlock-task Check the status of a contact email unlock job. Use this endpoint after `POST /external/contacts/unlock` returns `task_id`. ## When to use this endpoint Use `GET /external/contacts/unlock-tasks/{taskId}` to make email unlock workflows resumable. The task ID can be stored in your database, background worker, or frontend state and polled until the job completes or fails. Do not assume the unlock result is ready immediately after creating the task. ## Endpoint `GET /external/contacts/unlock-tasks/{taskId}` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Path parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `taskId` | Yes | string | The `task_id` returned by contact unlock. | ## Request example ```bash curl "https://platform.lensmor.com/external/contacts/unlock-tasks/321" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "taskId": "321", "taskStatus": "processing", "items": [ { "personnelId": "789", "status": "unlocked", "email": "jane@acme.example" }, { "personnelId": "790", "status": "processing" } ] } ``` ## Response fields | Field | Description | | --- | --- | | `taskId` | Contact unlock task identifier. | | `taskStatus` | Overall task state. | | `items` | Per-person unlock results. | | `items[].personnelId` | Personnel identifier submitted in the unlock request. | | `items[].status` | Item-level unlock state. | | `items[].email` | Email address when the item is unlocked successfully. | | `items[].errorCode` | Error code when an individual item fails, if provided. | ## Status values Task status: - `pending` - `processing` - `completed` - `failed` Item status: - `pending` - `processing` - `unlocked` - `failed` ## Result interpretation Treat each `items[]` entry independently: - Count a contact as delivered only when item `status` is `unlocked` and `email` is present. - Surface `failed` items separately with `errorCode` when provided. - A task can reach a terminal state while individual items have different outcomes. - Refresh credit balance and contact/personnel records after terminal states when your UI needs billing or access-state reconciliation. ## Polling guidance Use a backoff schedule instead of polling aggressively: ```text 3 seconds -> 5 seconds -> 10 seconds -> 20 seconds ``` Stop polling when `taskStatus` is `completed` or `failed`. If some items remain failed while the task is completed, handle those item statuses individually. ## Error responses - `401 Unauthorized` - `404 Not Found` - `429 Too Many Requests` ## Notes - `email` is present only when the individual item has been unlocked successfully. - Failed items can include `errorCode`. --- ## Unlock contact phone numbers Source: /api-reference/contacts/unlock-contact-phone-numbers Submit a request to unlock phone numbers for one or more personnel records. Chargeable work creates an asynchronous task; a request with no chargeable work completes immediately without a task. Use this endpoint after finding contacts from personnel or contact search results when you need direct phone numbers rather than email addresses. ## When to use this endpoint Use `POST /external/contacts/unlock-phone` when your integration needs verified phone numbers for outbound calling or SMS workflows. When the response has `status: "accepted"`, the create call returns a task identifier. Poll [Get phone unlock task](/api-reference/contacts/get-phone-unlock-task) until that job reaches a terminal state. When the response has `status: "success"`, no task was created and there is nothing to poll. Phone unlock is a separate workflow from email unlock. Use [Unlock contact emails](/api-reference/contacts/unlock-contact-emails) for email addresses. A `201 Created` response means the request was processed. `status: "accepted"` means a task was created; `status: "success"` means there was no chargeable work to enqueue. Neither state guarantees that every submitted personnel record will return a phone number. ## Credit behavior Phone unlock costs `150` credits per chargeable contact. - Already unlocked contacts are not charged again. - Insufficient balance returns `402 Payment Required`. - The API rejects batches larger than `2000` personnel IDs with `422 Unprocessable Entity`. If there is no chargeable work to enqueue, the endpoint can return `status: "success"` without `task_id` or `job_id`. The response can also include `skipped_personnel_ids` and `skipped_detail` so the caller can explain invalid, missing, unlinked, already unlocked, or currently unlocking records. ## Response handling - For `status: "accepted"`, use `task_id` (or its `job_id` alias) to poll the phone unlock task. - For `status: "success"`, do not poll. Inspect `skipped_personnel_ids` and `skipped_detail` to explain why no task was needed. ## Request body | Name | Required | Type | Notes | | --- | --- | --- | --- | | `personnel_ids` | Yes | string[] | One or more personnel identifiers. Maximum `2000` per request. | | `event_id` | No | string | Optional event `id` or `eventId`. When omitted, event membership validation is skipped. | ## Request example ```bash curl --request POST "https://platform.lensmor.com/external/contacts/unlock-phone" \ --header "Authorization: Bearer $LENSMOR_API_KEY" \ --header "Content-Type: application/json" \ --header "x-call-source: api" \ --data '{ "personnel_ids": ["789"], "event_id": "139574" }' ``` ## Accepted response ```json { "status": "accepted", "task_id": "321", "job_id": "321" } ``` ## No-work response ```json { "status": "success", "skipped_personnel_ids": ["789"], "skipped_detail": { "already_unlocked": ["789"] } } ``` ## Error responses | Status | Meaning | Recommended handling | | --- | --- | --- | | `400 Bad Request` | Invalid request body or field value. | Fix the request before retrying. | | `401 Unauthorized` | Missing or invalid API key. | Reconnect or rotate the key. | | `402 Payment Required` | Insufficient credits for chargeable contacts. | Stop retries and show the credit requirement. | | `404 Not Found` | Event or submitted personnel context is unavailable. | Re-check stored identifiers. | | `409 Conflict` | A task or resource state conflicts with the request. | Refresh task/access state before retrying. | | `422 Unprocessable Entity` | More than `2000` personnel IDs were submitted. | Split the request into smaller selected batches. | | `429 Too Many Requests` | Rate limit exceeded. | Honor `Retry-After` and retry with backoff. | ## Notes - Only poll [Get phone unlock task](/api-reference/contacts/get-phone-unlock-task) when `status` is `accepted` and `task_id` is present. - `task_id` and `job_id` are the same decimal numeric string. Do not add a `phone_` prefix. - Do not create duplicate unlock tasks while a prior task is still pending or processing. - See [Credits and access](/concepts/credits-and-access) for shared credit behavior. --- ## Get phone unlock task Source: /api-reference/contacts/get-phone-unlock-task Check the status of a phone number unlock job. Use this endpoint after `POST /external/contacts/unlock-phone` returns `task_id`. ## When to use this endpoint Use `GET /external/contacts/unlock-phone-tasks/{taskId}` to poll until the job completes or fails. Stop polling when `taskStatus` is `completed` or `failed`, and handle item-level failures independently. ## Path parameters | Name | Required | Type | Description | Example | | --- | --- | --- | --- | --- | | `taskId` | Yes | string | Decimal numeric phone-unlock task ID returned as `task_id` by the submission endpoint. | `321` | ## Request example ```bash curl "https://platform.lensmor.com/external/contacts/unlock-phone-tasks/321" \ --header "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "taskId": "321", "taskStatus": "completed", "items": [ { "personnelId": "789", "status": "unlocked", "phone": "+1 415 555 0100" } ] } ``` ## Task states | Value | Meaning | Client action | | --- | --- | --- | | `pending` | The task is queued. | Continue polling with backoff. | | `processing` | Phone enrichment is running. | Continue polling without creating another task. | | `completed` | The task reached a terminal success state. | Store successful phones and process item failures separately. | | `failed` | The task reached a terminal failure state. | Stop polling and surface the error. | ## Polling guidance - Persist `taskId` so polling can resume after a worker or page restart. - Use bounded exponential backoff and honor `429 Retry-After`. - Stop polling at `completed` or `failed`. - Do not create a replacement task merely because individual items failed. ## Error responses | Status | Meaning | Recommended handling | | --- | --- | --- | | `401 Unauthorized` | Missing or invalid API key. | Reconnect or rotate the key. | | `404 Not Found` | The task ID does not exist or is not visible to the caller. | Re-check the stored task ID; do not poll indefinitely. | | `429 Too Many Requests` | Polling exceeded the active rate limit. | Wait for `Retry-After` before polling again. | ## Notes - Phone number is present only when the individual item has been unlocked successfully. - Failed items can include `errorCode`. - `taskId` is a decimal numeric string because the service converts it to `BigInt`; prefixed values such as `phone_321` are invalid. --- ## Apply profile and get recommended events Source: /api-reference/profile-matching/apply-profile-and-get-recommended-events Apply company profile inputs and return paged recommended events synchronously. Use this endpoint when you want to rank events for a company website or target audience and retrieve the first page of results in one request. ## When to use this endpoint Use this endpoint when the integration starts from "who we sell to" rather than from a known event. Good fits include: - A CRM workflow where the user enters a company website and wants recommended trade shows. - A sales planning workflow that ranks events for a specific ICP. - A dashboard that combines event metadata with match score and matched exhibitor counts. - A one-request onboarding flow where users provide profile inputs and immediately see recommendations. If you need a neutral event catalog without profile scoring, use [Events list](/api-reference/events/list-events). ## Endpoint `POST /external/profile-matching/actions/apply-recommended-events/paged` ## Authentication See [Authentication](/authentication) ## Success status code `201 Created` ## Request body | Name | Required | Type | Notes | | --- | --- | --- | --- | | `company_url` | Conditionally | string | Public `http(s)` URL. Provide at least one of `company_url` or `target_audience`. | | `target_audience` | Conditionally | string | Audience description. Provide at least one of `company_url` or `target_audience`. | | `linkedin_url` | No | string | Safe public LinkedIn profile URL; maximum `500` characters. | | `company_description` | No | string | Additional company context; maximum `2000` characters. | | `industry` | No | string | Company industry; maximum `255` characters. | | `target_industry` | No | string[] | Target industries; each value is limited to `255` characters. | | `planned_events` | No | string | Free-text planned events; maximum `500` characters. | | `target_management_level` | No | string[] | Target management levels; each value is limited to `100` characters. | | `target_job_titles` | No | string[] | Target job titles/functions. The runtime retains the first five non-empty values; each is limited to `255` characters. | | `timeout_ms` | No | integer | Processing timeout from `60000` to `3600000` milliseconds. | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `20`; maximum `100`. | | `autoRecommendTop3EventsEnabled` | No | boolean | Override synchronous top-3 recommendation reason generation for this request. | | `city` | No | string | City filter. | | `region` | No | string | Region filter. | | `country` | No | string | Country filter. | | `category` | No | string[] | Category filter; arrays and comma-separated inputs are normalized. | | `eventTypeIds` | No | integer[] | Event type ID filter; arrays and comma-separated inputs are normalized. | | `dateStartFrom` | No | ISO date string | Lower date bound. | | `dateStartTo` | No | ISO date string | Upper date bound. | | `future` | No | integer | Future-event flag. | | `attendeeCountMin` | No | integer | Minimum attendee count. | | `attendeeCountMax` | No | integer | Maximum attendee count. | ## Request example ```bash curl -X POST "https://platform.lensmor.com/external/profile-matching/actions/apply-recommended-events/paged" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"company_url":"https://acme.example","page":1,"pageSize":20,"future":1}' ``` ### Recommend from an ICP description ```bash curl -X POST "https://platform.lensmor.com/external/profile-matching/actions/apply-recommended-events/paged" \ -H "Authorization: Bearer $LENSMOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target_audience": "Enterprise retailers and store operations leaders looking for workforce analytics software", "country": "United States", "future": 1, "page": 1, "pageSize": 20 }' ``` ## Response example ```json { "status": "completed", "items": [ { "id": "501", "eventId": "10067", "name": "American Banker Digital Banking 2026", "nickname": "", "description": "American Banker Digital Banking explores the future of finance...", "url": "https://digital-banking.americanbanker.com/", "dateStart": "2026-06-15", "dateEnd": "2026-06-17", "venue": "Hilton Orlando", "city": "Orlando", "region": "Florida", "country": "United States", "latitude": "28.4246951", "longitude": "-81.4591377", "attendeeCount": 1825, "declaredExpectedAttendees": 600, "priceLower": "1295", "priceUpper": "2395", "eventType": "conference_led", "quality": "Standard", "sponsorMatchStarred": 0, "sourceTags": [], "categories": [ { "id": 7, "code": "banking_finance", "name": "Banking & Finance", "description": null, "confidence": "1" } ], "topics": ["Banking", "Digital Banking"], "topicsCount": 8, "verified": 0, "future": 1, "historic": 1, "historicEvent": "American Banker Digital Banking", "image": "https://example.com/event-image.png", "dataSource": "Lensmor", "exhibitorCount": 120, "personnelCount": 450, "eventTypes": [], "createTime": "1700000000000", "updateTime": "1710000000000", "matched_exhibitor_count": 19, "matched_personnel_count": 85, "match_score": 0.84, "unlocked": false, "relevanceReason": null, "rank": 1 } ], "total": 42, "page": 1, "pageSize": 20, "totalPages": 3, "hasMore": true, "condition_tags": {}, "profile_version": 12, "active_result_version": 12, "is_stale": false } ``` ## Response fields ### Top-level fields | Field | Description | | --- | --- | | `status` | Profile-matching execution state for the request. | | `items` | Recommended event records for the requested page. Each item contains full event metadata plus recommendation signals. | | `condition_tags` | Normalized matching/filter metadata returned by the current profile version. | | `profile_version`, `active_result_version` | Version metadata for the profile-matching result set. | | `is_stale` | Indicates whether the current result set is stale relative to the active profile state. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | ### Recommendation fields (per item) | Field | Type | Description | | --- | --- | --- | | `match_score` | number | Event relevance score (0–1 scale) for the supplied profile inputs. | | `matched_exhibitor_count` | integer \| null | Number of exhibitors that matched the profile context. | | `matched_personnel_count` | integer \| null | Number of personnel that matched the profile context. | | `unlocked` | boolean | Whether the API key owner already has full event access. | | `relevanceReason` | string \| null | Human-readable explanation. `null` when not yet generated. | | `rank` | integer | One-based rank position in the result set. | ### Event metadata fields (per item) Each event item includes the full event detail shape: | Field | Type | Description | | --- | --- | --- | | `id`, `eventId` | string | Separate internal-row and public event identifiers; values can differ. | | `name`, `nickname`, `description` | string | Event identity fields. | | `url` | string \| null | Source or official event URL. | | `dateStart`, `dateEnd` | string | ISO date strings. | | `venue`, `city`, `region`, `country` | string | Location metadata. | | `latitude`, `longitude` | string \| null | Geographic coordinates as string values when available. | | `attendeeCount` | integer \| null | Known attendee count. | | `declaredExpectedAttendees` | integer \| null | Organizer-declared expected attendees. | | `priceLower`, `priceUpper` | string \| null | Price range as string values. | | `eventType` | string \| null | Event format classification (e.g. `"conference_led"`, `"In-person"`). | | `quality` | string \| null | Quality label (e.g. `"Standard"`, `"Verified"`). | | `sourceTags` | string[] | Source classification tags. | | `categories` | object[] | Category objects with `id`, `code`, `name`, `description`, `confidence`. See [Category object](#category-object). | | `topics` | string[] | Topic keywords. | | `topicsCount` | integer | Total topic count. | | `verified`, `future`, `historic` | integer | Status flags (0 or 1). | | `historicEvent` | string \| null | Name of the recurring event series. | | `image` | string \| null | Event image URL. | | `sponsorMatchStarred` | integer | Sponsor Match starred state (`0` or `1`). | | `dataSource` | string | Always `Lensmor` for public event records. | | `exhibitorCount`, `personnelCount` | integer \| null | Known counts when available. | | `eventTypes` | array | Event type metadata. | | `createTime`, `updateTime` | string | Unix timestamps in milliseconds as strings. | ### Category object ```json { "id": 7, "code": "banking_finance", "name": "Banking & Finance", "description": null, "confidence": "1" } ``` | Field | Type | Description | | --- | --- | --- | | `id` | integer | Category identifier. | | `code` | string | Machine-readable category code. | | `name` | string | Human-readable category name. | | `description` | string \| null | Category description when available. | | `confidence` | string | Match confidence as a string value. | ## Result freshness `profile_version`, `active_result_version`, and `is_stale` help clients decide whether to refresh recommendations after profile inputs change. For most UI integrations, show the current results and re-run the endpoint when the user edits the company URL, audience description, filters, or geography. ## Integration guidance - This endpoint is synchronous but can take several seconds for broad profiles or large result sets. Show a loading state and keep user input editable. - `relevanceReason` can be `null`. Do not require it before displaying ranked recommendations. - Use `match_score`, `rank`, matched counts, event dates, geography, categories, and follow-up evidence together. Avoid presenting score differences as exact mathematical certainty. - For the top events, follow up with [Event detail](/api-reference/events/get-event-detail), then preview [List event exhibitors](/api-reference/exhibitors/list-event-exhibitors) or [List event personnel](/api-reference/personnel/list-event-personnel) before asking the user to unlock an event. - If event summary counts are `null`, use the event-scoped list endpoint's `total` and `semantics` fields for the current access and filter context. ## Error responses - `400 Bad Request` - `401 Unauthorized` - `409 Conflict` - `429 Too Many Requests` ## Notes - Use this endpoint for profile-based event recommendations. - Event items return the full event detail shape including geographic coordinates, pricing, categories, and topics. - `latitude`, `longitude`, `priceLower`, `priceUpper`, `createTime`, `updateTime`, and `confidence` are returned as string values, not numbers. - `id` and `eventId` are separate values and can differ. Use `eventId` for endpoints that resolve either identifier. Use internal `id` for the current `POST /external/personnel/generate-outreach-message` exception documented under [Identifiers](/concepts/identifiers). - Status metadata such as `status`, `profile_version`, and `active_result_version` describes the current paged result set. --- ## Recommended exhibitors for event Source: /api-reference/profile-matching/get-recommended-exhibitors-for-event Return exhibitor recommendation state for a single event. Depending on the caller's current profile state, the endpoint can return populated recommendation metadata or an unranked fallback page. Use this endpoint when you already know the target event and want exhibitor recommendations under the caller's current recommendation state. ## When to use this endpoint Use `GET /external/profile-matching/recommendations/exhibitors` when you want recommended exhibitors for one event, not the neutral event exhibitor catalog. Choose this endpoint when: - the user has already selected an event - your product has profile or recommendation state for the caller - you want to inspect whether populated recommendation evidence is available Use [Exhibitors list](/api-reference/exhibitors/list-event-exhibitors) when you need the broader event-scoped exhibitor catalog with preview/full access semantics. ## Endpoint `GET /external/profile-matching/recommendations/exhibitors` ## Authentication See [Authentication](/authentication) ## Success status code `200 OK` ## Query parameters | Name | Required | Type | Notes | | --- | --- | --- | --- | | `event_id` | Yes | string | Event identifier. | | `page` | No | integer | Defaults to `1`. | | `pageSize` | No | integer | Defaults to `20`; maximum `100`. | | `location` | No | string[] | Country or region filter. Repeated query parameters are supported. | | `searchQuery` | No | string | Company-name or description search. | | `exhibitorName` | No | string[] | Exact-name filter; repeated query params are normalized into arrays. | | `category` | No | string[] | Category filter. | | `industry` | No | string[] | Industry filter. Repeated query parameters are supported. | | `employeesMin` | No | integer | Minimum employee count. | | `employeesMax` | No | integer | Maximum employee count. | ## Request example ```bash curl "https://platform.lensmor.com/external/profile-matching/recommendations/exhibitors?event_id=139574&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ### Filter recommended exhibitors ```bash curl "https://platform.lensmor.com/external/profile-matching/recommendations/exhibitors?event_id=139574&industry=Retail%20Technology&employeesMin=100&employeesMax=1000&page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` ## Response example ```json { "items": [ { "id": "456", "companyName": "Acme Retail Systems", "description": "Retail analytics platform", "logo": null, "website": "https://acme.example", "country": "United States", "industry": "Retail Technology", "categories": ["Retail", "POS"], "employeeCount": 220, "companySize": "201-500", "fundingRound": "Series B", "techStacks": ["Cloudflare", "Nginx"], "isRecommended": true, "recommendationRank": 1, "matchStatus": "ready", "matchScore": 91, "matchTier": "top_match", "reason": "Strong fit for the active profile." } ], "total": 87, "page": 1, "pageSize": 20, "totalPages": 5, "hasMore": true, "recommendationProcessing": false } ``` ## Response fields | Field | Description | | --- | --- | | `items` | Event exhibitor rows. Treat them as recommendations only when recommendation evidence is populated. | | `id` | Exhibitor identifier. | | `companyName`, `description`, `website` | Display and web presence fields. | | `country`, `industry`, `categories` | Classification fields. | | `employeeCount`, `companySize`, `fundingRound`, `techStacks` | Enrichment and firmographic signals. | | `isRecommended` | Whether the row belongs to the active recommendation snapshot. | | `recommendationRank`, `matchScore`, `matchTier` | Populated recommendation evidence when available; otherwise `null`. | | `matchStatus` | Recommendation metadata status: `pending_unlock`, `processing`, `ready`, `failed`, or `null`. | | `reason` | Recommendation reason when available. The response field is `reason`, not `matchReason`. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | | `recommendationProcessing` | `true` while recommendation detail generation is still processing for the event. | | `code` | Fallback code. Currently `AI_SEARCH_RESULT_MISMATCH` when fallback rows are returned. | | `show_refresh_hint` | Optional fallback hint. The flag does not identify a cause or prescribe a required action. | ## Fallback response When recommendation metadata is unavailable, the endpoint returns deterministic event exhibitor rows with null recommendation fields. Do not present their order as an ICP ranking. ```json { "items": [ { "id": "456", "companyName": "Acme Retail Systems", "description": "Retail analytics platform", "logo": null, "website": "https://acme.example", "country": "United States", "industry": "Retail Technology", "categories": ["Retail", "POS"], "employeeCount": 220, "companySize": "201-500", "fundingRound": "Series B", "techStacks": ["Cloudflare", "Nginx"], "isRecommended": false, "recommendationRank": null, "matchStatus": null, "matchScore": null, "matchTier": null, "reason": null } ], "total": 87, "page": 1, "pageSize": 20, "totalPages": 5, "hasMore": true, "recommendationProcessing": false, "code": "AI_SEARCH_RESULT_MISMATCH", "show_refresh_hint": true } ``` ## Filtering behavior Use filters to narrow the returned event exhibitor rows. Apply the same recommendation-evidence check after filtering. For exact company-name lookup, use [Exhibitor company search](/api-reference/exhibitors/search-exhibitors-by-company-name). ## Error responses - `400 Bad Request` - `401 Unauthorized` - `404 Not Found` - `429 Too Many Requests` ## Notes - Unknown `event_id` values are normalized to `404 EVENT_NOT_FOUND` responses. - The request parameter `event_id` refers to the `eventId` value returned by event endpoints. - `categories` is preserved when recommendation data contains category labels. - `techStacks` is always returned as an array; when no data is available, the API returns `techStacks: []`. - Only call the response AI-ranked when at least one row contains populated recommendation evidence such as `isRecommended: true`, `recommendationRank`, `matchScore`, `matchTier`, or `reason`. --- ## Error conventions Source: /concepts/errors Lensmor API exceptions normally use real HTTP status codes. A small number of business guards return `HTTP 200` with an error `code` in the body; clients must inspect both the HTTP status and `code`. ## Error body ```json { "code": 401, "message": "Invalid API key", "errorKey": "INVALID_API_KEY", "traceId": "2be6d7f4-3b2d-4d11-8e2d-7a2e4c3e9a10" } ``` `details` is optional. Business and validation errors can include structured context such as requested count, runtime limit, missing identifiers, or access state. The company-to-event reverse search and personnel events-by-name routes use an `HTTP 200` concurrency business error. It returns `code: 429`, `errorKey: "USER_CONCURRENCY_LIMIT_EXCEEDED"`, `data: null`, and `details: null` when the per-user concurrency limit is reached. Visitor unlock, full-access unlock, and personnel events-by-name can also return `HTTP 200` with `code: 400` and `errorKey: "USER_HAS_NO_FEATURE"` when an active paid subscription is required. Treat these as failed business actions even though the transport status is successful. ## Common status codes | Status | Meaning | Recommended client behavior | | --- | --- | --- | | `400 Bad Request` | Invalid input or unsupported request shape. | Validate user input and fix the request before retrying. | | `401 Unauthorized` | Missing, malformed, revoked, or invalid API key. | Ask the user to reconnect or rotate the key. | | `402 Payment Required` | Insufficient credits or access for a credit-consuming operation. | Stop automatic retries and surface the credit requirement. | | `404 Not Found` | Requested resource does not exist or is not visible to the caller. | Re-check stored identifiers; do not retry indefinitely. | | `409 Conflict` | Business conflict, profile state conflict, or task/workflow state conflict. | Refresh state before retrying. | | `422 Unprocessable Entity` | A selected personnel batch exceeds the runtime limit of `2000`. | Split the selected IDs into smaller batches. | | `429 Too Many Requests` | Rate limit exceeded. | Retry only after `Retry-After`; use exponential backoff. | ## Trace fields Error responses include `traceId` for troubleshooting. The same value is also returned in the `X-Request-ID` response header when available. When contacting support, include: - endpoint path and method - response status - `traceId` - approximate request time - non-sensitive identifiers such as `event_id`, `exhibitor_id`, `personnel_id`, or `taskId` Do not include API keys or unlocked email addresses in support logs. ## Retry guidance - Retry network failures and `429` responses with backoff. - Do not automatically retry `400` or `401` without changing the request. - Do not retry `422` with the same selected batch; reduce it to at most `2000` personnel IDs. - Treat `402` as a product state, not a transient failure. - For asynchronous contact unlocks, retry polling the task endpoint rather than creating duplicate unlock tasks. ## Notes - Error bodies do not include raw debugging fields, but can include the optional structured `details` field. - Inspect body `code` even when the HTTP status is `200` on endpoints that document a business-error success envelope. - Individual endpoint pages may list the subset of statuses most relevant to that route. --- ## Pagination conventions Source: /concepts/pagination Many list endpoints return a shared pagination envelope. ## Common fields | Field | Description | | --- | --- | | `items` | Records for the current page. Item shape varies by endpoint. | | `page` | Current page number. | | `pageSize` | Number of items requested per page. | | `total` | Total matching items. | | `totalPages` | Total number of pages. | | `hasMore` | Whether another page is available. | ## Request pattern Most list endpoints accept `page` and `pageSize`. ```bash curl "https://platform.lensmor.com/external/events/list?page=1&pageSize=20" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` Use explicit values instead of relying on defaults in production clients. ## What to expect - Exact item fields vary by endpoint family. - Some endpoints return extra route-specific metadata in addition to the shared pagination fields. - Event-scoped exhibitor and personnel list endpoints can include `semantics` metadata that explains preview access and unlock guidance. - Identifiers such as `event_id`, `exhibitor_id`, and `personnel_id` are string values in API requests and responses. - Parameter naming is route-specific. Treat each endpoint page as the source of truth for casing and field names. ## Preview semantics Event-scoped exhibitor and personnel lists can return an additional `semantics` object: ```json { "semantics": { "accessMode": "preview", "previewLimit": 50, "counts": { "actualTotal": 87, "visibleTotal": 50, "remainingLockedCount": 37 }, "unlock": { "requiredForMoreResults": true, "credits": 2000 } } } ``` Use `semantics` to decide whether a page is fully accessible or whether the user should unlock the event for more records. ## Requesting pages beyond the preview limit When an event is locked and you request a page outside the accessible preview window, the API returns: ```json { "items": [], "total": 0, "page": 4, "pageSize": 20, "totalPages": 0, "hasMore": false, "semantics": { "accessMode": "preview", "previewLimit": 50, "counts": { "actualTotal": 3256, "visibleTotal": 50, "remainingLockedCount": 3206 }, "pageState": { "requestedPage": 4, "accessible": false, "maxAccessiblePage": 1 }, "unlock": { "requiredForMoreResults": true, "actionType": "unlock_event_exhibitors", "credits": 2000 }, "guidance": { "code": "preview_page_inaccessible", "message": "This event is locked. Only the first 50 matching exhibitors are currently accessible. Unlock the event before requesting more results." } } } ``` Key behaviors: - `items` is empty — no records are returned for inaccessible pages. - `total` and `totalPages` are `0` — they reflect the inaccessible current page, not the true matching total. - `semantics.counts.actualTotal` is the true matching total when the API can calculate it. - `pageState.accessible` is `false` — use this flag to detect when a page is outside the preview window. - `pageState.maxAccessiblePage` tells you the last page you can request without unlocking. Treat this as authoritative instead of calculating from `previewLimit / pageSize`. - `guidance.code` changes to `"preview_page_inaccessible"` (versus `"preview_results_truncated"` on accessible preview pages). Handle this by checking `pageState.accessible` before rendering results. If `false`, show the unlock prompt rather than an empty state. ## Notes - Most endpoints use a maximum `pageSize` of `100`. - Personnel list endpoints use a maximum `pageSize` of `50`. - Use each endpoint page as the source of truth for route-specific filters, item fields, and any additional metadata returned with paginated results. --- ## Identifiers Source: /concepts/identifiers Lensmor API responses expose string identifiers so clients can pass values safely across JavaScript, spreadsheets, CRMs, and workflow tools. ## Event identifiers Many event-scoped request parameters are named `event_id`. In event-scoped requests, `event_id` accepts either: - `eventId` — the event identifier returned in event responses. - `id` — the Lensmor event identifier returned as a string in event responses. `id` is the Lensmor database row identifier and `eventId` is the public event identifier. They are returned separately and can differ. Event-scoped resolvers accept either value where the endpoint documents `event_id` support. Prefer `eventId` when storing or exchanging event identifiers with external systems. `POST /external/personnel/generate-outreach-message` is a current code-level exception: when `event_id` is supplied, it must be the numeric internal event `id`. That endpoint converts the value directly to `BigInt` and does not resolve public `eventId`. The related `GET /external/personnel/outreach` lookup does accept either event identifier. ## Entity identifiers Use the identifier returned by the matching resource endpoint: | Parameter | Source field | Notes | | --- | --- | --- | | `event_id` | `event.eventId` or `event.id` | Prefer `eventId`. | | `exhibitor_id` | `exhibitor.id` | Numeric string. | | `personnel_id` | `personnel.id` or contact `id` | Numeric string. | | `taskId` | `task_id` from contact unlock response | Decimal numeric string used for unlock task polling. | ## Naming conventions - Request parameters use the casing shown on each endpoint page. - Event responses include both `id` and `eventId`, while exhibitor event matches use `matched_event_ids`. Use the field names shown on each endpoint page. - Contact, phone, LinkedIn activity, and outreach task identifiers are decimal numeric strings. Do not add prefixes such as `task_` or `phone_`. - Array parameters are route-specific. Some body fields accept arrays directly; some query filters use repeated query parameters. Follow the endpoint page for each route. --- ## Attendee source types Source: /concepts/attendee-source-types Lensmor groups people connected to an event under **Attendees**. The `sourceType` array explains how each person is connected to the event or to Lensmor's broader event graph. ## Product labels and API values | Product label | API value | Meaning | | --- | --- | --- | | **Exhibitor** | `exhibitor` | A person associated with a company exhibiting at the event. | | **Social Signals** | `social` | A person identified through LinkedIn signals connected to the event. This label is not proof of official event registration. | | **Visitor** | `visitors` | A registered event attendee obtained through organizer or data-provider partnerships. Visitor data is available only for selected events. | The product displays **Visitor** in the singular, while the current API value is `"visitors"`. ## Labels are not mutually exclusive `sourceType` is an array because one person can have more than one source relationship: ```json { "id": "789", "fullName": "Jane Smith", "companyName": "Acme Retail Systems", "sourceType": ["exhibitor", "social"] } ``` Do not force each person into a single category. Preserve every returned value and treat an empty array as “no supported source label is available.” ## Source scope differs by endpoint - `GET /external/personnel/list` returns source labels in the requested event context. - `GET /external/personnel/profile` can aggregate source labels across the person's associated events. - `GET /external/contacts/search` can also return source labels aggregated across associated events because the search is company-scoped rather than event-scoped. If your UI needs to say “this person is a Visitor for this event,” use the event-scoped personnel list rather than an aggregated profile or contact-search result. ## Visitor access Visitor is a separate access layer from base event access: - Base event access expands event-scoped exhibitor and personnel coverage. - Visitor access adds eligible registered attendee records for events where visitor data is available. - Call `POST /external/actions/precheck` with `unlock_event_visitors` or `unlock_event_full_access` before attempting a paid unlock. - A visitor precheck can return `visitor_data_available: false`; treat that as an expected event-level availability state. The `semantics.accessMode` field on the personnel list describes base event preview/full access. It does not by itself prove that the separate Visitor layer is unlocked. ## Filtering `GET /external/personnel/list` accepts one or more `sourceType` values when you want the API to return people matching event-scoped source labels. Send a comma-separated value or repeat the query parameter: ```bash curl "https://platform.lensmor.com/external/personnel/list?event_id=139574&sourceType=visitors&page=1&pageSize=50" \ -H "Authorization: Bearer $LENSMOR_API_KEY" ``` Supported values are `exhibitor`, `social`, and `visitors`. For example, `sourceType=social,visitors` matches either supported source. The response still returns the complete non-exclusive `sourceType` array for each matched person. You can also fetch the accessible event-scoped result set without `sourceType`, then segment it client-side: ```js const visitors = items.filter((person) => person.sourceType?.includes("visitors") ); ``` Because labels can overlap, a person may match a source filter and still include additional `sourceType` values in the response. ## Related workflows - [Build attendee intelligence](/guides/build-attendee-intelligence) - [Find and unlock an event](/guides/find-and-unlock-event) - [Credits and access](/concepts/credits-and-access) - [List event personnel](/api-reference/personnel/list-event-personnel) --- ## Credits and access Source: /concepts/credits-and-access Some Lensmor API operations are credit-aware. Most read-only discovery endpoints are available with a valid API key, while unlock and paid company-name lookup workflows may consume credits. API credits are shared with the Lensmor SaaS app. The API key belongs to a subscribed Lensmor user account, and API usage draws from the same credit pool as product usage in the app. ## Credit balance Use `GET /external/credits/balance` to inspect the API key owner's current balance before running credit-consuming workflows. The balance is split into: - `subscriptionBalance` — credits from the active subscription period. - `addonBalance` — credits from active one-time add-on purchases. - `giftBalance` — non-expiring gift credits. - `totalBalance` — total currently available credits. - `resetAt` — the earliest active subscription credit expiration timestamp in milliseconds, or `null`. - `addonExpireAt` — the latest active add-on credit expiration timestamp in milliseconds, or `null`. ## Preview and full access Event-scoped exhibitor and personnel lists can return preview results before the event is unlocked. When an event is locked, list responses may include `semantics` metadata describing: - whether access is `preview` or `full` - how many matching records are visible - whether more results require unlocking the event - which unlock action and credit amount applies Base event unlock grants full access to event-scoped exhibitor and personnel results. Visitor access is a separate registered-attendee data layer, sourced through organizer or data-provider partnerships, that can be unlocked after base event access or together through the full-access endpoint. `semantics.accessMode` on an event-scoped list describes base event access only. It does not prove that Visitor access is unlocked. Use the Visitor or full-access action precheck when registered Visitor records are required. ## Credit-consuming operations | Operation | Typical cost | Notes | | --- | --- | --- | | Base event unlock | `2000` credits | Unlocks full exhibitor and personnel access when contacts are available and the event is not already unlocked. | | Event visitor unlock | `3000` credits | Requires base event access, an active subscription, and available visitor data. Repeated unlocks are free. | | Full event access | `0`–`5000` credits | Atomically unlocks missing base and visitor layers: `2000` for base event access plus `3000` for visitor access. | | Contact email unlock | `15` credits per chargeable contact | Batch unlock creates an asynchronous task. Already unlocked contacts are not charged again. | | Contact phone unlock | `150` credits per chargeable contact | Batch unlock creates an asynchronous task. Already unlocked contacts are not charged again. | | Exhibitor company search | `50` credits for a non-empty result | `POST /external/exhibitors/search-by-company-name` charges only when at least one exhibitor is returned. Empty results are free. | | Exhibitor event search | `50` credits for a non-empty result | `POST /external/exhibitors/search-events` charges only when at least one event is returned. Empty results are free. | | LinkedIn activity unlock | No email-unlock charge | `POST /external/personnel/unlock-linkedin-activity` unlocks or starts analysis for LinkedIn activity. It does not unlock contact emails. | Prices can change by plan or product policy. Treat the API response and your commercial agreement as the source of truth for billing. ## Final billing reconciliation Precheck responses, endpoint docs, and UI estimates help users decide whether to proceed, but they are not final billing records. After a paid action, refresh [Credits balance](/api-reference/credits/get-credits-balance) and the affected resource. This is especially important when: - an event or contact was already unlocked - an asynchronous contact unlock task completes with mixed item-level outcomes - a submitted contact is ineligible, failed, or does not produce an email - a workflow is retried after a timeout For contact unlocks, count delivered enrichment only when an item-level result is unlocked and contains an email. Keep failed or unresolved items visible separately from successful unlocks. ## Credit-safe integration pattern Use a confirmation step for operations that can spend credits: Show the event, company, or contact records the user is about to unlock or search. Call `GET /external/credits/balance` and show the current total balance. Call precheck for access-dependent actions and display the returned cost, or use the known cost from this page or `semantics.unlock`. Call the unlock or reverse-lookup endpoint only after the user confirms. Update balance, access state, and list results after the response. For workflows that depend on access state, call [Actions precheck](/api-reference/actions/precheck-an-external-action) before execution. Precheck is read-only and returns whether the action is allowed, whether it should charge credits, and the expected credit amount when known. ## Headers Some credit-consuming or task-producing routes accept `x-call-source`. Supported values are: - `api` — default customer API usage. - `agent` — agent-driven usage. If omitted or unrecognized, the API records the call as `api`. This header is currently used by base event unlock, visitor unlock, full event access, contact unlock, phone unlock, exhibitor company search, exhibitor event search, LinkedIn activity unlock, and outreach message generation workflows. ## Handling `402 Payment Required` `402 Payment Required` means the API understood the request but the current account cannot complete the credit-consuming action. Recommended behavior: - stop automatic retries - show the credit requirement or insufficient-balance message - let the user choose whether to add credits, change scope, or cancel - avoid creating duplicate tasks for the same unlock intent ## Related endpoints Check available subscription and gift credits. Unlock base event-scoped exhibitor and personnel coverage. Unlock missing base and registered Visitor access layers in one atomic call. Understand Exhibitor, Social Signals, and Visitor labels. Start an asynchronous email unlock task. Search events from a company name. --- ## Rate limits Source: /concepts/rate-limits Lensmor applies rate limits to API requests to protect availability for all customers. In the current `master` defaults, the window is `60` seconds, with `120` requests per API key and `600` requests per IP. Deployment configuration can override these values, so response headers remain authoritative. ## Default behavior Rate limits are evaluated by IP address and API key. When a request is allowed, the response includes rate-limit headers for the active API-key bucket when available. ```http X-RateLimit-Limit: 120 X-RateLimit-Remaining: 119 X-RateLimit-Reset: 1714521600 ``` `X-RateLimit-Reset` is a Unix timestamp in seconds. ## Limit exceeded When a request exceeds the active limit, the API returns `429 Too Many Requests`. ```json { "code": 429, "message": "Rate limit exceeded", "errorKey": "RATE_LIMIT_EXCEEDED", "traceId": "2be6d7f4-3b2d-4d11-8e2d-7a2e4c3e9a10" } ``` The response also includes: ```http Retry-After: 32 ``` Wait at least `Retry-After` seconds before retrying. ## Integration guidance - Use pagination instead of large repeated requests. - Back off when `X-RateLimit-Remaining` is low. - Retry `429` responses only after `Retry-After`. - Include the `traceId` when contacting support about rate-limit behavior. - In Clay or another scheduler, start at no more than `120` requests per `60000` ms for one API key and lower the rate when several tables or workers share that key.