Data Enrichment API: Build Reliable GTM Data Pipelines

A data enrichment API is most useful when it sits inside a controlled GTM data pipeline, not as an unreviewed source of CRM updates. You need clear identifiers, explicit field requests, durable job handling, and a way to distinguish returned data from unresolved data.
What a data enrichment API should do in a GTM pipeline
A data enrichment API should add usable data to a known workflow while preserving the difference between discovery and resolution.
Search discovers people or companies that match an intent. You might search for people by title, seniority, department, location, or employer. You might search for companies by industry, size, location, or founding year. The identity returned by search is itself the delivered unit.
Enrichment works from identifiers you already have. Your pipeline sends a person or company reference and asks for fields to be resolved. This distinction matters because the inputs, billing model, and downstream handling are different.
A practical GTM data pipeline commonly uses both request types:
- Complete CRM records. Resolve a company domain, a company LinkedIn URL, or a person’s LinkedIn URL after a record enters your CRM.
- Enrich form submissions. Use a work email, name plus employer, or company domain to add fields needed for routing.
- Qualify accounts. Resolve company details such as industry, employee count, location, and funding data before assigning an account to a workflow.
- Support internal tools. Give sales operations, support, research, or account teams a controlled way to look up company and person data.
- Build review queues. Enrich records, then send incomplete, conflicting, or failed results to an operator rather than writing them directly into a source system.
For a people enrichment API, the pipeline usually begins with an identifier already present in a lead, contact, event, or application record. For a company enrichment API, it often begins with a domain collected from a form, CRM account, or product sign-up.
Live enrichment also changes how you model freshness. Enrichment is not a licensed static database snapshot. It uses live data from the upstream source, so the same query can legitimately return different data at a later time as published information changes. Store when a field was retrieved. Do not assume an older value remains current merely because it was once returned.
Choose the right request type and identifiers
Choose enrichment when you have a usable identifier, and choose search when you need to find records that match a description.
For people enrichment, send one of these identifiers:
- A LinkedIn URL
- A work email address
- A name plus employer
For company enrichment, send one of these identifiers:
- A company domain
- A company name
- A company LinkedIn URL
Use the strongest identifier available. A company domain is often a practical key for account workflows. A LinkedIn URL can be useful when a person record already includes a profile reference. A name plus employer can work when your inbound system does not have a profile URL or work email.
Do not turn every identifier into a blanket enrichment request. Select fields based on the action your system will take after it receives them.
For example:
- A routing rule may need
seniority,department, andcompany_domain. - An account qualification process may need
industry,employeeCount,employeeRange, andlocation. - A personalization workflow may need
title,company, andheadline. - A review queue may need
linkedin_urlso an operator can inspect a record. - An email outreach workflow may need
email, but only after the record passes your own eligibility rules.
The people field catalogue is closed:
emaillinkedin_urltitlesenioritydepartmentlocationcountrycompanycompany_domainheadline
Company records use the applicable company fields. Company rows can also include firmographics such as industry, employeeCount, employeeRange, foundedYear, logoUrl, revenueAnnualUsd, fundingTotalUsd, latestFundingRound, and monthlyVisits.
Work email deserves separate treatment. Request email only when an address has a defined use in your workflow. It is not part of the default field set, and it is billed per address actually found. The returned work address is found from public sources and checked for deliverability as part of the run. Its status is one of verified, probable, unverified, risky, undeliverable, or unknown.
Do not use email enrichment as a substitute for field design. If a workflow only needs an owner assignment or segment label, title and company data may be enough. Asking for an address without a downstream use adds cost and creates another data-handling obligation.
Ask for the email field by name. It is not in the default set.
Phone enrichment is not supported. There is no phone field on any surface. Common phone aliases are rejected as validation errors rather than silently accepted, so remove the phone field before retrying a request.
Design requests around validation and predictable output
Validate identifiers and requested fields before records enter an enrichment queue.
A data validation API boundary is useful because it prevents predictable failures from consuming pipeline time. Your application should reject or route aside records that do not meet your own minimum input rules before calling the API.
For example, validate that:
- A LinkedIn URL is present when your person-resolution path requires one.
- A name-plus-employer record contains both values.
- A company record has a domain, name, or company LinkedIn URL.
- Requested fields belong to the supported catalogue.
- Your downstream system has a destination for every field you request.
- A record has not already been enriched recently under the same refresh policy.
A closed field catalogue is an important contract. Unsupported fields should fail validation. They should not become empty columns that look like valid output. An empty field can mean several different things: the request was invalid, the entity was not found, the source did not resolve that field, or your write step discarded it. A validation error makes the first case explicit.
Keep your application models aligned with the API schemas. The OpenAPI document is available at /api/v1/openapi, generated from the same schemas that validate requests. Use it as the contract for request validation, response parsing, and typed client generation where appropriate.
That contract should influence your internal data model. Model the response envelope separately from the person or company record. Model item outcomes separately from field values. Do not flatten a response into a CRM object before you know whether the item succeeded and whether the requested fields were actually resolved.
The REST API accepts API key authentication. Keep the key in your server-side integration or secret-management system. Do not expose it in browser code, CSV exports, or client-side automation.
You can use the API documentation as the reference point for your integration and the available field set:
/docsfor API and schema guidance/pricingfor credit allowances and billing details/mcpif an agent needs to call the same enrichment capabilities as tools
Handle inline responses and queued jobs correctly
Your client must handle both inline responses and queued jobs because request size, requested fields, and async preferences affect execution.
A people or company search asking for up to 10 results can return inline. An enrichment request for up to 5 rows can also return inline. That only applies when the request does not ask for email and does not explicitly request asynchronous handling.
Larger requests are queued. Requests that ask for email are queued. Requests that include async: true or Prefer: respond-async are queued as well.
This is the rule that prevents a common integration bug: do not assume every POST creates a job you must poll. A small eligible request can return with populated results immediately. Conversely, do not assume a request with a small input is inline if it requests email.
Every search, enrichment, and job-poll response uses the same top-level envelope:
{
"job": {
"object": "job",
"id": "job_id",
"type": "people_enrich",
"status": "succeeded",
"fields": ["title", "company_domain"],
"requestedCount": 0,
"resultCount": 0,
"creditsUsed": 0,
"error": null,
"createdAt": "timestamp",
"startedAt": "timestamp",
"finishedAt": "timestamp",
"url": "job_url"
},
"results": [
{
"position": 0,
"status": "succeeded",
"data": {
"title": "Example title",
"company_domain": "example.com"
},
"evidence": null,
"error": null
}
],
"page": null,
"estimatedCredits": 0
}
The values above illustrate the shape, not a record you should depend on. Your parser should read job.status, not a top-level status, because no top-level status exists.
The possible job states are:
queuedrunningsucceededemptyfailedcancelled
When a request is queued, results is null until the job reaches a terminal state. Poll GET /api/v1/jobs/{id} and inspect job.status. The poll endpoint returns HTTP 200; the job state tells you whether work is still in progress or has finished.
When results are present, each array item is a wrapper:
{
"position": 0,
"status": "succeeded",
"data": {},
"evidence": null,
"error": null
}
The enriched person or company record is in data. Do not parse the wrapper itself as the entity record.
Item status has its own state model:
pendingrunningsucceedednot_foundfailed
For larger jobs, retrieve results using the opaque, forward-only cursor carried in page. Treat it as an API token, not as an offset or value your code should construct. Persist the job ID and process pages in the order supplied. This keeps your pipeline compatible with pagination behavior without depending on invented cursor fields.
Make retries safe and observable
Use the same Idempotency-Key header when retrying a request so the API can deduplicate that retry.
Network failures produce ambiguity. Your worker may time out after the server accepted the request. A process may crash after sending a request but before persisting the response. A queue consumer may receive the same message again. These are normal failure modes, not edge cases.
Generate an idempotency key when you create the outbound enrichment operation. Persist it with the input record set. If you retry the same operation, send the same header value again:
curl -X POST /api/v1/people/search \
-H "Idempotency-Key: your-stable-operation-key" \
-H "Content-Type: application/json" \
-d '{
"query": "engineering leaders at software companies",
"fields": ["linkedin_url", "title", "company_domain"]
}'
Authenticate the request with your API key as required by your integration setup.
A retry without the same Idempotency-Key is a new request. It creates a separate job, takes a separate hold, and can be charged independently. Do not describe a request as retry-safe unless your code preserves and resends that header.
Make each operation observable enough to investigate later. At a minimum, log:
- Your internal operation ID
- The API job ID
- The idempotency key
- Input identifiers, subject to your own data-handling controls
- Requested fields
- Request creation time
job.status- Each item’s
positionand status - Item-level errors
- The fields delivered in
data - CRM or warehouse write outcomes
The usage ledger is append-only and readable line by line. Use it alongside your own operation logs when reconciling a run. Polling is free, so you can separate execution monitoring from enrichment spending.
Build data-quality controls after the API call
Treat the API response as an outcome stream, not as an automatic database update.
A successful job can contain different item-level outcomes. Your pipeline should route them separately:
succeeded: Parsedata, validate it against your write rules, then update the destination record.not_found: Preserve the input record, record the outcome, and route it for a later refresh or alternate internal process.failed: Capture the error and send the item to your retry or investigation path.pendingorrunning: These belong to an in-progress job path, not a CRM-write path.
Do not write unresolved fields as confirmed blank values. A missing title, company_domain, or email can mean the field was not resolved. It does not prove that the person lacks a title, employer domain, or work address.
Store provenance with every enriched field you write. A practical record includes:
- The source operation and job ID
- The identifier used to resolve the record
- The requested fields
- The item status
- The retrieval timestamp
- The enrichment response retained under your data policy
- A planned refresh time or refresh condition
- The destination system write result
This structure supports review when a CRM user asks where a value came from. It also makes refreshes safer. You can compare a newly returned value with the prior value, apply your conflict rules, and avoid overwriting manually maintained records without review.
Use delivered-data outcomes to reconcile writes. An enrichment row is charged only when at least one requested field was resolved. If the result contains no resolved fields, treat that as an unresolved outcome in your application model. Do not infer successful enrichment from a submitted identifier alone.
Enrichments supports this pattern across the REST API, CSV upload, chat agent, and MCP server, but the core design remains the same: validate inputs, request only useful fields, handle the response envelope correctly, make retries idempotent, and preserve enough context to explain every downstream write.
Frequently asked questions
- What is the difference between search and enrichment in a GTM data pipeline?
- Search discovers people or companies that match an intent, while enrichment resolves fields from identifiers you already hold. They have different inputs, billing models, and downstream handling requirements.
- Which identifiers can be used for people and company enrichment?
- People enrichment can use a LinkedIn URL, work email address, or name plus employer. Company enrichment can use a company domain, company name, or company LinkedIn URL.
- When should I request the email field?
- Request email only when it has a defined downstream use in your workflow. It is not included in the default field set, and a returned work address is checked for deliverability during the run.
- How should an integration handle inline responses and queued jobs?
- Small eligible requests can return populated results inline, while larger requests, email requests, and explicitly asynchronous requests are queued. For queued work, poll the job endpoint and inspect job.status until it reaches a terminal state.
- Why should I use an Idempotency-Key when retrying an API request?
- Reusing the same Idempotency-Key lets the API deduplicate a retry when a network failure or worker interruption makes the original request ambiguous. Retrying without the same key creates a separate request and separate job.
Keep reading
Put this into practice
Enrichments resolves people and companies from a REST API, an MCP server, the chat agent or a CSV upload, checks every email address it finds, and bills you only for the data that comes back.
Start enriching for free
