curl --request POST \
--url https://app.loice.ai/api/v1/lead-search \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"filters": {
"jobTitle": [
"Head of Sales"
],
"location": [
"Germany"
],
"headcount": [
"51-200"
]
}
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
filters: {jobTitle: ['Head of Sales'], location: ['Germany'], headcount: ['51-200']}
})
};
fetch('https://app.loice.ai/api/v1/lead-search', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://app.loice.ai/api/v1/lead-search';
const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
filters: {jobTitle: ['Head of Sales'], location: ['Germany'], headcount: ['51-200']}
})
};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error(err));import requests
url = "https://app.loice.ai/api/v1/lead-search"
payload = { "filters": {
"jobTitle": ["Head of Sales"],
"location": ["Germany"],
"headcount": ["51-200"]
} }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"data": {
"run": {
"id": "cmf3x0v2b0001abcd",
"status": "queued",
"requestedCount": 500,
"deliveredCount": 0,
"sourceExhausted": false,
"sourceTotalElements": 23000,
"sourceTotalPages": 100,
"continuedFromId": null,
"unitCost": 1,
"maxCost": 500,
"creditsCharged": null,
"error": null,
"criteria": {
"jobTitle": [
"Head of Sales"
],
"location": [
"Germany"
],
"headcount": [
"51-200"
]
},
"createdAt": "2026-09-01T09:00:00.000Z",
"startedAt": null,
"finishedAt": null
},
"balance": {
"available": 734,
"reserved": 500,
"granted": 1234
},
"pointsReservedMax": 500
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"message": "<string>"
}
}{
"error": {
"message": "<string>"
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"message": "<string>"
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}Start a lead-search run
Finds people by criteria — job title, location, company, industry, headcount, seniority, keywords — with no target person known in advance. Creates a background run that delivers up to count directory-style results (name, title, company, location, profile URL — never contact data) and returns immediately with the run handle and your post-reservation balance; poll GET /api/v1/lead-search/runs/{runId} for status and results. Set continueFromRunId to continue a finished run (“load more” or retry) — already-delivered leads are never re-delivered or re-billed. One active run per workspace at a time. Feed a result’s linkedinUrl into POST /api/v1/find (linkedin mode) to get a verified email. Rate limited per workspace. Breaking change from the earlier synchronous contract: the old one-batch page request/response is gone.
Billing
- per delivered lead, debited once when the run finalizes — an empty run is free, and a continuation run never re-bills previously delivered leads;
unitCost × countis reserved up front and the unused remainder is released at finalize.
The current cost is shown in the app under Settings → Billing and in the live GET /api/v1/openapi.json. Calls that end in a system or provider error (HTTP 500) or are rate limited (HTTP 429) are not billed. The charged and pointsCharged fields of the response state what this call actually consumed.
curl --request POST \
--url https://app.loice.ai/api/v1/lead-search \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"filters": {
"jobTitle": [
"Head of Sales"
],
"location": [
"Germany"
],
"headcount": [
"51-200"
]
}
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
filters: {jobTitle: ['Head of Sales'], location: ['Germany'], headcount: ['51-200']}
})
};
fetch('https://app.loice.ai/api/v1/lead-search', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://app.loice.ai/api/v1/lead-search';
const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
filters: {jobTitle: ['Head of Sales'], location: ['Germany'], headcount: ['51-200']}
})
};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error(err));import requests
url = "https://app.loice.ai/api/v1/lead-search"
payload = { "filters": {
"jobTitle": ["Head of Sales"],
"location": ["Germany"],
"headcount": ["51-200"]
} }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"data": {
"run": {
"id": "cmf3x0v2b0001abcd",
"status": "queued",
"requestedCount": 500,
"deliveredCount": 0,
"sourceExhausted": false,
"sourceTotalElements": 23000,
"sourceTotalPages": 100,
"continuedFromId": null,
"unitCost": 1,
"maxCost": 500,
"creditsCharged": null,
"error": null,
"criteria": {
"jobTitle": [
"Head of Sales"
],
"location": [
"Germany"
],
"headcount": [
"51-200"
]
},
"createdAt": "2026-09-01T09:00:00.000Z",
"startedAt": null,
"finishedAt": null
},
"balance": {
"available": 734,
"reserved": 500,
"granted": 1234
},
"pointsReservedMax": 500
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"message": "<string>"
}
}{
"error": {
"message": "<string>"
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"message": "<string>"
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}Authorizations
Workspace API key (loice_sk_ + 48 hex characters), created under Settings → API Keys. Sent as Authorization: Bearer loice_sk_…. Invalid, missing, or expired keys are rejected with 401 before any billable work happens.
Body
At least one non-empty filter is required — unless continueFromRunId is set, in which case filters may be omitted (the continuation inherits the prior run’s criteria; submitted filters must match them).
Show child attributes
Show child attributes
How many leads to deliver (default 25, maximum 10000). Billing is per lead actually delivered, never per lead requested. On a continuation this means additional leads beyond the prior run.
1 <= x <= 10000Id of a finished run to continue ("load more" / retry after a partial failure). The new run inherits its criteria, resumes where it stopped, and never re-delivers or re-bills leads the prior run already delivered.
Continuation only: fetch exactly this source page instead of resuming sequentially — the on-demand fetch behind as-if-local pagination. Leads the chain already delivered are never re-billed.
1 <= x <= 100Response
Run created (poll GET /api/v1/lead-search/runs/{runId}), or refused uncharged with reason: "insufficient-credits" when the balance cannot cover the worst case — inspect run and reason.
Show child attributes
Show child attributes

