Leads and enrichment
The global prospect pool, metered reveals, saved leads and conversion to contacts.
17 endpoints· 6 called live· 11 not exercised here· verified against http://127.0.0.1:8741
17 endpoints
GET/api/v1/salesshift/lead-searchesCalled liveThe organization's saved lead searches.
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/lead-searches' \
-H "Authorization: Bearer $SS_TOKEN"import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.list_saved_searches()import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.listSavedSearches();import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
searches, err := ss.ListSavedSearches(ctx)Response · 200
{
"success": "bool",
"data": [
"<empty list>"
]
}POST/api/v1/salesshift/lead-searchesNot exercised hereSave a named set of pool filters as a reusable search.
Request body (required)
Untyped Dict[str, Any] body — there is no Pydantic model, so these are the field names the handler actually reads.
filtersis_sharedname
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/lead-searches' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "filters": "<value>", "is_shared": "<value>", "name": "<value>" }'import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.save_search("US founders", filters={"country": ["US"]})import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.saveSearch({ name: 'US founders', filters: { country: ['US'] } });import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
saved, err := ss.SaveSearch(ctx, salesshift.SaveSearchInput{Name: "US founders"})Response · 201
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | name required |
GET/api/v1/salesshift/leadsCalled liveThe organization's saved leads, optionally filtered by status.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| status | string? | optional | |
| limit | integer | optional | Default 100. max 500. |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/leads' \
-H "Authorization: Bearer $SS_TOKEN"import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.list_leads(status="new", limit=100)import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.listLeads({ status: 'new', limit: 100 });import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
leads, err := ss.ListLeads(ctx, salesshift.ListLeadsInput{Status: "new", Limit: 100})Response · 200
{
"success": "bool",
"data": [
{
"id": "str",
"pool_person_id": "str",
"first_name": "str",
"last_name": "str",
"full_name": "str",
"title": "str",
"seniority": "str",
"department": "str",
"email": "str",
"email_status": "str",
"email_masked": "str",
"has_email": "bool",
"phone": "null",
"linkedin_url": "str",
"phone_available": "bool",
"company": {
"name": "...",
"domain": "...",
"employee_range": "...",
"industry": "..."
},
"location": "str",
"status": "str",
"score": {
"value": "..."
},
"source": "str",
"notes": "null",
"tags": [
"<empty list>"
],
"owner_id": "int",
"erasure_pending": "bool",
"converted_contact_id": "str",
"converted_at": "str",
"created_at": "str"
},
"...x2"
]
}GET/api/v1/salesshift/leads/{lead_id}Not exercised hereOne saved lead, plus the live pool record behind it.
One saved lead, plus the live pool record behind it. The two are shown side by side deliberately: the lead is a snapshot, the pool moves on, and someone working a list needs to see when the underlying record has changed rather than discovering it after a bounce.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| lead_id | string(uuid) | required |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/leads/{lead_id}' \
-H "Authorization: Bearer $SS_TOKEN"import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.get_lead(lead_id)import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.getLead(leadId);import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
lead, err := ss.GetLead(ctx, leadID)Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Lead not found |
PATCH/api/v1/salesshift/leads/{lead_id}Not exercised hereUpdate a saved lead's status, score, notes, disqualify reason, owner or tags.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| lead_id | string(uuid) | required |
Request body (required)
Accepts a JSON object; the source declares no field list.
Request
curl -X PATCH 'https://api.vxcloud.io/api/v1/salesshift/leads/{lead_id}' \
-H "Authorization: Bearer $SS_TOKEN"import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.update_lead(lead_id, status="qualified")import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.updateLead(leadId, { status: 'qualified' });import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
status := "qualified"
lead, err := ss.UpdateLead(ctx, leadID, salesshift.UpdateLeadInput{Status: &status})Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Lead not found |
POST/api/v1/salesshift/leads/{lead_id}/convertNot exercised hereLead → Contact.
Lead → Contact. The moment a record becomes mailable. Requires an email: a contact without one cannot be marketed to, and letting them through creates dead rows that quietly drag every campaign metric down. The lead row is KEPT as an audit trail, never moved.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| lead_id | string(uuid) | required |
Request body (optional)
Untyped Dict[str, Any] body — there is no Pydantic model, so these are the field names the handler actually reads.
lifecycle_stage
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/leads/{lead_id}/convert' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "lifecycle_stage": "<value>" }'import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.convert_lead(lead_id, lifecycle_stage="lead")import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.convertLead(leadId, { lifecycleStage: 'lead' });import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
res, err := ss.ConvertLead(ctx, leadID, salesshift.ConvertLeadInput{})Response · 200
{
"already_converted": "…",
"contact_id": "…",
"reused_existing_contact": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Lead not found |
| 400 | This record has been removed at the person's request and cannot be converted. |
| 400 | Reveal this lead's email before converting — a contact without an address cannot be emailed. |
POST/api/v1/salesshift/leads/bulk-convertNot exercised hereConvert many saved leads to contacts in one call, reporting every id's outcome.
Request body (required)
Untyped Dict[str, Any] body — there is no Pydantic model, so these are the field names the handler actually reads.
lead_ids
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/leads/bulk-convert' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "lead_ids": "<value>" }'import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.bulk_convert_leads([lead_id])import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.bulkConvertLeads([leadId]);import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
report, err := ss.BulkConvertLeads(ctx, []string{leadID})Response · 200
{
"already_converted": "…",
"converted": "…",
"skipped_no_email": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | lead_ids required |
| 402 | computed at runtime: # entitlements.assert_contact_headroom
detail = (
f"{ent.plan_name} stores up to {ent.contacts:,} contacts and this "
f"workspace has {current:,}. Upgrade to add more, or remove "
f"contacts you no longer need."
) |
GET/api/v1/salesshift/leads/company/{company_id}Not exercised hereA company in the pool, with the people behind it split by what this org already owns.
A company in the pool, with the people behind it split by what this org already owns. "New prospects" vs "Existing contacts" is the question someone actually has when they open an account page — they want to know what is left to work, not a raw headcount. Answering it here means one query instead of the client cross-referencing two endpoints and getting it subtly wrong.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| company_id | string(uuid) | required |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/leads/company/{company_id}' \
-H "Authorization: Bearer $SS_TOKEN"import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.get_pool_company(company_id)import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.getPoolCompany(companyId);import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
company, err := ss.GetPoolCompany(ctx, companyID)Response · 200
{
"data": "…",
"department": "…",
"email": "…",
"email_revealed": "…",
"email_status": "…",
"existing_contact_id": "…",
"full_name": "…",
"has_email": "…",
"location": "…",
"phone_available": "…",
"phone_count": "…",
"pool_id": "…",
"saved_lead_id": "…",
"score": "…",
"seniority": "…",
"success": "…",
"title": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Company not found in the pool |
POST/api/v1/salesshift/leads/convert-from-poolNot exercised herePool → Contact in one action: save, reveal if needed, convert.
Pool → Contact in one action: save, reveal if needed, convert. Without this the only route is save → switch tabs → convert, three steps for the single most common intent ("I want to email this person"). Revealing SPENDS QUOTA, so it is opt-in via `reveal_if_needed` and the response accounts for every id: nothing is silently dropped. When quota runs out mid-batch we convert what we can and say exactly how many we could not — a partial success reported as a success is how people lose trust in a meter.
Request body (required)
Untyped Dict[str, Any] body — there is no Pydantic model, so these are the field names the handler actually reads.
lifecycle_stagepool_person_idsreveal_if_needed
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/leads/convert-from-pool' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "lifecycle_stage": "<value>", "pool_person_ids": "<value>", "reveal_if_needed": "<value>" }'import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.convert_from_pool([pool_person_id], reveal_if_needed=True)import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.convertFromPool({ poolPersonIds: [poolPersonId], revealIfNeeded: true });import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
report, err := ss.ConvertFromPool(ctx, salesshift.ConvertFromPoolInput{PoolPersonIDs: []string{poolPersonID}, RevealIfNeeded: true})Response · 200
A flat report: {converted, revealed_now, already_converted, skipped_no_quota, skipped_no_email, skipped_erased, contact_ids}.
Errors
| Status | detail |
|---|---|
| 400 | pool_person_ids required |
| 400 | Max 200 per convert — reveals are metered, so a bigger batch would spend more quota than anyone intends in one click. |
POST/api/v1/salesshift/leads/enrichNot exercised hereCrawl a company's own site and fold what is found back into the pool.
Crawl a company's own site and fold what is found back into the pool. This is the first thing in the product that actually WRITES to the pool, so it is where the rules the schema has been carrying finally get enforced: · **Only fills gaps.** An existing description, keyword set or address is never overwritten by a crawl. A human-curated or verified value outranks a scrape, and a "refresh" that quietly replaced good data with worse data would be impossible to notice and impossible to undo. · **Erasure is checked before every insert**, via `is_erased()`. That hook has existed since the erasure endpoint shipped and had no caller — this is it. Without this, the next crawl would resurrect exactly the people we were asked to forget. · **Provenance is recorded** in `lp_record_sources`: which URL, which fields, when. "Where did this come from" has to be answerable later. · **Nothing is invented.** A name is only derived from an address when the local part is plausibly a name; role accounts are dropped entirely; and every discovered address is `unverified`, because finding a string on a web page is not verification. Accepts a `company_id` (pool company) or a bare `domain`.
Request body (required)
Untyped Dict[str, Any] body — there is no Pydantic model, so these are the field names the handler actually reads.
company_iddomain
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/leads/enrich' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "company_id": "<value>", "domain": "<value>" }'import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.enrich_company(domain="example.com")import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.enrich({ domain: 'example.com' });import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
res, err := ss.Enrich(ctx, salesshift.EnrichInput{Domain: "example.com"})Response · 200
{
"attempted": "…",
"changed": "…",
"company_created": "…",
"company_id": "…",
"crawled": "…",
"elapsed_ms": "…",
"note": "…",
"people_added": "…",
"people_already_known": "…",
"people_found": "…",
"people_skipped_erased": "…",
"status_codes": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | company_id or domain required |
| 502 | The web-research agent on your tenant node could not be reached, so nothing was crawled and nothing was changed. |
| 404 | Company not found in the pool |
POST/api/v1/salesshift/leads/erasureNot exercised hereErase a person from the pool and flag every tenant's saved copy.
Erase a person from the pool and flag every tenant's saved copy. Three things happen, and all three are necessary: 1. The address is recorded as a HASH. We must be able to honour the block on every future crawl without retaining the address we were asked to delete — storing it in plaintext to enforce its deletion is self-defeating. 2. The pool row is deactivated rather than deleted, so a re-crawl that finds the same person does not resurrect them. Contact fields are cleared. 3. Saved leads across ALL organisations are flagged and stripped. The request is about the person, not about the tenant who happens to hold a copy, so scoping this to the caller's org would leave the data live everywhere else. Deliberately org-wide despite being called by one org's user: an erasure that only cleaned the requester's copy would be theatre.
Request body (required)
Untyped Dict[str, Any] body — there is no Pydantic model, so these are the field names the handler actually reads.
emaillinkedin_urlnotereason
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/leads/erasure' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "email": "<value>", "linkedin_url": "<value>", "note": "<value>", "reason": "<value>" }'import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.request_erasure(email="[email protected]", confirm=True)import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.requestErasure({ email: '[email protected]', confirmGlobalErasure: true });import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
res, err := ss.RequestErasure(ctx, salesshift.ErasureInput{Email: "[email protected]", Confirm: true})Response · 201
{
"already_recorded": "…",
"pool_rows_erased": "…",
"saved_leads_flagged": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | email or linkedin_url required |
POST/api/v1/salesshift/leads/facetsCalled liveCounts beside each filter.
Counts beside each filter. Facets are pool-wide — no tenant overlay, because the number of Directors in the pool is the same for everyone.
Request body (required) — SearchIn
| Field | Type | Required | Notes |
|---|---|---|---|
| filters | LeadFilters | optional | |
| result_type | string | optional | Default "person". |
| cursor | string? | optional | |
| limit | integer | optional | Default 25. |
| sort | SortIn? | optional |
The body in the request below is the exact one sent during verification.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/leads/facets' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "filters": {} }'import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.lead_facets(filters={"countries": ["US"]})import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.leadFacets({ countries: ['US'] });import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
facets, err := ss.LeadFacets(ctx, salesshift.LeadFilters{})Response · 200
{
"success": "bool",
"data": {
"country": [
{
"value": "...",
"count": "..."
},
"...x7"
],
"department": [
{
"value": "...",
"count": "..."
},
"...x4"
],
"email_status": [
{
"value": "...",
"count": "..."
},
"...x3"
],
"industry": [
{
"value": "...",
"count": "..."
},
"...x2"
],
"seniority": [
{
"value": "...",
"count": "..."
},
"...x5"
],
"search_backend": "str"
}
}GET/api/v1/salesshift/leads/pool/{pool_id}Not exercised hereEverything the pool knows about one person, plus this org's relationship to them (revealed?
Everything the pool knows about one person, plus this org's relationship to them (revealed? saved? already a contact?). Masking applies here exactly as it does in search — a detail view is not a back door around the meter.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| pool_id | string(uuid) | required |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/leads/pool/{pool_id}' \
-H "Authorization: Bearer $SS_TOKEN"import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.get_pool_person(pool_id)import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.getPoolPerson(poolId);import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
person, err := ss.GetPoolPerson(ctx, poolID)Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Person not found in the pool |
| 410 | This record has been removed at the person's request. |
GET/api/v1/salesshift/leads/quotaCalled liveThe organization's reveal meter: used, allowance and remaining for the period.
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/leads/quota' \
-H "Authorization: Bearer $SS_TOKEN"import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.reveal_quota()import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.revealQuota();import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
quota, err := ss.RevealQuota(ctx)Response · 200
{
"success": "bool",
"data": {
"used": "int",
"allowance": "int",
"remaining": "int",
"display": "str"
}
}POST/api/v1/salesshift/leads/revealCalled liveUn-mask one pool person's contact details; spends one metered reveal.
Request body (required)
Untyped Dict[str, Any] body — there is no Pydantic model, so these are the field names the handler actually reads.
pool_person_id
The body in the request below is the exact one sent during verification.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/leads/reveal' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{}'import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.reveal_lead(pool_person_id)import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.revealLead(poolPersonId);import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
res, err := ss.RevealLead(ctx, poolPersonID)Response · 200
{
"detail": "str"
}Errors
| Status | detail |
|---|---|
| 404 | Person not found in the pool |
| 410 | This record has been removed at the person's request. |
| 400 | pool_person_id required |
| 402 | Reveal limit reached for this month ({...}/{...}). |
POST/api/v1/salesshift/leads/saveNot exercised hereCopy pool rows into the tenant's own list.
Copy pool rows into the tenant's own list. A SNAPSHOT, not a reference: the pool is re-crawled continuously and a saved list must not mutate under the person who qualified it.
Request body (required)
Untyped Dict[str, Any] body — there is no Pydantic model, so these are the field names the handler actually reads.
pool_person_ids
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/leads/save' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "pool_person_ids": "<value>" }'import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.save_leads([pool_person_id])import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.saveLeads([poolPersonId]);import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
res, err := ss.SaveLeads(ctx, []string{poolPersonID})Response · 201
{
"already_saved": "…",
"saved": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | pool_person_ids required |
| 400 | Max 200 leads per save |
POST/api/v1/salesshift/leads/searchCalled liveSearch the global pool.
Search the global pool. The Go node owns the query — it is the lean path over 64 partitions. This layer owns POLICY: which addresses this org has paid to see, and which rows it has already saved. The node is deliberately tenant-blind, so the overlay below is the only place an unmasked address can appear. The ORM path is a fallback for a node outage, not a second implementation to keep in step. The response says which one answered so a silent drift between them shows up in the payload instead of in a support ticket.
Request body (required) — SearchIn
| Field | Type | Required | Notes |
|---|---|---|---|
| filters | LeadFilters | optional | |
| result_type | string | optional | Default "person". |
| cursor | string? | optional | |
| limit | integer | optional | Default 25. |
| sort | SortIn? | optional |
The body in the request below is the exact one sent during verification.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/leads/search' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "filters": {}, "limit": 2 }'import vxsdk
client = vxsdk.Client(access_token="$SS_TOKEN", vxcloud_url="https://api.vxcloud.io")
ss = client.salesshift
ss.search_leads(filters={"countries": ["US"]}, result_type="person", limit=25)import { VxCloud } from '@vxcloud/sdk';
const vx = new VxCloud({
accessToken: process.env.SS_TOKEN!,
vxcloudURL: 'https://api.vxcloud.io',
});
await vx.leads.searchLeads({ filters: { countries: ['US'] }, limit: 25 });import vxsdk "github.com/prodxcloud/vxcloud"
c, err := vxsdk.New(ctx,
vxsdk.WithJWT(os.Getenv("SS_TOKEN"), ""),
vxsdk.WithVxCloudURL("https://api.vxcloud.io"),
)
ss := c.SalesShift()
page, err := ss.SearchLeads(ctx, salesshift.SearchLeadsInput{Limit: 25})Response · 200
{
"success": "bool",
"data": {
"items": [
{
"pool_id": "...",
"full_name": "...",
"title": "...",
"seniority": "...",
"department": "...",
"email_masked": "...",
"email_status": "...",
"has_email": "...",
"phone_count": "...",
"location": "...",
"score": "...",
"company": "...",
"email_revealed": "...",
"email": "...",
"phone": "...",
"linkedin_url": "...",
"phone_available": "...",
"saved_lead_id": "..."
},
"...x2"
],
"next_cursor": "str",
"result_type": "str",
"sort": {
"desc": "bool",
"field": "str"
},
"total": "int",
"total_display": "str",
"total_is_estimate": "bool",
"search_backend": "str"
}
}