Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions api-reference/contacts/unlock-task.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ Item status:
- `unlocked`
- `failed`

## Result interpretation

Treat each `items[]` entry independently:

- Count a contact as delivered only when item `status` is `unlocked` and `email` is present.
- Surface `failed` items separately with `errorCode` when provided.
- A task can reach a terminal state while individual items have different outcomes.
- Refresh credit balance and contact/personnel records after terminal states when your UI needs billing or access-state reconciliation.

## Polling guidance

Use a backoff schedule instead of polling aggressively:
Expand Down
7 changes: 7 additions & 0 deletions api-reference/contacts/unlock.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ This endpoint is intentionally asynchronous because email unlock can involve mul
For best user experience, show the number of locked contacts and available credit balance before creating the unlock task.
</Note>

<Warning>
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.
</Warning>

## Endpoint
`POST /external/contacts/unlock`

Expand Down Expand Up @@ -78,12 +82,14 @@ Use backoff instead of a tight polling loop. A practical pattern is:
2. Poll every few seconds for short jobs.
3. Increase the interval if the job remains in progress.
4. Stop polling when the task completes or fails.
5. Re-fetch personnel or contact records if your UI needs the latest `email` and `contactUnlockStatus` values.

## Credit behavior

Contact email unlock currently costs `15` credits per chargeable contact.

- Already unlocked contacts are not charged again.
- Failed or ineligible items may not produce an email. Treat the task result and refreshed balance as the source of truth for actual delivery and billing.
- The API rejects batches larger than `100` personnel IDs.
- Insufficient balance returns `402 Payment Required`.
- Retry behavior should be tied to the task state, not only the create response.
Expand All @@ -98,4 +104,5 @@ Contact email unlock currently costs `15` credits per chargeable contact.

## Notes
- This endpoint creates an asynchronous unlock job. Poll [Get contact unlock task](/api-reference/contacts/unlock-task) with `task_id`.
- Do not create duplicate unlock tasks while a prior task for the same selected contacts is still pending or processing.
- See [Credits and access](/concepts/credits-and-access) for shared credit behavior.
12 changes: 11 additions & 1 deletion api-reference/events/detail.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,22 @@ curl "https://platform.lensmor.com/external/events/139574" \
| `url` | Source or official event URL when available. |
| `dateStart`, `dateEnd` | Event date range. |
| `venue`, `city`, `region`, `country`, `latitude`, `longitude` | Location metadata. Coordinates are string values (e.g. `"36.1313238"`) or `null`. |
| `attendeeCount`, `exhibitorCount`, `personnelCount` | Known scale and coverage fields when available. |
| `attendeeCount`, `exhibitorCount`, `personnelCount` | Known summary scale and coverage fields when available. These fields can be `null` or differ from current event-scoped list totals. |
| `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. |

## Count fields

Treat `exhibitorCount` and `personnelCount` on event detail as summary metadata for display and planning. They are not the authoritative count for the current caller's access state, filters, or page visibility.

When you need current coverage numbers, call the event-scoped list endpoint and use its pagination metadata:

- [List event exhibitors](/api-reference/exhibitors/list) for exhibitor totals.
- [List event personnel](/api-reference/personnel/list) for personnel totals.
- `semantics.counts` when present for visible, locked, and matched access context.

## Error responses
- `401 Unauthorized`
- `404 Not Found`
Expand Down
4 changes: 3 additions & 1 deletion api-reference/events/rank.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ curl -X POST "https://platform.lensmor.com/external/events/rank" \

- Preserve the original submitted event IDs in your client if you need to reconcile ranked results with local records.
- Display `rank` and `match_score` together when showing ordered recommendations.
- If `reasons` is empty, use event metadata and match score rather than showing a blank explanation block.
- Treat the endpoint as shortlist ordering, not a complete recommendation report by itself.
- Live responses can return low or zero `match_score` values and empty `reasons`. Combine returned order with event metadata, matched counts, and follow-up detail or list calls before making a final business recommendation.
- If `reasons` is empty, use event metadata and evidence from follow-up calls rather than showing a blank explanation block.

## Error responses
- `400 Bad Request`
Expand Down
11 changes: 9 additions & 2 deletions api-reference/exhibitors/list.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Common use cases:

- build an event exhibitor directory
- filter exhibitors by industry, category, geography, or company keyword
- request a small nested personnel sample with `personnelLimit`
- request a best-effort nested personnel sample with `personnelLimit`
- decide whether an event is worth unlocking for full coverage

## Endpoint
Expand All @@ -40,7 +40,7 @@ See [Authentication](/authentication)
| `jobTitle` | No | string[] | Personnel job-title filter. Repeated query parameters are supported. |
| `managementLevel` | No | string[] | Personnel management-level filter, such as `vp` or `c_suite`. |
| `department` | No | string[] | Personnel department filter, such as `marketing` or `sales`. |
| `personnelLimit` | No | integer | Maximum nested personnel rows per exhibitor. Use `0` or omit for none. |
| `personnelLimit` | No | integer | Maximum nested personnel rows per exhibitor when available. Use `0` or omit for none. Treat nested personnel as best-effort; use `GET /external/personnel/list` with `event_id` and `exhibitor_id` when contacts are required. |

## Request example
```bash
Expand Down Expand Up @@ -153,6 +153,12 @@ curl "https://platform.lensmor.com/external/exhibitors/list?event_id=139574&indu
| `recommendationProcessingFeature` | Which recommendation feature is processing. `"none"` when idle. |
| `semantics` | Preview/full access metadata for the selected event. |

## Personnel samples

`personnelLimit` asks the API to include a small personnel sample for each exhibitor when the service has a suitable sample for the current event, filters, and access state. It is not a guarantee that every exhibitor item will contain nested personnel rows.

When your workflow needs buyer contacts for a specific company, call [Personnel list](/api-reference/personnel/list) with both `event_id` and `exhibitor_id`. Treat that event-scoped personnel response as the source of truth for people coverage and contact unlock state.

## Access semantics

When an event is locked, this endpoint can return a preview slice instead of full results. Use `semantics` to explain the state to users:
Expand All @@ -174,3 +180,4 @@ When an event is locked, this endpoint can return a preview slice instead of ful
- `matched_event_ids` reflects the requested event scope.
- `techStacks` is always returned as an array; when no data is available, the API returns `techStacks: []`.
- Locked events can return preview results. Use `semantics.unlock` to decide whether to call [Unlock event](/api-reference/events/unlock).
- After event unlock, request the target page again and confirm `semantics.accessMode` moved to `full` before assuming all matching exhibitors are available.
11 changes: 11 additions & 0 deletions api-reference/exhibitors/search-by-company-name.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Choose this endpoint when:

Use [Exhibitor event search](/api-reference/exhibitors/search-events) instead when the desired output is events related to a company name.

<Warning>
This endpoint consumes `50` credits per successful search attempt. Check [Credits balance](/api-reference/credits/balance) and confirm with the user before calling it in a credit-safe workflow.
</Warning>

## Endpoint
`POST /external/exhibitors/search-by-company-name`

Expand Down Expand Up @@ -97,13 +101,20 @@ curl -X POST "https://platform.lensmor.com/external/exhibitors/search-by-company
| `buyingSignals` | Full latest-batch buying-signal objects (see [Exhibitors list](/api-reference/exhibitors/list) for the object shape). `[]` when none. |
| `total`, `page`, `pageSize`, `totalPages`, `hasMore` | Pagination metadata. |

## 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`.

Refresh [Credits balance](/api-reference/credits/balance) after successful searches when your UI or audit log needs final credit reconciliation.

## Matching behavior

This endpoint is precision-first. It is designed to avoid surprising broad matches for short or ambiguous company names. If no company passes matching rules, the API returns a successful empty paginated response.

## Error responses
- `400 Bad Request`
- `401 Unauthorized`
- `402 Payment Required`
- `429 Too Many Requests`

## Notes
Expand Down
8 changes: 5 additions & 3 deletions api-reference/exhibitors/search-events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This endpoint returns events, not exhibitor records. It is useful for:
- starting an event workflow without first calling exhibitor search

<Warning>
This endpoint can consume credits. Check [Credits balance](/api-reference/credits/balance) before running it in bulk.
This endpoint can consume credits. Check [Credits balance](/api-reference/credits/balance) before running it in bulk, and use a confirmation step when a user-facing workflow will spend credits.
</Warning>

## Endpoint
Expand Down Expand Up @@ -98,15 +98,17 @@ curl -X POST "https://platform.lensmor.com/external/exhibitors/search-events" \
| `id`, `eventId` | Event identifiers returned with each event. |
| `name`, `nickname`, `description`, `url` | Event display and source fields. |
| `dateStart`, `dateEnd` | Event date range. |
| `venue`, `city`, `region`, `country` | Event location fields. |
| `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. |
| `matchedExhibitors` | Matched company records that connect the input to the returned event. |
| `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`.

Refresh [Credits balance](/api-reference/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.

## Error responses
- `400 Bad Request`
- `401 Unauthorized`
Expand Down
11 changes: 11 additions & 0 deletions api-reference/personnel/events-by-linkedin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ curl "https://platform.lensmor.com/external/personnel/events/by-linkedin?linkedi

Send the full LinkedIn URL and URL-encode it in the query string. The API performs normalized matching internally, but clients should avoid sending partial names or non-LinkedIn URLs.

## Latency and fallback

LinkedIn URL matching can take longer than direct Lensmor ID lookups because the API normalizes the URL and resolves the person before listing events.

Recommended production behavior:

- Use a request timeout that keeps the user workflow responsive.
- If you already have `personnel.id`, prefer [Personnel events](/api-reference/personnel/events) with `personnel_id`.
- If URL lookup times out or returns no match, keep the workflow usable by continuing with event-scoped personnel search, a selected `personnel_id`, or a retry action.
- Do not block a whole lead-prioritization workflow on one slow LinkedIn URL lookup. Mark that person as unresolved and continue ranking the rest of the batch.

## Error responses
- `400 Bad Request`
- `401 Unauthorized`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ Each event item includes the full event detail shape:

`profile_version`, `active_result_version`, and `is_stale` help clients decide whether to refresh recommendations after profile inputs change. For most UI integrations, show the current results and re-run the endpoint when the user edits the company URL, audience description, filters, or geography.

## Integration guidance

- This endpoint is synchronous but can take several seconds for broad profiles or large result sets. Show a loading state and keep user input editable.
- `relevanceReason` can be `null`. Do not require it before displaying ranked recommendations.
- Use `match_score`, `rank`, matched counts, event dates, geography, categories, and follow-up evidence together. Avoid presenting score differences as exact mathematical certainty.
- For the top events, follow up with [Event detail](/api-reference/events/detail), then preview [List event exhibitors](/api-reference/exhibitors/list) or [List event personnel](/api-reference/personnel/list) before asking the user to unlock an event.
- If event summary counts are `null`, use the event-scoped list endpoint's `total` and `semantics` fields for the current access and filter context.

## Error responses
- `400 Bad Request`
- `401 Unauthorized`
Expand Down
6 changes: 6 additions & 0 deletions changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ description: "Versioned Lensmor API documentation updates, newly documented endp

Track documentation updates, newly documented API capabilities, and behavior clarifications that may affect integrations.

## Unreleased

### Changed

- Clarified live API edge cases for best-effort nested personnel samples, event summary counts, LinkedIn URL lookup fallback, shortlist ranking, exhibitor event search billing, and item-level contact unlock outcomes.

## v0.21.0

Released June 12, 2026.
Expand Down
14 changes: 14 additions & 0 deletions concepts/credits-and-access.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,25 @@ 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. |
| LinkedIn activity unlock | No email-unlock charge | `POST /external/personnel/unlock-linkedin-activity` unlocks or starts analysis for LinkedIn activity. It does not unlock contact emails. |

Prices can change by plan or product policy. Treat the API response and your commercial agreement as the source of truth for billing.

## Final billing reconciliation

Precheck responses, endpoint docs, and UI estimates help users decide whether to proceed, but they are not final billing records. After a paid action, refresh [Credits balance](/api-reference/credits/balance) and the affected resource.

This is especially important when:

- an event or contact was already unlocked
- an asynchronous contact unlock task completes with mixed item-level outcomes
- a submitted contact is ineligible, failed, or does not produce an email
- a workflow is retried after a timeout

For contact unlocks, count delivered enrichment only when an item-level result is unlocked and contains an email. Keep failed or unresolved items visible separately from successful unlocks.

## Credit-safe integration pattern

Use a confirmation step for operations that can spend credits:
Expand Down
11 changes: 9 additions & 2 deletions guides/production-readiness.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Production integrations should handle authentication, pagination, credit-aware a
<Step title="Make asynchronous jobs resumable">
Store `task_id` from contact unlock responses so polling can continue after page refreshes, worker restarts, or network failures.
</Step>
<Step title="Handle item-level async outcomes">
Treat a terminal task as complete for the job, then inspect each item result before counting delivered emails or failed contacts.
</Step>
<Step title="Back off on rate limits">
Respect `Retry-After` for `429 Too Many Requests` and use `X-RateLimit-*` headers for proactive throttling.
</Step>
Expand All @@ -51,15 +54,17 @@ For credit-consuming actions, use a two-step confirmation pattern:
4. Ask the user to confirm.
5. Execute the API call.
6. Refresh balance and result state.
7. Reconcile final billing against the post-action balance and task or endpoint response.

This pattern is especially important for [Unlock event](/api-reference/events/unlock), [Unlock contact emails](/api-reference/contacts/unlock), and [Exhibitor event search](/api-reference/exhibitors/search-events).
This pattern is especially important for [Unlock event](/api-reference/events/unlock), [Unlock contact emails](/api-reference/contacts/unlock), [Exhibitor company search](/api-reference/exhibitors/search-by-company-name), and [Exhibitor event search](/api-reference/exhibitors/search-events).

## Credit-consuming actions

| Action | Endpoint | Current behavior |
| --- | --- | --- |
| Event unlock | `POST /external/events/:id/unlock` | Charges `2000` credits when the event was not already unlocked. Repeating the same unlock is idempotent and returns `creditsUsed: 0`. |
| Contact email unlock | `POST /external/contacts/unlock` | Starts an async task and charges `15` credits per chargeable contact. |
| 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. |

Read-only list, profile, search, and precheck endpoints do not spend credits by themselves.
Expand All @@ -74,6 +79,8 @@ Create task -> wait 3s -> poll -> wait 5s -> poll -> wait 10s -> poll

Stop polling when the task is completed or failed. If the user leaves the page, store the task ID and resume later.

For completed contact unlock tasks, inspect item-level results before updating CRM fields. Count only records that include an unlocked email, surface item-level failures separately, and refresh credit balance after terminal states when users need a spend audit.

## Logging and support

Log enough context to investigate issues without storing sensitive data:
Expand Down
Loading