Skip to content

Screening Address Trace (SAT) API ​

Screening Address Trace finds names, aliases, address history, and counties using a full SSN or a name and date of birth. Each search saves the acquired results so you can reprocess them with different filters later. Add a webhookUrl to run the trace in the background and have the finished result sent to you; see Webhooks.

SAT is available for enabled accounts. Contact your Infocus Info representative to enable the product and configure account pricing.

You can also run SAT as part of an Instant Criminal Search by adding addressTrace to that request. The combined search uses eligible SAT names and aliases for the criminal search and returns the trace alongside the criminal results. Use the standalone endpoints below when you need an address trace by itself or want to reprocess a saved trace.

Authentication ​

All endpoints require an API key passed as a Bearer token in the Authorization header:

http
Authorization: Bearer your-api-key

The key must belong to an account with SAT enabled. Saved searches can only be reprocessed by their owning account. All SAT endpoint responses include Cache-Control: no-store.

A missing or invalid key returns 403 with an empty body.

Endpoints ​

MethodPathDescription
POST/sat/searchExecute and save an original search
POST/sat/search/{id}/reprocessReprocess the saved acquisition with new options, without a new provider search or a charge
http
POST /sat/search

Send 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.

Request Fields ​

FieldTypeRequiredDescription
clientRequestIdString (≤36 chars)NoYour own identifier for the request; echoed back in request.clientRequestId
subject.name.firstNameString (≤120 chars)Without SSNSubject's first name
subject.name.middleNameString (≤120 chars)NoSubject's middle name
subject.name.lastNameString (≤120 chars)Without SSNSubject's last name
subject.name.suffixStringNoName suffix, such as Jr
subject.birthDateObjectWithout SSNComplete, valid birth date as numeric year, month and day; year must be at least 1 and the date cannot be in the future
subject.ssnStringInstead of name/birth dateFull SSN: nine digits or NNN-NN-NNNN. Send a string to preserve leading zeros
optionsObjectNoFilters described in Search Options
userIdString (numeric)NoConsole-only user attribution; requires the protected Console credential. Omit from ordinary API integrations
webhookUrlString (≤2048 chars)NoRun the trace 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 the provider search criterion. An invalid SSN produces a validation error rather than falling back to name/DOB. A nonblank birthDate supplied alongside an SSN must still be valid.

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. Provider SSN fields are removed from the saved acquisition.

When an SSN is supplied, only the SSN is sent to the provider; any name and date of birth you also send are kept for matching but not used to search. Without an SSN, the name (including middleName) and date of birth are sent. Unknown request fields are ignored, and a field of the wrong type returns 400 with an errors.request message.

Search Options ​

FieldTypeDefaultDescription
options.includePartialNamesBooleantrueInclude names where either first or last name matches
options.includeNonMatchingNamesBooleanfalseInclude names where neither first nor last name matches
options.addressYearsInteger | nullnullRestrict results to addresses overlapping the past number of years, measured from acquisition date. Accepts 1–9999; null includes all available years. A person with no address inside the window is excluded from the results entirely, not just trimmed of addresses

Name Matching ​

Matching ignores case and normalizes accents, punctuation, and spacing. Exact matches have matching first and last names and are always included. Partial matches have either first or last name matching. Middle names and suffixes do not determine the match category.

When both first and last name are supplied, they form the matching baseline, including for SSN searches. Otherwise, each returned person's primary name is the baseline. The first eligible name becomes the person's display name; other eligible names become aliases. A person with no eligible names, or (when addressYears is set) no address inside the window, is excluded. A name identical to the baseline is always eligible, which keeps unnamed people in SSN-only results.

Address History and Counties ​

The year filter includes addresses whose reported date interval overlaps the requested window through the original acquiredDate. Reprocessing later uses that same acquisition date, not the current date. Dates are understood as a year (YYYY), a year and month (YYYY-MM), an ISO date, or M/D/YYYY; a year covers that year and a year/month covers that month. Any other form, including M/YYYY and masked dates, counts as an unknown bound.

Missing or unparseable date bounds are treated as unknown; a known bound can still place an address outside the window. Addresses with both bounds unknown, or inconsistent intervals where the first date follows the last, remain visible. When a year filter is set, a person with no addresses left inside the window is excluded from the results.

Counties are resolved against the state/county catalog, ignoring case and a trailing County or Parish. A resolved address has its state and county replaced with the catalog's abbreviation and county name, and gains a countyId. Unresolved county names, and names that match more than one catalog county, are preserved without a countyId. The top-level counties list is deduplicated (ignoring case and County) from the addresses in the filtered response and sorted by state and county. Addresses without county names do not contribute to that list.

Name and Date of Birth Example ​

All identities and identifiers in these examples are synthetic.

bash
curl -X POST "https://api.infocusinfo.com/sat/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 }
  },
  "options": {
    "includePartialNames": true,
    "includeNonMatchingNames": false,
    "addressYears": 7
  }
}'
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/sat/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 }
          },
          "options": {
            "includePartialNames": true,
            "includeNonMatchingNames": false,
            "addressYears": 7
          }
        }
        """))
    .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/sat/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 }
      },
      "options": {
        "includePartialNames": true,
        "includeNonMatchingNames": false,
        "addressYears": 7
      }
    }
    """, 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/sat/search
Content-Type: application/json
Authorization: Bearer your-api-key

{
  "subject": {
    "name": { "firstName": "Jane", "lastName": "Doe" },
    "birthDate": { "year": 1980, "month": 1, "day": 2 }
  },
  "options": {
    "includePartialNames": true,
    "includeNonMatchingNames": false,
    "addressYears": 7
  }
}

SSN Example ​

This example uses a fictitious SSN and the default filters.

bash
curl -X POST "https://api.infocusinfo.com/sat/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/sat/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/sat/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/sat/search
Content-Type: application/json
Authorization: Bearer your-api-key

{
  "subject": {
    "ssn": "000-00-0000"
  }
}

Search Response ​

Create, reprocess, and webhook deliveries return the same response structure. Default values are included; nullable fields are omitted when null. For example, addressYears is absent from request.options when all years are included.

Top-Level Fields ​

FieldTypeDescription
requestObjectThe request as saved, in the same shape you sent, except that options holds the filters this response was produced with (for a reprocess, the new ones). The SSN and webhookUrl are never included
resultObjectThe 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
searchIdString (numeric)Saved search identifier; use this in reprocess URLs. A reprocess is saved as a new search with its own
uuidStringSearch identifier that is also the webhook-id of a webhook delivery. Accepted in reprocess URLs in place of searchId. Omitted for searches saved before it was introduced
statusStringcompleted, error, or processing
messageStringOptional explanation for an empty result, processing state, or error
acquiredDateStringOriginal acquisition date in YYYY-MM-DD format; omitted when no snapshot is available
countiesArray of objectsDistinct counties from the returned addresses

People and Names ​

FieldTypeDescription
result.data[].idStringIdentifier of the person in the acquisition: the provider's identifier, or person-N when it has none
result.data[].nameObjectFirst eligible name after filtering
result.data[].aliasArray of objectsOther eligible names
result.data[].birthDateObjectThe most complete date of birth reported, as numeric year, month and day; parts the provider did not report are omitted, and the field is omitted when none was reported
result.data[].birthDatesArray of objectsEvery date of birth reported, in the same shape as birthDate; empty when none was reported
result.data[].ageIntegerAge, when reported
result.data[].addressesArray of objectsPerson's address history after filtering

Each name and each alias entry uses the following fields:

FieldTypeDescription
firstName, lastNameStringName components; may be empty if unavailable
middleName, suffixStringOptional name components

Addresses ​

FieldTypeDescription
streetStringStreet address assembled from its parts, with the unit when there is one (for example 307 Pine St, # 0); may be empty if unavailable
city, state, postalCodeStringOptional locality fields. postalCode includes the ZIP+4 extension when the provider reports one
countyStringCounty name, when available
countyIdIntegerResolved catalog identifier; omitted for unresolved counties
firstReportedDate, lastReportedDateStringProvider-reported date bounds, when available, exactly as the provider formats them (for example 2/28/1996); they may be partial dates and are not ISO dates

Counties ​

FieldTypeDescription
stateStringState; may be empty if unavailable
countyStringCounty name
countyIdIntegerResolved catalog identifier, when available

Completed Response Example ​

json
{
  "request": {
    "subject": {
      "name": {
        "firstName": "Jane",
        "lastName": "Doe"
      },
      "birthDate": {
        "year": 1980,
        "month": 1,
        "day": 2
      },
      "alias": []
    },
    "options": {
      "includePartialNames": true,
      "includeNonMatchingNames": false,
      "addressYears": 7
    }
  },
  "result": {
    "offset": 0,
    "limit": 100,
    "hasMore": false,
    "size": 1,
    "data": [
      {
        "id": "example-person-1",
        "name": {
          "firstName": "Jane",
          "lastName": "Doe"
        },
        "alias": [
          {
            "firstName": "Jane",
            "lastName": "Smith"
          }
        ],
        "addresses": [
          {
            "street": "123 Example Lane",
            "city": "Los Angeles",
            "state": "CA",
            "postalCode": "90012",
            "county": "Los Angeles",
            "countyId": 101,
            "firstReportedDate": "3/1/2020",
            "lastReportedDate": "8/15/2026"
          }
        ],
        "birthDate": {
          "year": 1980,
          "month": 1,
          "day": 2
        },
        "birthDates": [
          {
            "year": 1980,
            "month": 1,
            "day": 2
          }
        ],
        "age": 46
      }
    ]
  },
  "searchId": "12001",
  "uuid": "3f2b8c1e-9d4a-4c6b-8e1f-0a1b2c3d4e5f",
  "status": "completed",
  "acquiredDate": "2026-09-09",
  "counties": [
    {
      "state": "CA",
      "county": "Los Angeles",
      "countyId": 101
    }
  ]
}

No Matching Records ​

A successful search with no matching people is completed, not an error. An empty view can also mean the selected filters excluded all acquired people; different filters may reveal saved results.

json
{
  "request": {
    "subject": {
      "name": {
        "firstName": "Jane",
        "lastName": "Doe"
      },
      "birthDate": {
        "year": 1980,
        "month": 1,
        "day": 2
      },
      "alias": []
    },
    "options": {
      "includePartialNames": true,
      "includeNonMatchingNames": false
    }
  },
  "result": {
    "offset": 0,
    "limit": 100,
    "hasMore": false,
    "size": 0,
    "data": []
  },
  "searchId": "12002",
  "uuid": "7a91d2c4-5b3e-4f08-9c1d-2e6f8a0b3c47",
  "status": "completed",
  "message": "No matching address trace records for these filters.",
  "acquiredDate": "2026-09-09",
  "counties": []
}

POST /sat/search/{id}/reprocess reprocesses the original saved acquisition with new options. It is saved as a new search with its own searchId and uuid, so it can be delivered to a webhook like any other search.

FieldTypeRequiredDescription
optionsObjectNoThe filters for this run, described in Search Options. Omit to use the defaults
webhookUrlString (≤2048 chars)NoRun the reprocess in the background and send the finished search to this https URL. The immediate reply is 202 with status: processing. See Webhooks

Omitted option fields use the defaults, rather than inheriting the original or most recent filters. Sending {} includes partial names, excludes nonmatching names, and includes all available years. An empty body returns 400.

The response has the new searchId and uuid, the original acquisition date, and the filters it was produced with in request.options. The saved acquisition and the original search are not modified. addressYears is validated before the search is looked up, so an invalid value returns 400 even for an unknown search. Reprocessing a search that is still processing, or ended in error, returns 400 with an error under searchId.

bash
curl -X POST "https://api.infocusinfo.com/sat/search/12001/reprocess" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
  "options": {
    "includePartialNames": true,
    "includeNonMatchingNames": true,
    "addressYears": null
  }
}'
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/sat/search/12001/reprocess"))
    .header("Content-Type", "application/json")
    .header("Authorization", "Bearer your-api-key")
    .method("POST", HttpRequest.BodyPublishers.ofString("""
        {
          "options": {
            "includePartialNames": true,
            "includeNonMatchingNames": true,
            "addressYears": null
          }
        }
        """))
    .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/sat/search/12001/reprocess");
request.Headers.Add("Authorization", "Bearer your-api-key");
request.Content = new StringContent("""
    {
      "options": {
        "includePartialNames": true,
        "includeNonMatchingNames": true,
        "addressYears": null
      }
    }
    """, 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/sat/search/12001/reprocess
Content-Type: application/json
Authorization: Bearer your-api-key

{
  "options": {
    "includePartialNames": true,
    "includeNonMatchingNames": true,
    "addressYears": null
  }
}

All acquired names and addresses are saved before these local filters are applied. Reprocessing can reveal previously excluded data, but cannot obtain records the provider did not return in the original search. It makes no provider call and creates no charge.

Webhooks ​

Add webhookUrl to run the address trace 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. The webhook carries the original-filter response; use POST /sat/search/{id}/reprocess afterwards to apply other filters. A reprocess takes a webhookUrl of its own.

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/sat/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/sat"
}'
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/sat/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/sat"
        }
        """))
    .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/sat/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/sat"
    }
    """, 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/sat/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/sat"
}

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": []
    },
    "options": {
      "includePartialNames": true,
      "includeNonMatchingNames": false
    }
  },
  "result": {
    "offset": 0,
    "limit": 100,
    "hasMore": false,
    "size": 0,
    "data": []
  },
  "searchId": "12001",
  "uuid": "3f2b8c1e-9d4a-4c6b-8e1f-0a1b2c3d4e5f",
  "status": "processing",
  "message": "The address trace is processing.",
  "counties": []
}

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:

HeaderDescription
webhook-idThe search's uuid. Identical on every retry, so use it to de-duplicate
webhook-timestampUnix time in seconds of the attempt
webhook-signaturev1, 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 ​

CodeDescription
200A search response returned. Inspect status to distinguish completion from processing or failure
202The search was accepted for background processing because the request included a webhookUrl. The result is sent to the webhook
400Invalid fields or malformed JSON; includes an errors object keyed by field or request category
403Missing or invalid API key, SAT is not enabled for the account on search creation, or console-only attribution is not authorized
404SAT product or saved search not found, invalid search ID, or search is not owned by the account
500Unexpected request failure
503SAT setup is incomplete or the search service is not configured

Errors other than field validation use these keys in the errors object:

KeyMessageCode
requestMalformed request.400 (invalid JSON or a field of the wrong type)
userIdInvalid user ID.400
accessAddress trace is not available for this account.403
searchNot found.404
searchAddress trace is not configured. (or: Address trace setup is incomplete. Contact your administrator.)503
searchThe request could not be completed.500

For example, an invalid DOB in a name/DOB request returns HTTP 400:

json
{
  "errors": {
    "birthDate": "Enter a valid complete date of birth (year, month and day)."
  }
}

An invalid addressYears value on either creation or reprocessing returns HTTP 400:

json
{
  "errors": {
    "addressYears": "Address history years must be between 1 and 9999, or blank for all."
  }
}

Use null or omit addressYears for all years; do not send a blank string.

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": []
    },
    "options": {
      "includePartialNames": true,
      "includeNonMatchingNames": false
    }
  },
  "result": {
    "offset": 0,
    "limit": 100,
    "hasMore": false,
    "size": 0,
    "data": []
  },
  "searchId": "12003",
  "uuid": "c04e6b79-1d2a-4a35-b8f0-91d7e3a5c612",
  "status": "error",
  "message": "The address trace could not be completed. No automatic retry was made.",
  "counties": []
}

Billing and Retries ​

Each completed original 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. Reprocessing a saved search is free.

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 /sat/search creates another original 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.