curl --request POST \
--url https://app.loice.ai/api/v1/find \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"mode": "email",
"name": "Satya Nadella",
"company": "microsoft.com"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({mode: 'email', name: 'Satya Nadella', company: 'microsoft.com'})
};
fetch('https://app.loice.ai/api/v1/find', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://app.loice.ai/api/v1/find';
const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({mode: 'email', name: 'Satya Nadella', company: 'microsoft.com'})
};
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/find"
payload = {
"mode": "email",
"name": "Satya Nadella",
"company": "microsoft.com"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"data": {
"found": true,
"email": "satya.nadella@microsoft.com",
"status": "valid",
"confidence": 0.97,
"charged": true,
"pointsCharged": 1,
"balance": {
"available": 99,
"reserved": 0,
"granted": 100
},
"reason": null,
"replay": false,
"person": null,
"creditsCharged": 1,
"resolvedProfile": null
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded",
"retryAfterSeconds": 12
}
}{
"error": {
"code": "internal_error",
"message": "Internal error"
}
}Find and verify contact data
Runs one enrichment lookup for the workspace behind the API key. Three modes share this endpoint: email (default) finds a verified work email from a name and company domain, company resolves a company name to its domain and then finds a verified work email there, and linkedin finds a verified work email from a LinkedIn profile URL. Every result is verified before delivery — when we cannot verify, found is false with a reason rather than a guess. Rate limited per workspace; check GET /api/v1/points for headroom first.
Billing
- email mode (default): charged per completed lookup — found or not — never on provider errors.
- company mode: two parts, each charged on its own. The company lookup is charged once the name search answers — resolved or not; the email search is then charged per completed lookup — found or not. If the company can’t be resolved, or it resolves to a domain that cannot receive email (
unusable-domain), only the company part is charged. - linkedin mode: charged per completed lookup — found or not — never on provider errors.
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/find \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"mode": "email",
"name": "Satya Nadella",
"company": "microsoft.com"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({mode: 'email', name: 'Satya Nadella', company: 'microsoft.com'})
};
fetch('https://app.loice.ai/api/v1/find', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://app.loice.ai/api/v1/find';
const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({mode: 'email', name: 'Satya Nadella', company: 'microsoft.com'})
};
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/find"
payload = {
"mode": "email",
"name": "Satya Nadella",
"company": "microsoft.com"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"data": {
"found": true,
"email": "satya.nadella@microsoft.com",
"status": "valid",
"confidence": 0.97,
"charged": true,
"pointsCharged": 1,
"balance": {
"available": 99,
"reserved": 0,
"granted": 100
},
"reason": null,
"replay": false,
"person": null,
"creditsCharged": 1,
"resolvedProfile": null
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"code": "validation_error",
"message": "<string>",
"retryAfterSeconds": 123
}
}{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded",
"retryAfterSeconds": 12
}
}{
"error": {
"code": "internal_error",
"message": "Internal error"
}
}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.
Headers
Optional, 1–255 printable ASCII characters, scoped to your workspace. Send the same key when you retry the same request: the original outcome is replayed (replay: true, charged: false, pointsCharged: 0) instead of running and charging again. The same key with a different request is refused with 409 idempotency_conflict, as is a key reused after its replay record expired (kept 30 days); a key whose first request is still running answers 409 idempotency_in_progress. Without a key, every request is a new lookup, billed at most once on its own.
1 - 255Body
Lookup mode. email (default, also used for an unrecognised value): find a verified work email from a name and the company domain. company: resolve a company NAME to its domain, then find a verified work email there. linkedin: find a verified work email from a LinkedIn profile URL.
email, company, linkedin Full name of the person. Required for email and company modes.
Required for email and company modes. email mode: the company domain or website URL (reduced to its bare host). company mode: the company name.
LinkedIn profile URL. Required for linkedin mode.
Response
Every outcome, including refusals — inspect found, reason, charged and replay. A refusal is a 200 with found: false and a reason.
Show child attributes
Show child attributes

