---
title: "Enrich places in bulk"
method: POST
path: "/v1/jobs/enrich"
tags: ["core"]
---

# Enrich places in bulk

`POST /v1/jobs/enrich`

## Request

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

Enrich up to 10,000 places asynchronously using /v1/enrich request bodies. Poll GET /v1/jobs/{batch_id} until completed, then download result.jsonl.gz: one result per input row, in order. A standard attribute the quick extraction leaves empty is also researched, at no extra charge, so rows that need it take minutes rather than seconds. Each row holds what its /v1/enrich call would cost and is charged as that call would be. A value research found counts like any other, and unused credits are released. Failed rows are free. Save batch_id and poll GET /v1/jobs/{batch_id} after acceptance. For an uncertain submission, retry promptly with the same Idempotency-Key and identical body. Replay only applies while the idempotency record is retained and the job is not cancelled. Reusing a key after cancellation or retention expiry can create new work and charges. While replay applies, a different body returns 409 IDEMPOTENCY_CONFLICT. Use a new key for new work.

## Headers

- `Idempotency-Key` string, nullable

## Request body

- EnrichJobSubmit: Submit body for `POST /v1/jobs/enrich`. Each row is the flat `/v1/enrich` body for one place (`EnrichJobRow`): standard attributes, custom research attributes, the reserved research fields and the check flags, with the same validation. Unknown keys are refused.
  - `pois` EnrichJobRow[], required: Inline places to enrich; each is a flat /v1/enrich body
    - `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')
    - `address` string, nullable: Unstructured address string. May also be sent as `freeform_address`.
    - `latitude` number, nullable: Latitude (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 `place_summary`. Does not change billing.
    - `response_format` 'attributes' | 'cell': Flat shape only: attribute representation. A request that also asks for a research field always answers in `cell` form, the one representation both lanes share; a standard-only request honors this field.
    - `pois` UnifiedEnrichPOI[], nullable: Places to enrich (list shape). A single place may be a list of one.
      - `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. A bare string names a standard attribute (opening_hours, phone, website, facebook, instagram, tiktok, x, email, existence, open_closed) or a research field; an object additionally describes a research field. Defaults to the standard set. Billing follows the attribute's lane, not its spelling: standard attributes cost a flat credit per place however many you ask for, research fields cost per place per field.
      - 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

- ValidationJobSubmitResponse: Accepted validation or enrichment job submission.
  - `batch_id` string, required
  - `type` 'validate' | 'enrich', nullable
  - `status` 'queued' | 'running' | 'completed' | 'failed' | 'cancelled', required
  - `created_at` string, required
  - `total_items` integer, required
  - `eta_seconds` integer, required
  - `eta_completion_at` string, required

## Other responses

- `400`: Invalid request. Correct the fields before retrying.
- `401`: Missing or invalid X-API-Key.
- `402`: The available balance cannot cover the request's credit hold. Nothing ran.
- `403`: The key lacks the entitlement this operation requires.
- `409`: IDEMPOTENCY_CONFLICT: this Idempotency-Key was already used with a different body. Use a new key for new work.
- `422`: An attribute asks for something the service does not research. Remove or rephrase the named attributes and resend; nothing is charged.
- `429`: BATCH_CONCURRENCY_LIMIT: the key already has 20 jobs in progress; wait retry_after_seconds (no Retry-After header) or until one finishes. Any other 429 is the rate limit: 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. The job may still have been created: retry promptly with the same Idempotency-Key and body, which returns it if it exists.
- `504`: The service behind the gateway did not answer in time. The job may still have been created: retry promptly with the same Idempotency-Key and body.

## Changes

- **2026-10-29** `03b7688c4c40`: 8 info
  - added the non-success response with the status `401`
  - added the non-success response with the status `402`
  - added the non-success response with the status `403`
  - added the non-success response with the status `409`
  - …4 more
- **2026-10-13** `e8adfc0a3ec6`: 15 info
  - added the new optional request property `pois/items/estimate_only`
  - added the new optional request property `pois/items/max_credits`
  - added the new optional request property `pois/items/pois`
  - added the new optional request property `pois/items/registered_address`
  - …11 more
- **2026-09-16** `892990bd38e1`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/voygr/apis/voygr-calls-api/changes/v1/jobs/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)
