Appearance
Person Search API
Person Search looks up a person by full SSN, or by name and date of birth, and returns the identifying information on record: names and aliases, date of birth and age, address history, phone numbers, email addresses, relatives, associates, and death records. Each search is saved. Add a webhookUrl to run the search in the background and have the finished result sent to you; see Webhooks.
Person Search is available for enabled accounts. Contact your Infocus Info representative to enable the product and configure account pricing.
Authentication
All endpoints require an API key passed as a Bearer token in the Authorization header:
http
Authorization: Bearer your-api-keyThe key must belong to an account with Person Search enabled. Saved searches belong to the account that placed them. All Person Search endpoint responses include Cache-Control: no-store.
A missing or invalid key returns 403 with an empty body.
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /person/search | Execute and save a search |
Use https://api.infocusinfo.com as the base URL. The {id} path parameter is the searchId (a numeric string) or the uuid returned when the search is created.
Create a Search
http
POST /person/searchSend a JSON object with the following fields. The subject goes in subject, the same object an Instant Criminal Search takes. Supply either a full SSN or both first and last name with a complete birth date. subject.alias is not supported on this search. The remaining fields are optional hints that help narrow the match.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
clientRequestId | String (≤36 chars) | No | Your own identifier for the request; echoed back in request.clientRequestId |
subject.name.firstName | String (≤120 chars) | Without SSN | Subject's first name |
subject.name.middleName | String (≤120 chars) | No | Subject's middle name |
subject.name.lastName | String (≤120 chars) | Without SSN | Subject's last name |
subject.name.suffix | String | No | Name suffix, such as Jr |
subject.birthDate | Object | Without SSN | Complete, valid birth date as numeric year, month and day; year must be at least 1 and the date cannot be in the future |
subject.ssn | String | Instead of name/birth date | Full SSN: nine digits or NNN-NN-NNNN. Send a string to preserve leading zeros |
email | String (≤255 chars) | No | Email address hint |
phone | String (≤255 chars) | No | Phone number hint |
address | String (≤255 chars) | No | Street address hint |
city | String (≤255 chars) | No | City hint |
state | String (≤255 chars) | No | State hint |
userId | String (numeric) | No | Console-only user attribution; requires the protected Console credential. Omit from ordinary API integrations |
productId | String (numeric) | No | Person Search product to bill against. Omit to use your account's standard Person Search product |
webhookUrl | String (≤2048 chars) | No | Run the search in the background and send the finished search to this https URL. The immediate reply is 202 with status: processing. See Webhooks |
When a nonblank SSN is supplied it is sent to the provider together with any name, date of birth, and hint fields you also supply; the provider decides how to weigh them. To search on the SSN alone, send only the SSN. An invalid SSN produces a validation error rather than falling back to name and date of birth. A nonblank birthDate supplied alongside an SSN must still be valid. Name length limits apply whenever names are supplied.
The full SSN is input-only: it is not echoed in responses or saved requests, and only its last four digits are retained on the search record. SSN fields are removed from the saved and returned person records.
Name and Date of Birth Example
All identities and identifiers in these examples are synthetic.
bash
curl -X POST "https://api.infocusinfo.com/person/search" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"subject": {
"name": { "firstName": "Jane", "lastName": "Doe" },
"birthDate": { "year": 1980, "month": 1, "day": 2 }
},
"state": "TX"
}'java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
var request = HttpRequest.newBuilder(URI.create("https://api.infocusinfo.com/person/search"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer your-api-key")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"subject": {
"name": { "firstName": "Jane", "lastName": "Doe" },
"birthDate": { "year": 1980, "month": 1, "day": 2 }
},
"state": "TX"
}
"""))
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());csharp
using System.Net.Http;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.infocusinfo.com/person/search");
request.Headers.Add("Authorization", "Bearer your-api-key");
request.Content = new StringContent("""
{
"subject": {
"name": { "firstName": "Jane", "lastName": "Doe" },
"birthDate": { "year": 1980, "month": 1, "day": 2 }
},
"state": "TX"
}
""", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");http
POST https://api.infocusinfo.com/person/search
Content-Type: application/json
Authorization: Bearer your-api-key
{
"subject": {
"name": { "firstName": "Jane", "lastName": "Doe" },
"birthDate": { "year": 1980, "month": 1, "day": 2 }
},
"state": "TX"
}SSN Example
This example uses a fictitious SSN.
bash
curl -X POST "https://api.infocusinfo.com/person/search" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"subject": {
"ssn": "000-00-0000"
}
}'java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
var request = HttpRequest.newBuilder(URI.create("https://api.infocusinfo.com/person/search"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer your-api-key")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"subject": {
"ssn": "000-00-0000"
}
}
"""))
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());csharp
using System.Net.Http;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.infocusinfo.com/person/search");
request.Headers.Add("Authorization", "Bearer your-api-key");
request.Content = new StringContent("""
{
"subject": {
"ssn": "000-00-0000"
}
}
""", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");http
POST https://api.infocusinfo.com/person/search
Content-Type: application/json
Authorization: Bearer your-api-key
{
"subject": {
"ssn": "000-00-0000"
}
}Search Response
Create operations and webhook deliveries return the same response structure.
Top-Level Fields
| Field | Type | Description |
|---|---|---|
request | Object | The request as saved, in the same shape you sent. The SSN and webhookUrl are never included |
result | Object | The people found: offset, limit, hasMore, size and data, the same envelope an Instant Criminal Search uses. All results are returned at once, so hasMore is false |
searchId | String (numeric) | Saved search identifier |
uuid | String | Search identifier that is also the webhook-id of a webhook delivery. |
status | String | completed, error, or an in-progress status (processing or new) |
message | String | Optional explanation for an empty result, processing state, or error |
People
Each entry in result.data is a person record in the same shape as the other searches: name, alias and birthDate use the same objects as an Instant Criminal Search's subject, and addresses match Screening Address Trace. SSN fields are never returned. Dates are strings exactly as reported, such as 1/11/2025. Fields with no value are omitted and lists are empty arrays.
| Field | Type | Description |
|---|---|---|
id | String | Identifier of the person in this search |
name | Object | firstName, middleName, lastName and suffix |
alias | Array of objects | Other names the person is known by, in the same shape as name |
birthDate | Object | The most complete date of birth reported, as numeric year, month and day; parts not reported are omitted |
birthDates | Array of objects | Every date of birth reported, in the same shape as birthDate |
age | Integer | Age, when reported |
deceased | Boolean | Whether a death record exists |
addresses | Array of objects | Address history: street, city, state, postalCode, county, firstReportedDate and lastReportedDate |
phones | Array of objects | Phone numbers with number, type, company, firstReportedDate and lastReportedDate |
emails | Array of strings | Email addresses |
relatives | Array of objects | Relatives, each with name, relation, birthDate and deceased |
associates | Array of objects | Associates, in the same shape as relatives |
Person Search does not filter or deduplicate names and addresses, and it does not resolve counties.
Result limits
Only the provider's first page of results is returned, which is about 10 people. The API does not expose paging, so narrow the search with more identifying details (such as an SSN or date of birth) rather than expecting additional pages. Every search also asks the provider to leave out people with an incomplete or missing date of birth, so a person who is known only by name is not returned.
Completed Search Example
The person record below is abbreviated.
json
{
"request": {
"subject": {
"name": {
"firstName": "Jane",
"lastName": "Doe"
},
"birthDate": {
"year": 1980,
"month": 1,
"day": 2
},
"alias": []
}
},
"result": {
"offset": 0,
"limit": 100,
"hasMore": false,
"size": 1,
"data": [
{
"id": "G1234567890",
"name": {
"firstName": "Jane",
"middleName": "Q",
"lastName": "Doe"
},
"alias": [
{
"firstName": "Jane",
"lastName": "Smith"
}
],
"birthDate": {
"year": 1980,
"month": 1,
"day": 2
},
"birthDates": [
{
"year": 1980,
"month": 1,
"day": 2
}
],
"age": 46,
"deceased": false,
"addresses": [
{
"street": "100 Example St",
"city": "Austin",
"state": "TX",
"postalCode": "78701",
"county": "Travis",
"firstReportedDate": "1/1/2017",
"lastReportedDate": "1/1/2026"
}
],
"phones": [
{
"number": "(555) 010-0100",
"type": "Wireless",
"company": "Example Wireless",
"firstReportedDate": "1/1/2018",
"lastReportedDate": "1/1/2026"
}
],
"emails": [
"jane.doe@example.com"
],
"relatives": [
{
"name": {
"firstName": "John",
"lastName": "Doe"
},
"relation": "Spouse",
"birthDate": {
"year": 1978,
"month": 5
},
"deceased": false
}
],
"associates": []
}
]
},
"searchId": "14001",
"uuid": "3f2b8c1e-9d4a-4c6b-8e1f-0a1b2c3d4e5f",
"status": "completed"
}No Matching Records
A successful search with no matching people is completed, not an error, and is still billed.
json
{
"request": {
"subject": {
"name": {
"firstName": "Jane",
"lastName": "Doe"
},
"birthDate": {
"year": 1980,
"month": 1,
"day": 2
},
"alias": []
}
},
"result": {
"offset": 0,
"limit": 100,
"hasMore": false,
"size": 0,
"data": []
},
"searchId": "14002",
"uuid": "7a91d2c4-5b3e-4f08-9c1d-2e6f8a0b3c47",
"status": "completed",
"message": "No matching person records."
}Webhooks
Add webhookUrl to run a person search in the background. The API saves the search and replies right away with HTTP 202 and status: processing, then sends the finished search to your URL. Without it, the request waits and returns the result as described above.
webhookUrl must be an absolute https URL, up to 2048 characters, without embedded credentials, whose host resolves to a public address (loopback, private, link-local, multicast, carrier-grade NAT, and IPv6 unique-local addresses are refused, as is a host that cannot be resolved). An invalid URL returns 400 with an error under webhookUrl. The URL is kept only to deliver the webhook; it is not echoed in responses or stored in the saved request.
Example
bash
curl -X POST "https://api.infocusinfo.com/person/search" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"subject": {
"name": { "firstName": "Jane", "lastName": "Doe" },
"birthDate": { "year": 1980, "month": 1, "day": 2 }
},
"webhookUrl": "https://example.com/hooks/person"
}'java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
var request = HttpRequest.newBuilder(URI.create("https://api.infocusinfo.com/person/search"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer your-api-key")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"subject": {
"name": { "firstName": "Jane", "lastName": "Doe" },
"birthDate": { "year": 1980, "month": 1, "day": 2 }
},
"webhookUrl": "https://example.com/hooks/person"
}
"""))
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());csharp
using System.Net.Http;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.infocusinfo.com/person/search");
request.Headers.Add("Authorization", "Bearer your-api-key");
request.Content = new StringContent("""
{
"subject": {
"name": { "firstName": "Jane", "lastName": "Doe" },
"birthDate": { "year": 1980, "month": 1, "day": 2 }
},
"webhookUrl": "https://example.com/hooks/person"
}
""", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");http
POST https://api.infocusinfo.com/person/search
Content-Type: application/json
Authorization: Bearer your-api-key
{
"subject": {
"name": { "firstName": "Jane", "lastName": "Doe" },
"birthDate": { "year": 1980, "month": 1, "day": 2 }
},
"webhookUrl": "https://example.com/hooks/person"
}The reply is the saved search in the processing state. Keep its uuid:
json
{
"request": {
"subject": {
"name": {
"firstName": "Jane",
"lastName": "Doe"
},
"birthDate": {
"year": 1980,
"month": 1,
"day": 2
},
"alias": []
}
},
"result": {
"offset": 0,
"limit": 100,
"hasMore": false,
"size": 0,
"data": []
},
"searchId": "14001",
"uuid": "3f2b8c1e-9d4a-4c6b-8e1f-0a1b2c3d4e5f",
"status": "processing",
"message": "The person search is processing."
}Placement problems (validation, product access, account checks) are reported immediately with the usual 400, 403, 404, or 503, before anything is accepted.
When the search finishes, the API sends a POST to your URL with the same body as the create response, including status (completed or error). The request follows the Standard Webhooks specification and carries these headers:
| Header | Description |
|---|---|
webhook-id | The search's uuid. Identical on every retry, so use it to de-duplicate |
webhook-timestamp | Unix time in seconds of the attempt |
webhook-signature | v1, plus the base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{raw body}, keyed with the API key that placed the search |
Respond with any 2xx status to acknowledge. Other responses, timeouts (10 seconds), and connection errors are retried after 1 minute, 5 minutes, 15 minutes, 1 hour, 4 hours, 12 hours, and 24 hours, after which the API stops. Delivery is at least once, and redirects are not followed. If the API key that placed the search has since been revoked or has expired, the delivery cannot be signed and is abandoned without retries. See Verifying a webhook for signature code samples.
A search that cannot be completed is delivered with status: error. A search that was running when the API restarted cannot be resumed (the full SSN is never stored); if it is still processing 15 minutes after it was created, it is marked error and that status is delivered. A search without a webhook that is interrupted this way is not swept and may stay processing. Searches are never re-sent to a provider automatically. Billing is unchanged: a search is billed once, when it completes.
Validation and Errors
HTTP Response Codes
| Code | Description |
|---|---|
200 | A search response returned. Inspect status to distinguish completion from processing or failure |
202 | The search was accepted for background processing because the request included a webhookUrl. The result is sent to the webhook |
400 | Invalid fields or malformed JSON; includes an errors object keyed by field or request category |
403 | Missing or invalid API key, Person Search is not enabled for the account, or console-only attribution is not authorized |
404 | Person Search product or saved search not found, invalid search ID, or search is not owned by the account |
500 | Unexpected request failure |
503 | The search service is not configured |
Errors other than field validation use these keys in the errors object:
| Key | Message | Code |
|---|---|---|
request | Malformed request. | 400 (invalid JSON or a field of the wrong type) |
userId | Invalid user ID. | 400 |
access | This search product is not enabled for this account. | 403 |
search | Not found. | 404 |
search | Person search is not configured. | 503 |
search | The request could not be completed. | 500 |
For example, a request with no SSN and no date of birth returns HTTP 400:
json
{
"errors": {
"birthDate": "Enter a valid complete date of birth (year, month and day)."
}
}A malformed SSN returns HTTP 400:
json
{
"errors": {
"ssn": "Enter a full nine-digit SSN."
}
}Search-Level Failure
A provider failure can return HTTP 200 with a persisted search whose status is error. Check the status even when the HTTP request succeeds:
json
{
"request": {
"subject": {
"name": {
"firstName": "Jane",
"lastName": "Doe"
},
"birthDate": {
"year": 1980,
"month": 1,
"day": 2
},
"alias": []
}
},
"result": {
"offset": 0,
"limit": 100,
"hasMore": false,
"size": 0,
"data": []
},
"searchId": "14003",
"uuid": "c04e6b79-1d2a-4a35-b8f0-91d7e3a5c612",
"status": "error",
"message": "The person search could not be completed. No automatic retry was made."
}Billing and Retries
Each completed search is billed once at the account's price when the order is written (its active price override, otherwise the product price), including searches with no matching records. Billing delays do not discard completed results. A search that ends in error is not billed.
Without a webhookUrl, search creation is synchronous, with a 30-second provider timeout and no automatic provider retry; with one, the same limits apply to the background request. There is no request-level idempotency key: submitting another POST /person/search creates another search and can incur another charge.
If a request is interrupted, do not automatically resubmit it; the provider may already have executed the search. If you received a searchId, the search was placed, and its result is sent to your webhook if you set one.