curl --request POST \
--url https://app.loice.ai/api/v1/enrich/email \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"email": "jane.doe@acme.com"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({email: 'jane.doe@acme.com'})
};
fetch('https://app.loice.ai/api/v1/enrich/email', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://app.loice.ai/api/v1/enrich/email';
const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({email: 'jane.doe@acme.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/enrich/email"
payload = { "email": "jane.doe@acme.com" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"data": {
"found": false,
"email": "jane.doe@acme.com",
"person": null,
"channels": null,
"sources": [],
"cached": false,
"charged": true,
"pointsCharged": 5,
"balance": {
"available": 245,
"reserved": 0,
"granted": 250
},
"reason": null,
"replay": false
}
}{
"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"
}
}Enrich an email address into a contact profile
Turns one email address into the canonical Person behind it plus the contact channels a Person does not carry (associated emails, X, Telegram). Sources: our own records first, then two reverse-lookup sources and, when they name someone at a company domain or state a LinkedIn profile, a LinkedIn cross-reference. Every delivered field was stated by a source — nothing is inferred from the address. If any source the enrichment needs cannot answer, nothing is delivered and nothing is charged. Phone numbers are never returned. People on the workspace do-not-contact list are refused before any work. Rate limited per workspace, shared with the in-app tool.
Billing
- charged per completed enrichment — found or not, or answered from our own records — never on a refusal, a source that could not answer, or a replay.
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/enrich/email \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"email": "jane.doe@acme.com"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({email: 'jane.doe@acme.com'})
};
fetch('https://app.loice.ai/api/v1/enrich/email', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://app.loice.ai/api/v1/enrich/email';
const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({email: 'jane.doe@acme.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/enrich/email"
payload = { "email": "jane.doe@acme.com" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"data": {
"found": false,
"email": "jane.doe@acme.com",
"person": null,
"channels": null,
"sources": [],
"cached": false,
"charged": true,
"pointsCharged": 5,
"balance": {
"available": 245,
"reserved": 0,
"granted": 250
},
"reason": null,
"replay": false
}
}{
"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
The email address to enrich. Trimmed and lower-cased before anything else; a non-blank value that is not a well-formed address is answered reason: "invalid-input", uncharged.
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

