> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goliathdata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Skip Trace API

> Run skip traces programmatically from your own systems using the Goliath developer API

## About

The Skip Trace API lets you run skip traces from your own code — any server, script, or third-party tool that can make HTTPS requests. Send an address (and optionally a person or entity name), receive the matched owner records — phones, emails, and names — in the same response. Each call consumes one skip-trace credit from your organization's balance, the same way an in-app skip trace does.

<Warning>
  This is a server-to-server API. Keep your key secret. Never ship it in a browser, mobile app, or any client-side code.
</Warning>

## Generating an API key

<Steps>
  <Step title="Open the Integrations page">
    From the left sidebar, click **Integrations** under the Automations section.
  </Step>

  <Step title="Find the Skip Trace API card">
    It lives under the **Developer** section. The card is only visible to organization admins — if you do not see it, ask an admin to generate a key for you.
  </Step>

  <Step title="Click Generate API key">
    Goliath shows your new key once, in a dialog. **Copy it now and store it in your secrets manager** — the full key is never shown again. After you close the dialog, only the first and last characters are visible in the UI.
  </Step>

  <Step title="Use the key">
    Pass it as a Bearer token on every request:

    ```
    Authorization: Bearer gd_...
    ```
  </Step>
</Steps>

<Warning>
  **One key per organization.** Each organization has exactly one active Skip Trace API key at a time. Use **Rotate** to replace it with a new secret — the previous key stops working immediately. Use **Revoke** to remove it entirely.
</Warning>

## Making a request

### Endpoint

```
POST https://server.goliathdata.com/api/v1/skiptrace
```

### Headers

| Header          | Value              |
| --------------- | ------------------ |
| `Authorization` | `Bearer gd_...`    |
| `Content-Type`  | `application/json` |

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://server.goliathdata.com/api/v1/skiptrace \
    -H "Authorization: Bearer gd_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "address": {
        "line1": "123 Main St",
        "city": "Austin",
        "state": "TX",
        "zip": "78701"
      }
    }'
  ```

  ```ts Node.js theme={null}
  const res = await fetch("https://server.goliathdata.com/api/v1/skiptrace", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.GOLIATH_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      address: {
        line1: "123 Main St",
        city: "Austin",
        state: "TX",
        zip: "78701",
      },
    }),
  })
  const data = await res.json()
  ```
</CodeGroup>

## Request body

<ParamField body="address" type="object" required>
  The property address to look up.

  <Expandable title="address properties">
    <ParamField body="line1" type="string" required>
      Street address.
    </ParamField>

    <ParamField body="line2" type="string">
      Apartment, unit, suite.
    </ParamField>

    <ParamField body="city" type="string" required>
      City name.
    </ParamField>

    <ParamField body="state" type="string" required>
      Two-letter USPS state abbreviation (e.g. `TX`, `CA`).
    </ParamField>

    <ParamField body="zip" type="string" required>
      ZIP code. Five or nine digits.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="actor" type="object">
  Narrow the trace to a specific person or entity. If omitted, the API traces against whichever owner records are attached to the property.

  <Expandable title="actor properties">
    <ParamField body="type" type="string" required>
      Either `person` or `entity`.
    </ParamField>

    <ParamField body="firstName" type="string">
      First name. Person only.
    </ParamField>

    <ParamField body="lastName" type="string">
      Last name. Person only.
    </ParamField>

    <ParamField body="name" type="string">
      Entity or company name. Entity only.
    </ParamField>
  </Expandable>
</ParamField>

## Response

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "id": "3f21d6e0-a8a4-4a0e-8f25-9e9a74c37c2e",
    "status": "records_found",
    "persons": [
      {
        "firstName": "Jane",
        "lastName": "Doe",
        "middleName": null,
        "age": 47,
        "phones": [
          { "number": "5125551234", "type": "mobile" },
          { "number": "5125559999", "type": "residential" }
        ],
        "emails": ["jane@example.com"]
      }
    ]
  }
  ```
</ResponseExample>

<ResponseField name="id" type="string">
  Skip-trace request ID.
</ResponseField>

<ResponseField name="status" type="string">
  Either `records_found` or `no_records_found`.
</ResponseField>

<ResponseField name="persons" type="array">
  Array of matched owner records.

  <Expandable title="person properties">
    <ResponseField name="firstName" type="string | null">
      First name, or `null` if unknown.
    </ResponseField>

    <ResponseField name="lastName" type="string | null">
      Last name, or `null` if unknown.
    </ResponseField>

    <ResponseField name="middleName" type="string | null">
      Middle name, or `null` if unknown.
    </ResponseField>

    <ResponseField name="age" type="number | null">
      Approximate age, or `null` if unknown.
    </ResponseField>

    <ResponseField name="phones" type="array">
      Array of `{ number, type }` objects. `type` is `mobile`, `residential`, or `unknown`.
    </ResponseField>

    <ResponseField name="emails" type="string[]">
      Array of email addresses.
    </ResponseField>
  </Expandable>
</ResponseField>

<Note>
  A `status` of `no_records_found` means the address and actor were valid but no owner records were located. Credits are still consumed in this case — the work to look up records has already happened.
</Note>

## Errors

All errors return a JSON body of the shape:

```json theme={null}
{ "error": { "code": "insufficient_credits", "message": "..." } }
```

| HTTP | Code                   | Meaning                                                                   |
| ---- | ---------------------- | ------------------------------------------------------------------------- |
| 401  | `invalid_key`          | Missing, malformed, or revoked Bearer key.                                |
| 402  | `insufficient_credits` | Your organization has no skip-trace credits. Buy more in the Goliath app. |
| 403  | `feature_unavailable`  | Your organization's plan or trial does not currently permit skip tracing. |
| 422  | `invalid_request`      | Request body failed validation, or the address could not be resolved.     |
| 429  | `rate_limited`         | Too many requests per minute. Back off for `Retry-After` seconds.         |
| 429  | `concurrency_limit`    | Too many in-flight requests from this key at once.                        |
| 504  | `timeout`              | Skip trace did not complete within 30 seconds. Retry the request.         |
| 500  | `internal_error`       | Unexpected server error. Retry; contact support if it persists.           |

## Rate limits

* **1,000 requests per minute** per API key.
* **10 concurrent requests** per API key per server instance.

Every response includes:

| Header                  | Meaning                                           |
| ----------------------- | ------------------------------------------------- |
| `X-RateLimit-Limit`     | Requests allowed per window (1000).               |
| `X-RateLimit-Remaining` | Requests remaining in the current window.         |
| `X-RateLimit-Reset`     | Unix timestamp when the window resets.            |
| `Retry-After`           | Seconds to wait. Only set when you receive a 429. |

## Credits and billing

Each successful skip-trace call consumes **one SKIPTRACE credit** from your organization's balance. This is the same credit pool as in-app skip traces — running either path debits the same bucket.

When your balance hits zero, subsequent calls return `402 insufficient_credits`. Buy more credits in the Goliath app under **Billing → Credits** and then retry the request.

<Note>
  **Auto top-up is not supported yet.** The API does not yet auto-purchase credits on your behalf. If you expect heavy usage, monitor your balance and top up in the app before you run out.
</Note>

## Frequently asked questions

<AccordionGroup>
  <Accordion title="How long does a request take?">
    Most calls complete in a few seconds. The server will wait up to 30 seconds for the pipeline to terminate before returning `504 timeout`. Retrying a timed-out request is safe and will return the cached result if one has since been produced.
  </Accordion>

  <Accordion title="Is the result cached?">
    Yes. Repeat calls for the same address + actor combination return the cached result from the previous successful trace, without triggering another provider call. Credits are still consumed for each call.
  </Accordion>

  <Accordion title="Can I use this from the browser?">
    No. API keys are org-level secrets. Anyone with the key can run traces and burn your credits. Keep it on your server and never expose it to client code.
  </Accordion>

  <Accordion title="What happens when I rotate the key?">
    The old secret stops working immediately. Any systems still using it start receiving `401 invalid_key` on their next request. Update them with the new secret before rotating if you can coordinate the switch.
  </Accordion>

  <Accordion title="Do you support property IDs as input?">
    No. The public API only accepts street addresses. Property IDs are internal identifiers that change over time and aren't safe to expose on a stable public surface.
  </Accordion>
</AccordionGroup>
