# Available Fields
Source: https://docs.lonescale.com/api-reference/crm-sync-fields
The records you send to [CRM Sync](/api-reference/endpoint/crm-sync) are free-form JSON objects. Their keys are **LoneScale field keys**, the same vocabulary your CRM integration mapping is built on.
Two rules decide what actually reaches your CRM:
1. LoneScale only reads the keys your mapping references. A key you send that no mapped field and no unicity criterion points at is ignored.
2. A record whose keys match **none** of them is failed rather than silently written as a no-op, and the error lists the keys that mapping expects.
Call [`GET /crm/mapping`](/api-reference/endpoint/crm-mapping) to see exactly which of the fields below your own mapping uses, before pushing anything.
**The prefix is optional.** Every key below can be sent without its `lonescale_` or `main_` prefix: `first_name`, `job_title`, `company_domain`, `email`, `phone`. Both forms work, and the bare one never overrides a prefixed key you sent explicitly.
## People
| Field key | Description |
| ----------------------------- | -------------------------------------------------------- |
| `lonescale_first_name` | First name |
| `lonescale_last_name` | Last name |
| `lonescale_full_name` | Full name |
| `lonescale_linkedin_url` | LinkedIn profile URL |
| `lonescale_universal_name` | LinkedIn public identifier (the slug in the profile URL) |
| `lonescale_job_title` | Job title |
| `lonescale_job_seniority` | Seniority level |
| `lonescale_department` | Department |
| `lonescale_sub_department` | Sub-department |
| `lonescale_level` | Hierarchical level |
| `lonescale_summary` | Profile summary |
| `lonescale_city` | City |
| `lonescale_geo_location_name` | Location name |
| `lonescale_geo_country_name` | Country name |
| `lonescale_country_code` | Country code |
## Contact details
| Field key | Description |
| ------------------------- | ---------------------------------------------- |
| `main_email` | Primary email |
| `main_email_status` | Deliverability status of the primary email |
| `secondary_emails` | Additional emails |
| `secondary_emails_status` | Deliverability status of the additional emails |
| `main_phone` | Primary phone |
| `main_phone_region` | Region code of the primary phone |
| `secondary_phones` | Additional phones |
| `secondary_phones_region` | Region codes of the additional phones |
## Company
Company keys can be embedded on a contact record (to create or update the associated account at the same time) or sent on their own in the `companies` array.
| Field key | Description |
| ---------------------------------- | -------------------- |
| `lonescale_company_name` | Company name |
| `lonescale_company_domain` | Company domain |
| `lonescale_company_description` | Company description |
| `lonescale_company_industry` | Industry |
| `lonescale_company_linkedin_url` | Company LinkedIn URL |
| `lonescale_company_linkedin_id` | Company LinkedIn id |
| `lonescale_company_staff_count` | Employee count |
| `lonescale_company_employee_range` | Employee range |
| `lonescale_company_country` | Country |
| `lonescale_company_country_code` | Country code |
## CRM ids
| Field key | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contact_crm_id` | Update this exact CRM contact by id (for example the id returned by [`POST /crm/search`](/api-reference/endpoint/crm-search)), skipping unicity matching |
| `account_crm_id` | Target this exact CRM company by id instead of matching by unicity criteria |
| `crm_company_id` | The associated company id, available to your mapping as a value to write |
## Accepted aliases
Field keys are resolved before the mapping runs, so several spellings of the same field work.
**Without the prefix.** Drop `lonescale_` or `main_` from any key in the tables above and it still resolves: `first_name`, `full_name`, `job_title`, `linkedin_url`, `department`, `city`, `country_code`, `company_name`, `company_domain`, `company_staff_count`, `email`, `email_status`, `phone`, `phone_region`. Person and company keys stay distinct: `city` is the contact's, `company_city` is the account's.
**From an enrich payload.** You can also send our enrich output verbatim.
| You send | Resolved to |
| ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
| `firstName`, `lastName`, `fullName` | `lonescale_first_name`, `lonescale_last_name`, `lonescale_full_name` |
| `linkedinUrl`, `universalName` | `lonescale_linkedin_url`, `lonescale_universal_name` |
| `jobTitle` | `lonescale_job_title` |
| `email`, `emailStatus` | `main_email`, `main_email_status` |
| `phone`, `phoneCountryCode` | `main_phone`, `main_phone_region` |
| `lonescale_email`, `lonescale_phone` | `main_email`, `main_phone` |
| `companyName`, `companyDomain`, `companyDescription`, `companyIndustry` | the matching `lonescale_company_*` key |
| `companyEmployeeCount`, `companyEmployeeRange` | `lonescale_company_staff_count` and `lonescale_company_employee_count`, `lonescale_company_employee_range` |
| `companyLinkedinUrl`, `companyLinkedinId`, `companyCity`, `companyCountry`, `companyCountryCode` | the matching `lonescale_company_*` key |
| any bare `company_*` key | the matching `lonescale_company_*` key |
An alias never overwrites a key you sent explicitly.
## Salesforce: Contacts or Leads
For Salesforce, the mapping also decides **which object** is written, exactly like in a workflow. No parameter on the request controls it:
* Map the Contact section and the sync writes Contacts.
* Map only the Lead section, leaving the Contact one empty, and the sync searches your Lead unicity criteria, then updates the match or creates a Lead.
`GET /crm/mapping` reports this as `object: "contact"` or `object: "lead"`, and returns the resolved lead mapping alongside it.
Salesforce requires `Company` on Lead creation, so map it from `lonescale_company_name` and send that key on every record that may create a Lead.
## Example
```json theme={null}
{
"crm": "salesforce",
"settings_type": "api_sync",
"contacts": [
{
"first_name": "Ada",
"last_name": "Lovelace",
"job_title": "Head of Data",
"linkedin_url": "https://www.linkedin.com/in/ada-lovelace",
"email": "ada@acme.com",
"phone": "+33600000000",
"company_name": "Acme",
"company_domain": "acme.com"
}
]
}
```
The same record written as `lonescale_first_name`, `main_email` and so on is equivalent.
# Search Company
Source: https://docs.lonescale.com/api-reference/endpoint/companies-search
GET /companies/search
Resolve a single company by domain, LinkedIn id, LinkedIn slug, or name and return a stable, fixed-shape result.
At least one of `domain`, `linkedin_id`, `slug`, or `name` is required. The response always returns the same set of fields — `null` (or `[]` for arrays) is used for any field we do not have data for, so client integrations can rely on a stable schema.
When `enrich=true` and no cached match is found, the API will trigger an on-demand enrichment via data-sync; when `enrich=true` and a result has both a LinkedIn id and a universal name, a function-by-function headcount breakdown is also attached.
The optional `custom` map (e.g. `custom[crm_id]=abc-123`) is echoed back unchanged at the top level of the response — useful for correlating results with records on your side.
Rate limited to **5 requests per minute**.
# Search Contacts
Source: https://docs.lonescale.com/api-reference/endpoint/contact-sourcing
POST /trigger/contact-sourcing
Search Contacts.
LoneScale will call your `webhook_url` with this payload once the job is completed.
See the [Sourcing payload](/api-reference/webhookpayload/sourcing-payload) documentation for the full webhook schema.
# Search Contacts (Sync)
Source: https://docs.lonescale.com/api-reference/endpoint/contact-sourcing-sync
POST /trigger/contact-sourcing/sync
Search contacts synchronously and return results directly in the response — no webhook required.
The response contains all sourced contacts in a single call. **Maximum 10 contacts per request.** Rate limited to **5 requests per minute**.
# Enrich Contacts
Source: https://docs.lonescale.com/api-reference/endpoint/create
POST /trigger/enrich
Start waterfall enrichment for contacts.
LoneScale will call your `webhook_url` with this payload once the job is completed.
See the [Enrichment payload](/api-reference/webhookpayload/enrichment-payload) documentation for the full webhook schema.
# CRM Mapping Preview
Source: https://docs.lonescale.com/api-reference/endpoint/crm-mapping
GET /crm/mapping
Preview which CRM fields a `POST /trigger/crm-sync` will write: the team's resolved contact/company create & update field mappings, the unicity criteria used for matching, and whether creation is enabled. Read-only.
# CRM Search
Source: https://docs.lonescale.com/api-reference/endpoint/crm-search
POST /crm/search
Look up a contact in your connected CRM(s) and surface the record owner. Provide `email`, `linkedin_url`, or a name with a company. Only connected CRMs are queried. Pipedrive and Sellsy search is email-only, so a linkedin-only lookup returns HubSpot/Salesforce matches only. Each match carries `owned_by_you`, and `caller_crm_links` tells which CRM user the caller is linked to, so a client can say "already yours" vs "owned by X" and warn when a create would go to the default owner.
# CRM Sync
Source: https://docs.lonescale.com/api-reference/endpoint/crm-sync
POST /trigger/crm-sync
Push contacts and/or companies directly into your connected CRM. LoneScale creates or updates the records asynchronously, applying the team-level integration field mappings already configured in LoneScale (create/update fields and unicity criteria). No workflow is involved.
Record keys must match the LoneScale field keys referenced by your configured mappings (for example `lonescale_first_name`, `lonescale_company_domain`, or any custom key you mapped); see [Available Fields](/api-reference/crm-sync-fields) for the full list. For Salesforce the mapping also decides the object: map only the Lead section and the sync searches, updates or creates Leads instead of Contacts. Set `account_crm_id` on a record to target an existing CRM company by id instead of matching by unicity criteria.
Poll `GET /trigger/crm-sync/{id}` with the returned `id`, or pass a `webhook_url` to be called once with the run summary when every record has been processed.
# CRM Sync Delivery Log
Source: https://docs.lonescale.com/api-reference/endpoint/crm-sync-records
GET /trigger/crm-sync/{id}/records
Delivery log of a CRM sync run: one entry per record with its final status (`synced` / `failed` / `pending`), the CRM ids created or updated, the error message when it failed and the number of attempts. Records not consumed yet (run still processing) are reported as `pending`.
# CRM Sync Status
Source: https://docs.lonescale.com/api-reference/endpoint/crm-sync-status
GET /trigger/crm-sync/{id}
Get the status and progress counters of a CRM sync run.
# Enrich Contacts (Sync)
Source: https://docs.lonescale.com/api-reference/endpoint/enrich-sync
POST /trigger/enrich/sync
Enrich contacts synchronously and return results directly in the response — no webhook required.
The response contains all enriched contacts in a single call. **Maximum 10 contacts per request.** Rate limited to **5 requests per minute, counted per API key** — contact us if your integration needs a higher limit, it can be raised per account.
# Contact Enrichment History
Source: https://docs.lonescale.com/api-reference/endpoint/enrichment-history
GET /contacts/{identifier}/enrichment-history
The last enrichment LoneScale holds for a contact, so you can keep your own long-lived cache and skip an enrichment you have already paid for.
**This is a per-contact record, not a log of your calls.** Three limits follow from that, worth reading before you build on it:
- At most **one entry per contact**: the latest state we know, not one entry per `/trigger/enrich` call. `providers` and `credits` are therefore not returned, since they are not retained per contact.
- An enrichment that **found nothing leaves no entry**. A contact we searched and missed is indistinguishable from one we never searched: both return an empty `data`.
- A hit on the 24h enrichment cache does **not** refresh `date`. It is the date of the last enrichment that actually ran, which is the date your cache should key on.
The history is scoped to your API key: you only ever read contacts your own account enriched.
# Get user information
Source: https://docs.lonescale.com/api-reference/endpoint/get
GET /users
Get profile information
# Add an item to a list
Source: https://docs.lonescale.com/api-reference/endpoint/lists-add-item
POST /lists/{listId}/item
Add an item to a list.
**Validation rules by entity type:**
- **COMPANY**: must include `domain` OR both `name` and `location`.
- **PEOPLE**: must include at least 2 of: `linkedin_url`, `name`, `location`, `first_name`, `last_name`, `full_name`, `company_name`, `current_position`.
# Create a list
Source: https://docs.lonescale.com/api-reference/endpoint/lists-create
POST /lists
Create a new imported list.
# Delete a list
Source: https://docs.lonescale.com/api-reference/endpoint/lists-delete
DELETE /lists/{listId}
Delete a list. The list must not be actively used by any workflows.
# Remove an item from a list
Source: https://docs.lonescale.com/api-reference/endpoint/lists-delete-item
DELETE /lists/{listId}/item/{itemId}
Remove an item from a list.
# Get a list
Source: https://docs.lonescale.com/api-reference/endpoint/lists-get
GET /lists/{listId}
Retrieve a specific list by ID.
# Get all lists
Source: https://docs.lonescale.com/api-reference/endpoint/lists-get-all
GET /lists
Retrieve a paginated list of imported lists for the authenticated user, optionally filtered by entity type.
# Get list items
Source: https://docs.lonescale.com/api-reference/endpoint/lists-get-items
GET /lists/{listId}/items
Retrieve paginated items from a specific list.
# Verify Phone Number
Source: https://docs.lonescale.com/api-reference/endpoint/verify-phone
POST /verify-phone
Verify Phone Numbers
[Special Access Required]
# List Webhooks
Source: https://docs.lonescale.com/api-reference/endpoint/webhooks-list
GET /webhooks
List the platform webhooks configured for your team, so you can pass a `webhook_id` destination to `POST /trigger/crm-sync` (push records without a connected CRM). Deduped by URL.
# Contact Enrichment Payload
Source: https://docs.lonescale.com/api-reference/webhookpayload/enrichment-payload
# Search Contacts Payload
Source: https://docs.lonescale.com/api-reference/webhookpayload/sourcing-payload
# Authentication
Source: https://docs.lonescale.com/authentication
You need to include a valid API key in the Authorization header of each request you make to our API. Each request to our API endpoints must include this authorization header.
```bash theme={null}
x-api-key: YOUR_API_KEY
```
Your API key can be found on the Lonescale App > Integrations > Public API
Be sure to replace YOUR\_API\_KEY with the actual API key. Without a valid API key in your request, you will not be able to access the API endpoints and will receive a 401 Unauthorized response.
Keep your API key secure and do not share it publicly.
# lonescale companies search
Source: https://docs.lonescale.com/cli/companies-search
Look up a single company on LoneScale by name, domain, LinkedIn slug, or LinkedIn ID. Returns one fixed-shape company profile (every field is always present, with `null` / `[]` for missing data). This is a synchronous call — results come back inline.
## Synopsis
```bash theme={null}
lonescale companies search [--name ] [--domain ] [--linkedin-id ] [--slug ] [--enrich] [--custom ]
```
At least one of `--name`, `--domain`, `--linkedin-id`, `--slug` is required.
## Examples
```bash theme={null}
# By domain
lonescale companies search --domain stripe.com
# By LinkedIn slug, with function-by-function headcount
lonescale companies search --slug stripe --enrich
# By name (partial match)
lonescale companies search --name "Anthropic"
# Attach custom passthrough metadata
lonescale companies search --domain stripe.com \
--custom '{"campaign":"q2-pipeline","owner":"yann"}'
```
## Options
| Option | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--name ` | Company name (partial match). |
| `--domain ` | Company website domain. |
| `--linkedin-id ` | LinkedIn company numeric ID. |
| `--slug ` | LinkedIn universal name — the slug from `linkedin.com/company/`. |
| `--enrich` | Fall back to live LoneScale enrichment if the company isn't cached, and fetch the function-by-function headcount. |
| `--custom ` | Flat key/value JSON object. Echoed unchanged at the top of the response — useful for joining results back to a row id in your CRM / spreadsheet. |
Plus all [global options](/cli/overview#global-options).
## Response shape
```json theme={null}
{
"linkedinId": "stripe",
"linkedinUrl": "https://www.linkedin.com/company/stripe",
"name": "Stripe",
"domain": "stripe.com",
"industry": "Financial Services",
"staffCount": 8000,
"headcount": {
"engineering": 2400,
"sales": 600,
"marketing": 250
}
}
```
Without `--enrich`, `headcount` is `null` for companies that haven't been previously enriched.
`GET /companies/search`
# lonescale enrich
Source: https://docs.lonescale.com/cli/enrich
Trigger waterfall enrichment (email and/or phone) for a list of contacts. By default the command is asynchronous — it returns a job ID immediately and you poll for results via [`lonescale result`](/cli/result), or pass `--wait` to have the CLI block until the job finishes.
## Synopsis
```bash theme={null}
lonescale enrich --type [--contacts | --contacts-inline ] [options]
```
## Examples
```bash theme={null}
# From a JSON file, polling until done
lonescale enrich --type email,phone --contacts contacts.json --wait
# Inline JSON for a single contact
lonescale enrich --type email --contacts-inline '[
{ "firstname": "John", "lastname": "Doe", "domain": "acme.com" }
]'
# Sync mode (returns results directly, rate-limited to 5 req/min)
lonescale enrich --type email --contacts contacts.json --sync
# Pipe from stdin (any source that produces a JSON array of contacts)
cat contacts.json | lonescale enrich --type email
```
## Options
| Option | Description |
| -------------------------- | ------------------------------------------------------------------------------------------------ |
| `--type ` | **Required.** Comma-separated enrichment types: `email`, `phone`, or `email,phone`. |
| `--contacts ` | Path to a JSON file containing the contacts array. |
| `--contacts-inline ` | Inline JSON array (use when piping or scripting). |
| `--custom ` | Custom metadata JSON, echoed back in the result and any webhook payload. |
| `--wait` | Poll until the job completes. Default timeout is 120 s. |
| `--sync` | Use the synchronous endpoint. Returns results inline but is rate-limited to 5 requests / minute. |
| `--timeout ` | Override the `--wait` / `--sync` timeout (default 120). |
Plus all [global options](/cli/overview#global-options) (`--api-key`, `--output`, `--debug`).
## Contact schema
```json theme={null}
[
{
"firstname": "John",
"lastname": "Doe",
"email": "john@acme.com",
"job_title": "CTO",
"linkedin_url": "https://linkedin.com/in/johndoe",
"domain": "acme.com",
"company_name": "Acme Inc"
}
]
```
Each contact needs at least one identifier: `linkedin_url`, or `firstname` + `lastname` + (`domain` or `company_name`), or `email` alone. Every other field is optional — but more context means better match accuracy.
**Email as a standalone identifier.** An `email` sent on its own resolves the person behind it — name, LinkedIn URL, job title, company — as long as `enrichment_type` includes `profile`. Add `email` / `phone` to the same request to also get their contact details.
**Predicted email fallback.** For `email` enrichment, if no provider returns an email, the result falls back to a pattern-based **predicted email** built from the contact's name and company `domain`. It is returned with `most_probable_email_status: "predicted"` (and an `emails[]` entry with the same status), alongside `predicted_email`, `email_pattern` (e.g. `firstname.lastname@domain`), and `predicted_email_score`. Predicted emails are **free** — they don't consume enrichment credits. Provide a `domain` to enable this fallback.
## Async vs sync at a glance
| Mode | When to use | Tradeoff |
| -------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------- |
| **Async (default)** | Production workloads, batches > 5 contacts, anything you can poll | Returns a job ID; you poll `lonescale result ` or pass `--wait`. |
| **Async + `--wait`** | Scripts that want a single blocking call | CLI does the polling for you. Hits the `--timeout` if the job takes longer. |
| **Sync (`--sync`)** | Single-contact quick checks, exploration | 5 req/min hard rate limit per account; not for batches. |
`POST /trigger/enrich` (async) and `POST /trigger/enrich/sync` (sync).
# Overview
Source: https://docs.lonescale.com/cli/overview
The **LoneScale CLI** (`lonescale`) is the official command-line client for the LoneScale Public API. Use it to enrich contacts, source contacts by persona, look up companies, and poll async jobs — all from your terminal or a shell script.
It's a thin wrapper around the same REST endpoints documented under [API Reference](/authentication), with terminal-friendly output, file/stdin/inline input, and built-in polling.
## Install
```bash theme={null}
npm install -g @lonescale/cli
```
Or run without installing:
```bash theme={null}
npx @lonescale/cli --help
```
Requires Node.js ≥ 18.
## Authenticate
Get your API key from **Dashboard → Integrations → Public API** and provide it in one of three ways (checked in this order):
```bash theme={null}
lonescale enrich --api-key ls_xxx ...
```
```bash theme={null}
export LONESCALE_API_KEY=ls_xxx
lonescale enrich ...
```
```bash theme={null}
mkdir -p ~/.lonescale
echo '{ "api_key": "ls_xxx" }' > ~/.lonescale/config.json
```
## Commands
| Command | Purpose |
| ----------------------------------------------------- | -------------------------------------------------------------- |
| [`lonescale enrich`](/cli/enrich) | Waterfall enrichment (email / phone) for a list of contacts |
| [`lonescale source`](/cli/source) | Source contacts at a company by persona, seniority, location |
| [`lonescale companies search`](/cli/companies-search) | Look up a single company by name, domain, LinkedIn slug, or ID |
| [`lonescale result`](/cli/result) | Poll the status / result of an async job |
## Global options
These flags work with every command:
| Option | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------- |
| `--api-key ` | LoneScale API key (overrides env + config file) |
| `--output ` | `json` (default when piped), `table` (default in TTY), or `minimal` (just the id/status — script-friendly) |
| `--debug` | Print HTTP requests / responses to stderr. API keys are redacted automatically. |
## Output formats
Data goes to **stdout**, logs go to **stderr** — so you can pipe results cleanly into `jq`, `xargs`, `tee`, etc.
```bash theme={null}
# Count returned contacts
lonescale source --domain acme.com --personas p.json --wait --output json \
| jq '.contacts | length'
# Capture just the job ID
JOB_ID=$(lonescale enrich --type email --contacts c.json --output minimal)
```
See [Recipes](/cli/recipes) for more scripting patterns.
# Recipes
Source: https://docs.lonescale.com/cli/recipes
Real-world scripting patterns that combine the CLI commands. All examples assume `LONESCALE_API_KEY` is already exported (see [Overview → Authenticate](/cli/overview#authenticate)).
## Fire and poll
The async pattern, broken into two explicit steps:
```bash theme={null}
JOB_ID=$(lonescale enrich --type email --contacts contacts.json --output minimal)
echo "Started job $JOB_ID"
lonescale result "$JOB_ID" --poll --output json > results.json
```
Or condensed into a single blocking call:
```bash theme={null}
lonescale enrich --type email --contacts contacts.json --wait --output json > results.json
```
## Batch enrichment across multiple files
```bash theme={null}
mkdir -p results
for file in contacts/*.json; do
echo "Processing $file..."
lonescale enrich --type email --contacts "$file" --wait --output json \
> "results/$(basename "$file")"
done
```
## Pipe sourcing → enrichment
Source contacts at a company, then immediately enrich the LinkedIn URLs:
```bash theme={null}
lonescale source --domain figma.com \
--personas-inline '[{"name":"Eng","job_titles":["engineering manager"]}]' \
--locations US --limit 10 --wait --output json \
| jq '[.contacts[] | { firstname, lastname, linkedin_url }]' \
| lonescale enrich --type email --wait --output json \
> figma-eng-emails.json
```
## Filter with `jq`
```bash theme={null}
# Just the names + most probable email
lonescale source --domain acme.com \
--personas-inline '[{"name":"Sales","job_titles":["VP Sales","Head of Sales"]}]' \
--wait --output json \
| jq '.contacts[] | { name: (.firstname + " " + .lastname), email: .most_probable_email }'
```
## Look up a list of companies
```bash theme={null}
# domains.txt — one domain per line
while read domain; do
lonescale companies search --domain "$domain" --output json
done < domains.txt | jq -s '.' # combine into a single JSON array
```
## Debug mode
Pass `--debug` to print every HTTP request and response to stderr. API keys are redacted automatically.
```
$ lonescale enrich --type email --contacts c.json --debug --wait
[DEBUG] POST https://public-api.lonescale.com/trigger/enrich
[DEBUG] Body: { "enrichment_type": ["email"], "contacts": [...] }
[DEBUG] Response: 200 { "id": "abc-123", "status": "..." }
[INFO] Job abc-123 started, polling...
[DEBUG] GET https://public-api.lonescale.com/trigger/run/abc-123
[DEBUG] Response: 200 { "status": "processing" }
[DEBUG] GET https://public-api.lonescale.com/trigger/run/abc-123
[DEBUG] Response: 200 { "status": "finished", "contacts": [...] }
```
Because logs go to stderr and data goes to stdout, you can debug a pipeline without polluting the JSON output:
```bash theme={null}
lonescale enrich --type email --contacts c.json --debug --wait --output json 2> debug.log | jq '.contacts | length'
```
## CI / cron usage
```yaml theme={null}
# Example: nightly enrichment in GitHub Actions
- name: Enrich new leads
env:
LONESCALE_API_KEY: ${{ secrets.LONESCALE_API_KEY }}
run: |
npx -y @lonescale/cli enrich \
--type email,phone \
--contacts ./pipeline/new-leads.json \
--wait --output json > ./pipeline/enriched.json
```
Tell us your use case at [happy@lonescale.com](mailto:happy@lonescale.com).
# lonescale result
Source: https://docs.lonescale.com/cli/result
Retrieve the status and result of an async job started by [`lonescale enrich`](/cli/enrich) or [`lonescale source`](/cli/source).
## Synopsis
```bash theme={null}
lonescale result [--poll] [--interval ] [--timeout ]
```
## Examples
```bash theme={null}
# One-shot status check
lonescale result abc-123
# Poll every 5 seconds until the job finishes, max 3 minutes
lonescale result abc-123 --poll --interval 5 --timeout 180
# Capture only the final JSON for further processing
lonescale result abc-123 --poll --output json > results.json
```
## Options
| Option | Description |
| ---------------------- | ------------------------------------------------------------------- |
| `` | **Required.** The job ID returned by `enrich` or `source`. |
| `--poll` | Keep polling until the job finishes (or until `--timeout` expires). |
| `--interval ` | Seconds between polls (default 3). |
| `--timeout ` | Max wait time before giving up (default 120). |
Plus all [global options](/cli/overview#global-options).
## Exit codes
| Code | Meaning |
| ---- | ------------------------------------------------------------------------------------- |
| `0` | Job finished successfully (or status was returned without `--poll`). |
| `1` | Job failed, timed out, or the CLI couldn't reach the API. Inspect stderr for details. |
This means you can chain `lonescale result` safely in shell scripts:
```bash theme={null}
JOB_ID=$(lonescale enrich --type email --contacts c.json --output minimal)
if lonescale result "$JOB_ID" --poll --output json > results.json; then
echo "Enrichment ready in results.json"
else
echo "Job failed — check the logs"
fi
```
# lonescale source
Source: https://docs.lonescale.com/cli/source
Source contacts at a company by persona, seniority level, and location. Like [`enrich`](/cli/enrich), it is async by default — pass `--wait` to block until the job completes, or call [`lonescale result`](/cli/result) with the returned job ID.
## Synopsis
```bash theme={null}
lonescale source --domain [--personas | --personas-inline ] [options]
```
## Examples
```bash theme={null}
# Basic — VP+ at acme.com, all locations
lonescale source --domain acme.com \
--personas-inline '[{"name":"Leadership","job_titles":["VP","Head","Director"]}]' \
--seniority vp,head,director \
--wait
# Multi-filter with US/UK only, capped at 50 results
lonescale source \
--domain acme.com \
--company-name "Acme Inc" \
--company-linkedin "https://linkedin.com/company/acme" \
--personas personas.json \
--locations US,GB \
--seniority c-suite,vp,director \
--limit 50 \
--wait
```
## Options
| Option | Description |
| -------------------------- | --------------------------------------------------------- |
| `--domain ` | **Required.** Company website domain (e.g. `acme.com`). |
| `--personas ` | Path to a JSON file with the personas array. |
| `--personas-inline ` | Inline personas array. |
| `--company-name ` | Company name. Helpful when the domain is ambiguous. |
| `--company-linkedin ` | Company LinkedIn URL. Boosts accuracy by \~25 %. |
| `--locations ` | Comma-separated ISO country codes (see list below). |
| `--seniority ` | Comma-separated seniority levels (see list below). |
| `--limit ` | Max contacts to return. |
| `--disable-company-info` | Skip the company enrichment block in the response. |
| `--custom ` | Custom metadata JSON, echoed back. |
| `--wait` | Poll until the job completes (timeout 120 s by default). |
| `--sync` | Use the synchronous endpoint (rate-limited to 5 req/min). |
| `--timeout ` | Override the `--wait` / `--sync` timeout. |
Plus all [global options](/cli/overview#global-options).
## Persona schema
```json theme={null}
[
{
"name": "Engineering Leaders",
"job_titles": ["CTO", "VP Engineering", "Head of Engineering"],
"exclude_job_titles": ["Junior", "Intern"]
}
]
```
`job_titles` is matched as **partial / fuzzy** — `"VP Engineering"` will pick up `"VP, Engineering"` and `"VP of Engineering"`. Use `exclude_job_titles` to filter out look-alikes.
## Allowed seniority levels
`owner`, `founder`, `c-suite`, `partner`, `vp`, `head`, `director`, `manager`, `senior`, `entry`, `intern`
## Allowed location codes
`AT`, `AU`, `BE`, `CA`, `CH`, `DE`, `DK`, `ES`, `FI`, `FR`, `GB`, `IE`, `IL`, `IT`, `LU`, `NL`, `NO`, `PT`, `SE`, `US`
`POST /trigger/source-contact` (async) and `POST /trigger/source-contact/sync` (sync).
# Connect to Claude
Source: https://docs.lonescale.com/mcp/connect
The LoneScale MCP server can be added to Claude as a **custom connector**. Connection uses OAuth 2.0 — Claude will pop up a LoneScale consent screen where you paste your API key, then Claude stores the access token and reuses it for every tool call.
## Prerequisites
* A LoneScale API key — copy it from **Dashboard → Integrations → Public API**.
* A Claude account on a plan that supports custom connectors (currently Claude.ai web + Claude Desktop).
## Add the connector
In **Claude.ai** → **Settings → Connectors** (or **Claude Desktop → Settings → Connectors**), click **Add custom connector**.
Server URL:
```
https://mcp-enrich.lonescale.com/mcp
```
Name it `LoneScale` (or whatever you'd like to see in the tool picker).
Claude redirects you to the LoneScale consent screen. Paste your API key and click **Authorize**. You'll be sent back to Claude, and the connector status flips to **Connected**.
Start a new chat and ask Claude something like *"Search LoneScale for the company at stripe.com"*. You should see Claude call `public-api-companies-search` and return the result. Head to [Examples](/mcp/examples) for more.
## OAuth flow under the hood
For client implementers (or anyone curious), here's what happens during the consent flow:
1. Claude discovers the server's auth metadata via:
* `GET /.well-known/oauth-protected-resource` (RFC 9728)
* `GET /.well-known/oauth-authorization-server` (RFC 8414)
2. Claude **dynamically registers** itself via `POST /register` (RFC 7591). It receives a `client_id` scoped to its `redirect_uri` (`https://claude.com/api/mcp/auth_callback`).
3. Claude opens `/authorize?client_id=…&redirect_uri=…&code_challenge=…&code_challenge_method=S256` in your browser. You see the LoneScale consent screen and paste your API key.
4. LoneScale verifies the API key, issues a one-time `code`, and redirects back to Claude with `?code=…&state=…`.
5. Claude exchanges the code at `POST /token` with the PKCE `code_verifier`. The response contains an `access_token` that Claude attaches as `Authorization: Bearer …` to every subsequent `/mcp` call.
## Disconnect
To revoke access, open **Settings → Connectors** in Claude and click **Disconnect** on the LoneScale row. Your API key itself stays valid — rotate it in the LoneScale dashboard if you want to invalidate it everywhere.
## Other clients
The same server works with any MCP-aware client that supports Streamable HTTP + OAuth, including:
* [Cursor](https://docs.cursor.com/context/mcp) — add it under **Settings → MCP → Add custom server**.
* Custom agents built with the [Anthropic SDK](https://docs.anthropic.com/) via the `mcp-remote` adapter.
## Troubleshooting
| Symptom | Likely cause | Fix |
| ---------------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `401 Unauthorized` after authorizing | API key was rotated or revoked | Reconnect with a fresh key |
| Consent screen says "Unknown client\_id" | Stale connector entry | Remove the connector and add it again |
| Tool calls hang on async jobs | Webhook delay | Claude will poll `get-job-result`; results usually arrive within 60s for enrichment, a few minutes for sourcing |
| Need help | — | Email [happy@lonescale.com](mailto:happy@lonescale.com) |
# Examples
Source: https://docs.lonescale.com/mcp/examples
Three prompts you can paste straight into a Claude chat once the LoneScale connector is connected. Each shows the tool Claude is expected to pick and the rough shape of the answer.
## 1. Look up a company
> *"Look up the company at **stripe.com** on LoneScale — I want the name, industry, headcount, and LinkedIn URL."*
**What Claude does:** calls `public-api-companies-search` with `domain: "stripe.com"`.
**Sample response:**
```json theme={null}
{
"linkedinId": "stripe",
"linkedinUrl": "https://www.linkedin.com/company/stripe",
"name": "Stripe",
"domain": "stripe.com",
"industry": "Financial Services",
"staffCount": 8000,
"headcount": { "engineering": 2400, "sales": 600, ... }
}
```
Add **"and break headcount down by function"** to your prompt and Claude will set `enrich: true`, which returns a function-level breakdown.
## 2. Enrich a list of contacts with email + phone
> \_"Here are 3 LinkedIn URLs:
>
> * [https://www.linkedin.com/in/patrickcollison](https://www.linkedin.com/in/patrickcollison)
> * [https://www.linkedin.com/in/johncollison](https://www.linkedin.com/in/johncollison)
> * [https://www.linkedin.com/in/cristinacordova](https://www.linkedin.com/in/cristinacordova)
>
> Enrich each one with email and phone using LoneScale."\_
**What Claude does:**
1. Calls `public-api-enrich` with `enrichment_type: ["email", "phone"]` and the 3 contacts. Gets back a `lonescale_job_id`.
2. Polls `get-job-result` every few seconds until the status flips from `pending` to `ready`.
3. Shows you a table with the enriched email + phone for each contact.
Enrichment is **waterfall** — LoneScale tries multiple providers and returns the first hit per contact. Most jobs finish in under 60 seconds.
When no provider returns an email, the result falls back to a free **predicted email** built from the contact's name and company domain, flagged with `most_probable_email_status: "predicted"` and accompanied by `predicted_email`, `email_pattern`, and `predicted_email_score`. Predicted emails don't consume credits.
## 3. Source contacts at a company
> *"Find 10 engineering managers at **figma.com** based in the US, and show me their LinkedIn URLs and current titles."*
**What Claude does:**
1. Calls `public-api-contact-sourcing` with `company_domain: "figma.com"`, `personas: ["engineering manager"]`, `included_locations: ["United States"]`, `limit: 10`. Gets back a `lonescale_job_id`.
2. Polls `get-job-result` until the job is `ready` (sourcing typically takes 1–3 minutes).
3. Returns the list of sourced contacts inline.
Chain it with prompt 2 ("…now enrich those 10 contacts with email") and Claude will pipe the LinkedIn URLs from the sourcing result straight into `public-api-enrich`.
## Tips
* **Be explicit about LoneScale.** Mentioning "LoneScale" in the prompt makes Claude pick the right connector when you have several connected.
* **Don't interrupt async jobs.** While Claude is polling `get-job-result`, don't send a new message — it will lose the `lonescale_job_id` and have to restart.
* **Custom metadata.** All tools accept an optional `custom` map that's echoed back in webhook payloads. Useful when you're chaining LoneScale with a CRM update further downstream.
# Overview
Source: https://docs.lonescale.com/mcp/overview
The **LoneScale MCP server** lets AI assistants (Claude, Cursor, ChatGPT, custom agents) call LoneScale's enrichment and sourcing capabilities as tools — directly from a chat, no glue code required.
It implements the [Model Context Protocol](https://modelcontextprotocol.io) over Streamable HTTP and authenticates users via OAuth 2.0 with PKCE. Behind the scenes, every tool call uses the same LoneScale Public API documented elsewhere on this site.
## Endpoint
```
https://mcp-enrich.lonescale.com/mcp
```
Transport: **Streamable HTTP** (MCP spec ≥ 2025-06-18).
Auth: **OAuth 2.0** (user-consent flow — see [Connect to Claude](/mcp/connect)).
## Available tools
The tools below are available once the user has authorized with their LoneScale API key.
| Tool | Mode | Description |
| ----------------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `whoami` | sync | The user behind the API key: name, remaining credits, and `crm_links` (the HubSpot/Salesforce user they are linked to, or `null`). Tells whether a CRM push will be assigned to them or to the default owner. |
| `public-api-companies-search` | sync | Look up a company by name, domain, LinkedIn slug, or LinkedIn ID. Returns one fixed-shape company profile. |
| `public-api-crm-search` | sync | Look up a contact in the connected CRM(s) by email, LinkedIn URL, or name + company. Returns matches with owner and `owned_by_you`, plus `caller_crm_links`. |
| `public-api-enrich` | async | Trigger waterfall enrichment (email and/or phone) for a list of contacts. Checks the CRM first and stops if the contact already exists. With `sync_to_crm: true` the enriched contact is written to the CRM and assigned to the caller's linked CRM user. Returns a `lonescale_job_id`. |
| `public-api-contact-sourcing` | async | Source contacts at a company by persona, seniority, and location. Returns a `lonescale_job_id`. |
| `get-job-result` | sync | Poll for results of an async job using its `lonescale_job_id`. Returns `pending` until status is `ready`. Sourced contacts come back with a `crm` block (existing record + owner, or not in CRM). |
Sync tools return results inline. Async tools return a job id immediately and the assistant must call `get-job-result` every few seconds until the status is `ready`.
## What you'll need
* A LoneScale account with an API key (Dashboard → Integrations → Public API).
* A client that supports MCP custom connectors — for example [Claude](https://claude.ai), [Cursor](https://cursor.sh), or any agent built with the Anthropic SDK.
Step-by-step setup, including the OAuth consent screen.
Three concrete prompts you can copy-paste into Claude.