From cdfda91674f6cca14d76fe8ebf34ed47e1c55c48 Mon Sep 17 00:00:00 2001 From: weilun <1083432533@qq.com> Date: Sun, 19 Jul 2026 12:18:39 +0800 Subject: [PATCH] refresh API contract documentation for v0.23.2 --- README.md | 2 +- api-reference-backup/contacts/search.mdx | 8 +- .../contacts/unlock-phone.mdx | 19 +- api-reference-backup/contacts/unlock.mdx | 10 +- api-reference-backup/events/brief.mdx | 3 +- api-reference-backup/events/detail.mdx | 7 +- api-reference-backup/events/fit-score.mdx | 3 +- api-reference-backup/events/list.mdx | 4 +- api-reference-backup/exhibitors/events.mdx | 7 +- .../exhibitors/search-by-company-name.mdx | 12 +- .../exhibitors/search-events.mdx | 18 +- api-reference-backup/openapi.json | 380 ++++++++++++++++-- .../personnel/events-by-linkedin.mdx | 13 +- api-reference-backup/personnel/events.mdx | 7 +- .../personnel/generate-outreach-message.mdx | 22 +- api-reference-backup/personnel/list.mdx | 12 +- api-reference-backup/personnel/outreach.mdx | 2 + api-reference-backup/personnel/profile.mdx | 8 +- ...actions-apply-recommended-events-paged.mdx | 4 +- api-reference/openapi.json | 380 ++++++++++++++++-- changelog.mdx | 17 + concepts/credits-and-access.mdx | 9 +- guides/production-readiness.mdx | 7 +- index.mdx | 6 +- llms-full.txt | 196 ++++++--- openapi.json | 380 ++++++++++++++++-- scripts/test_sync_public_assets.py | 122 ++++++ 27 files changed, 1474 insertions(+), 184 deletions(-) diff --git a/README.md b/README.md index 68682db..97913f9 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ pnpm dlx mintlify dev ## Changelog -See `changelog.mdx` for versioned documentation updates. The current documentation version is `v0.23.1`. +See `changelog.mdx` for versioned documentation updates. The current documentation version is `v0.23.2`. --- diff --git a/api-reference-backup/contacts/search.mdx b/api-reference-backup/contacts/search.mdx index b3c5ccc..8f5974f 100644 --- a/api-reference-backup/contacts/search.mdx +++ b/api-reference-backup/contacts/search.mdx @@ -64,9 +64,12 @@ curl "https://platform.lensmor.com/external/contacts/search?company_name=Acme&pe "companyName": "Acme", "sourceType": "exhibitor", "email": null, + "phone": null, "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, "linkedinActivity": null, - "linkedinActivityStatus": null + "linkedinActivityStatus": null, + "eventCount": 3 } ], "total": 1, @@ -89,7 +92,10 @@ curl "https://platform.lensmor.com/external/contacts/search?company_name=Acme&pe | `companyName` | Matched company name. | | `sourceType` | Source context for the contact record. | | `email` | Email if already unlocked for the caller; otherwise `null`. | +| `phone` | Phone number if already unlocked for the caller; otherwise `null`. | | `contactUnlockStatus` | Contact access state for this API key owner. | +| `phoneUnlockStatus` | `ready`, `processing`, `failed`, or `null`. | +| `eventCount` | Number of associated events when available. | | `linkedinActivity` | Always `null` on this endpoint. Only [Personnel list](/api-reference/personnel/list-event-personnel) populates LinkedIn activity data. | | `linkedinActivityStatus` | Always `null` on this endpoint. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | diff --git a/api-reference-backup/contacts/unlock-phone.mdx b/api-reference-backup/contacts/unlock-phone.mdx index 0a955bd..e3d1952 100644 --- a/api-reference-backup/contacts/unlock-phone.mdx +++ b/api-reference-backup/contacts/unlock-phone.mdx @@ -1,10 +1,10 @@ --- title: "Unlock contact phone numbers" -description: "Start an asynchronous phone number unlock workflow for one or more personnel records in a Lensmor event." +description: "Submit a phone unlock request and receive either an accepted asynchronous task or an immediate no-work success result." openapi: "openapi.json POST /external/contacts/unlock-phone" --- -Start an asynchronous job to unlock phone numbers for one or more personnel records. +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. @@ -12,25 +12,32 @@ Use this endpoint after finding contacts from personnel or contact search result Use `POST /external/contacts/unlock-phone` when your integration needs verified phone numbers for outbound calling or SMS workflows. -This endpoint is asynchronous. The create call returns a task identifier; poll [Get phone unlock task](/api-reference/contacts/get-phone-unlock-task) until the job reaches a terminal state. +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 task was accepted. Always inspect item-level results before counting a contact phone as delivered. + 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 credits per chargeable contact. +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 100 personnel IDs. +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. + ## Notes -- This endpoint creates an asynchronous unlock job. Poll [Get phone unlock task](/api-reference/contacts/get-phone-unlock-task) with `task_id`. +- Only poll [Get phone unlock task](/api-reference/contacts/get-phone-unlock-task) when `status` is `accepted` and `task_id` is present. - 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. diff --git a/api-reference-backup/contacts/unlock.mdx b/api-reference-backup/contacts/unlock.mdx index 74dbbe9..f915cf6 100644 --- a/api-reference-backup/contacts/unlock.mdx +++ b/api-reference-backup/contacts/unlock.mdx @@ -19,7 +19,7 @@ This endpoint is intentionally asynchronous because email unlock can involve mul - A `201 Created` response means the unlock task was accepted. It does not guarantee that every submitted personnel record will return an email. Always inspect item-level task results before counting a contact as enriched. + 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 @@ -64,9 +64,11 @@ curl -X POST "https://platform.lensmor.com/external/contacts/unlock" \ | Field | Description | | --- | --- | -| `status` | Initial task acceptance state. Usually `accepted` when the task was created. | -| `task_id` | Identifier used to poll task status. Store this value. | -| `job_id` | Alias for the background job identifier. It currently matches `task_id`. | +| `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 diff --git a/api-reference-backup/events/brief.mdx b/api-reference-backup/events/brief.mdx index 5a5d148..92dc453 100644 --- a/api-reference-backup/events/brief.mdx +++ b/api-reference-backup/events/brief.mdx @@ -51,8 +51,9 @@ curl "https://platform.lensmor.com/external/events/brief?event_id=139574" \ "region": "NY", "country": "United States", "exhibitorCount": 950, + "sponsorMatchStarred": 0, "image": null, - "dataSource": "database" + "dataSource": "Lensmor" }, "summary": { "attendeeCount": 40000, diff --git a/api-reference-backup/events/detail.mdx b/api-reference-backup/events/detail.mdx index c485d8c..3e5ff0f 100644 --- a/api-reference-backup/events/detail.mdx +++ b/api-reference-backup/events/detail.mdx @@ -61,6 +61,7 @@ curl "https://platform.lensmor.com/external/events/139574" \ "priceLower": "0", "priceUpper": "0", "eventType": "In-person", + "sponsorMatchStarred": 0, "categories": [ { "id": 11, @@ -107,7 +108,9 @@ curl "https://platform.lensmor.com/external/events/139574" \ | `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. | -| `image`, `dataSource` | Media and source metadata. | +| `sponsorMatchStarred` | Sponsor Match starred state (`0` or `1`). | +| `image` | Event image URL when available. | +| `dataSource` | Always `Lensmor` for public event records. | ## Count fields @@ -128,6 +131,6 @@ When you need current coverage numbers, call the event-scoped list endpoint and - For event identifiers, see [Identifiers](/concepts/identifiers). In the current API version, `id` and `eventId` return the same value. - 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` indicates the data origin (e.g. `"Lensmor"`, `"local_import"`). Do not hard-code checks against specific values. +- `dataSource` is always `"Lensmor"` for public event records. - `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. diff --git a/api-reference-backup/events/fit-score.mdx b/api-reference-backup/events/fit-score.mdx index 3af66a1..a9c62f6 100644 --- a/api-reference-backup/events/fit-score.mdx +++ b/api-reference-backup/events/fit-score.mdx @@ -53,8 +53,9 @@ curl -X POST "https://platform.lensmor.com/external/events/fit-score" \ "region": "NY", "country": "United States", "exhibitorCount": 950, + "sponsorMatchStarred": 0, "image": null, - "dataSource": "database" + "dataSource": "Lensmor" }, "score": 8.1, "recommendation": "recommended", diff --git a/api-reference-backup/events/list.mdx b/api-reference-backup/events/list.mdx index f46b4ca..d5d12c7 100644 --- a/api-reference-backup/events/list.mdx +++ b/api-reference-backup/events/list.mdx @@ -81,6 +81,7 @@ curl "https://platform.lensmor.com/external/events/list?event_type=conference_le "region": "Nevada", "country": "United States", "exhibitorCount": 3259, + "sponsorMatchStarred": 0, "image": "https://example.com/ces-image.png", "dataSource": "Lensmor" } @@ -107,7 +108,8 @@ curl "https://platform.lensmor.com/external/events/list?event_type=conference_le | `dateStart`, `dateEnd` | Event date range. | | `venue`, `city`, `region`, `country` | Location metadata. | | `exhibitorCount` | Known exhibitor count when available. | -| `dataSource` | Source category for the returned record (e.g. `"Lensmor"`, `"local_import"`). Do not hard-code checks against specific values. | +| `sponsorMatchStarred` | Sponsor Match starred state (`0` or `1`). | +| `dataSource` | Always `Lensmor` for public event records. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | ## Error responses diff --git a/api-reference-backup/exhibitors/events.mdx b/api-reference-backup/exhibitors/events.mdx index 7173de3..3d911b9 100644 --- a/api-reference-backup/exhibitors/events.mdx +++ b/api-reference-backup/exhibitors/events.mdx @@ -54,8 +54,9 @@ curl "https://platform.lensmor.com/external/exhibitors/events?exhibitor_id=456&p "region": "NY", "country": "United States", "exhibitorCount": 950, + "sponsorMatchStarred": 0, "image": null, - "dataSource": "database" + "dataSource": "Lensmor" } ], "total": 12, @@ -76,7 +77,9 @@ curl "https://platform.lensmor.com/external/exhibitors/events?exhibitor_id=456&p | `dateStart`, `dateEnd` | Event date range. | | `venue`, `city`, `region`, `country` | Location metadata. | | `exhibitorCount` | Known exhibitor count when available. | -| `image`, `dataSource` | Media and source metadata. | +| `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 diff --git a/api-reference-backup/exhibitors/search-by-company-name.mdx b/api-reference-backup/exhibitors/search-by-company-name.mdx index db43e8d..26e11ff 100644 --- a/api-reference-backup/exhibitors/search-by-company-name.mdx +++ b/api-reference-backup/exhibitors/search-by-company-name.mdx @@ -21,7 +21,7 @@ Choose this endpoint when: 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 per successful search attempt. Check [Credits balance](/api-reference/credits/get-credits-balance) and confirm with the user before calling it in a credit-safe workflow. + 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 @@ -40,11 +40,17 @@ See [Authentication](/authentication) | `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}' ``` @@ -104,7 +110,7 @@ curl -X POST "https://platform.lensmor.com/external/exhibitors/search-by-company ## Credit behavior -This endpoint consumes `50` credits per successful search attempt. If the account cannot spend the required credits, the API returns `402 Payment Required`. +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. @@ -120,5 +126,5 @@ This endpoint is precision-first. It is designed to avoid surprising broad match ## 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`. +- 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`. diff --git a/api-reference-backup/exhibitors/search-events.mdx b/api-reference-backup/exhibitors/search-events.mdx index b5dfb62..5b4ccda 100644 --- a/api-reference-backup/exhibitors/search-events.mdx +++ b/api-reference-backup/exhibitors/search-events.mdx @@ -36,6 +36,7 @@ See [Authentication](/authentication) | 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`. | @@ -50,7 +51,7 @@ 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","page":1,"pageSize":20}' + -d '{"company_name":"Acme","sponsor_match_starred":1,"page":1,"pageSize":20}' ``` ## Response example @@ -70,11 +71,10 @@ curl -X POST "https://platform.lensmor.com/external/exhibitors/search-events" \ "city": "Las Vegas", "region": "Nevada", "country": "United States", - "attendeeCount": null, "exhibitorCount": 100, - "personnelCount": 50, + "sponsorMatchStarred": 1, "image": null, - "dataSource": null, + "dataSource": "Lensmor", "matchedExhibitors": [ { "id": "456", @@ -100,13 +100,15 @@ curl -X POST "https://platform.lensmor.com/external/exhibitors/search-events" \ | `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. | -| `attendeeCount`, `exhibitorCount`, `personnelCount` | Event scale and coverage fields when available. | +| `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 currently consumes `50` credits per successful search attempt. If the account cannot spend the required credits, the API returns `402 Payment Required`. +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. @@ -118,8 +120,8 @@ Refresh [Credits balance](/api-reference/credits/get-credits-balance) after succ ## Notes - This endpoint is separate from `GET /external/exhibitors/events`, which requires a known `exhibitor_id`. -- This endpoint currently consumes `50` credits per successful search attempt. See [Credits and access](/concepts/credits-and-access). +- 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`. +- 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. diff --git a/api-reference-backup/openapi.json b/api-reference-backup/openapi.json index dd14ecf..697697b 100644 --- a/api-reference-backup/openapi.json +++ b/api-reference-backup/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "Lensmor API", - "version": "0.23.1", + "version": "0.23.2", "description": "Lensmor Event Intelligence API for event discovery, exhibitor research, personnel lookup, credits, and profile matching.", "license": { "name": "Lensmor Terms of Service", @@ -251,6 +251,7 @@ "region": "Nevada", "country": "United States", "exhibitorCount": 3259, + "sponsorMatchStarred": 0, "image": "https://example.com/ces-image.png", "dataSource": "Lensmor" } @@ -316,6 +317,7 @@ "region": "Nevada", "country": "United States", "exhibitorCount": 3259, + "sponsorMatchStarred": 0, "dataSource": "Lensmor" } } @@ -401,7 +403,8 @@ "region": "NY", "country": "United States", "exhibitorCount": 950, - "dataSource": "database" + "sponsorMatchStarred": 0, + "dataSource": "Lensmor" }, "summary": { "attendeeCount": 40000, @@ -458,7 +461,8 @@ "region": "NY", "country": "United States", "exhibitorCount": 950, - "dataSource": "database" + "sponsorMatchStarred": 0, + "dataSource": "Lensmor" }, "score": 8.1, "recommendation": "recommended", @@ -838,8 +842,13 @@ "Exhibitors" ], "summary": "Search exhibitors by company name", - "description": "Run a precision-first exhibitor lookup by company name; a successful search attempt can consume 50 credits.", + "description": "Run a precision-first exhibitor lookup by company name. A non-empty result consumes 50 credits; an empty result does not consume credits.", "operationId": "searchExhibitorsByCompanyName", + "parameters": [ + { + "$ref": "#/components/parameters/CallSource" + } + ], "requestBody": { "$ref": "#/components/requestBodies/CompanyNameBody" }, @@ -880,6 +889,9 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, + "402": { + "$ref": "#/components/responses/PaymentRequired" + }, "429": { "$ref": "#/components/responses/RateLimited" } @@ -892,7 +904,7 @@ "Exhibitors" ], "summary": "Search events by exhibitor company name", - "description": "Find events associated with a company name; a successful reverse event search can consume 50 credits.", + "description": "Find events associated with a company name. A non-empty result consumes 50 credits; an empty result does not consume credits.", "operationId": "searchEventsByExhibitorCompanyName", "parameters": [ { @@ -900,7 +912,7 @@ } ], "requestBody": { - "$ref": "#/components/requestBodies/CompanyNameBody" + "$ref": "#/components/requestBodies/SearchEventsByCompanyNameBody" }, "responses": { "201": { @@ -908,7 +920,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EventPage" + "$ref": "#/components/schemas/EventReverseSearchPage" }, "example": { "items": [ @@ -919,7 +931,15 @@ "dateStart": "2026-03-15", "dateEnd": "2026-03-18", "city": "Las Vegas", - "country": "United States" + "country": "United States", + "sponsorMatchStarred": 1, + "dataSource": "Lensmor", + "matchedExhibitors": [ + { + "id": "456", + "companyName": "Acme Retail Systems" + } + ] } ], "total": 1, @@ -1135,7 +1155,12 @@ "title": "VP of Retail Technology", "companyName": "Acme Retail Systems", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5, + "outreachMessageStatus": null, + "outreachMessageChannels": [] } ], "total": 42, @@ -1193,7 +1218,12 @@ "linkedinUrl": "https://linkedin.com/in/jane-smith", "companyName": "Acme Retail Systems", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5, + "outreachMessageStatus": null, + "outreachMessageChannels": [] } } } @@ -1317,7 +1347,10 @@ "fullName": "Jane Smith", "linkedinUrl": "https://linkedin.com/in/jane-smith", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5 }, "events": { "items": [ @@ -1473,9 +1506,12 @@ "title": "VP Marketing", "companyName": "Acme", "email": null, + "phone": null, "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, "linkedinActivity": null, - "linkedinActivityStatus": null + "linkedinActivityStatus": null, + "eventCount": 3 } ], "total": 1, @@ -1550,11 +1586,11 @@ }, "responses": { "201": { - "description": "Unlock task accepted", + "description": "Unlock submission accepted, or completed with no chargeable work", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ContactUnlockAccepted" + "$ref": "#/components/schemas/ContactUnlockSubmission" }, "example": { "status": "accepted", @@ -1814,7 +1850,7 @@ "Contacts" ], "summary": "Unlock contact phone numbers", - "description": "Start an asynchronous phone unlock task that consumes credits per chargeable contact.", + "description": "Start an asynchronous phone unlock task that consumes 150 credits per chargeable contact.", "operationId": "unlockContactPhones", "parameters": [ { @@ -1839,11 +1875,11 @@ }, "responses": { "201": { - "description": "Phone unlock task accepted", + "description": "Phone unlock submission accepted, or completed with no chargeable work", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ContactUnlockAccepted" + "$ref": "#/components/schemas/ContactUnlockSubmission" }, "example": { "status": "accepted", @@ -1955,6 +1991,10 @@ "email", "linkedin_message" ], + "linkedin_message_types": [ + "inmail", + "connection_note" + ], "outreach_purpose": "Book a product demo" } } @@ -2043,7 +2083,16 @@ ], "messages": { "email": "Hi Jane, I would like to compare notes before Retail Technology Expo.", - "linkedin_message": "Hi Jane, are you open to a quick conversation before the event?" + "linkedin_message": "Hi Jane, are you open to a quick conversation before the event?", + "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", @@ -2273,6 +2322,51 @@ } } }, + "SearchEventsByCompanyNameBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "company_name" + ], + "properties": { + "company_name": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "sponsor_match_starred": { + "type": "integer", + "enum": [ + 0, + 1 + ], + "description": "Filter to events whose Sponsor Match starred state is 0 or 1. Omit the field to include both states." + }, + "page": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "pageSize": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + } + } + }, + "example": { + "company_name": "Acme", + "sponsor_match_starred": 1, + "page": 1, + "pageSize": 20 + } + } + } + }, "ProfileMatchingBody": { "required": true, "content": { @@ -2572,6 +2666,14 @@ "null" ] }, + "sponsorMatchStarred": { + "type": "integer", + "enum": [ + 0, + 1 + ], + "description": "Sponsor Match starred state returned by Lensmor." + }, "image": { "type": [ "string", @@ -2579,10 +2681,8 @@ ] }, "dataSource": { - "type": [ - "string", - "null" - ] + "type": "string", + "const": "Lensmor" } }, "additionalProperties": true @@ -2714,6 +2814,79 @@ }, "additionalProperties": true }, + "MatchedExhibitorSummary": { + "type": "object", + "required": [ + "id", + "companyName" + ], + "properties": { + "id": { + "type": "string" + }, + "companyName": { + "type": [ + "string", + "null" + ] + } + } + }, + "EventReverseSearchItem": { + "allOf": [ + { + "$ref": "#/components/schemas/EventItem" + }, + { + "type": "object", + "required": [ + "matchedExhibitors" + ], + "properties": { + "matchedExhibitors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MatchedExhibitorSummary" + } + } + } + } + ] + }, + "EventReverseSearchPage": { + "type": "object", + "required": [ + "items", + "total", + "page", + "pageSize", + "totalPages", + "hasMore" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventReverseSearchItem" + } + }, + "total": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "totalPages": { + "type": "integer" + }, + "hasMore": { + "type": "boolean" + } + } + }, "EventFitScore": { "type": "object", "required": [ @@ -3449,9 +3622,27 @@ "null" ] }, + "phone": { + "type": [ + "string", + "null" + ] + }, "contactUnlockStatus": { "type": "string" }, + "phoneUnlockStatus": { + "type": [ + "string", + "null" + ], + "enum": [ + "ready", + "processing", + "failed", + null + ] + }, "linkedinActivity": { "type": [ "object", @@ -3477,6 +3668,26 @@ "string", "null" ] + }, + "eventCount": { + "type": [ + "integer", + "null" + ] + }, + "outreachMessageStatus": { + "type": [ + "string", + "null" + ], + "description": "Current outreach generation state when this endpoint includes outreach metadata." + }, + "outreachMessageChannels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Generated or requested outreach channels when outreach metadata is present." } } }, @@ -3611,25 +3822,90 @@ }, "additionalProperties": true }, - "ContactUnlockAccepted": { + "ContactUnlockSubmission": { "type": "object", "required": [ - "status", - "task_id", - "job_id" + "status" ], "properties": { "status": { "type": "string", - "const": "accepted" + "enum": [ + "accepted", + "success" + ], + "description": "accepted when an asynchronous task was created; success when there was no chargeable work to enqueue." }, "task_id": { - "type": "string" + "type": "string", + "description": "TaskCenter identifier. Present when status is accepted." }, "job_id": { - "type": "string" + "type": "string", + "description": "Alias of task_id. Present when status is accepted." + }, + "skipped_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipped_detail": { + "type": "object", + "properties": { + "invalid_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "missing_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "unlinked_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "already_unlocked_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "unlocking_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } } - } + }, + "oneOf": [ + { + "properties": { + "status": { + "const": "accepted" + } + }, + "required": [ + "task_id", + "job_id" + ] + }, + { + "properties": { + "status": { + "const": "success" + } + } + } + ] }, "ContactUnlockTask": { "type": "object", @@ -3991,6 +4267,18 @@ ] } }, + "linkedin_message_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "inmail", + "connection_note", + "connected_message" + ] + }, + "description": "LinkedIn message variants to generate when linkedin_message is requested." + }, "outreach_purpose": { "type": "string" } @@ -4074,6 +4362,38 @@ "string", "null" ] + }, + "linkedin": { + "type": "object", + "properties": { + "inmail": { + "type": "object", + "properties": { + "subject": { + "type": "string" + }, + "body": { + "type": "string" + } + } + }, + "connection_note": { + "type": "object", + "properties": { + "body": { + "type": "string" + } + } + }, + "connected_message": { + "type": "object", + "properties": { + "body": { + "type": "string" + } + } + } + } } } }, diff --git a/api-reference-backup/personnel/events-by-linkedin.mdx b/api-reference-backup/personnel/events-by-linkedin.mdx index afd5246..a957a42 100644 --- a/api-reference-backup/personnel/events-by-linkedin.mdx +++ b/api-reference-backup/personnel/events-by-linkedin.mdx @@ -54,7 +54,10 @@ curl "https://platform.lensmor.com/external/personnel/events/by-linkedin?linkedi "companyName": "Acme Retail Systems", "sourceType": "exhibitor", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5 }, "events": { "items": [ @@ -72,8 +75,9 @@ curl "https://platform.lensmor.com/external/personnel/events/by-linkedin?linkedi "region": "NY", "country": "United States", "exhibitorCount": 950, + "sponsorMatchStarred": 0, "image": null, - "dataSource": "database" + "dataSource": "Lensmor" } ], "total": 1, @@ -92,9 +96,14 @@ curl "https://platform.lensmor.com/external/personnel/events/by-linkedin?linkedi | `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 diff --git a/api-reference-backup/personnel/events.mdx b/api-reference-backup/personnel/events.mdx index adb72bb..8db9733 100644 --- a/api-reference-backup/personnel/events.mdx +++ b/api-reference-backup/personnel/events.mdx @@ -54,8 +54,9 @@ curl "https://platform.lensmor.com/external/personnel/events?personnel_id=789&pa "region": "NY", "country": "United States", "exhibitorCount": 950, + "sponsorMatchStarred": 0, "image": null, - "dataSource": "database" + "dataSource": "Lensmor" } ], "total": 5, @@ -76,7 +77,9 @@ curl "https://platform.lensmor.com/external/personnel/events?personnel_id=789&pa | `dateStart`, `dateEnd` | Event date range. | | `venue`, `city`, `region`, `country` | Location metadata. | | `exhibitorCount` | Known exhibitor count when available. | -| `image`, `dataSource` | Media and source metadata. | +| `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 diff --git a/api-reference-backup/personnel/generate-outreach-message.mdx b/api-reference-backup/personnel/generate-outreach-message.mdx index 4ac29ee..b7ca639 100644 --- a/api-reference-backup/personnel/generate-outreach-message.mdx +++ b/api-reference-backup/personnel/generate-outreach-message.mdx @@ -19,9 +19,29 @@ Use `POST /external/personnel/generate-outreach-message` when you want to: 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`. You can request both simultaneously. + 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`. +## Request body + +| Name | Required | Type | Notes | +| --- | --- | --- | --- | +| `personnel_ids` | Yes | string[] | Maximum `50` personnel IDs. | +| `event_id` | Yes | string | Supports external `eventId` or internal event ID. | +| `channels` | Yes | string[] | One or both of `email`, `linkedin_message`. | +| `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": "139574", + "channels": ["email", "linkedin_message"], + "linkedin_message_types": ["inmail", "connection_note"], + "outreach_purpose": "Invite the contact to a product demo" +} +``` + ## Credit behavior Outreach message generation consumes credits per personnel record processed. diff --git a/api-reference-backup/personnel/list.mdx b/api-reference-backup/personnel/list.mdx index 105c6dd..c6ce88f 100644 --- a/api-reference-backup/personnel/list.mdx +++ b/api-reference-backup/personnel/list.mdx @@ -66,13 +66,18 @@ curl "https://platform.lensmor.com/external/personnel/list?event_id=139574&depar "companyName": "Acme Retail Systems", "sourceType": "exhibitor", "email": null, + "phone": null, "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, "linkedinActivity": { "activityLevel": "ACTIVE", "isActive": true, "latestPostAt": "1747123200000" }, - "linkedinActivityStatus": "ready" + "linkedinActivityStatus": "ready", + "eventCount": 5, + "outreachMessageStatus": "ready", + "outreachMessageChannels": ["email", "linkedin_message"] } ], "total": 3, @@ -118,9 +123,14 @@ curl "https://platform.lensmor.com/external/personnel/list?event_id=139574&depar | `companyName` | Associated company or exhibitor name. | | `sourceType` | Source context for the person record. | | `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 diff --git a/api-reference-backup/personnel/outreach.mdx b/api-reference-backup/personnel/outreach.mdx index 38bf674..03a71b4 100644 --- a/api-reference-backup/personnel/outreach.mdx +++ b/api-reference-backup/personnel/outreach.mdx @@ -18,4 +18,6 @@ Use `GET /external/personnel/outreach` when: ## Notes - Returns the most recent outreach generation result for the given personnel and event combination. +- `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. diff --git a/api-reference-backup/personnel/profile.mdx b/api-reference-backup/personnel/profile.mdx index f10cd25..4ee5c8e 100644 --- a/api-reference-backup/personnel/profile.mdx +++ b/api-reference-backup/personnel/profile.mdx @@ -46,7 +46,10 @@ curl "https://platform.lensmor.com/external/personnel/profile?personnel_id=789" "companyName": "Acme Retail Systems", "sourceType": "exhibitor", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5 } ``` @@ -61,7 +64,10 @@ curl "https://platform.lensmor.com/external/personnel/profile?personnel_id=789" | `companyName` | Associated company or exhibitor. | | `sourceType` | Source context, such as exhibitor-derived data. | | `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 diff --git a/api-reference-backup/profile-matching/actions-apply-recommended-events-paged.mdx b/api-reference-backup/profile-matching/actions-apply-recommended-events-paged.mdx index f30255a..f25d88c 100644 --- a/api-reference-backup/profile-matching/actions-apply-recommended-events-paged.mdx +++ b/api-reference-backup/profile-matching/actions-apply-recommended-events-paged.mdx @@ -98,6 +98,7 @@ curl -X POST "https://platform.lensmor.com/external/profile-matching/actions/app "priceUpper": "2395", "eventType": "conference_led", "quality": "Standard", + "sponsorMatchStarred": 0, "sourceTags": [], "categories": [ { @@ -189,7 +190,8 @@ Each event item includes the full event detail shape: | `verified`, `future`, `historic` | integer | Status flags (0 or 1). | | `historicEvent` | string \| null | Name of the recurring event series. | | `image` | string \| null | Event image URL. | -| `dataSource` | string | Source label (e.g. `"Lensmor"`, `"local_import"`). | +| `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. | diff --git a/api-reference/openapi.json b/api-reference/openapi.json index dd14ecf..697697b 100644 --- a/api-reference/openapi.json +++ b/api-reference/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "Lensmor API", - "version": "0.23.1", + "version": "0.23.2", "description": "Lensmor Event Intelligence API for event discovery, exhibitor research, personnel lookup, credits, and profile matching.", "license": { "name": "Lensmor Terms of Service", @@ -251,6 +251,7 @@ "region": "Nevada", "country": "United States", "exhibitorCount": 3259, + "sponsorMatchStarred": 0, "image": "https://example.com/ces-image.png", "dataSource": "Lensmor" } @@ -316,6 +317,7 @@ "region": "Nevada", "country": "United States", "exhibitorCount": 3259, + "sponsorMatchStarred": 0, "dataSource": "Lensmor" } } @@ -401,7 +403,8 @@ "region": "NY", "country": "United States", "exhibitorCount": 950, - "dataSource": "database" + "sponsorMatchStarred": 0, + "dataSource": "Lensmor" }, "summary": { "attendeeCount": 40000, @@ -458,7 +461,8 @@ "region": "NY", "country": "United States", "exhibitorCount": 950, - "dataSource": "database" + "sponsorMatchStarred": 0, + "dataSource": "Lensmor" }, "score": 8.1, "recommendation": "recommended", @@ -838,8 +842,13 @@ "Exhibitors" ], "summary": "Search exhibitors by company name", - "description": "Run a precision-first exhibitor lookup by company name; a successful search attempt can consume 50 credits.", + "description": "Run a precision-first exhibitor lookup by company name. A non-empty result consumes 50 credits; an empty result does not consume credits.", "operationId": "searchExhibitorsByCompanyName", + "parameters": [ + { + "$ref": "#/components/parameters/CallSource" + } + ], "requestBody": { "$ref": "#/components/requestBodies/CompanyNameBody" }, @@ -880,6 +889,9 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, + "402": { + "$ref": "#/components/responses/PaymentRequired" + }, "429": { "$ref": "#/components/responses/RateLimited" } @@ -892,7 +904,7 @@ "Exhibitors" ], "summary": "Search events by exhibitor company name", - "description": "Find events associated with a company name; a successful reverse event search can consume 50 credits.", + "description": "Find events associated with a company name. A non-empty result consumes 50 credits; an empty result does not consume credits.", "operationId": "searchEventsByExhibitorCompanyName", "parameters": [ { @@ -900,7 +912,7 @@ } ], "requestBody": { - "$ref": "#/components/requestBodies/CompanyNameBody" + "$ref": "#/components/requestBodies/SearchEventsByCompanyNameBody" }, "responses": { "201": { @@ -908,7 +920,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EventPage" + "$ref": "#/components/schemas/EventReverseSearchPage" }, "example": { "items": [ @@ -919,7 +931,15 @@ "dateStart": "2026-03-15", "dateEnd": "2026-03-18", "city": "Las Vegas", - "country": "United States" + "country": "United States", + "sponsorMatchStarred": 1, + "dataSource": "Lensmor", + "matchedExhibitors": [ + { + "id": "456", + "companyName": "Acme Retail Systems" + } + ] } ], "total": 1, @@ -1135,7 +1155,12 @@ "title": "VP of Retail Technology", "companyName": "Acme Retail Systems", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5, + "outreachMessageStatus": null, + "outreachMessageChannels": [] } ], "total": 42, @@ -1193,7 +1218,12 @@ "linkedinUrl": "https://linkedin.com/in/jane-smith", "companyName": "Acme Retail Systems", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5, + "outreachMessageStatus": null, + "outreachMessageChannels": [] } } } @@ -1317,7 +1347,10 @@ "fullName": "Jane Smith", "linkedinUrl": "https://linkedin.com/in/jane-smith", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5 }, "events": { "items": [ @@ -1473,9 +1506,12 @@ "title": "VP Marketing", "companyName": "Acme", "email": null, + "phone": null, "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, "linkedinActivity": null, - "linkedinActivityStatus": null + "linkedinActivityStatus": null, + "eventCount": 3 } ], "total": 1, @@ -1550,11 +1586,11 @@ }, "responses": { "201": { - "description": "Unlock task accepted", + "description": "Unlock submission accepted, or completed with no chargeable work", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ContactUnlockAccepted" + "$ref": "#/components/schemas/ContactUnlockSubmission" }, "example": { "status": "accepted", @@ -1814,7 +1850,7 @@ "Contacts" ], "summary": "Unlock contact phone numbers", - "description": "Start an asynchronous phone unlock task that consumes credits per chargeable contact.", + "description": "Start an asynchronous phone unlock task that consumes 150 credits per chargeable contact.", "operationId": "unlockContactPhones", "parameters": [ { @@ -1839,11 +1875,11 @@ }, "responses": { "201": { - "description": "Phone unlock task accepted", + "description": "Phone unlock submission accepted, or completed with no chargeable work", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ContactUnlockAccepted" + "$ref": "#/components/schemas/ContactUnlockSubmission" }, "example": { "status": "accepted", @@ -1955,6 +1991,10 @@ "email", "linkedin_message" ], + "linkedin_message_types": [ + "inmail", + "connection_note" + ], "outreach_purpose": "Book a product demo" } } @@ -2043,7 +2083,16 @@ ], "messages": { "email": "Hi Jane, I would like to compare notes before Retail Technology Expo.", - "linkedin_message": "Hi Jane, are you open to a quick conversation before the event?" + "linkedin_message": "Hi Jane, are you open to a quick conversation before the event?", + "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", @@ -2273,6 +2322,51 @@ } } }, + "SearchEventsByCompanyNameBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "company_name" + ], + "properties": { + "company_name": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "sponsor_match_starred": { + "type": "integer", + "enum": [ + 0, + 1 + ], + "description": "Filter to events whose Sponsor Match starred state is 0 or 1. Omit the field to include both states." + }, + "page": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "pageSize": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + } + } + }, + "example": { + "company_name": "Acme", + "sponsor_match_starred": 1, + "page": 1, + "pageSize": 20 + } + } + } + }, "ProfileMatchingBody": { "required": true, "content": { @@ -2572,6 +2666,14 @@ "null" ] }, + "sponsorMatchStarred": { + "type": "integer", + "enum": [ + 0, + 1 + ], + "description": "Sponsor Match starred state returned by Lensmor." + }, "image": { "type": [ "string", @@ -2579,10 +2681,8 @@ ] }, "dataSource": { - "type": [ - "string", - "null" - ] + "type": "string", + "const": "Lensmor" } }, "additionalProperties": true @@ -2714,6 +2814,79 @@ }, "additionalProperties": true }, + "MatchedExhibitorSummary": { + "type": "object", + "required": [ + "id", + "companyName" + ], + "properties": { + "id": { + "type": "string" + }, + "companyName": { + "type": [ + "string", + "null" + ] + } + } + }, + "EventReverseSearchItem": { + "allOf": [ + { + "$ref": "#/components/schemas/EventItem" + }, + { + "type": "object", + "required": [ + "matchedExhibitors" + ], + "properties": { + "matchedExhibitors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MatchedExhibitorSummary" + } + } + } + } + ] + }, + "EventReverseSearchPage": { + "type": "object", + "required": [ + "items", + "total", + "page", + "pageSize", + "totalPages", + "hasMore" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventReverseSearchItem" + } + }, + "total": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "totalPages": { + "type": "integer" + }, + "hasMore": { + "type": "boolean" + } + } + }, "EventFitScore": { "type": "object", "required": [ @@ -3449,9 +3622,27 @@ "null" ] }, + "phone": { + "type": [ + "string", + "null" + ] + }, "contactUnlockStatus": { "type": "string" }, + "phoneUnlockStatus": { + "type": [ + "string", + "null" + ], + "enum": [ + "ready", + "processing", + "failed", + null + ] + }, "linkedinActivity": { "type": [ "object", @@ -3477,6 +3668,26 @@ "string", "null" ] + }, + "eventCount": { + "type": [ + "integer", + "null" + ] + }, + "outreachMessageStatus": { + "type": [ + "string", + "null" + ], + "description": "Current outreach generation state when this endpoint includes outreach metadata." + }, + "outreachMessageChannels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Generated or requested outreach channels when outreach metadata is present." } } }, @@ -3611,25 +3822,90 @@ }, "additionalProperties": true }, - "ContactUnlockAccepted": { + "ContactUnlockSubmission": { "type": "object", "required": [ - "status", - "task_id", - "job_id" + "status" ], "properties": { "status": { "type": "string", - "const": "accepted" + "enum": [ + "accepted", + "success" + ], + "description": "accepted when an asynchronous task was created; success when there was no chargeable work to enqueue." }, "task_id": { - "type": "string" + "type": "string", + "description": "TaskCenter identifier. Present when status is accepted." }, "job_id": { - "type": "string" + "type": "string", + "description": "Alias of task_id. Present when status is accepted." + }, + "skipped_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipped_detail": { + "type": "object", + "properties": { + "invalid_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "missing_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "unlinked_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "already_unlocked_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "unlocking_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } } - } + }, + "oneOf": [ + { + "properties": { + "status": { + "const": "accepted" + } + }, + "required": [ + "task_id", + "job_id" + ] + }, + { + "properties": { + "status": { + "const": "success" + } + } + } + ] }, "ContactUnlockTask": { "type": "object", @@ -3991,6 +4267,18 @@ ] } }, + "linkedin_message_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "inmail", + "connection_note", + "connected_message" + ] + }, + "description": "LinkedIn message variants to generate when linkedin_message is requested." + }, "outreach_purpose": { "type": "string" } @@ -4074,6 +4362,38 @@ "string", "null" ] + }, + "linkedin": { + "type": "object", + "properties": { + "inmail": { + "type": "object", + "properties": { + "subject": { + "type": "string" + }, + "body": { + "type": "string" + } + } + }, + "connection_note": { + "type": "object", + "properties": { + "body": { + "type": "string" + } + } + }, + "connected_message": { + "type": "object", + "properties": { + "body": { + "type": "string" + } + } + } + } } } }, diff --git a/changelog.mdx b/changelog.mdx index 196e8f5..2a6c20a 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -5,6 +5,23 @@ description: "Versioned Lensmor API documentation updates, newly documented endp Track documentation updates, newly documented API capabilities, and behavior clarifications that may affect integrations. +## v0.23.2 + +Released July 19, 2026. + +### Changed + +- 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.23.2`. + ## v0.23.1 Released July 19, 2026. diff --git a/concepts/credits-and-access.mdx b/concepts/credits-and-access.mdx index 9aa8a90..0fe2e33 100644 --- a/concepts/credits-and-access.mdx +++ b/concepts/credits-and-access.mdx @@ -3,7 +3,7 @@ title: "Credits and access" description: "Learn how Lensmor credits, locked event access, preview limits, unlock guidance, and credit-safe integration patterns work across the API." --- -Some Lensmor API operations are credit-aware. Read-only discovery endpoints are available with a valid API key, while unlock and reverse-lookup workflows may consume credits. +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. @@ -34,8 +34,9 @@ Unlocking an event grants full access to event-scoped exhibitor and personnel re | --- | --- | --- | | Event unlock | `2000` credits | Unlocks full event access when contacts are available and the event is not already unlocked. | | Contact email unlock | `15` credits per chargeable contact | Batch unlock creates an asynchronous task. Already unlocked contacts are not charged again. | -| Exhibitor company search | `50` credits | `POST /external/exhibitors/search-by-company-name` finds exhibitor company records from a company name. | -| Exhibitor event search | `50` credits | `POST /external/exhibitors/search-events` reverse-lookups events from a company name. | +| 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. @@ -85,7 +86,7 @@ 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 event unlock, contact unlock, phone unlock, exhibitor event search, LinkedIn activity unlock, and outreach message generation workflows. +If omitted or unrecognized, the API records the call as `api`. This header is currently used by event unlock, contact unlock, phone unlock, exhibitor company search, exhibitor event search, LinkedIn activity unlock, and outreach message generation workflows. ## Handling `402 Payment Required` diff --git a/guides/production-readiness.mdx b/guides/production-readiness.mdx index a32ab7e..413f251 100644 --- a/guides/production-readiness.mdx +++ b/guides/production-readiness.mdx @@ -64,10 +64,11 @@ This pattern is especially important for [Unlock event](/api-reference/events/un | --- | --- | --- | | 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`. | | 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. | -| Exhibitor company search | `POST /external/exhibitors/search-by-company-name` | Charges `50` credits per successful company search attempt. | -| Exhibitor event search | `POST /external/exhibitors/search-events` | Charges `50` credits per successful search attempt. | +| 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. | -Read-only list, profile, search, and precheck endpoints do not spend credits by themselves. +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 diff --git a/index.mdx b/index.mdx index 5336e7d..7e9daa8 100644 --- a/index.mdx +++ b/index.mdx @@ -7,7 +7,7 @@ Build event intelligence workflows with the Lensmor API. Use it to discover trad 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.23.1` +Current documentation version: `v0.23.2` ## Base URL `https://platform.lensmor.com` @@ -48,8 +48,8 @@ Authorization: Bearer sk_your_api_key | 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, with preview/full access semantics. | -| A company name and you want companies | [Exhibitor company search](/api-reference/exhibitors/search-exhibitors-by-company-name) | Precision-first company lookup. | -| 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. This can consume 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 diff --git a/llms-full.txt b/llms-full.txt index d24b506..b8f2046 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -11,7 +11,7 @@ Build event intelligence workflows with the Lensmor API. Use it to discover trad 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.23.1` +Current documentation version: `v0.23.2` ## Base URL `https://platform.lensmor.com` @@ -52,8 +52,8 @@ Authorization: Bearer sk_your_api_key | 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, with preview/full access semantics. | -| A company name and you want companies | [Exhibitor company search](/api-reference/exhibitors/search-exhibitors-by-company-name) | Precision-first company lookup. | -| 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. This can consume 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 @@ -201,6 +201,23 @@ Source: /changelog Track documentation updates, newly documented API capabilities, and behavior clarifications that may affect integrations. +## v0.23.2 + +Released July 19, 2026. + +### Changed + +- 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.23.2`. + ## v0.23.1 Released July 19, 2026. @@ -822,10 +839,11 @@ This pattern is especially important for [Unlock event](/api-reference/events/un | --- | --- | --- | | 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`. | | 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. | -| Exhibitor company search | `POST /external/exhibitors/search-by-company-name` | Charges `50` credits per successful company search attempt. | -| Exhibitor event search | `POST /external/exhibitors/search-events` | Charges `50` credits per successful search attempt. | +| 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. | -Read-only list, profile, search, and precheck endpoints do not spend credits by themselves. +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 @@ -1110,6 +1128,7 @@ curl "https://platform.lensmor.com/external/events/list?event_type=conference_le "region": "Nevada", "country": "United States", "exhibitorCount": 3259, + "sponsorMatchStarred": 0, "image": "https://example.com/ces-image.png", "dataSource": "Lensmor" } @@ -1136,7 +1155,8 @@ curl "https://platform.lensmor.com/external/events/list?event_type=conference_le | `dateStart`, `dateEnd` | Event date range. | | `venue`, `city`, `region`, `country` | Location metadata. | | `exhibitorCount` | Known exhibitor count when available. | -| `dataSource` | Source category for the returned record (e.g. `"Lensmor"`, `"local_import"`). Do not hard-code checks against specific values. | +| `sponsorMatchStarred` | Sponsor Match starred state (`0` or `1`). | +| `dataSource` | Always `Lensmor` for public event records. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | ## Error responses @@ -1214,6 +1234,7 @@ curl "https://platform.lensmor.com/external/events/139574" \ "priceLower": "0", "priceUpper": "0", "eventType": "In-person", + "sponsorMatchStarred": 0, "categories": [ { "id": 11, @@ -1260,7 +1281,9 @@ curl "https://platform.lensmor.com/external/events/139574" \ | `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. | -| `image`, `dataSource` | Media and source metadata. | +| `sponsorMatchStarred` | Sponsor Match starred state (`0` or `1`). | +| `image` | Event image URL when available. | +| `dataSource` | Always `Lensmor` for public event records. | ## Count fields @@ -1281,7 +1304,7 @@ When you need current coverage numbers, call the event-scoped list endpoint and - For event identifiers, see [Identifiers](/concepts/identifiers). In the current API version, `id` and `eventId` return the same value. - 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` indicates the data origin (e.g. `"Lensmor"`, `"local_import"`). Do not hard-code checks against specific values. +- `dataSource` is always `"Lensmor"` for public event records. - `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. @@ -1338,8 +1361,9 @@ curl "https://platform.lensmor.com/external/events/brief?event_id=139574" \ "region": "NY", "country": "United States", "exhibitorCount": 950, + "sponsorMatchStarred": 0, "image": null, - "dataSource": "database" + "dataSource": "Lensmor" }, "summary": { "attendeeCount": 40000, @@ -1431,8 +1455,9 @@ curl -X POST "https://platform.lensmor.com/external/events/fit-score" \ "region": "NY", "country": "United States", "exhibitorCount": 950, + "sponsorMatchStarred": 0, "image": null, - "dataSource": "database" + "dataSource": "Lensmor" }, "score": 8.1, "recommendation": "recommended", @@ -2044,7 +2069,7 @@ Choose this endpoint when: 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 per successful search attempt. Check [Credits balance](/api-reference/credits/get-credits-balance) and confirm with the user before calling it in a credit-safe workflow. + 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 @@ -2063,11 +2088,17 @@ See [Authentication](/authentication) | `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}' ``` @@ -2127,7 +2158,7 @@ curl -X POST "https://platform.lensmor.com/external/exhibitors/search-by-company ## Credit behavior -This endpoint consumes `50` credits per successful search attempt. If the account cannot spend the required credits, the API returns `402 Payment Required`. +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. @@ -2143,7 +2174,7 @@ This endpoint is precision-first. It is designed to avoid surprising broad match ## 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`. +- 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`. --- @@ -2184,6 +2215,7 @@ See [Authentication](/authentication) | 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`. | @@ -2198,7 +2230,7 @@ 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","page":1,"pageSize":20}' + -d '{"company_name":"Acme","sponsor_match_starred":1,"page":1,"pageSize":20}' ``` ## Response example @@ -2218,11 +2250,10 @@ curl -X POST "https://platform.lensmor.com/external/exhibitors/search-events" \ "city": "Las Vegas", "region": "Nevada", "country": "United States", - "attendeeCount": null, "exhibitorCount": 100, - "personnelCount": 50, + "sponsorMatchStarred": 1, "image": null, - "dataSource": null, + "dataSource": "Lensmor", "matchedExhibitors": [ { "id": "456", @@ -2248,13 +2279,15 @@ curl -X POST "https://platform.lensmor.com/external/exhibitors/search-events" \ | `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. | -| `attendeeCount`, `exhibitorCount`, `personnelCount` | Event scale and coverage fields when available. | +| `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 currently consumes `50` credits per successful search attempt. If the account cannot spend the required credits, the API returns `402 Payment Required`. +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. @@ -2266,10 +2299,10 @@ Refresh [Credits balance](/api-reference/credits/get-credits-balance) after succ ## Notes - This endpoint is separate from `GET /external/exhibitors/events`, which requires a known `exhibitor_id`. -- This endpoint currently consumes `50` credits per successful search attempt. See [Credits and access](/concepts/credits-and-access). +- 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`. +- 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. --- @@ -2413,8 +2446,9 @@ curl "https://platform.lensmor.com/external/exhibitors/events?exhibitor_id=456&p "region": "NY", "country": "United States", "exhibitorCount": 950, + "sponsorMatchStarred": 0, "image": null, - "dataSource": "database" + "dataSource": "Lensmor" } ], "total": 12, @@ -2435,7 +2469,9 @@ curl "https://platform.lensmor.com/external/exhibitors/events?exhibitor_id=456&p | `dateStart`, `dateEnd` | Event date range. | | `venue`, `city`, `region`, `country` | Location metadata. | | `exhibitorCount` | Known exhibitor count when available. | -| `image`, `dataSource` | Media and source metadata. | +| `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 @@ -2517,13 +2553,18 @@ curl "https://platform.lensmor.com/external/personnel/list?event_id=139574&depar "companyName": "Acme Retail Systems", "sourceType": "exhibitor", "email": null, + "phone": null, "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, "linkedinActivity": { "activityLevel": "ACTIVE", "isActive": true, "latestPostAt": "1747123200000" }, - "linkedinActivityStatus": "ready" + "linkedinActivityStatus": "ready", + "eventCount": 5, + "outreachMessageStatus": "ready", + "outreachMessageChannels": ["email", "linkedin_message"] } ], "total": 3, @@ -2569,9 +2610,14 @@ curl "https://platform.lensmor.com/external/personnel/list?event_id=139574&depar | `companyName` | Associated company or exhibitor name. | | `sourceType` | Source context for the person record. | | `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 @@ -2643,7 +2689,10 @@ curl "https://platform.lensmor.com/external/personnel/profile?personnel_id=789" "companyName": "Acme Retail Systems", "sourceType": "exhibitor", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5 } ``` @@ -2658,7 +2707,10 @@ curl "https://platform.lensmor.com/external/personnel/profile?personnel_id=789" | `companyName` | Associated company or exhibitor. | | `sourceType` | Source context, such as exhibitor-derived data. | | `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 @@ -2731,8 +2783,9 @@ curl "https://platform.lensmor.com/external/personnel/events?personnel_id=789&pa "region": "NY", "country": "United States", "exhibitorCount": 950, + "sponsorMatchStarred": 0, "image": null, - "dataSource": "database" + "dataSource": "Lensmor" } ], "total": 5, @@ -2753,7 +2806,9 @@ curl "https://platform.lensmor.com/external/personnel/events?personnel_id=789&pa | `dateStart`, `dateEnd` | Event date range. | | `venue`, `city`, `region`, `country` | Location metadata. | | `exhibitorCount` | Known exhibitor count when available. | -| `image`, `dataSource` | Media and source metadata. | +| `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 @@ -2822,7 +2877,10 @@ curl "https://platform.lensmor.com/external/personnel/events/by-linkedin?linkedi "companyName": "Acme Retail Systems", "sourceType": "exhibitor", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5 }, "events": { "items": [ @@ -2840,8 +2898,9 @@ curl "https://platform.lensmor.com/external/personnel/events/by-linkedin?linkedi "region": "NY", "country": "United States", "exhibitorCount": 950, + "sponsorMatchStarred": 0, "image": null, - "dataSource": "database" + "dataSource": "Lensmor" } ], "total": 1, @@ -2860,9 +2919,14 @@ curl "https://platform.lensmor.com/external/personnel/events/by-linkedin?linkedi | `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 @@ -2993,9 +3057,29 @@ Use `POST /external/personnel/generate-outreach-message` when you want to: 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`. You can request both simultaneously. + 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`. +## Request body + +| Name | Required | Type | Notes | +| --- | --- | --- | --- | +| `personnel_ids` | Yes | string[] | Maximum `50` personnel IDs. | +| `event_id` | Yes | string | Supports external `eventId` or internal event ID. | +| `channels` | Yes | string[] | One or both of `email`, `linkedin_message`. | +| `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": "139574", + "channels": ["email", "linkedin_message"], + "linkedin_message_types": ["inmail", "connection_note"], + "outreach_purpose": "Invite the contact to a product demo" +} +``` + ## Credit behavior Outreach message generation consumes credits per personnel record processed. @@ -3027,6 +3111,8 @@ Use `GET /external/personnel/outreach` when: ## Notes - Returns the most recent outreach generation result for the given personnel and event combination. +- `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. --- @@ -3095,9 +3181,12 @@ curl "https://platform.lensmor.com/external/contacts/search?company_name=Acme&pe "companyName": "Acme", "sourceType": "exhibitor", "email": null, + "phone": null, "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, "linkedinActivity": null, - "linkedinActivityStatus": null + "linkedinActivityStatus": null, + "eventCount": 3 } ], "total": 1, @@ -3120,7 +3209,10 @@ curl "https://platform.lensmor.com/external/contacts/search?company_name=Acme&pe | `companyName` | Matched company name. | | `sourceType` | Source context for the contact record. | | `email` | Email if already unlocked for the caller; otherwise `null`. | +| `phone` | Phone number if already unlocked for the caller; otherwise `null`. | | `contactUnlockStatus` | Contact access state for this API key owner. | +| `phoneUnlockStatus` | `ready`, `processing`, `failed`, or `null`. | +| `eventCount` | Number of associated events when available. | | `linkedinActivity` | Always `null` on this endpoint. Only [Personnel list](/api-reference/personnel/list-event-personnel) populates LinkedIn activity data. | | `linkedinActivityStatus` | Always `null` on this endpoint. | | `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. | @@ -3161,7 +3253,7 @@ This endpoint is intentionally asynchronous because email unlock can involve mul - A `201 Created` response means the unlock task was accepted. It does not guarantee that every submitted personnel record will return an email. Always inspect item-level task results before counting a contact as enriched. + 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 @@ -3206,9 +3298,11 @@ curl -X POST "https://platform.lensmor.com/external/contacts/unlock" \ | Field | Description | | --- | --- | -| `status` | Initial task acceptance state. Usually `accepted` when the task was created. | -| `task_id` | Identifier used to poll task status. Store this value. | -| `job_id` | Alias for the background job identifier. It currently matches `task_id`. | +| `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 @@ -3366,7 +3460,7 @@ Stop polling when `taskStatus` is `completed` or `failed`. If some items remain Source: /api-reference/contacts/unlock-contact-phone-numbers -Start an asynchronous job to unlock phone numbers for one or more personnel records. +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. @@ -3374,26 +3468,33 @@ Use this endpoint after finding contacts from personnel or contact search result Use `POST /external/contacts/unlock-phone` when your integration needs verified phone numbers for outbound calling or SMS workflows. -This endpoint is asynchronous. The create call returns a task identifier; poll [Get phone unlock task](/api-reference/contacts/get-phone-unlock-task) until the job reaches a terminal state. +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 task was accepted. Always inspect item-level results before counting a contact phone as delivered. + 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 credits per chargeable contact. +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 100 personnel IDs. +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. + ## Notes -- This endpoint creates an asynchronous unlock job. Poll [Get phone unlock task](/api-reference/contacts/get-phone-unlock-task) with `task_id`. +- Only poll [Get phone unlock task](/api-reference/contacts/get-phone-unlock-task) when `status` is `accepted` and `task_id` is present. - 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. @@ -3515,6 +3616,7 @@ curl -X POST "https://platform.lensmor.com/external/profile-matching/actions/app "priceUpper": "2395", "eventType": "conference_led", "quality": "Standard", + "sponsorMatchStarred": 0, "sourceTags": [], "categories": [ { @@ -3606,7 +3708,8 @@ Each event item includes the full event detail shape: | `verified`, `future`, `historic` | integer | Status flags (0 or 1). | | `historicEvent` | string \| null | Name of the recurring event series. | | `image` | string \| null | Event image URL. | -| `dataSource` | string | Source label (e.g. `"Lensmor"`, `"local_import"`). | +| `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. | @@ -4031,7 +4134,7 @@ Use the identifier returned by the matching resource endpoint: Source: /concepts/credits-and-access -Some Lensmor API operations are credit-aware. Read-only discovery endpoints are available with a valid API key, while unlock and reverse-lookup workflows may consume credits. +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. @@ -4062,8 +4165,9 @@ Unlocking an event grants full access to event-scoped exhibitor and personnel re | --- | --- | --- | | Event unlock | `2000` credits | Unlocks full event access when contacts are available and the event is not already unlocked. | | Contact email unlock | `15` credits per chargeable contact | Batch unlock creates an asynchronous task. Already unlocked contacts are not charged again. | -| Exhibitor company search | `50` credits | `POST /external/exhibitors/search-by-company-name` finds exhibitor company records from a company name. | -| Exhibitor event search | `50` credits | `POST /external/exhibitors/search-events` reverse-lookups events from a company name. | +| 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. @@ -4113,7 +4217,7 @@ 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 event unlock, contact unlock, phone unlock, exhibitor event search, LinkedIn activity unlock, and outreach message generation workflows. +If omitted or unrecognized, the API records the call as `api`. This header is currently used by event unlock, contact unlock, phone unlock, exhibitor company search, exhibitor event search, LinkedIn activity unlock, and outreach message generation workflows. ## Handling `402 Payment Required` diff --git a/openapi.json b/openapi.json index dd14ecf..697697b 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "Lensmor API", - "version": "0.23.1", + "version": "0.23.2", "description": "Lensmor Event Intelligence API for event discovery, exhibitor research, personnel lookup, credits, and profile matching.", "license": { "name": "Lensmor Terms of Service", @@ -251,6 +251,7 @@ "region": "Nevada", "country": "United States", "exhibitorCount": 3259, + "sponsorMatchStarred": 0, "image": "https://example.com/ces-image.png", "dataSource": "Lensmor" } @@ -316,6 +317,7 @@ "region": "Nevada", "country": "United States", "exhibitorCount": 3259, + "sponsorMatchStarred": 0, "dataSource": "Lensmor" } } @@ -401,7 +403,8 @@ "region": "NY", "country": "United States", "exhibitorCount": 950, - "dataSource": "database" + "sponsorMatchStarred": 0, + "dataSource": "Lensmor" }, "summary": { "attendeeCount": 40000, @@ -458,7 +461,8 @@ "region": "NY", "country": "United States", "exhibitorCount": 950, - "dataSource": "database" + "sponsorMatchStarred": 0, + "dataSource": "Lensmor" }, "score": 8.1, "recommendation": "recommended", @@ -838,8 +842,13 @@ "Exhibitors" ], "summary": "Search exhibitors by company name", - "description": "Run a precision-first exhibitor lookup by company name; a successful search attempt can consume 50 credits.", + "description": "Run a precision-first exhibitor lookup by company name. A non-empty result consumes 50 credits; an empty result does not consume credits.", "operationId": "searchExhibitorsByCompanyName", + "parameters": [ + { + "$ref": "#/components/parameters/CallSource" + } + ], "requestBody": { "$ref": "#/components/requestBodies/CompanyNameBody" }, @@ -880,6 +889,9 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, + "402": { + "$ref": "#/components/responses/PaymentRequired" + }, "429": { "$ref": "#/components/responses/RateLimited" } @@ -892,7 +904,7 @@ "Exhibitors" ], "summary": "Search events by exhibitor company name", - "description": "Find events associated with a company name; a successful reverse event search can consume 50 credits.", + "description": "Find events associated with a company name. A non-empty result consumes 50 credits; an empty result does not consume credits.", "operationId": "searchEventsByExhibitorCompanyName", "parameters": [ { @@ -900,7 +912,7 @@ } ], "requestBody": { - "$ref": "#/components/requestBodies/CompanyNameBody" + "$ref": "#/components/requestBodies/SearchEventsByCompanyNameBody" }, "responses": { "201": { @@ -908,7 +920,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EventPage" + "$ref": "#/components/schemas/EventReverseSearchPage" }, "example": { "items": [ @@ -919,7 +931,15 @@ "dateStart": "2026-03-15", "dateEnd": "2026-03-18", "city": "Las Vegas", - "country": "United States" + "country": "United States", + "sponsorMatchStarred": 1, + "dataSource": "Lensmor", + "matchedExhibitors": [ + { + "id": "456", + "companyName": "Acme Retail Systems" + } + ] } ], "total": 1, @@ -1135,7 +1155,12 @@ "title": "VP of Retail Technology", "companyName": "Acme Retail Systems", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5, + "outreachMessageStatus": null, + "outreachMessageChannels": [] } ], "total": 42, @@ -1193,7 +1218,12 @@ "linkedinUrl": "https://linkedin.com/in/jane-smith", "companyName": "Acme Retail Systems", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5, + "outreachMessageStatus": null, + "outreachMessageChannels": [] } } } @@ -1317,7 +1347,10 @@ "fullName": "Jane Smith", "linkedinUrl": "https://linkedin.com/in/jane-smith", "email": null, - "contactUnlockStatus": "locked" + "phone": null, + "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, + "eventCount": 5 }, "events": { "items": [ @@ -1473,9 +1506,12 @@ "title": "VP Marketing", "companyName": "Acme", "email": null, + "phone": null, "contactUnlockStatus": "locked", + "phoneUnlockStatus": null, "linkedinActivity": null, - "linkedinActivityStatus": null + "linkedinActivityStatus": null, + "eventCount": 3 } ], "total": 1, @@ -1550,11 +1586,11 @@ }, "responses": { "201": { - "description": "Unlock task accepted", + "description": "Unlock submission accepted, or completed with no chargeable work", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ContactUnlockAccepted" + "$ref": "#/components/schemas/ContactUnlockSubmission" }, "example": { "status": "accepted", @@ -1814,7 +1850,7 @@ "Contacts" ], "summary": "Unlock contact phone numbers", - "description": "Start an asynchronous phone unlock task that consumes credits per chargeable contact.", + "description": "Start an asynchronous phone unlock task that consumes 150 credits per chargeable contact.", "operationId": "unlockContactPhones", "parameters": [ { @@ -1839,11 +1875,11 @@ }, "responses": { "201": { - "description": "Phone unlock task accepted", + "description": "Phone unlock submission accepted, or completed with no chargeable work", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ContactUnlockAccepted" + "$ref": "#/components/schemas/ContactUnlockSubmission" }, "example": { "status": "accepted", @@ -1955,6 +1991,10 @@ "email", "linkedin_message" ], + "linkedin_message_types": [ + "inmail", + "connection_note" + ], "outreach_purpose": "Book a product demo" } } @@ -2043,7 +2083,16 @@ ], "messages": { "email": "Hi Jane, I would like to compare notes before Retail Technology Expo.", - "linkedin_message": "Hi Jane, are you open to a quick conversation before the event?" + "linkedin_message": "Hi Jane, are you open to a quick conversation before the event?", + "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", @@ -2273,6 +2322,51 @@ } } }, + "SearchEventsByCompanyNameBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "company_name" + ], + "properties": { + "company_name": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "sponsor_match_starred": { + "type": "integer", + "enum": [ + 0, + 1 + ], + "description": "Filter to events whose Sponsor Match starred state is 0 or 1. Omit the field to include both states." + }, + "page": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "pageSize": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + } + } + }, + "example": { + "company_name": "Acme", + "sponsor_match_starred": 1, + "page": 1, + "pageSize": 20 + } + } + } + }, "ProfileMatchingBody": { "required": true, "content": { @@ -2572,6 +2666,14 @@ "null" ] }, + "sponsorMatchStarred": { + "type": "integer", + "enum": [ + 0, + 1 + ], + "description": "Sponsor Match starred state returned by Lensmor." + }, "image": { "type": [ "string", @@ -2579,10 +2681,8 @@ ] }, "dataSource": { - "type": [ - "string", - "null" - ] + "type": "string", + "const": "Lensmor" } }, "additionalProperties": true @@ -2714,6 +2814,79 @@ }, "additionalProperties": true }, + "MatchedExhibitorSummary": { + "type": "object", + "required": [ + "id", + "companyName" + ], + "properties": { + "id": { + "type": "string" + }, + "companyName": { + "type": [ + "string", + "null" + ] + } + } + }, + "EventReverseSearchItem": { + "allOf": [ + { + "$ref": "#/components/schemas/EventItem" + }, + { + "type": "object", + "required": [ + "matchedExhibitors" + ], + "properties": { + "matchedExhibitors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MatchedExhibitorSummary" + } + } + } + } + ] + }, + "EventReverseSearchPage": { + "type": "object", + "required": [ + "items", + "total", + "page", + "pageSize", + "totalPages", + "hasMore" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventReverseSearchItem" + } + }, + "total": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "totalPages": { + "type": "integer" + }, + "hasMore": { + "type": "boolean" + } + } + }, "EventFitScore": { "type": "object", "required": [ @@ -3449,9 +3622,27 @@ "null" ] }, + "phone": { + "type": [ + "string", + "null" + ] + }, "contactUnlockStatus": { "type": "string" }, + "phoneUnlockStatus": { + "type": [ + "string", + "null" + ], + "enum": [ + "ready", + "processing", + "failed", + null + ] + }, "linkedinActivity": { "type": [ "object", @@ -3477,6 +3668,26 @@ "string", "null" ] + }, + "eventCount": { + "type": [ + "integer", + "null" + ] + }, + "outreachMessageStatus": { + "type": [ + "string", + "null" + ], + "description": "Current outreach generation state when this endpoint includes outreach metadata." + }, + "outreachMessageChannels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Generated or requested outreach channels when outreach metadata is present." } } }, @@ -3611,25 +3822,90 @@ }, "additionalProperties": true }, - "ContactUnlockAccepted": { + "ContactUnlockSubmission": { "type": "object", "required": [ - "status", - "task_id", - "job_id" + "status" ], "properties": { "status": { "type": "string", - "const": "accepted" + "enum": [ + "accepted", + "success" + ], + "description": "accepted when an asynchronous task was created; success when there was no chargeable work to enqueue." }, "task_id": { - "type": "string" + "type": "string", + "description": "TaskCenter identifier. Present when status is accepted." }, "job_id": { - "type": "string" + "type": "string", + "description": "Alias of task_id. Present when status is accepted." + }, + "skipped_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipped_detail": { + "type": "object", + "properties": { + "invalid_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "missing_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "unlinked_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "already_unlocked_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "unlocking_personnel_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } } - } + }, + "oneOf": [ + { + "properties": { + "status": { + "const": "accepted" + } + }, + "required": [ + "task_id", + "job_id" + ] + }, + { + "properties": { + "status": { + "const": "success" + } + } + } + ] }, "ContactUnlockTask": { "type": "object", @@ -3991,6 +4267,18 @@ ] } }, + "linkedin_message_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "inmail", + "connection_note", + "connected_message" + ] + }, + "description": "LinkedIn message variants to generate when linkedin_message is requested." + }, "outreach_purpose": { "type": "string" } @@ -4074,6 +4362,38 @@ "string", "null" ] + }, + "linkedin": { + "type": "object", + "properties": { + "inmail": { + "type": "object", + "properties": { + "subject": { + "type": "string" + }, + "body": { + "type": "string" + } + } + }, + "connection_note": { + "type": "object", + "properties": { + "body": { + "type": "string" + } + } + }, + "connected_message": { + "type": "object", + "properties": { + "body": { + "type": "string" + } + } + } + } } } }, diff --git a/scripts/test_sync_public_assets.py b/scripts/test_sync_public_assets.py index 565f1e2..c4cd4aa 100644 --- a/scripts/test_sync_public_assets.py +++ b/scripts/test_sync_public_assets.py @@ -94,6 +94,128 @@ def test_public_openapi_has_descriptions_and_examples(self) -> None: self.assertEqual(missing_request_examples, []) self.assertEqual(missing_response_examples, []) + def test_company_search_contract_matches_current_credit_rule(self) -> None: + spec = json.loads(self.sync.OPENAPI_SOURCE.read_text(encoding="utf-8")) + self.assertEqual(spec["info"]["version"], "0.23.2") + + company_search = spec["paths"]["/external/exhibitors/search-by-company-name"]["post"] + self.assertIn("non-empty", company_search["description"]) + self.assertIn("402", company_search["responses"]) + + def test_event_search_and_event_item_contracts(self) -> None: + spec = json.loads(self.sync.OPENAPI_SOURCE.read_text(encoding="utf-8")) + schemas = spec["components"]["schemas"] + request_bodies = spec["components"]["requestBodies"] + + event_search = spec["paths"]["/external/exhibitors/search-events"]["post"] + self.assertEqual( + event_search["requestBody"]["$ref"], + "#/components/requestBodies/SearchEventsByCompanyNameBody", + ) + self.assertEqual( + event_search["responses"]["201"]["content"]["application/json"]["schema"]["$ref"], + "#/components/schemas/EventReverseSearchPage", + ) + event_search_fields = request_bodies["SearchEventsByCompanyNameBody"]["content"][ + "application/json" + ]["schema"]["properties"] + self.assertEqual(event_search_fields["sponsor_match_starred"]["enum"], [0, 1]) + + event_fields = schemas["EventItem"]["properties"] + self.assertEqual(event_fields["dataSource"]["const"], "Lensmor") + self.assertEqual(event_fields["sponsorMatchStarred"]["enum"], [0, 1]) + + event_examples = [ + spec["paths"]["/external/events/list"]["get"]["responses"]["200"]["content"] + ["application/json"]["example"]["items"][0], + spec["paths"]["/external/events/{id}"]["get"]["responses"]["200"]["content"] + ["application/json"]["example"]["event"], + spec["paths"]["/external/events/brief"]["get"]["responses"]["200"]["content"] + ["application/json"]["example"]["event"], + spec["paths"]["/external/events/fit-score"]["post"]["responses"]["201"] + ["content"]["application/json"]["example"]["event"], + ] + for example in event_examples: + with self.subTest(event=example["eventId"]): + self.assertIn(example["sponsorMatchStarred"], [0, 1]) + self.assertEqual(example["dataSource"], "Lensmor") + + def test_contact_unlock_and_outreach_contracts(self) -> None: + spec = json.loads(self.sync.OPENAPI_SOURCE.read_text(encoding="utf-8")) + schemas = spec["components"]["schemas"] + + contact_fields = schemas["ContactItem"]["properties"] + self.assertTrue( + { + "phone", + "phoneUnlockStatus", + "eventCount", + "outreachMessageStatus", + "outreachMessageChannels", + }.issubset(contact_fields) + ) + + unlock_submission = schemas["ContactUnlockSubmission"] + self.assertEqual(unlock_submission["properties"]["status"]["enum"], ["accepted", "success"]) + self.assertIn("skipped_personnel_ids", unlock_submission["properties"]) + self.assertIn("skipped_detail", unlock_submission["properties"]) + + outreach_request_fields = schemas["OutreachMessageRequest"]["properties"] + self.assertEqual( + outreach_request_fields["linkedin_message_types"]["items"]["enum"], + ["inmail", "connection_note", "connected_message"], + ) + outreach_message_fields = schemas["OutreachDetail"]["properties"]["messages"][ + "properties" + ] + self.assertIn("linkedin", outreach_message_fields) + + def test_event_prose_uses_the_public_event_shape(self) -> None: + event_pages = [ + ROOT / "api-reference-backup" / "events" / "list.mdx", + ROOT / "api-reference-backup" / "events" / "detail.mdx", + ROOT / "api-reference-backup" / "exhibitors" / "events.mdx", + ROOT + / "api-reference-backup" + / "profile-matching" + / "actions-apply-recommended-events-paged.mdx", + ROOT / "api-reference-backup" / "personnel" / "events.mdx", + ROOT / "api-reference-backup" / "personnel" / "events-by-linkedin.mdx", + ] + + for path in event_pages: + with self.subTest(path=path.relative_to(ROOT)): + page = path.read_text(encoding="utf-8") + self.assertIn("sponsorMatchStarred", page) + self.assertIn("Always `Lensmor`", page) + self.assertNotIn('"dataSource": "database"', page) + self.assertNotIn('"local_import"', page) + + def test_personnel_prose_matches_endpoint_specific_shapes(self) -> None: + profile_page = ( + ROOT / "api-reference-backup" / "personnel" / "profile.mdx" + ).read_text(encoding="utf-8") + linkedin_events_page = ( + ROOT / "api-reference-backup" / "personnel" / "events-by-linkedin.mdx" + ).read_text(encoding="utf-8") + + self.assertNotIn("outreachMessageStatus", profile_page) + self.assertNotIn("outreachMessageChannels", profile_page) + for field in ("phone", "phoneUnlockStatus", "eventCount"): + with self.subTest(field=field): + self.assertIn(field, linkedin_events_page) + + def test_phone_unlock_prose_handles_accepted_and_no_work_success(self) -> None: + page = ( + ROOT / "api-reference-backup" / "contacts" / "unlock-phone.mdx" + ).read_text(encoding="utf-8") + + self.assertIn('status: "accepted"', page) + self.assertIn('status: "success"', page) + self.assertIn("there is nothing to poll", page) + self.assertIn("Only poll", page) + self.assertNotIn("A `201 Created` response means the task was accepted", page) + def test_agent_integrations_are_explicitly_excluded(self) -> None: inventory = ( ROOT / "docs" / "endpoint-inventory" / "external-api-endpoints.md"