Documentation

Provenance and freshness: the response headers

Every Triguna response carries where the record came from and how old it is, in headers rather than in the body. How to read them and how to control freshness.

On this page

Every successful response tells you where the record came from and how old it is. That information travels in response headers, not in the JSON body.

What you get

HeaderValuesMeaning
X-Data-Source live | store | stale_fallback Whether this call hit the upstream provider, was served from our stored copy, or fell back to a stored copy because upstream was failing.
X-Fetched-At ISO 8601 UTC When the upstream provider was last hit for this record.
X-Data-Age-Seconds integer How old the record is, in seconds. 0 on a live fetch.
X-RateLimit-Limit integer Requests per second granted to your account.
X-RateLimit-Remaining integer Tokens left in your bucket right now.
curl -i -H "ApiKey: $TRIGUNA_API_KEY" \
  "https://api.triguna.ai/v1/people/profile?profile_id=williamhgates"
HTTP/1.1 200 OK
Content-Type: application/json
X-Data-Source: live
X-Fetched-At: 2026-07-31T14:02:11+00:00
X-Data-Age-Seconds: 0
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 8

Reading them:

const res = await fetch(url, { headers: { ApiKey: KEY } });
const record = await res.json();

const provenance = {
  source: res.headers.get('X-Data-Source'),        // live | store | stale_fallback
  fetchedAt: res.headers.get('X-Fetched-At'),
  ageSeconds: Number(res.headers.get('X-Data-Age-Seconds') ?? 0),
};

Controlling freshness

Both enrichment endpoints accept max_age_days.

ParameterTypeBehaviour
max_age_days number Accept a stored copy up to this many days old. 0 means always fetch live. Your value is capped by the platform ceiling, currently 7 days.
bypass_cache boolean Force a genuinely fresh fetch, skipping both our store and the provider cache.

The current default is live. Every call fetches from upstream unless you ask otherwise, and the result is stored with its fetched_at.

# Live, the default.
curl -H "ApiKey: $KEY" ".../v1/people/profile?profile_id=williamhgates"

# A day-old copy is fine, so serve from the store when one exists.
curl -H "ApiKey: $KEY" ".../v1/people/profile?profile_id=williamhgates&max_age_days=1"

A stored hit is faster and does not touch the upstream provider. Whether it consumes a credit is a platform setting (charge_on_cache_hit), so treat a store response as billable unless told otherwise for your account.

Staleness fallback

If the upstream provider fails transiently and we hold any copy of the record, we serve that copy with X-Data-Source: stale_fallback rather than returning an error. Your integration keeps working through a vendor outage for every entity seen before.

if (res.headers.get('X-Data-Source') === 'stale_fallback') {
  // Upstream is degraded. The record is real but may be out of date;
  // check X-Data-Age-Seconds before acting on time-sensitive fields.
}

A profile that was never found does not fall back. Not-found stays not-found.

Using it well

Log the headers with any decision the record drove. If a record routed a signup, store X-Data-Source and X-Fetched-At on the routing event. Six weeks later “the data was wrong” becomes a question you can answer.

Treat X-Fetched-At as the age of the claim, not the age of the fact. A record fetched a second ago can still describe a job someone left last month. Freshness of retrieval and freshness of truth are different things, and only the first is something an API can promise.

Set max_age_days per call site, not globally. A nightly scoring job is usually happy with a week-old copy. A record shown to a user mid-conversation probably is not.

A minimal audit shape

create table enrichment_events (
  id            bigserial primary key,
  entity_key    text        not null,  -- member_urn or company_id, never the slug
  decision      text        not null,
  data_source   text        not null,  -- X-Data-Source
  fetched_at    timestamptz not null,  -- X-Fetched-At
  raw_payload   jsonb       not null,  -- keep it; re-fetching costs a credit
  created_at    timestamptz not null default now()
);

Keying on member_urn or company_id rather than the slug matters. See identifiers and slugs for why slugs break joins.