For the complete documentation index, see llms.txt. This page is also available as Markdown.

API reference

Full request and response specification for the Contact Enrichment API.

Base URL

https://api.goodfit.io/contacts

Authentication

Send your API key in the Authorization header on every request:

Authorization: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

This is the same key used for the company enrichment API β€” your account manager will provide it. Keys are secrets: never expose one in client-side code.

Status
Meaning

401

Header missing, or not a valid key format.

403

Key not recognised, revoked, or lacking enrichment access.

Rate limits

20 requests per second, shared across all API keys belonging to your account. Exceeding it returns 429 with a Retry-After header giving the number of seconds to wait:

HTTP/1.1 429
Retry-After: 1

{ "error": "Rate limit exceeded. Retry after 1s" }

Honour Retry-After and retry β€” a 429 is never a permanent failure.

POST /enrichment β€” submit a contact

At least one of identity.linkedInUrl or identity.workEmail is required. Everything else is optional. Matching on name alone is not supported.

Field
Type
Default
Notes

identity.linkedInUrl

string

β€”

A LinkedIn profile URL. Any common form is accepted.

identity.workEmail

string

β€”

Must be a company address; personal email domains are rejected.

identity.companyName

string

β€”

Optional hint, used only to identify the right employment.

identity.companyDomain

string

β€”

Optional hint, reduced to a registrable domain. More reliable than companyName.

identity.selectedCompanyId

string

β€”

The employment you picked from a MULTIPLE_EMPLOYMENTS_FOUND response.

externalIds.salesforce

string

β€”

15 or 18 character ID. 15 character IDs are expanded to 18.

externalIds.hubspot

string | number

β€”

Numeric ID.

enrichPhoneNumber

boolean

false

Request a mobile phone number as well as an email.

syncToCRM

boolean

false

Write the enriched contact back to your connected CRM.

Both flags default to false, so a request that omits them enriches the email only and leaves your CRM untouched. You need a connected integration for each externalIds provider you supply.

A successful submission returns 201:

Submitting the same contact twice is safe. If an identical request is still in progress you get 200 with the existing requestId rather than a second request, so a retrying integration never duplicates work or spends credits twice. Poll the returned requestId either way.

Changing enrichPhoneNumber or syncToCRM makes it a different request, so asking for a phone number after an email-only request is always honoured.

Status
Cause

400

Body is not valid JSON, neither identifier was supplied, or a field is invalid.

402

Out of credits β€” nothing was queued or charged.

429

Rate limit exceeded.

A 402 is returned before any work starts, so retrying after a top-up is safe. Asking for a phone number requires both contact and phone credits. A 400 names the problem:

GET /enrichment/{requestId} β€” read a request

Status
Meaning

QUEUED

Accepted, not started yet.

IN_PROGRESS

Being enriched.

COMPLETE

Finished β€” result is present.

ERROR

Failed β€” error explains why. Safe to resubmit.

result appears only when COMPLETE, and error only when ERROR.

A few fields are worth calling out:

  • inputs echoes back your identity in the canonical form used for matching, so you can confirm what was actually looked up.

  • startDate is the start of the employment, as YYYY-MM-DD.

  • isFormerEmployment is true when the person has left the company you asked about. We still resolve them, so you can see who they were and act on the change.

  • addedToDataset is true when this request added the record to your dataset, and false when it was already there.

  • company.addedToDataset being true means the company was new to you, so we added it and charged one enrichment credit. A newly added company appears immediately with core firmographics only; scores, segments and the rest of its data arrive with your next daily sync.

Note β€” personas and markets are part of the contract but not yet populated β€” persona and market matching for this API are still being built. They are empty rather than NOT_FOUND, so you can tell "not built yet" from "we looked and found nothing".

Status
Cause

400

requestId is missing or not a UUID.

404

No such request under your account.

429

Rate limit exceeded.

Email and phone values

Every resolved contact is enriched for a work email. A mobile number is looked up only when you set enrichPhoneNumber. Three sentinel values tell the cases apart:

Value
Meaning

NOT_FOUND

Searched, nothing available.

NOT_REQUESTED

Not asked for (mobilePhone only).

SKIPPED_EX_EMPLOYEE

Skipped, the resolved employment is one they have left (workEmail only).

Enrichment is best effort. If a lookup fails we still return the resolved contact, with NOT_FOUND in place of the value β€” the request doesn't fail.

Returned emails carry the same verification statuses as every other email GoodFit supplies β€” see Email and Phone enrichment.

When we couldn't resolve someone

found is false and reason says why. COMPLETE still means we finished β€” only ERROR means something went wrong on our side.

reason

Meaning

PERSON_NOT_FOUND

No person matches the identity you gave.

MULTIPLE_EMPLOYMENTS_FOUND

We found the person but they hold several roles.

NO_COMPANY_MATCH

We found the person but couldn't tie them to a company we can serve.

NOT_ENOUGH_INFORMATION

The identity was too thin to search on.

COMPANY_HAS_NO_DOMAIN

Their employer has no website domain β€” see below.

COMPANY_RESOLUTION_CONFLICT

The employer we found conflicts with the CRM record you supplied.

CONTACT_RESOLUTION_CONFLICT

The CRM record you supplied belongs to a different person.

You aren't charged when we can't deliver a contact.

MULTIPLE_EMPLOYMENTS_FOUND is the one reason you can act on directly β€” see Handling multiple employments.

The two conflict reasons mean your request and your CRM disagree about who or where this person is. Rather than guess, we refuse β€” it's worth resolving in the CRM before retrying.

COMPANY_HAS_NO_DOMAIN

We identified the person, but their employer has no website domain β€” commonly a LinkedIn placeholder page (for example "Confidential Government") or a subsidiary with no site of its own. Companies are keyed on domain in your dataset, so this one can't be added and the contact couldn't be attached to it.

Retrying won't help. If the person in fact works at a company you can name, supply that companyDomain.

Syncing to your CRM

Set syncToCRM: true and we write the enriched contact to every connected CRM that has contact sync enabled, normally within five minutes of submission. The result carries syncedToCRM and, under externalIds, the record ID in each CRM.

How we choose the record to write to:

  • Send an externalIds value we recognise and we update that exact record.

  • Send one we've never seen and we adopt it and update it, rather than creating a second contact.

  • Send none and we look for an existing contact on the work email β€” using whichever CRM field you have mapped to email in Data Points β€” and only create a new record when there's no match.

Two cases are deliberately refused rather than guessed at, and appear on your Connector Logs page:

  • the ID you sent points at a different record from the one we already have this person linked to;

  • several contacts in your CRM share the email, so there's no single right record to update.

Note β€” We search your CRM by mapped email field only

Last updated