Guides

Record Lookup

Full records for companies, people, and documents you already know — batch lookups with misses reported rather than omitted.

Search finds; Record Lookup enriches. When you already know which records you want, the entities routes return the full record for each one, in batch, with every miss reported honestly instead of silently dropped. The identifiers might be:

  • a list of domains from your CRM,
  • the domains and LinkedIn URLs on a search you just ran,
  • LinkedIn URLs from a signup form.

Use Record Lookup when the identifiers are already in hand. If you are still discovering which records exist, that is Search. If you want more companies like ones you have, that is Similar Companies.

Use cases#

Three situations cover most uses of these routes.

Enriching a CRM list by domain

Your CRM holds domains; your team needs firmographics (industry, size, funding) on every record. You get one result per domain, in order: fields to write back, and misses to route to a manual queue.

Hydrating search hits

A search gave you lean rows; the next step needs the complete record. You get full records for every resolvable hit: an export-ready dataset in two calls.

Auditing what your data covers

You want to know how much of your account list the index can serve. You get a known-or-not answer per account, so the hit rate shows what an enrichment integration will deliver before you build it.

One call, up to 50 domains with by, misses included:

curl -X POST https://api.sproutsdata.ai/v1/entities/companies \
  -H "X-API-Key: $SPROUTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "ids": ["adyen.com", "checkout.com"],
        "by": "domain",
        "fields": ["account_name", "industry", "employee_count"]
      }'

Outcome: one result per domain you sent, in order. found: true rows carry the requested fields under matches; found: false rows mark what the index doesn't hold. Write the fields back to your CRM and route the misses to a manual queue.

How it works#

The entities routes return full records by id. There is one route per category (nine paths with plural slugs) rather than a category parameter in the body. The reference has a page for companies, persons and the seven document routes:

POST /v1/entities/companies          POST /v1/entities/market-reports
POST /v1/entities/persons            POST /v1/entities/fundings
POST /v1/entities/patents            POST /v1/entities/academic-reports
POST /v1/entities/news               POST /v1/entities/job-openings
POST /v1/entities/trends

All nine require the dsp.entities.read scope and are metered under the dsp.entities action.

Request

FieldNotes
idsRequired, 1 to the route's cap (below). Order and duplicates preserved in the response
fieldsOptional list of field names. Omit for the full record
byHow to read ids. companies/persons resolve raw identifiers; four document routes choose between id and name (below)

Batch caps

The ceilings differ by an order of magnitude. The reason is response size, not caution: a company record is a few kilobytes; a trend document averages 4.5 MB.

RouteMax ids per call
companies, persons1,000 — 50 when by is set
news, market-reports, fundings, job-openings100 with by: "id" — 50 with by: "name", which is the default
patents, trends, academic-reports100

The by cap is tighter on companies and persons because each raw value has to be classified before it can be looked up, and on the four named document routes because one name can resolve to several documents. Exceeding a cap is a 422 before anything is charged.

by — resolving raw identifiers

What by accepts depends on the route.

Without by, ids are internal record ids. The id on a search hit is not one — it is the company's domain or an opaque 16-hex hash — so look a hit up by its own fields: a company hit with by: "domain" and its domain, a person hit with by: "li_url" and its linkedin_url. With by, ids are raw values:

byMeaningAvailable on
autoClassify each value independently (the same detection /v1/similar uses on anchors)companies, persons
domainEvery value is a company domaincompanies only — a person has no domain
li_urlEvery value is a LinkedIn URLcompanies, persons
nameEvery value is a name (full-text match, least reliable). Returns only the best-ranked match, so matches holds one entrycompanies, persons

Response

The standard envelope, where each result is an EntityHit:

{
  "results": [
    {
      "id": "adyen.com",
      "category": "company",
      "found": true,
      "matches": [{ "account_name": "Adyen", "industry": "Finance", "employee_count": 4000 }]
    },
    {
      "id": "definitely-not-a-real-company.example",
      "category": "company",
      "found": false,
      "matches": []
    }
  ],
  "total": 2,
  "total_is_estimate": false,
  "has_more": false,
  "next_cursor": null,
  "resolved_search": null,
  "degradation": [],
  "meta": { "request_id": "bd22794d0c5c06f1d3e7045e3c0e32ab", "environment": "live", "as_of": "2026-08-13T10:00:00Z", "credential": "api_key" }
}

Each hit:

FieldMeaning
idThe id you asked with, echoed back
categoryAlways the route's category
foundWhether the record exists. Misses come back as found: false; they are never omitted
matchesThe records, always a list, and [] on a miss. Typed for companies and persons; the document categories return each vertical's own field vocabulary

An id lookup resolves to exactly one entry, and so does a company or person lookup by any by, so matches[0] is the record.

Record Lookup responses are never paginated: has_more is always false, next_cursor always null, and total is the exact count of hits returned (found and not-found together).

Search hits chain into record lookups for companies and persons. A hit's id is not a lookup id, so send one of the hit's own fields instead:

  • A company hit chains via POST /v1/entities/companies with by: "domain" and the hit's domain.
  • A person hit chains via POST /v1/entities/persons with by: "li_url" and the hit's linkedin_url.
  • A hit with neither field can't be chained. When a company has no domain its id is a 16-hex hash, which is one-way, and the entity routes don't accept it.
import os, requests

BASE = "https://api.sproutsdata.ai"
headers = {"X-API-Key": os.environ["SPROUTS_API_KEY"]}

# Companies: look each hit up by its domain.
companies = requests.post(f"{BASE}/v1/search", headers=headers, json={
    "query": "payment orchestration platforms", "category": "company", "limit": 10,
}).json()

domains = [h["domain"] for h in companies["results"] if h.get("domain")][:50]

records = requests.post(f"{BASE}/v1/entities/companies", headers=headers, json={
    "ids": domains, "by": "domain",
    "fields": ["account_name", "industry", "employee_count", "total_funding"],
}).json()

for hit in records["results"]:
    if hit["found"]:
        record = hit["matches"][0]
        print(hit["id"], record.get("account_name"))

# People: look each hit up by its LinkedIn URL.
people = requests.post(f"{BASE}/v1/search", headers=headers, json={
    "category": "person",
    "filters": {"title": {"include": ["head of payments"]}},
    "limit": 10,
}).json()

urls = [h["linkedin_url"] for h in people["results"] if h.get("linkedin_url")][:50]

records = requests.post(f"{BASE}/v1/entities/persons", headers=headers, json={
    "ids": urls, "by": "li_url",
    "fields": ["contact_name", "title", "employment_history"],
}).json()

Errors

The entities routes share one error surface:

StatusWhen
401 / 402 / 403Charged and scoped
422Validation, including the batch caps
429Reserved for rate limits. There is no per-key rate limit today, but if you get a 429, wait for Retry-After and retry
503 / 504The retrieval engine is down (not charged)

There is no 501 here. An unwired vertical 501s on search; a lookup by id either finds the record or reports the miss.

Best practices#

  • Know the batch caps before you batch. 1,000 internal ids for companies/persons, 50 when by is set; 100 on document routes, 50 for a by: "name" lookup. Exceeding a cap is a 422 before anything is charged.
  • Treat found: false as data, not noise. Misses are free and reported, so a list of false rows is a coverage report over your own data.
  • Request only the fields you need. Full records are the default and can be large (a trend document averages 4.5 MB); a field list keeps batches fast and cheap to move.
  • Read matches as a list. matches[0] is the record for an id lookup; a by: "name" lookup on the four named document routes can return several, and taking the first is a silent choice you did not make.
  • Read attribution[].as_of when freshness matters. It is the per-corpus answer to "how current is this employee count?", stated once for the response rather than per field.

More on Record Lookup#