# Look up a parcel (/api-reference/lookup)

```http
POST https://api.fieldgeist.com/v1/verify/lookup
```

Matches a street address to a parcel and returns its Fieldgeist ID, county APN, county FIPS code and coordinates. When the address fits more than one parcel, or the building has unit parcels and no unit was given, the answer says so (`ambiguous`, `unit_required`) and lists candidates instead of picking one.

Send either the components of an address or a whole [parse](/api-reference/parse) response.

## Authentication [#authentication]

`Authorization: Bearer <key>`. See [Authentication](/authentication).

## Request body [#request-body]

The body is a JSON object in one of two forms:

- **A parse response**, unchanged: `{"type": "street", "components": {...}, ...}`. Only `type` and `components` are read.
- **Bare components**: `{"addressNumber": "400", "streetName": "BROAD", ...}`, optionally with `"type"`. Without `type`, the address is treated as a street address.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `streetName` | string | Yes | Street name. |
| `zip5` | string | One of `zip5` or `city` | 5-digit ZIP Code. The fastest and most precise way to locate the address. |
| `city` | string | One of `zip5` or `city` | City. With `state`, or alone when the city's name exists in only one state. |
| `state` | string | No | Two-letter state code. Needed with `city` when the city's name exists in more than one state. |
| `addressNumber` | string | No | House number. Without it, the answer can only be a parcel with no house number (`no_situs_parcel`) or `not_found`. |
| `addressNumberSuffix` | string | No | Such as `1/2`. |
| `predirectional`, `postdirectional` | string | No | Street directions, such as `W` or `NW`. |
| `streetSuffix` | string | No | Street type, such as `ST`. Leave it out only if you don't know it: without it, every suffix at that house number is a candidate. |
| `occupancyType`, `occupancyId` | string | No | Unit designator and number. The unit number picks a unit parcel in a building that has them. |
| `zip4` | string | No | Accepted, not used for matching. |
| `type` | string | No | Address type from a parse. Anything other than `street` is refused with `422 unsupported_type`. |

Values can be strings or numbers. Case and punctuation don't matter: values are normalized the same way the parcel index is.

## Request example [#request-example]

<Tabs items={["cURL", "JavaScript", "Python"]}>
<Tab value="cURL">

```bash
curl https://api.fieldgeist.com/v1/verify/lookup \
  -H "Authorization: Bearer $FIELDGEIST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "addressNumber": "400",
    "streetName": "BROAD",
    "streetSuffix": "ST",
    "city": "SEATTLE",
    "state": "WA",
    "zip5": "98109"
  }'
```

</Tab>
<Tab value="JavaScript">

```js
const res = await fetch("https://api.fieldgeist.com/v1/verify/lookup", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FIELDGEIST_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    addressNumber: "400",
    streetName: "BROAD",
    streetSuffix: "ST",
    city: "SEATTLE",
    state: "WA",
    zip5: "98109",
  }),
});
const match = await res.json();
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.fieldgeist.com/v1/verify/lookup",
    headers={"Authorization": f"Bearer {os.environ['FIELDGEIST_API_KEY']}"},
    json={
        "addressNumber": "400",
        "streetName": "BROAD",
        "streetSuffix": "ST",
        "city": "SEATTLE",
        "state": "WA",
        "zip5": "98109",
    },
    timeout=10,
)
res.raise_for_status()
match = res.json()
```

</Tab>
</Tabs>

## Response [#response]

<Callout type="info">
Parcel values on this page (IDs, APNs, coordinates) are illustrative.
</Callout>

### 200 OK: matched [#200-ok-matched]

```json
{
  "status": "matched",
  "fieldgeist_id": "adr_01JA2Z3Y4X5W6V7T8S9R0QPNMA",
  "building_id": null,
  "apn": "198520-0495",
  "fips": "53033",
  "lat": 47.620494,
  "lon": -122.349235,
  "matchConfidence": 1,
  "candidates": [],
  "searched": ["matchkey/v3.2/zip5=98109/part.parquet"],
  "readPath": "chunk",
  "fuzzy": false
}
```

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | The outcome. See [Statuses](#statuses). |
| `fieldgeist_id` | string \| null | The matched parcel's [Fieldgeist ID](/concepts/fieldgeist-ids) (`adr_…`). `null` when there is no single parcel. |
| `building_id` | string \| null | The building's ID (`bld_…`) when the address has unit parcels. See [Condos and units](/concepts/condos-and-units). |
| `apn` | string \| null | The county's assessor parcel number, as the county writes it. |
| `fips` | string \| null | 5-digit county FIPS code. |
| `lat`, `lon` | number \| null | Coordinates of the parcel. |
| `matchConfidence` | number | 0 to 1. How closely the input matched the parcel. `0` when there's no single parcel (`ambiguous`, `not_found`); each candidate then has its own. See [Match confidence](/concepts/confidence-and-warnings#match-confidence). |
| `unit_required` | boolean | Present and `true` when the address has unit parcels and the input named no unit that exists. |
| `unit_count` | number | Present with `unit_required`: how many unit parcels the address has. |
| `candidates` | object[] | Other parcels that fit, up to 10. Empty for a clean single match. See [Candidates](#candidates). |
| `fuzzy` | boolean | `true` when the street name matched as a near miss (a typo or variant) rather than exactly. Confidence is lowered. |
| `state_inferred` | string | Present when you sent a city without a state or ZIP: the state the city was found in. |
| `searched` | string[] | Diagnostic: which parts of the index were read. |
| `readPath` | string | Diagnostic: how the answer was found (`chunk`, `fuzzy_chunk`, `whole`, `fuzzy`, `none`). |

### Statuses [#statuses]

| `status` | Meaning | Top-level parcel fields | What to do |
| --- | --- | --- | --- |
| `matched` | One parcel fits. | Set | Use it. Check `unit_required` and `fuzzy`. |
| `matched_building` | The building matched, but not a unit parcel. Either the input named a unit that has no parcel of its own (the answer is the building's parcel), or the building has only unit parcels and the input named none of them (no top-level parcel; the units are the candidates). | The building's parcel, or `null` with unit candidates | Use the building's parcel if that's enough. If `unit_required` is `true`, ask for the unit. |
| `ambiguous` | Two or more different parcels fit equally well, for example `3 SPRUCE` when there's a `3 SPRUCE ST` and a `3 SPRUCE CT`. | `null` | Show the candidates, or ask for the missing part (usually the suffix or a direction). |
| `no_situs_parcel` | A weak match. The input had no house number, so the answer is a parcel on that street; or, in a single-ZIP search, the house number isn't on the street and the street has exactly one parcel with no house number on record. | Set | Don't treat it as a match without checking. `matchConfidence` is low. |
| `not_found` | No parcel fits. | `null` | Check the input. Billed like any answer. |

### 200 OK: ambiguous [#200-ok-ambiguous]

```json
{
  "status": "ambiguous",
  "fieldgeist_id": null,
  "building_id": null,
  "apn": null,
  "fips": null,
  "lat": null,
  "lon": null,
  "matchConfidence": 0,
  "candidates": [
    {
      "apn": "1234-0001",
      "fips": "53033",
      "lat": 47.611,
      "lon": -122.335,
      "zip5": "98101",
      "addressNumber": "3",
      "streetName": "SPRUCE",
      "streetSuffix": "CT",
      "occupancyId": null,
      "matchConfidence": 0.85,
      "fieldgeist_id": "adr_01JA2Z3Y4X5W6V7T8S9R0QPSP1",
      "building_id": null
    },
    {
      "apn": "1234-0002",
      "fips": "53033",
      "lat": 47.612,
      "lon": -122.336,
      "zip5": "98101",
      "addressNumber": "3",
      "streetName": "SPRUCE",
      "streetSuffix": "ST",
      "occupancyId": null,
      "matchConfidence": 0.85,
      "fieldgeist_id": "adr_01JA2Z3Y4X5W6V7T8S9R0QPSP2",
      "building_id": null
    }
  ],
  "searched": ["matchkey/v3.2/zip5=98101/part.parquet"],
  "readPath": "chunk",
  "fuzzy": false
}
```

The input was `{"addressNumber": "3", "streetName": "SPRUCE", "zip5": "98101"}`. With `"streetSuffix": "CT"` the same request returns `matched`.

### 200 OK: unit required [#200-ok-unit-required]

A condo building whose units are separate parcels, looked up without a unit:

```json
{
  "status": "matched_building",
  "fieldgeist_id": null,
  "building_id": "bld_01JA2Z3Y4X5W6V7T8S9R0QPNMC",
  "apn": null,
  "fips": null,
  "lat": null,
  "lon": null,
  "matchConfidence": 0.8,
  "unit_required": true,
  "unit_count": 2,
  "candidates": [
    {
      "apn": "777-1",
      "fips": "53033",
      "lat": 47.61,
      "lon": -122.31,
      "zip5": "98109",
      "addressNumber": "600",
      "streetName": "PINE",
      "streetSuffix": "ST",
      "occupancyId": "1",
      "matchConfidence": 0.8,
      "fieldgeist_id": "adr_01JA2Z3Y4X5W6V7T8S9R0QPNMD",
      "building_id": "bld_01JA2Z3Y4X5W6V7T8S9R0QPNMC"
    },
    {
      "apn": "777-2",
      "fips": "53033",
      "lat": 47.61,
      "lon": -122.31,
      "zip5": "98109",
      "addressNumber": "600",
      "streetName": "PINE",
      "streetSuffix": "ST",
      "occupancyId": "2",
      "matchConfidence": 0.8,
      "fieldgeist_id": "adr_01JA2Z3Y4X5W6V7T8S9R0QPNME",
      "building_id": "bld_01JA2Z3Y4X5W6V7T8S9R0QPNMC"
    }
  ],
  "searched": ["matchkey/v3.2/zip5=98109/part.parquet"],
  "readPath": "chunk",
  "fuzzy": false
}
```

Sending `"occupancyId": "2"` returns `matched` with that unit's parcel (`adr_…NME`) and the same `building_id`. A building that has its own parcel *and* unit parcels returns `matched` with the building's parcel plus `unit_required: true`. See [Condos and units](/concepts/condos-and-units).

`unit_required` can come with `matched` or `matched_building`. Check the flag, not the status.

### Candidates [#candidates]

Each candidate is a parcel that fits the input:

| Field | Type | Description |
| --- | --- | --- |
| `fieldgeist_id` | string \| null | The parcel's Fieldgeist ID. |
| `building_id` | string \| null | Its building's ID, for unit parcels. |
| `apn`, `fips`, `lat`, `lon` | | As at the top level. |
| `zip5`, `addressNumber`, `streetName`, `streetSuffix`, `occupancyId` | string \| null | The parcel's address as the index records it, so you can tell candidates apart. |
| `matchConfidence` | number | How closely the input matched this candidate. |
| `fuzzy` | boolean | Present and `true` when this candidate's street name was a near miss. |

Candidates are capped at 10 per answer; `unit_count` gives the full number of units.

### City without a ZIP [#city-without-a-zip]

Without `zip5`, the lookup searches the city. When the city's name exists in only one state, the state can be left out and the answer includes `state_inferred`:

```json
{ "status": "matched", "fieldgeist_id": "adr_…", "state_inferred": "TX", "...": "..." }
```

When the name exists in several states, the request is refused:

```json
{
  "error": "location_required",
  "message": "city is in 3 states: add a state or zip5",
  "states": 3
}
```

### Errors [#errors]

| Status | `error` | Cause |
| --- | --- | --- |
| `400` | `invalid_json` | The body isn't valid JSON. |
| `400` | `components_required` | The body isn't an object, or a component is not a string or number. |
| `400` | `street_required` | No `streetName`. |
| `400` | `location_required` | Neither `zip5` nor `city`, or a `city` that exists in more than one state without a `state`. The second case adds `states`. |
| `422` | `unsupported_type` | `type` isn't `street` (PO boxes, rural routes and other types have no parcel). The response echoes `type`. |
| `500` | `internal_error` | The lookup failed on our side. Retry. |
| `503` | `matchkey_unavailable` | The parcel index is unavailable. Retry later. |
| `401`, `402`, `403`, `429` | | See [Errors](/api-reference/errors). |

```json
{
  "error": "unsupported_type",
  "type": "po_box",
  "message": "only type=street addresses can be matched to a parcel"
}
```
