Skip to content

Research custom attributes for places

POST /v1/deep-research

https://api.voygr.tech/v1/deep-research

Open in playground

Overview

Research shared attributes for a batch of places, returning values, confidence tiers, and statuses. Unavailable values are null; individual failures return error rows without failing the batch. If research is unavailable, the response is still 200 with abstained rows and meta.stopped_reason=agent_unavailable; nothing is billed. Ordinary researched fields, including abstentions, use the standard per-field rate. Requires a key permitted to use research.

Verifying the name and address you sent (verify_identity)

Set verify_identity: true when your records may be close but not exact - an abbreviation, an initialism, a store number, or a slightly stale spelling. We settle what the business is actually called and where it actually is BEFORE researching anything else, and every attribute you asked for is then researched against the verified values rather than the ones you sent.

Each row carries an identity block giving, for the name and the address, 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 "&", "St" vs "Street" and a dropped or added Inc/Ltd/LLC all come back confirmed with your own string.

This is not a postal validation - a confirmed address matches what the business publishes about itself, not a postal database - and it makes no claim about whether the business is still open.

Costs a flat 1 extra credit per row, independent of how many attributes you requested.

Owner discovery (owner_person, owner_org)

Request these as ordinary field specs. owner_person is the individual owner, co-owners or franchisee; multiple owners are comma-separated in one string. owner_org is the parent organisation when no individual owner exists. They are mutually exclusive per place: when one is established, the other abstains. "no individual owner" is a legitimate answer.

Results may be confirmed (high or medium confidence), estimated (low confidence, treat as a lead), or unconfirmed (value is null). Unavailable ownership is a normal result; do not interpret an abstention as a claim that no owner exists. Owner fields use the same billing rules as other fields on this endpoint.

Authentication

  • API key in header X-API-Key: Your customer API key (pk_live_...), sent on every request. Self-serve at https://api.voygr.tech/checkout.

Request body

required

application/json

Schema: DeepResearchRequest

Responses

200

Successful Response

application/json

Schema: DeepResearchResponse

401

Missing or invalid X-API-Key.

application/json

X-API-Key header absent

{
  "detail": {
    "error": "API key required"
  }
}

X-API-Key doesn't resolve to an active key

{
  "detail": {
    "error": "invalid API key"
  }
}

402

Not enough credits, or the estimate exceeds the caller's ceiling.

application/json

Insufficient available credits

{
  "detail": {
    "error": "quota_exceeded",
    "needed_credits": 120,
    "checkout_url": "/checkout/buy"
  }
}

Up-front estimate exceeds request maxcredits (nothing charged)

{
  "detail": {
    "success": false,
    "error": "max_credits_exceeded",
    "detail": "Estimated cost 120 credits exceeds max_credits 100; nothing was run or charged.",
    "estimated_credits": 120
  }
}

403

This key is not permitted to use research.

application/json

notentitled

{
  "detail": {
    "error": "endpoint not permitted for this key"
  }
}

422

An attribute asks for something the service does not research. Remove or rephrase the named attributes and resend; nothing is charged.

application/json

Schema: object

Fields

FieldTypeRequiredDescription and constraints
successbooleanrequired
errorstringrequired
error_codestringrequired
rejected_fieldsobject mapping names to stringrequiredAttribute name to the reason it was refused.
request_idstring | nulloptional
validation_timestampstringoptional

fieldrejected

{
  "success": false,
  "error": "field_rejected_by_policy",
  "error_code": "FIELD_REJECTED",
  "rejected_fields": {
    "alarm_code": "harmful"
  },
  "request_id": "example-request-id",
  "validation_timestamp": "2026-09-16T12:00:00Z"
}

400

Invalid request. Correct the fields before retrying.

application/json

Schema: object | object

Option 1 fields

FieldTypeRequiredDescription and constraints
successbooleanrequiredconstant False
errorstringrequired
error_codestringrequiredconstant VALIDATION_ERROR
request_idstring | nullrequired
validation_timestampstringrequired
detailarray of objectrequired

Item fields

FieldTypeRequiredDescription and constraints
fieldstringrequired
messagestringrequired
typestringrequired

Option 2 fields

FieldTypeRequiredDescription and constraints
detailobjectrequiredGateway rejection, for example an invalid Idempotency-Key header.

Fields

FieldTypeRequiredDescription and constraints
errorstringrequired
error_codestringoptional

One of

  • Option 1: requires success, error, error_code, request_id, validation_timestamp, detail
  • Option 2: requires detail

invalidinput

{
  "success": false,
  "error": "Invalid input provided",
  "error_code": "VALIDATION_ERROR",
  "request_id": "example-request-id",
  "validation_timestamp": "2026-09-16T12:00:00Z",
  "detail": [
    {
      "field": "pois.0.poi_name",
      "message": "Field required",
      "type": "missing"
    }
  ]
}

Schemas

DeepResearchRequest

Type: object

Research a shared list of attributes for a batch of places. A single place is a list of one. Individual row failures do not fail the whole request.

FieldTypeRequiredDescription and constraints
poisarray of DeepResearchPOIrequiredPlaces to enrich (batch). Single-POI = a list of one.
1 to 10 items
attributesarray of DeepResearchFieldSpecrequiredAttributes to research for every place. Unsupported requests can return unavailable results.
1 to 20 items
validate_existencebooleanoptionalAlso validate whether each place exists and return an existence attribute per row. Default false.
default False
verify_identitybooleanoptionalIf true, verify the name and address you supplied before researching anything else, and research every requested attribute against the verified values rather than the ones you sent. Each row returns an identity block giving, for name and address, the value we settled on, a verdict (confirmed | corrected | unresolved) and your original. Costs a flat 1 extra credit per row, independent of attribute count. Default false.
default False
estimate_onlybooleanoptionalEstimate without research or charges. Returns meta.estimated_credits for all requested rows and an empty rows list. Default false.
default False
sampleinteger (min 1.0) | nulloptionalCost preview: process only the first N POIs (a cheap subset) and bill only those N rows. Omit to process the whole list. Precedence: estimate_only (no run) > sample (subset) > full.
max_creditsinteger (min 0.0) | nulloptionalPer-request credit ceiling. If the up-front estimate (for the rows that would run - the sampled subset when sample is set, else all rows) exceeds this, the request is REJECTED with 402 before running and nothing is charged. The estimate is still reported.

Example

{
  "pois": [
    {
      "id": "bakery-001",
      "poi_name": "Example Bakery",
      "address": "123 Example Street",
      "country": "US",
      "latitude": 37.7749,
      "longitude": -122.4194
    },
    {
      "id": "cafe-002",
      "poi_name": "Example Cafe",
      "address": "456 Sample Avenue",
      "country": "US",
      "latitude": 37.7755,
      "longitude": -122.4183
    }
  ],
  "attributes": [
    {
      "name": "outdoor_seating",
      "description": "Does the place have outdoor seating?",
      "value_type": "bool"
    },
    {
      "name": "price_band",
      "description": "Typical price level",
      "value_type": "enum",
      "allowed_values": [
        "cheap",
        "mid",
        "expensive"
      ]
    }
  ]
}

DeepResearchResponse

Type: object

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, allowing other rows to succeed.

FieldTypeRequiredDescription and constraints
successbooleanrequiredWhether the request ran (individual rows/attributes may still be null or errored)
rowsarray of DeepResearchRowoptionalPer-POI results (matrix), in request order
metaDeepResearchMeta | nulloptionalRequest-level aggregate bookkeeping (see DeepResearchMeta)
enrichment_timestampstring | nulloptionalUTC timestamp (ISO 8601)
errorstring | nulloptionalError message if success=false
error_codestring | nulloptionalError category code
request_idstring | nulloptionalRequest ID for tracing

Example 1

{
  "success": true,
  "rows": [
    {
      "id": "poi-12345",
      "poi_name": "Zuni Cafe",
      "status": "enriched",
      "outcome": "enriched",
      "fields": {
        "phone": {
          "value": "+14155551234",
          "confidence": "high",
          "status": "confirmed"
        },
        "outdoor_seating": {
          "value": true,
          "confidence": "medium",
          "status": "confirmed"
        }
      }
    },
    {
      "id": "poi-67890",
      "poi_name": "Tartine Bakery",
      "status": "abstain",
      "outcome": "abstain",
      "fields": {
        "outdoor_seating": {
          "status": "unconfirmed"
        }
      }
    }
  ],
  "meta": {
    "stopped_reason": "completed"
  },
  "enrichment_timestamp": "2026-06-29T12:34:56Z",
  "request_id": "ad33009a-fdfe-4e25-9650-4a49099c3d4a"
}

DeepResearchPOI

Type: object

One place to research. Provide structured address fields, an unstructured address (also accepted as freeform_address), or coordinates. The attributes to research are shared across the request.

FieldTypeRequiredDescription and constraints
idstring (max length 100) | nulloptionalClient-provided identifier (keys the response row)
poi_namestringrequiredPlace name
length 1 to 200
streetstring | nulloptionalStreet address
citystring | nulloptionalCity
regionstring | nulloptionalState/province
postcodestring | nulloptionalPostal/ZIP code
countrystring | nulloptional2-letter ISO country code (e.g. US) locating the place.
addressstring | nulloptionalUnstructured address string. May also be sent as freeform_address (input-only alias).
latitudenumber (-90.0 to 90.0) | nulloptionalLatitude in decimal degrees (WGS84).
longitudenumber (-180.0 to 180.0) | nulloptionalLongitude in decimal degrees (WGS84). Provide with latitude.

DeepResearchFieldSpec

Type: object

One requested research field. The name identifies the field; description defines the requested information and is treated as untrusted data, never instructions.

FieldTypeRequiredDescription and constraints
namestringrequiredField identifier, e.g. wheelchair_accessible. owner_person identifies individual owners or franchisees; owner_org identifies a parent organisation when there is no individual owner. These owner fields are mutually exclusive per place.
length 1 to 100
descriptionstring (max length 500) | nulloptionalNatural-language definition of the field (treated as untrusted data)
value_typestring | nulloptionalExpected value type hint: bool | enum | string | number
allowed_valuesarray of string | nulloptionalFor enum fields, the permitted values

DeepResearchRow

Type: object

One POI's result in a batch: the researched attributes + a row-level status.

A row NEVER fails the request: on a per-POI error it carries status="error" + error, so callers get partials for the rest.

FieldTypeRequiredDescription and constraints
idstring | nulloptionalClient-provided POI id (pass-through)
poi_namestring | nulloptionalPlace name (echoed from the request)
identityResolvedIdentity | nulloptionalVerified name and address - present only when verify_identity=true. Every other attribute on this row was researched against these values.
statusstringrequiredRow outcome: enriched | abstain | error | cost_capped
outcomestring (one of enriched, abstain) | nulloptional'enriched' if any attribute was confirmed, else 'abstain' (null on error)
fieldsobject mapping names to ResultCell | nulloptionalAttribute name -> result (null when the row errored)
errorstring | nulloptionalSafe error message; set ONLY when the row failed

DeepResearchMeta

Type: object

Request outcome and estimated credit cost.

FieldTypeRequiredDescription and constraints
stopped_reasonstring | nulloptionalWhy the run stopped: completed | cost_capped | agent_unavailable | estimate_only
estimated_creditsinteger | nulloptionalCost estimate for all requested rows before sampling or capping. Present on every response, including estimate_only requests.

ResolvedIdentity

Type: object

Per-row result of verify_identity. Present only when it was requested.

FieldTypeRequiredDescription and constraints
nameResolvedIdentityFieldrequiredThe business name we settled on
addressResolvedIdentityFieldrequiredThe address we settled on

ResultCell

Type: object

One returned value, confidence tier, and status, returned by enrichment in cell format. Unavailable values are null.

FieldTypeRequiredDescription and constraints
valueany | nulloptionalThe field value; null when unconfirmed
confidencestring | nulloptionalTrust tier: high | medium | low; null when value is null
statusstringrequiredconfirmed | estimated | unconfirmed | cost_capped | rejected. Estimated values have low confidence; filter by status if you need confirmed values only.

ResolvedIdentityField

Type: object

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

  • the corrected string when we corrected it, and the caller's own string when we confirmed it or could not establish it. original is always what the caller sent, so a consumer can branch on the verdict instead of diffing strings (is "St." -> "Street" a change worth persisting? they can't tell).

Deliberately NO confidence, evidence URLs, source excerpts, candidates or method: those are computed to DECIDE the verdict and stay internal. The verdict is the entire public surface.

FieldTypeRequiredDescription and constraints
valuestring | nulloptionalThe value we settled on and researched against
verdictstringrequiredconfirmed = yours was already right; corrected = we replaced it; unresolved = we could not establish it and left yours untouched
one of confirmed, corrected, unresolved
originalstring | nulloptionalThe value you supplied, echoed back unchanged