Blog

Why profile and company lookups return 404 when the identifier looks correct

A display name, a urn:li: prefix, or the wrong url field 404s an enrichment lookup, and every 404 still costs a credit. Fix the four identifier traps first.

The Triguna team 17 August 2026

A 404 on an enrichment lookup is not a miss you can shrug off. It costs one credit, the same credit a 200 costs, because searching for an identifier that turns out to have no record is the same retrieval as finding one. So a run that 404s on a bad identifier is a run you paid for and got nothing back from. Four input mistakes cause almost all of these, and all four are fixable before you spend a credit.

Both endpoints key off a slug, not a name

The Person API and the Company API each take one required identifier: profile_id on GET /v1/people/profile, and company_id on GET /v1/companies/details. Despite the name, company_id takes the URL slug from /company/<slug>, not the display name. Send Acme Corporation and you get a 404, and that 404 is billed like any answered lookup.

You send as the identifierWhat resolvesOutcome
Acme Corporationnothing, it is not a slug404, 1 credit
acmethe company slug200, 1 credit
urn:li:member:214703388rejected, prefixed urn400, free
ACoAAB1exampleUrn9Xythe bare entity urn200, 1 credit

Trap 1: a display name where a slug belongs

Company display names never resolve. company_id wants the slug that appears in the address bar on a /company/<slug>/ page, which is not the name printed on the page and is often not even close to it. Resolve the slug from a real company URL, never from the name. If all you have is a name, that is a search problem, and there is no search endpoint here: enriching by name means finding the slug first, then spending one credit on the lookup.

Trap 2: the urn:li: prefix on an entity urn

For people, the identifier worth storing is entity_urn, returned as a bare ACoAAB1exampleUrn9Xy string. Feed that straight back in as profile_id and it resolves. Feed back the urn:li:member:214703388 form and you get a 400 Bad Request for a malformed identifier: the prefixed member urn is an output field, not an input. When you map a person record onto your own schema, store the ACo… value, not the public_identifier slug, which the profile owner can edit and eventually will.

Trap 3: company_url is not platform_url

A company record carries two URL fields whose names are the reverse of what intuition suggests. platform_url is the page on the source platform, https://www.linkedin.com/company/northwind-labs. company_url is the company’s own website, https://www.northwindlabs.com. Feed company_url back in as an identifier and you 404 on a domain that was never a slug. The slug you can re-request with lives in platform_url. This mix-up, and the rest of the company field traps, is covered in how to store and refresh company records.

Trap 4: specialities, not specialties

This one does not 404, which is worse. The call succeeds, you read the American spelling specialties, get back nothing, and ship an empty array to production. The field is spelled the British way:

// The record came back 200. This still returns undefined.
const wrong = company.specialties;   // undefined, wrong spelling
const right = company.specialities;  // ["Data infrastructure", ...]

Provenance lives in the response headers, never in the body, so a wrong key name fails silently against the JSON and there is no error to catch. Spell it the way the payload does.

Resolve before you spend

Put one function between your data and the request, so a display name or a stray URL can never reach the wire as an identifier. Node:

// A full profile URL and a bare slug both work; a display name does not.
function profileId(input) {
  const m = input.match(/linkedin\.com\/in\/([^/?#]+)/i);
  return (m ? m[1] : input).trim();
}

const res = await fetch(
  `https://api.triguna.ai/v1/people/profile?profile_id=${encodeURIComponent(profileId(who))}`,
  { headers: { ApiKey: process.env.TRIGUNA_KEY } },
);
if (res.status === 404) {
  markMissing(who); // Billed once. Do not retry: the identifier resolved to no record.
}

Python, for the company side:

import os, re, requests

def company_id(value: str) -> str:
    # Pull the slug out of a /company/<slug>/ URL; never pass a display name.
    m = re.search(r"/company/([^/?#]+)", value)
    return (m.group(1) if m else value).strip()

r = requests.get(
    "https://api.triguna.ai/v1/companies/details",
    params={"company_id": company_id(value)},
    headers={"ApiKey": os.environ["TRIGUNA_KEY"]},
)

The whole point of the surface being two endpoints and one auth header is that this guard is small enough to hold in your head. The full accepted forms are in the identifiers reference.

Next

identifiersdebuggingcost

Start with one request

Free credits on signup, no card required. Usage-based pricing after that.