Skip to content
KnockDevelopers

Get results

Poll an enrichment request until it's final, match items to your records, and read the person and company result schemas.

Chat with a Developer

Read an enrichment request with the enrichRequestId you got when you submitted it. The response tells you the request's overall status and, for each item, its own status and result.

Endpoint
GET https://app.knock-ai.com/v1/enrichment-requests/{enrichRequestId}

Reading a request never starts new enrichment, so polling costs nothing beyond the items you submitted (see Credits and side effects). Each read counts toward your rate limit.

Read a request

Terminal
curl https://app.knock-ai.com/v1/enrichment-requests/7d0f4f1e-2c3b-4a8e-9a51-0e6f3b8c2d14 \
  -H "x-api-key: $KNOCK_API_KEY"
200 OK
{
  "enrichRequestId": "7d0f4f1e-2c3b-4a8e-9a51-0e6f3b8c2d14",
  "type": "people",
  "status": "in_progress",
  "createdAt": "2026-10-20T09:15:02.418Z",
  "items": [
    {
      "input": "jane@acme.com",
      "status": "completed",
      "result": {
        "firstName": "Jane",
        "lastName": "Doe",
        "title": "VP Sales",
        "seniority": "VP",
        "profession": "Sales",
        "linkedinUrl": "https://linkedin.com/in/janedoe",
        "location": "Austin, Texas, United States",
        "photoUrl": null,
        "currentPositionStartDate": "2024-03",
        "company": null
      }
    },
    { "input": "bob@globex.com", "status": "pending", "result": null },
    { "input": "nobody@initech.com", "status": "not_found", "result": null }
  ]
}

Response fields

FieldTypeDescription
enrichRequestIdstring (UUID)The ID of the enrichment request.
typepeople or companiesWhich kind of items the request holds. It decides the shape of each result.
statusstringpending, in_progress, completed, partially_completed or failed. Derived from the items every time you read the request; see Request statuses.
createdAtstring (ISO 8601, UTC)When the request was created. The 24-hour deadline counts from this time.
itemsobject[]One entry per distinct input (1 to 100), in the order you submitted them.
items[].inputstringPeople: the email, trimmed and lowercased. Companies: the domain or URL exactly as you sent it.
items[].statusstringpending, completed, not_found or failed. The last three are final; see Item statuses.
items[].resultobject or nullA person or company when the item is completed; null for every other status.

Every key is always present, including the null ones. A completed item's result can still be null in the rare case that Knock finished the item but holds no data for it, so check result before using it.

Match items to your records

Items come back in the order you submitted them, but repeated inputs are collapsed into one item, so positions can shift. Match on input instead:

  • People: input is the email trimmed and lowercased. Normalize your own emails the same way before looking them up: email.trim().toLowerCase().
  • Companies: input is the exact string you sent. For repeated companies it's the first occurrence; later duplicates (for example acme.com after https://www.acme.com) map to that same item.
match-results.mjs
const byEmail = new Map(request.items.map((item) => [item.input, item]));

for (const lead of leads) {
  const item = byEmail.get(lead.email.trim().toLowerCase());
  if (item?.status === 'completed' && item.result) {
    saveEnrichment(lead.id, item.result);
  }
}

Poll until the request is final

A request is final once its status is completed, partially_completed or failed. Until then, read it again with a growing delay:

  1. Start short. Some items are final within seconds, so make the first read after about 5 seconds.
  2. Back off. Double the delay after every read that isn't final, up to a cap of about 2 minutes.
  3. Honor 429. If you're rate limited, wait the number of seconds in the Retry-After header before the next read.
  4. Retry temporary errors. 5xx responses (including 502 external-api.enrichment.unavailable) and brief 401s are never final answers. Back off and read again.
  5. Stop at the deadline. An item still pending 24 hours after createdAt is settled on the first read after that point (or a later one, if Knock can't check it then). If a request is somehow still not final an hour after the deadline, stop polling and handle the remaining pending items as failed, or alert someone.
wait-for-enrichment.mjs
const BASE_URL = 'https://app.knock-ai.com';
const FINAL_STATUSES = new Set(['completed', 'partially_completed', 'failed']);
const DEADLINE_MS = 24 * 60 * 60 * 1000; // items still pending after 24 h settle on the next read
const GRACE_MS = 60 * 60 * 1000; // keep reading for 1 h after the deadline
const FIRST_DELAY_MS = 5_000;
const MAX_DELAY_MS = 2 * 60_000;
const MAX_AUTH_RETRIES = 5;

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

/** Reads the request until it's final. Returns the last response either way. */
export async function waitForEnrichment(enrichRequestId) {
  let delay = FIRST_DELAY_MS;
  let authFailures = 0;
  const backOff = () => {
    delay = Math.min(delay * 2, MAX_DELAY_MS);
  };

  for (;;) {
    await sleep(delay);

    let res;
    try {
      res = await fetch(`${BASE_URL}/v1/enrichment-requests/${enrichRequestId}`, {
        headers: { 'x-api-key': process.env.KNOCK_API_KEY },
        signal: AbortSignal.timeout(30_000),
      });
    } catch {
      backOff(); // network error or timeout
      continue;
    }

    if (res.status === 429) {
      delay = Number(res.headers.get('retry-after') ?? 60) * 1000;
      continue;
    }
    if (res.status === 401 && authFailures < MAX_AUTH_RETRIES) {
      authFailures += 1; // Knock fails closed: a brief 401 can be temporary
      backOff();
      continue;
    }
    if (res.status >= 500) {
      backOff(); // temporary, never a final answer
      continue;
    }
    if (!res.ok) {
      // 400, 403, 404 or a persistent 401: reading again won't help.
      const error = await res.json().catch(() => ({}));
      throw new Error(`Reading ${enrichRequestId} failed: ${res.status} ${error.code ?? ''}`);
    }

    authFailures = 0;
    const request = await res.json();
    if (FINAL_STATUSES.has(request.status)) return request;

    const age = Date.now() - Date.parse(request.createdAt);
    if (age > DEADLINE_MS + GRACE_MS) {
      // Still not final well past the deadline: give up and treat pending items as failed.
      return request;
    }
    backOff();
  }
}

Poll from a durable job

A request can take up to 24 hours, which is longer than most processes live. In production, store each enrichRequestId with its createdAt and poll from a scheduled job or queue worker, so a restart doesn't lose track of requests in flight.

Budget your reads. All endpoints share one rate-limit bucket per account (600 requests per minute by default). At a 2-minute cap, each request in flight costs about one read every 2 minutes, so hundreds of requests can be polled at once with plenty of room for new submissions. If you have more in flight, raise the cap or poll requests in rotation.

Person result

result for a completed item in a people request. Every key is always present.

FieldTypeDescription
firstNamestring or nullFirst name.
lastNamestring or nullLast name.
titlestring or nullCurrent job title, for example VP Sales.
senioritystring or nullSeniority level, for example VP.
professionstring or nullProfessional area, for example Sales.
linkedinUrlstringLinkedIn profile URL. Always set: Knock only returns a person once they're matched to a LinkedIn profile.
locationstring or nullWhere the person is based, for example Austin, Texas, United States.
photoUrlstring or nullProfile photo URL.
currentPositionStartDatestring or nullWhen the person started their current role. Precision varies, for example 2024-03 or 2024-03-01.
companyCompany or nullThe person's current company, with every field of a company result. null while the company isn't resolved yet.

Company result

result for a completed item in a companies request, and the company of a person result. Every key is always present: text fields Knock has no data for are null, and list fields are empty arrays. Where a text field usually holds one of a known set of values, the typical values are listed, but treat them as free-form strings.

FieldTypeDescription
namestring or nullCompany name.
domainstring or nullPrimary domain, for example acme.com.
logoUrlstring or nullLogo URL.
descriptionstring or nullShort description of what the company does.
industrystring or nullIndustry in upper snake case, for example COMPUTER_SOFTWARE.
sizestring or nullEmployee range. Typically 0-10, 11-50, 51-100, 101-200, 201-300, 301-400, 401-500, 501-600, 601-700, 701-800, 801-900, 901-1000, 1001-5000, 5001-10000 or 10000+.
employeesstring or nullEmployee count, as a string. For example 120.
foundedDatestring or nullWhen the company was founded, for example 2015.
totalFundingstring or nullTotal funding raised, for example $22M.
lastFundingstring or nullType of the latest funding round, for example seed, series_a, private_equity or undisclosed.
annualRevenuestring or nullRevenue range. Typically $0-$1m, $1m-$10m, $10m-$25m, $25m-$50m, $50m-$100m, $100m-$250m, $250m-$500m, $500m-$1b, $1b-$10b or $10b+.
ownershipstring or nullOwnership type. Typically Private, Public, Acquired/Merged, PARTNERSHIP, NON-PROFIT, government or Other.
headquartersstring or nullHeadquarters location.
countrystring or nullCountry of the headquarters: a country name or a two-letter ISO 3166-1 code, depending on the source (for example US).
regionstring or nullSales region. Typically North America, South America, EMEA, APAC, Japan or Greater China.
statestring or nullState or province, for example California.
competitorsstring or nullTop competitors, as free text.
techStackobject[]Technologies the company uses.
techStack[].namestringTechnology name, for example Salesforce.
techStack[].categorystringTechnology category, for example CRM.
teamSizeobject[]Headcount by department.
teamSize[].departmentstringDepartment name, for example Sales.
teamSize[].headcountnumberPeople in that department.
fundingRoundsobject[]Funding history.
fundingRounds[].typestringRound type, for example series_a.
fundingRounds[].amountMillionsnumber or nullAmount raised, in millions of currency.
fundingRounds[].currencystringCurrency of the amount, for example USD.
fundingRounds[].datestring or nullWhen the round closed.
fundingRounds[].investorsstring[]Investor names.
accountIntelobject or nullHow the company goes to market. null when unknown.
accountIntel.targetPersonastring[]Who the company sells to.
accountIntel.segmentsstring[]Customer segments it serves.
accountIntel.verticalsstring[]Customer verticals it serves.
accountIntel.spacesstring[]Market spaces it competes in.
accountIntel.productTypesstring[]Kinds of products it sells.
accountIntel.salesMotionsstring[]How it sells.
decisionMakersobject[]People at the company who match the decision-maker roles set up in your Knock account.
decisionMakers[].namestringFull name.
decisionMakers[].titlestringCurrent job title.
decisionMakers[].linkedinUrlstringLinkedIn profile URL.
decisionMakers[].matchedRolestringThe decision-maker role from your Knock account that this person matched.

New fields may be added to these objects over time, so ignore fields you don't recognize (see Versioning).

Errors

StatuscodeCauseRetry?
400validationenrichRequestId isn't a UUID: enrichRequestId: enrichRequestId must be a UUID.No
404external-api.enrichment-request.not-foundNo request with this ID exists for your account. Requests created with another account's key return 404 too.No
502external-api.enrichment.unavailableResults can't be read right now. This is not a final answer about your items.Yes, with backoff

Authentication, permission and rate-limit errors apply too. See Errors and the API reference.

Keep your results

Save each item's result on your side as soon as the item is final. Knock may remove old enrichment requests after a retention period, and there's no way to list past requests.

Chat with a Developer

On this page