Appearance
County Verification API
County Verification searches the court records of a single county for a subject's criminal case history. Provide the subject's name and date of birth and the state and county to search. Each search is saved.
Unlike a real-time search, a county search is asynchronous: the request returns immediately with a processing status, and the records are available once the county search completes. You provide a webhookUrl and the finished search is sent to it when it completes; see Webhooks.
County Verification 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 County Verification enabled. Saved searches belong to the account that placed them. All County Verification endpoint responses include Cache-Control: no-store.
A missing or invalid key returns 403 with an empty body.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /county/coverage | List the states and counties that can be searched |
POST | /county/search | Place 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.
Coverage
GET /county/coverage returns every state and county that can be searched, with the county names exactly as the API recognizes them. Use it to build state and county pickers or to check a county before submitting.
bash
curl "https://api.infocusinfo.com/county/coverage" \
-H "Authorization: Bearer your-api-key"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/county/coverage"))
.header("Authorization", "Bearer your-api-key")
.GET()
.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.Get, "https://api.infocusinfo.com/county/coverage");
request.Headers.Add("Authorization", "Bearer your-api-key");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");http
GET https://api.infocusinfo.com/county/coverage
Authorization: Bearer your-api-keyjson
{
"states": [
{
"state": "TX",
"counties": [
{ "countyId": 101, "county": "Harris" },
{ "countyId": 102, "county": "Travis" }
]
}
]
}Create a Search
http
POST /county/searchSend a JSON object with the following fields. The subject goes in subject, the same object an Instant Criminal Search takes (subject.alias is not supported on this search). A subject's first name, last name, and complete birth date are required, along with the state and county to search and a webhookUrl to receive the result.
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) | Yes | Subject's first name |
subject.name.middleName | String (≤120 chars) | No | Subject's middle name |
subject.name.lastName | String (≤120 chars) | Yes | Subject's last name |
subject.name.suffix | String | No | Name suffix, such as Jr |
subject.birthDate | Object | Yes | 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 | No | The last four digits, or a full nine-digit SSN (NNNNNNNNN or NNN-NN-NNNN). Send a string to preserve leading zeros |
gender | String | No | Male or Female |
state | String | Yes | Two-letter state abbreviation, such as TX |
county | String | Yes | County name from the coverage list. The County suffix is optional |
searchYears | Integer | No | How many years back to search, 0–99. 0 or omitted uses the default search period |
clientCode | String (≤255 chars) | No | Your own reference code for the search |
specialInstructions | String (≤2000 chars) | No | Instructions for the researcher performing the search |
userId | String (numeric) | No | Console-only user attribution; requires the protected Console credential. Omit from ordinary API integrations |
productId | String (numeric) | No | County Verification product to bill against. Required only when your account has more than one |
webhookUrl | String (≤2048 chars) | Yes | Where the completed search is sent. Must be an absolute https URL that resolves to a public address. See Webhooks |
A state and county outside the coverage list return a 400 error. The 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.
Example
All identities and identifiers in these examples are synthetic.
bash
curl -X POST "https://api.infocusinfo.com/county/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 }
},
"gender": "Female",
"state": "TX",
"county": "Travis",
"searchYears": 7,
"webhookUrl": "https://example.com/hooks/county"
}'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/county/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 }
},
"gender": "Female",
"state": "TX",
"county": "Travis",
"searchYears": 7,
"webhookUrl": "https://example.com/hooks/county"
}
"""))
.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/county/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 }
},
"gender": "Female",
"state": "TX",
"county": "Travis",
"searchYears": 7,
"webhookUrl": "https://example.com/hooks/county"
}
""", 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/county/search
Content-Type: application/json
Authorization: Bearer your-api-key
{
"subject": {
"name": { "firstName": "Jane", "lastName": "Doe" },
"birthDate": { "year": 1980, "month": 1, "day": 2 }
},
"gender": "Female",
"state": "TX",
"county": "Travis",
"searchYears": 7,
"webhookUrl": "https://example.com/hooks/county"
}The response confirms the search was placed. The records are not available yet; they are sent to your webhookUrl when the search completes:
json
{
"request": {
"subject": {
"name": {
"firstName": "Jane",
"lastName": "Doe"
},
"birthDate": {
"year": 1980,
"month": 1,
"day": 2
},
"alias": []
},
"state": "TX",
"county": "Travis",
"searchYears": 7
},
"result": {
"offset": 0,
"limit": 100,
"hasMore": false,
"size": 0,
"data": []
},
"searchId": "16001",
"uuid": "3f2b8c1e-9d4a-4c6b-8e1f-0a1b2c3d4e5f",
"status": "processing",
"message": "The county search is processing."
}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 court records found: offset, limit, hasMore, size and data, the same envelope an Instant Criminal Search uses. Empty until the search completes, and may be empty after |
searchId | String (numeric) | Saved search identifier |
uuid | String | Search identifier that is also sent as the webhook-id header of the webhook delivery. |
status | String | processing, completed, or error |
message | String | Optional explanation for a processing state, empty result, or error |
The state and county searched, as named in the coverage list, are in request.state and request.county.
Records
Each entry in result.data is one court record, shaped like an Instant Criminal Search result: name, alias and birthDate are the same objects, and physical details sit in offender. The subject's SSN is never returned. Fields with no value are omitted and lists are empty arrays.
| Field | Type | Description |
|---|---|---|
caseNum | String | Court case number |
sourceTitle | String | The court or source of the record |
name | Object | Name on the record: firstName, middleName, lastName and suffix |
alias | Array of objects | Other names on the record, in the same shape as name |
birthDate | Object | Date of birth on the record as numeric year, month and day; parts not on the record are omitted |
offender | Object | Description on the record: age, sex, race, hair, eye, height, weight, scarsMarks and fullAddress |
matchedBy | Array of strings | The subject details the record matched on, such as FirstName, LastName, DateOfBirth |
fileDate, arrestDate, dispositionDate | String | Case dates |
caseType, disposition | String | Type of case and its disposition |
defendantNames, plaintiffNames | Array of strings | Parties on the case |
offenses | Array of objects | Charges on the case. See Offenses |
comments | Array of strings | Notes on the case |
attachments | Array of objects | Documents associated with the case, each with id, name, description, and viewUrl |
Offenses
| Field | Type | Description |
|---|---|---|
offenseDesc1, offenseCode, severity, level | String | The charge as recorded after any reduction |
originalOffense, originalStatute, originalSeverity, originalLevel | String | The charge as originally filed |
plea | String | Plea entered |
offenseDate | String | Date of the offense |
disposition, dispositionDate | String | Outcome of the charge and its date |
jailTime, jailTimeSuspended, jailTimeCredit | String | Jail sentence, suspended portion, and credit |
probationTime | String | Probation sentence |
prisonTime, prisonTimeCredit | String | Prison sentence and credit |
communityServiceTime | String | Community service sentence |
fine, courtCosts | String | Fine and court costs |
comments | Array of strings | Notes on the charge |
Completed Search Example
The record below is abbreviated.
json
{
"request": {
"subject": {
"name": {
"firstName": "Jane",
"lastName": "Doe"
},
"birthDate": {
"year": 1980,
"month": 1,
"day": 2
},
"alias": []
},
"state": "TX",
"county": "Travis",
"searchYears": 7
},
"result": {
"offset": 0,
"limit": 100,
"hasMore": false,
"size": 1,
"data": [
{
"sourceTitle": "Travis County District Court",
"caseNum": "D-1-DC-20-000000",
"name": {
"firstName": "Jane",
"lastName": "Doe"
},
"birthDate": {
"year": 1980,
"month": 1,
"day": 2
},
"alias": [
{
"firstName": "Jane",
"lastName": "Smith"
}
],
"offender": {
"age": 46,
"sex": "Female",
"race": "White",
"hair": "Brown",
"eye": "Green",
"height": "5'6\"",
"weight": "140",
"fullAddress": "100 Example St, Austin, TX"
},
"matchedBy": [
"FirstName",
"LastName",
"DateOfBirth"
],
"fileDate": "2020-03-14",
"caseType": "Felony",
"disposition": "Convicted",
"dispositionDate": "2020-09-30",
"defendantNames": [],
"plaintiffNames": [],
"offenses": [
{
"offenseDesc1": "Theft of Property",
"offenseCode": "31.03",
"severity": "Felony",
"level": "State Jail",
"disposition": "Guilty",
"dispositionDate": "2020-09-30",
"probationTime": "2 years",
"fine": "500.00",
"comments": []
}
],
"comments": [],
"attachments": []
}
]
},
"searchId": "16001",
"uuid": "3f2b8c1e-9d4a-4c6b-8e1f-0a1b2c3d4e5f",
"status": "completed"
}No Matching Records
A completed search with no matching records 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": []
},
"state": "TX",
"county": "Travis",
"searchYears": 7
},
"result": {
"offset": 0,
"limit": 100,
"hasMore": false,
"size": 0,
"data": []
},
"searchId": "16002",
"uuid": "7a91d2c4-5b3e-4f08-9c1d-2e6f8a0b3c47",
"status": "completed",
"message": "No matching county records."
}Webhooks
A webhookUrl is required for County Verification. County searches always complete later, so the API replies as soon as the search is placed (HTTP 200, status: processing) and sends the finished search to your URL when the county completes it.
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/county/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",
"county": "Travis",
"webhookUrl": "https://example.com/hooks/county"
}'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/county/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",
"county": "Travis",
"webhookUrl": "https://example.com/hooks/county"
}
"""))
.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/county/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",
"county": "Travis",
"webhookUrl": "https://example.com/hooks/county"
}
""", 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/county/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",
"county": "Travis",
"webhookUrl": "https://example.com/hooks/county"
}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": []
},
"state": "TX",
"county": "Travis",
"searchYears": 7
},
"result": {
"offset": 0,
"limit": 100,
"hasMore": false,
"size": 0,
"data": []
},
"searchId": "16001",
"uuid": "3f2b8c1e-9d4a-4c6b-8e1f-0a1b2c3d4e5f",
"status": "processing",
"message": "The county 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 county search is finished by the provider's order rather than a background job, so it is not affected by an API restart: the API keeps checking the order until it completes. 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 processing, completion, and failure. A search the provider rejects when the order is placed is also a 200 with status: error |
400 | Invalid fields (including a missing or invalid webhookUrl), a state or county outside coverage, or malformed JSON; includes an errors object keyed by field or request category |
403 | Missing or invalid API key, County Verification is not enabled for the account, or console-only attribution is not authorized |
404 | County Verification 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 | County verification is not configured. | 503 |
search | The request could not be completed. | 500 |
For example, a county outside coverage returns HTTP 400:
json
{
"errors": {
"county": "County is not covered. See the coverage list for supported states and counties."
}
}A request missing required fields returns every problem at once:
json
{
"errors": {
"lastName": "Last name is required.",
"birthDate": "Enter a valid complete date of birth (year, month and day).",
"state": "State is required.",
"county": "County is required."
}
}Search-Level Failure
A search that could not be placed, or that the county could not complete, has a persisted search whose status is error. The status can appear on the create response, in the webhook delivery, so check it every time:
json
{
"request": {
"subject": {
"name": {
"firstName": "Jane",
"lastName": "Doe"
},
"birthDate": {
"year": 1980,
"month": 1,
"day": 2
},
"alias": []
},
"state": "TX",
"county": "Travis",
"searchYears": 7
},
"result": {
"offset": 0,
"limit": 100,
"hasMore": false,
"size": 0,
"data": []
},
"searchId": "16003",
"uuid": "c04e6b79-1d2a-4a35-b8f0-91d7e3a5c612",
"status": "error",
"message": "The county search could not be completed. No automatic retry was made."
}Billing and Retries
A search is billed once, when it completes, at the account's price when the order is written: its active price override, otherwise the product price. This includes completed searches with no matching records. A search that ends in error is not billed.
There is no automatic retry and no request-level idempotency key: submitting another POST /county/search places another search and can incur another charge. If a request is interrupted, do not automatically resubmit it; the search may already have been placed. If you received a searchId, the search was placed, and its result is sent to your webhook if you set one.