# TruePeopleSearch Skip-Tracing API

Public-records search endpoints under `/search/*`, backed by TruePeopleSearch
through the NodeMaven Scraping Browser.

- Transport: JSON over HTTPS
- Authentication: `X-API-Key`
- Default response format: `competitor`
- Upstream: TruePeopleSearch
- Parsed responses are cached for `CACHE_TTL_SECONDS` (default 900 seconds).
  Identical concurrent requests are collapsed into one upstream fetch.

## LLM documentation endpoints

- `GET /search/docs.md` — this document (`text/markdown`)
- `GET /search/llms.txt` — alias of this document
- `GET /docs.md` and `GET /llms.txt` — combined documentation for both APIs
- `GET /swagger` — interactive OpenAPI UI
- `GET /openapi.json` — OpenAPI schema

Credit-score documentation is at `/api/apply/docs.md` and
`/api/apply/llms.txt`.

The Swagger UI groups these routes under **TruePeopleSearch**. They use the
shared `ApiKeyAuth` scheme (`X-API-Key`) in the **Authorize** dialog.

## Authentication

Every `/search/*` data endpoint requires the API key in the `X-API-Key` header:

```bash
curl -H "X-API-Key: $API_KEY" \
  "$BASE_URL/search/byname?name=John%20Smith&page=1"
```

`Authorization: Bearer <key>` is accepted by `POST /api/apply`, not by the
`/search/*` endpoints.

Missing or wrong key:

```json
401 { "detail": "Invalid or missing API key" }
```

The response includes `WWW-Authenticate: ApiKey`.

If the service itself has no key configured:

```json
503 { "detail": "API authentication is not configured" }
```

## Common query parameters

Every data endpoint accepts:

- `format=competitor` (default) — competitor-compatible envelope
- `format=native` — native TruePeopleSearch envelope

Endpoints with pagination accept `page` (integer, minimum `1`, default `1`).
Invalid `format` or `page` values are rejected by request validation with
HTTP `422`.

Trailing slashes are accepted directly, for example `/search/byname/` and
`/search/detailsbyID/`.

## Endpoints

| Endpoint | Required parameters | Pagination |
| --- | --- | --- |
| `GET /search/byname` | `name` | `page` |
| `GET /search/byaddress` | `street`, `citystatezip` | `page` |
| `GET /search/bynameaddress` | `name`, `citystatezip` | `page` |
| `GET /search/byphone` | `phoneno` | `page` |
| `GET /search/byemail` | `email` | no |
| `GET /search/detailsbyID` | `peo_id` | no |

### Search by name

```bash
curl -H "X-API-Key: $API_KEY" \
  "$BASE_URL/search/byname?name=John%20Smith&page=1"
```

### Search by address

```bash
curl -H "X-API-Key: $API_KEY" \
  "$BASE_URL/search/byaddress?street=1%20Main%20St&citystatezip=Austin%2C%20TX&page=1"
```

### Search by name and city/state/ZIP

```bash
curl -H "X-API-Key: $API_KEY" \
  "$BASE_URL/search/bynameaddress?name=John%20Smith&citystatezip=Austin%2C%20TX&page=1"
```

### Search by phone

```bash
curl -H "X-API-Key: $API_KEY" \
  "$BASE_URL/search/byphone?phoneno=5125550100&page=1"
```

### Search by email

```bash
curl -H "X-API-Key: $API_KEY" \
  "$BASE_URL/search/byemail?email=jane%40example.com"
```

### Person details by ID

```bash
curl -H "X-API-Key: $API_KEY" \
  "$BASE_URL/search/detailsbyID?peo_id=p123"
```

## Missing parameters

Missing required query parameters return **HTTP 200** with the legacy error
envelope, not `400`:

```json
{"error": "name parameter Not found"}
```

| Endpoint | Competitor `error` | Native `Message` |
| --- | --- | --- |
| `/search/byname` | `name parameter Not found` | `name parameter Not found` |
| `/search/byaddress` | `street and citystatezip parameter Not found` | `street, citystatezip parameter Not found` |
| `/search/bynameaddress` | `name and address parameter Not found` | `name, citystatezip parameter Not found` |
| `/search/byphone` | `phone parameter Not found` | `phoneno parameter Not found` |
| `/search/byemail` | `email parameter Not found` | `email parameter Not found` |
| `/search/detailsbyID` | `peo_id parameter Not found` | `peo_id parameter Not found` |

The native missing-parameter envelope is:

```json
{
  "Status": 200,
  "Source": "_cch_dch",
  "Message": "name parameter Not found",
  "Records": 0,
  "Page": 1,
  "PropertyDetails": {},
  "PeopleDetails": []
}
```

## Competitor response format (default)

### Search

```json
{
  "Status": 200,
  "Message": "state_1; Use the /person_details_by_ID endpoint to gather more info!",
  "Source": "ncch_dch",
  "Records": 1,
  "Page": 1,
  "PropertyDetails": {},
  "PeopleDetails": [
    {
      "Name": "Jane Q Example",
      "Link": "https://www.truepeoplesearch.com/find/person/p123",
      "Person ID": "p123",
      "Age": "42",
      "Lives in": "Austin, TX",
      "Used to live in": "1 Old St, Austin, TX 78701, 2 Older St, Austin, TX 78702",
      "Related to": "Alex Example, Sam Example",
      "peo_id": "p123",
      "Used_Names": ["J Q Example"],
      "Previous_Addresses": [
        "1 Old St, Austin, TX 78701",
        "2 Older St, Austin, TX 78702"
      ],
      "Related": ["Alex Example", "Sam Example"]
    }
  ]
}
```

When no records are found:

```json
{
  "Status": 200,
  "Message": "state_1; We could not find any records for that search criteria",
  "Source": "ncch_dch",
  "Records": 0,
  "Page": 1,
  "PropertyDetails": {},
  "PeopleDetails": []
}
```

`Records` is the total record count reported by TruePeopleSearch when
available, otherwise the number of returned records. `Page` echoes the
requested page.

### Details

```json
{
  "Status": 200,
  "Message": "state_1",
  "Source": "ncch_dch",
  "Person Details": [
    {
      "Person_name": "Jane Q Example",
      "Age": "42",
      "Born": "January 1980",
      "Lives in": "Austin, TX",
      "Telephone": "(512) 555-0100"
    }
  ],
  "Current Address Details List": [
    {
      "street_address": "1 Old St",
      "address_locality": "Austin",
      "address_region": "TX",
      "postal_code": "78701",
      "county": "Travis County",
      "date_range": "(Jan 2020 - Jan 2026)",
      "property_summary": "$100,000 | 3 Bed"
    }
  ],
  "All Phone Details": [
    {
      "phone_number": "(512) 555-0100",
      "phone_type": "Wireless",
      "phone_info": "Possible Primary Phone",
      "last_reported": "Last reported Jan 2026",
      "provider": "Example Wireless",
      "status": "Active"
    }
  ],
  "Email Addresses": ["jane@example.com"],
  "Previous Address Details": [],
  "All Relatives": [
    {
      "Name": "Alex Example",
      "Age": "30",
      "Person Link": "https://www.truepeoplesearch.com/find/person/p456",
      "Person ID": "p456",
      "Relationship": "Sibling"
    }
  ],
  "All Associates": [],
  "Records": 1,
  "Page": 1,
  "PropertyDetails": {},
  "PeopleDetails": ["<native detail record>"],
  "peo_id": "p123",
  "AKAs": ["J Q Example"],
  "Phone_Numbers": ["(512) 555-0100"],
  "All_Addresses": ["1 Old St, Austin, TX 78701"],
  "Property_Details": {
    "Bedrooms": "3",
    "Bathrooms": "2",
    "Sq_Ft": "1500",
    "Year_Built": "1985",
    "Estimated_Value": "$100,000",
    "Estimated_Equity": "$50,000",
    "Last_Sale_Amount": "$80,000",
    "Last_Sale_Date": "Jan 2020",
    "Occupancy": "Owner",
    "Ownership": "Individual",
    "Type": "Single Family",
    "Class": "Residential",
    "Subdivision": "Example",
    "Lot_Sq_Ft": "5000",
    "APN": "000-000-000",
    "School_District": "Example ISD",
    "Address": "1 Old St, Austin, TX 78701"
  },
  "Possible_Relatives": ["Alex Example"],
  "Possible_Associates": []
}
```

If the details adapter receives no records, it returns:

```json
{
  "Status": 200,
  "Source": "_cch_dch",
  "Message": "peo_id Not Valid",
  "Records": 0,
  "PeopleDetails": []
}
```

### Competitor key mapping

| Competitor key | Source field |
| --- | --- |
| `Person ID` | `peo_id` |
| `Lives in` | `Lives_In` |
| `Used to live in` | `Previous_Addresses` joined with `, ` |
| `Related to` | `Related` joined with `, ` |
| `Person Details` | `Name`, `Age`, `Born`, `Lives_In`, first phone |
| `Current Address Details List` | `Current_Address_Details` |
| `All Phone Details` | `Phone_Details` |
| `Email Addresses` | `Emails` |
| `Previous Address Details` | `Previous_Address_Details` |
| `All Relatives` | `Relatives_Details` |
| `All Associates` | `Associates_Details` |

Additive extras retained for existing clients: `peo_id`, `Used_Names`,
`Previous_Addresses`, `Related`, `Records`, `Page`, `PropertyDetails`,
`PeopleDetails`, `AKAs`, `Phone_Numbers`, `All_Addresses`, `Property_Details`,
`Possible_Relatives`, `Possible_Associates`, `property_summary`, `status` and
`Relationship`.

## Native response format (`format=native`)

### Search

```json
{
  "Status": 200,
  "Source": "ncch_dch",
  "Message": "Successful Search Result",
  "Records": 1,
  "Page": 1,
  "PropertyDetails": {},
  "PeopleDetails": [
    {
      "peo_id": "p123",
      "Name": "Jane Q Example",
      "Age": "42",
      "Lives_In": "Austin, TX",
      "Previous_Addresses": [],
      "Related": []
    }
  ]
}
```

### Details

The native details response uses the same envelope with one record:

```json
{
  "Status": 200,
  "Source": "ncch_dch",
  "Message": "Successful Search Result",
  "Records": 1,
  "Page": 1,
  "PropertyDetails": {},
  "PeopleDetails": ["<native detail record>"]
}
```

## Errors

| Status | Body | Meaning |
| --- | --- | --- |
| `200` | Legacy missing-parameter envelope | A required query parameter is empty or absent |
| `401` | `{"detail": "Invalid or missing API key"}` | Missing or wrong key |
| `422` | FastAPI validation details | Invalid `format` or `page` |
| `500` | `{"detail": "Unable to retrieve data from the source. Please try again later."}` | Upstream or NodeMaven failure |
| `503` | `{"detail": "API authentication is not configured"}` | No server API key configured |

Upstream details are logged server-side only. The client-facing 500 body never
contains proxy URLs, NodeMaven tokens, attempt counts or upstream HTML.

## Notes

- The `Source` key is preserved for compatibility; its value is an internal
  cache indicator, not a copy of the competitor value.
- The upstream is scraped through a warm, auto-scaling NodeMaven session pool.
  Pool size, session TTL, retries and cache TTL are configured through
  environment variables.
- Response sets are not guarantees of identity; treat public-records matches
  as candidates and verify them.
