Blog
Key your enriched records on the entity urn, because the slug you looked them up by will change
The slug you enrich by is a vanity URL the owner can rename. Key your records on the entity urn instead, or a renamed profile quietly becomes a second row.
A profile you enriched last month as /in/jane-doe is /in/jane-doe-cto today. Same person, new vanity URL. If the row in your database is keyed on that slug, your next refresh does one of two costly things, and neither of them is an error you will see in a log.
The slug is an attribute, not an identity
Both endpoints take a slug as input: profile_id on the Person API and the company slug on the Company API. That slug is the /in/<handle> or /company/<handle> string from the address bar, and its owner can edit it whenever they like. It is a display choice, not a primary key.
The response hands you something that does not move. On a person record it is entity_urn, returned as a bare string, next to the mutable handle:
{
"entity_urn": "ACoAAB1exampleUrn9Xy",
"public_identifier": "jane-doe-cto",
"first_name": "Jane",
"last_name": "Doe",
"current_title": "Chief Technology Officer"
}
Provenance lives in the response headers, never in the body, so entity_urn is a normal data field. It is the one field in that object the owner cannot change.
Two ways keying on the slug bites you
Key your rows on public_identifier and a single rename splits into two failures, neither of which throws:
| What happens | Rows keyed on the slug | Rows keyed on entity_urn |
|---|---|---|
| The owner renames the vanity URL | Next lookup inserts a second row; the old one rots untouched | The upsert matches, one row survives, the slug is updated in place |
| You re-request by a slug you stored earlier | 404, one credit spent, no record returned | You already hold the urn, so you re-resolve and never send the dead slug |
The duplicate is the worse of the two, because it is silent. You now have two rows for one human, your counts are wrong, and whichever row a downstream join happens to hit decides what the record says. Nothing errored, so nothing alerts.
Key on the urn, keep the slug as a column you overwrite
The fix is which column carries the on conflict. Store the urn as the primary key and treat the slug as refreshable data, matching the schema the person records guide lays out:
insert into people (entity_urn, public_id, current_title, raw)
values ($1, $2, $3, $4)
on conflict (entity_urn) do update
set public_id = excluded.public_id, -- the slug moved, take the latest
current_title = excluded.current_title,
raw = excluded.raw;
A rename now updates public_id on the same row instead of forking a new one. The urn is what your foreign keys point at, so nothing downstream has to care that the handle changed.
You look them up by the slug, you join on the urn
These are two different jobs for two different values, and the trap is using one where the other belongs. You send the slug to find the record. You store and dedupe on the urn the record returns. When you re-read, send the bare urn back as profile_id:
// Re-resolve by the stable urn, not the slug you first saw.
const res = await fetch(
`https://api.triguna.ai/v1/people/profile?profile_id=${encodeURIComponent(entityUrn)}`,
{ headers: { ApiKey: process.env.TRIGUNA_KEY } },
);
const record = await res.json();
// record.public_identifier can differ from the slug you stored last time. That is expected.
Send the bare ACoAAB1exampleUrn9Xy form, not the urn:li:member:... version, which is an output field and is rejected as input with a 400. That trap and the rest of the identifier mistakes that cost a credit are in why profile and company lookups return 404.
Companies rename too
A company changing its vanity slug does the same damage. Key a company row on the stable id the record resolves to, not on the /company/<handle> you looked it up with, which is exactly how the company records guide models the table. When you do need to re-request one, the slug lives in platform_url, the page on the source platform, not in company_url, which is the company’s own website. Feed company_url back as an identifier and you buy a 404 on a domain that was never a slug.
The rule
Look up by the slug. Store, join, and deduplicate on the urn. They are different values doing different jobs, and only one of them is still pointing at the same entity next quarter. The full list of accepted identifier forms is in the identifiers reference.
Next
- Map a person record to your own schema: where the urn is the primary key and the slug is lookup input only.
- Store and refresh company records: the stable company id and the
platform_urlslug you re-request with. - Why a lookup returns 404: the input traps, including the prefixed urn that a re-read must avoid.
- Store the raw payload: keep the whole response, keyed on the urn, so a later field or audit is free.
- Start with five free credits at app.triguna.ai/signup, no card.