---
title: "Enrich a place"
method: POST
path: "/v1/enrich"
tags: ["core"]
---

# Enrich a place

`POST /v1/enrich`

## Request

- Base URL: `https://api.voygr.tech`
- URL: `https://api.voygr.tech/v1/enrich`
- Auth: API key in header `X-API-Key`

Return requested attributes for one place or a list of places. Standard attributes (opening_hours, phone, website, facebook, instagram, tiktok, x, email, existence, open_closed) cost one credit per place however many you request. Any other attribute name is a research field, charged per place per field. A request may mix both and pays for each. Send poi_name for one place and get the flat response; send pois for a list and get a rows-by-attributes matrix. include_summary=true also returns a short factual place_summary. Requires a key with the enrich or deep_research entitlement; either one may request any attribute, standard or custom. See [Research custom attributes](/docs/guides/research-custom-attributes/) for custom attributes.

### Verifying the name and address you sent

Three independent checks cover records that are close but not exact (an
abbreviation, an initialism, a store number or a stale spelling):

- `verify_name` settles the business's name before researching anything else,
  and every attribute you asked for is then researched against the verified
  name. Each row carries `identity.name`.
- `verify_address` checks the trading address after research and never replaces
  it. Each row carries `address_checks.address`, whose `verdict` is
  `confirmed_at_address`, `not_found_at_address`, `found_elsewhere` (with the
  `found_address`) or `not_checked` when there was no address to check.
- `verify_registered_address` checks the registered office. Each row carries
  `address_checks.registered_address`.

`identity.name` gives the value we settled on, a verdict, and your original. The
verdict is `confirmed` (yours was already right), `corrected` (we replaced it) or
`unresolved` (we could not establish it, so your value comes back untouched).
`value` is always what the rest of the row was researched against, so branch on
the verdict rather than comparing strings.

Formatting is not a correction: punctuation, casing, "and" vs "&" and a dropped
or added Inc/Ltd/LLC all come back `confirmed` with your own string.

`verify_name` and `verify_address` share one lookup, so either or both cost a flat
1 extra credit per row, however many attributes you requested;
`verify_registered_address` costs 1 more. Each can be overridden per place.

`verify_identity` is deprecated. It means `verify_name` plus `verify_address` and
costs the same 1. It also rewrites the address research runs against to the same
premises written in full and returns `identity.address`. Use the separate flags
in new integrations, so a place can decline either check on its own.

These checks are not postal validation: a `confirmed` address matches what the
business publishes about itself. They make no claim about whether the business
is still open.


### Owner discovery (`owners`, `owner_org`)

`owners` is the individual owner, co-owners or franchisee, as one value: a list
of people with a `name` and a `role`. Add `include` (`phone`, `email`,
`linkedin`) to look up each owner's direct contacts, returned in the same
objects. `owner_org` is the operating company when no individual owner exists.
The two are mutually exclusive per place: when one is established, the other
abstains, and "no individual owner" is a legitimate answer. `key_people` takes
the same shape with `title` in place of `role`.

`owners` and `key_people` carry no confidence score. An abstention is a
normal result, not a claim that no owner exists. `owners` and `key_people` cost 1
credit for the names, 1 for the roles or titles and 1 per included contact;
`owner_org` is billed like any other field.
See [Owners and key people](/docs/guides/owners-and-key-people/).

## Request body

- UnifiedEnrichRequest: The body for `POST /v1/enrich`, for one place or a list. The route serves two pipelines: a cheap extractor for a closed set of standard attributes, and a research loop for anything else. An attribute's name decides which pipeline answers it. The two are priced differently, and a request may use one or both. The body has two shapes: - Flat, for one place: `poi_name` and its address fields at the top level. The response is the flat `POIEnrichResponse`. - List: `pois: [...]`. The response is the `DeepResearchResponse` rows x attributes matrix. Exactly one of `poi_name` / `pois` is required. The shape you send decides the shape you get back.
  - `id` string, nullable: Client-provided identifier
  - `poi_name` string, nullable: Place name (flat shape)
  - `street` string, nullable: Street address
  - `city` string, nullable: City
  - `region` string, nullable: State/province
  - `postcode` string, nullable: Postal/ZIP code
  - `country` string, nullable: 2-letter ISO country code (e.g. US) locating the place.
  - `address` string, nullable: Unstructured address string. May also be sent as `freeform_address`.
  - `latitude` number, nullable: Latitude in decimal degrees (WGS84).
  - `longitude` number, nullable: Longitude (WGS84)
  - `registered_address` string, nullable: The place's registered (legal) address, if you hold one. `address` remains the trading address.
  - `include_summary` boolean: Flat shape only: also return a short factual place_summary. Default false. Does not change billing.
  - `response_format` 'attributes' | 'cell': Flat shape only: 'attributes' (default, EnrichedAttribute) or 'cell' (the unified ResultCell shape).
  - `pois` UnifiedEnrichPOI[], nullable: Places to enrich. Send this or the top-level poi_name: a list is answered as a rows x attributes matrix, a single place in the flat shape.
    - `id` string, nullable: Client-provided identifier (keys the response row)
    - `poi_name` string, required: Place name
    - `street` string, nullable: Street address
    - `city` string, nullable: City
    - `region` string, nullable: State/province
    - `postcode` string, nullable: Postal/ZIP code
    - `country` string, nullable: 2-letter ISO country code (e.g. 'US'). Biases the web search.
    - `address` string, nullable: Where the business trades: its own premises, the address a customer visits. Unstructured address string. May also be sent as `freeform_address` (input-only alias). Leave it out and request the `address` attribute to have us research it.
    - `registered_address` string, nullable: The company's registered address (its registered office, as filed with the company register), which may differ from where it trades. Optional. Checked by verify_registered_address; leave it out and request the `registered_address` attribute to have us research it.
    - `verify_name` boolean, nullable: Per-row override of the request's `verify_name`.
    - `verify_address` boolean, nullable: Per-row override of the request's `verify_address`.
    - `verify_registered_address` boolean, nullable: Per-row override of the request's `verify_registered_address`.
    - `latitude` number, nullable: Latitude in decimal degrees (WGS84).
    - `longitude` number, nullable: Longitude in decimal degrees (WGS84). Provide with latitude.
    - `attributes` union[], nullable: Attributes for this place only, overriding the request's shared list. Omit to use the shared list.
      - union
        - string
        - DeepResearchFieldSpec: One requested research field. The name identifies the field; description defines the requested information and is treated as untrusted data, never instructions.
          - `name` string, required: Field identifier, e.g. wheelchair_accessible. owners returns the individual owners or franchisees as a list of people with a name and role; owner_org identifies the operating company when there is no individual owner. These owner fields are mutually exclusive per place. key_people returns the leadership beyond the owner as a list of people with a name and title. Add include to owners or key_people for each person's phone, email or LinkedIn.
          - `description` string, nullable: Natural-language definition of the field (treated as untrusted data)
          - `value_type` string, nullable: Expected value type hint: bool | enum | string | number
          - `allowed_values` string[], nullable: For enum fields, the permitted values
          - `include` string[], nullable: `owners` / `key_people` only: contacts to look up for each person, returned inside the same value. Each costs one more credit per row (the name and the role/title are one credit each).
  - `attributes` union[], nullable: What to return for every place. Standard attributes (opening_hours, phone, website, facebook, instagram, tiktok, x, email, existence, open_closed) cost a flat credit per place however many you request. Any other name is a research field, charged per place per field. A request may mix both and pays for each.
    - union
      - string
      - DeepResearchFieldSpec: One requested research field. The name identifies the field; description defines the requested information and is treated as untrusted data, never instructions.
        - `name` string, required: Field identifier, e.g. wheelchair_accessible. owners returns the individual owners or franchisees as a list of people with a name and role; owner_org identifies the operating company when there is no individual owner. These owner fields are mutually exclusive per place. key_people returns the leadership beyond the owner as a list of people with a name and title. Add include to owners or key_people for each person's phone, email or LinkedIn.
        - `description` string, nullable: Natural-language definition of the field (treated as untrusted data)
        - `value_type` string, nullable: Expected value type hint: bool | enum | string | number
        - `allowed_values` string[], nullable: For enum fields, the permitted values
        - `include` string[], nullable: `owners` / `key_people` only: contacts to look up for each person, returned inside the same value. Each costs one more credit per row (the name and the role/title are one credit each).
  - `validate_existence` boolean: Also validate whether each place exists, returned as an 'existence' attribute.
  - `verify_name` boolean: Verify the business name before researching, and research every attribute against the verified name. Each row returns `identity.name`. Flat 1 credit per place; shares one lookup with verify_address, so the two together still cost 1.
  - `verify_identity` boolean: Deprecated: use `verify_name` and `verify_address`. Equivalent to both and billed the same. It also rewrites the address research runs against to the same premises in full, never to different premises.
  - `verify_address` boolean: Check the trading address after research: the one you supplied, else the one we researched. It is never replaced. Implied by the deprecated verify_identity. Costs a flat credit per place, shared with verify_name.
  - `verify_registered_address` boolean: Check the registered address itself, not the business: the one you supplied, else the one we researched. Costs a flat credit per place, independent of the other checks.
  - `estimate_only` boolean: Dry-run: do not run and do not charge; return meta.estimated_credits.
  - `sample` integer, nullable: List shape only: process and bill only the first N places.
  - `max_credits` integer, nullable: Per-request credit ceiling; rejected with 402 before running if exceeded.

## Response `200`

Successful Response

- union
  - POIEnrichResponse: Response model for place attribute enrichment.
    - `success` boolean, required: Whether enrichment ran (individual attributes may still be null)
    - `id` string, nullable: Client-provided ID (pass-through)
    - `attributes` object, nullable: Requested web-extracted attribute -> result (opening_hours, phone, website, facebook, instagram, tiktok, x, email). EnrichedAttribute by default; the unified ResultCell when the request sets response_format='cell'.
    - `status` EnrichStatus: Existence results returned alongside enriched attributes. Interpret closure only when existence is established; read operational_status.status for the decision. Unavailable validation can return null fields. An operational-status failure can return HTTP 503 with operational_status_unavailable; retry the request.
      - `existence_confidence` number, nullable: Confidence (0-1) that a matching place ever existed at this address. A score of 0.5 or more means we judge that the place exists. Below 0.5 we judge that it does not. A closed place can still have high existence confidence. This does not describe whether it is operating today. Interpret closure only when existence is established; use operational_status.status for the decision.
    - `operational_status` OperationalStatusBlock: Operational status for a place. Use operational_status.status for the decision. A closed verdict is evidence-backed, not a decision based on score alone. closure_confidence sits above 0.5 for 'closed' and below 0.5 for 'open', and is not a probability. 'unknown' means the place exists but the evidence does not say whether it is open or closed; null means no closure assessment, not zero confidence.
      - `status` 'open' | 'closed' | 'unknown' | 'not_exists', required: Operational status. 'open' means assessed as operating. 'closed' means evidence supports closure. 'unknown' means the place exists but the evidence does not say whether it is open or closed, so re-check it later. 'not_exists' means the place was not judged to exist and no closure assessment was made. closure_confidence and closure_reason are null for 'unknown' and 'not_exists'.
      - `closure_confidence` number, nullable, required: How strongly the closure model leans closed (0-1). Above 0.5 when operational_status.status is 'closed', below 0.5 when it is 'open'. On each side, a higher number means a stronger lean towards closed. Rank places with it within one status. The same number means a different chance of closure in different lists, so it is not a probability. It is null, not zero, when the status is 'unknown' or 'not_exists': we make no open or closed call. Use operational_status.status for the decision.
      - `closure_reason` 'permanent' | 'temporary' | 'relocated' | 'replaced' | 'dissolved' | 'unspecified', nullable: What kind of evidence closed the place: permanent, temporary, relocated, replaced, dissolved, or unspecified. Null whenever status is not 'closed': an open or unknown verdict has no closure to attribute.
    - `place_summary` string, nullable: Short factual description of the place. Present only with include_summary=true; null when unavailable.
    - `enrichment_timestamp` string, nullable: UTC timestamp (ISO 8601)
    - `error` string, nullable: Error message if success=false
    - `error_code` string, nullable: Error category code
    - `request_id` string, nullable: Request ID for tracing
  - DeepResearchResponse: Research results in request order. Each field returns a value, confidence tier, and status; unavailable values are null. A per-place failure is a row with status=error; the other rows still succeed.
    - `success` boolean, required: Whether the request ran (individual rows/attributes may still be null or errored)
    - `rows` DeepResearchRow[]: Per-POI results (matrix), in request order
      - `id` string, nullable: Client-provided POI id (pass-through)
      - `poi_name` string, nullable: Place name (echoed from the request)
      - `identity` ResolvedIdentity: Per-row result of `verify_name` (or the deprecated `verify_identity`).
        - `name` ResolvedIdentityField: One half of a verified identity: the name, or the address. `value` is what we settled on and what the rest of the research ran against. It is the corrected string when we corrected it, and your own string when we confirmed it or could not establish it. `original` is always what you sent, so you can branch on the verdict. Comparing strings cannot tell you whether a change such as "St." to "Street" is worth saving. The response carries `value`, `verdict` and `original`, and no confidence, evidence URLs, source excerpts, candidates or method.
          - `value` string, nullable: The value we settled on and researched against
          - `verdict` 'confirmed' | 'corrected' | 'unresolved', required: confirmed = yours was already right; corrected = we replaced it; unresolved = we could not establish it and left yours untouched
          - `original` string, nullable: The value you supplied, echoed back unchanged
        - `address` ResolvedIdentityField: One half of a verified identity: the name, or the address. `value` is what we settled on and what the rest of the research ran against. It is the corrected string when we corrected it, and your own string when we confirmed it or could not establish it. `original` is always what you sent, so you can branch on the verdict. Comparing strings cannot tell you whether a change such as "St." to "Street" is worth saving. The response carries `value`, `verdict` and `original`, and no confidence, evidence URLs, source excerpts, candidates or method.
          - `value` string, nullable: The value we settled on and researched against
          - `verdict` 'confirmed' | 'corrected' | 'unresolved', required: confirmed = yours was already right; corrected = we replaced it; unresolved = we could not establish it and left yours untouched
          - `original` string, nullable: The value you supplied, echoed back unchanged
      - `address_checks` AddressChecks: Per-row address checks; each block is present only when its toggle was on.
        - `address` AddressCheck: `address_checks.address`: is the business at its trading address?
          - `address_source` 'provided' | 'researched', nullable: Where the checked address came from: provided = yours, researched = ours; null when there was no address to check
          - `checked_address` string, nullable: The address that was checked, unchanged
          - `verdict` 'confirmed_at_address' | 'not_found_at_address' | 'found_elsewhere' | 'not_checked', required: confirmed_at_address = a source places the business there; not_found_at_address = no source does; found_elsewhere = a source places it at `found_address`; not_checked = there was no address to check
          - `found_address` string, nullable: Where the evidence places the business: the same premises written in full when confirmed, a different premises when found_elsewhere. Never replaces yours.
          - `researched_address` string, nullable: When you supplied an address and also requested it researched: our researched value, for comparison. Yours is still the one checked.
          - `matches_provided` boolean, nullable: With researched_address: whether it is the same premises as yours
        - `registered_address` RegisteredAddressCheckResult: `address_checks.registered_address`: the address itself, never the business at it.
          - `address_source` 'provided' | 'researched', nullable: Where the checked address came from; null when there was none to check
          - `checked_address` string, nullable: The address that was checked, unchanged
          - `exists` boolean, nullable: Whether the address matched a real building or street; null when that could not be checked
          - `match_quality` 'premises' | 'street' | 'locality' | 'none': How precisely it matched: the building, only the street, only the locality, or not at all
          - `missing_components` string[]: Components the checked address does not contain
          - `premises_type` 'premises' | 'po_box' | 'mail_drop' | 'unknown': po_box by rule; mail_drop when the address is shared by many unrelated companies or offered as a virtual-office/formation-agent service
          - `normalized_address` string, nullable: The same premises written in full; null unless the building itself matched
          - `researched_address` string, nullable: When you supplied one and also requested it researched: our researched value
          - `matches_provided` boolean, nullable: With researched_address: whether it is the same premises as yours
      - `status` string, required: Row outcome: enriched | abstain | error | cost_capped
      - `outcome` 'enriched' | 'abstain', nullable: 'enriched' if any attribute was confirmed, else 'abstain' (null on error)
      - `fields` object, nullable: Attribute name -> result (null when the row errored)
      - `error` string, nullable: Safe error message; set only when the row failed
    - `meta` DeepResearchMeta: Request outcome and estimated credit cost.
      - `stopped_reason` string, nullable: Why the run stopped: completed | cost_capped | agent_unavailable | estimate_only
      - `estimated_credits` integer, nullable: Cost estimate for all requested rows before sampling or capping. Present on every response, including estimate_only requests.
    - `enrichment_timestamp` string, nullable: UTC timestamp (ISO 8601)
    - `error` string, nullable: Error message if success=false
    - `error_code` string, nullable: Error category code
    - `request_id` string, nullable: Request ID for tracing

## Other responses

- `400`: Invalid request. Correct the fields before retrying.
- `401`: Missing or invalid X-API-Key.
- `402`: Not enough credits, or the estimate exceeds the caller's ceiling.
- `403`: This key is not permitted to use this endpoint. It needs the enrich or deep_research entitlement.
- `422`: VALIDATION_ERROR: coordinates could not be resolved to a location; correct the coordinates or send an address. FIELD_REJECTED: a research attribute asks for something the service does not research; rejected_fields names it, so remove or rephrase it and resend. Nothing is charged.
- `429`: Rate limit exceeded; wait and follow Retry-After when returned. Free keys are limited to 10 requests per second and 120 requests per minute, and can place 1 call at a time, up to 25 calls per day. Paid keys are limited to 10 requests per second and 120 requests per minute. A top-up raises a key's daily cap to 5,000 calls and its concurrent-call ceiling to 10. Enterprise limits are set per contract.
- `502`: The service behind the gateway failed. Nothing is charged; retry later.
- `504`: The service behind the gateway did not answer in time. Nothing is charged; retry later.

## Changes

- **2026-10-29** `03b7688c4c40`: 1 breaking, 2 warning, 4 info
  - the `error_code` response property const value `VALIDATION_ERROR` was removed for the status `422`
  - added the new `FIELD_REJECTED` enum value to the `error_code` response property for the response status `422`
  - added the new `VALIDATION_ERROR` enum value to the `error_code` response property for the response status `422`
  - added the non-success response with the status `429`
  - …3 more
- **2026-10-13** `e8adfc0a3ec6`: 3 info
  - added the optional property `anyOf[subschema #1: POIEnrichResponse]/attributes/anyOf[subschema #1]/additionalProperties/anyOf[subschema #1: EnrichedAttribute]/abstain_reason` to the response with the `200` status
  - added the optional property `anyOf[subschema #1: POIEnrichResponse]/attributes/anyOf[subschema #1]/additionalProperties/anyOf[subschema #2: ResultCell]/abstain_reason` to the response with the `200` status
  - added the optional property `anyOf[subschema #2: DeepResearchResponse]/rows/items/fields/anyOf[subschema #1]/additionalProperties/abstain_reason` to the response with the `200` status
- **2026-10-05** `49a33af13fbc`: 2 info
  - added the new optional request property `attributes/anyOf[subschema #1]/items/anyOf[subschema #2: DeepResearchFieldSpec]/include`
  - added the new optional request property `pois/anyOf[subschema #1]/items/attributes/anyOf[subschema #1]/items/anyOf[subschema #2: DeepResearchFieldSpec]/include`
- …earlier changes not shown

[Operation history](https://skmtc.dev/voygr/apis/voygr-calls-api/changes/v1/enrich/post.md)

---

[API](https://skmtc.dev/voygr/apis/voygr-calls-api.md) - [All operations](https://skmtc.dev/voygr/apis/voygr-calls-api/llms.txt) - [OpenAPI document](https://skmtc.dev/voygr/apis/voygr-calls-api/revisions/7199881ed19e?raw)
