Blog

What an enrichment run costs, and how to reconcile the bill against the headers

One credit per answered lookup, no bulk endpoint, and 404s still bill. Here is how to estimate an enrichment run and reconcile it against X-Credits-Charged.

The Triguna team 19 August 2026

Enriching 10,000 companies is 10,000 calls and 10,000 credits, which is $50.00 at $0.005 per credit. There is no line item where that number comes out smaller than the count of answered lookups, so budgeting an enrichment run is really just counting the lookups you cannot avoid, then checking after the run that you spent what you predicted.

The bill is answered lookups, not records and not requests

Billing is one credit per answered lookup. A 200 bills, a 404 bills because searching for an identifier with no record is the same retrieval as finding one, and a cache hit bills because it is the same data delivered faster. What does not bill is a request that never resolves to an answer.

ResponseBilled
200 with a record1 credit
200 from a cache hit1 credit
404, no record for the identifier1 credit
400, 401, 402free
429, 502, 503free

Read that table as a budget, not a status reference. Every row that costs a credit is a lookup you asked for and got answered. Every free row is a lookup that did not complete, so a rate-limited 429 you back off and retry costs nothing until the retry succeeds. The pricing page has the same split with the per-status detail.

Estimate before you start

There is no bulk path and no search endpoint, so enriching N entities is N calls, one credit each. That makes the estimate arithmetic short. Count the unique identifiers you will resolve, multiply by how many times a month you refresh them, and you have both the credit spend and, at the published 10 requests per second, the wall-clock floor.

CREDIT_USD = 0.005

def estimate_run(unique_identifiers, refreshes_per_month):
    # No bulk endpoint, so answered lookups equals calls equals credits.
    lookups = unique_identifiers * refreshes_per_month
    seconds = lookups / 10  # published limit is 10 rps
    return lookups, round(lookups * CREDIT_USD, 2), round(seconds / 60)

calls, usd, minutes = estimate_run(10_000, refreshes_per_month=1)
print(calls, usd, minutes)   # 10000 50.0 17

The single biggest lever in that formula is unique_identifiers. The only free lookup is the one you do not make, so deduplicating your input list before the run, not after, is the one optimization that reduces the credit line rather than moving it around. A list with 30% duplicates is a bill 30% larger than the work required.

The 404 tax is a real budget line

A run over dirty identifiers pays for every miss at the same rate it pays for every hit. Send a display name where a slug belongs, or feed a urn:li: prefixed value back as an input, and you buy a 404. That is money spent for nothing returned, and it is entirely avoidable by resolving identifiers before they reach the wire, which is the whole argument in why profile and company lookups return 404.

Do not model a retry as a way to recover the spend. A retried 404 bills every time, and if you retry hard enough on a depleted balance you hit the wall instead:

HTTP/1.1 402 Payment Required
content-type: text/plain; charset=utf-8

insufficient credits

The 402 itself is free, but everything you spent getting there was not. Budget an expected 404 rate as pure waste, then drive it toward zero by cleaning the input, not by retrying the output.

Reconcile after with X-Credits-Charged

Every response carries X-Credits-Charged, and it reads 1 on a hit, a 404, or a cache hit, and is absent on the free codes. Summing it across a run gives you the real bill, which you compare against the estimate to catch the config bugs an estimate cannot see.

let spent = 0;
for (const id of identifiers) {
  const res = await fetch(
    `https://api.triguna.ai/v1/companies/details?company_id=${encodeURIComponent(id)}`,
    { headers: { ApiKey: process.env.TRIGUNA_KEY } },
  );
  spent += Number(res.headers.get('X-Credits-Charged') ?? 0);
}
console.log(`spent ${spent} credits, $${(spent * 0.005).toFixed(2)}`);

When the reconciled number runs over the estimate, the cause is almost always one of two things: a retry loop that is re-buying 404s, or use_cache=false forcing fresh fetches on data a stored copy would have answered. The cache does not save the credit, but forcing it off spends one you did not budget, which is the point of why a cache hit still costs a credit. The ledger is how you notice either one on the run that caused it, not on the invoice a month later.

The levers, in the order that pays off

  1. Deduplicate the input. The cheapest call is the one you never send. This is the only lever that shrinks the credit line itself.
  2. Store what you fetch. Read your own database inside your own freshness window so the next run does not re-buy last week’s data. That storage layer, not the 24 hour API cache, is your cost control, and it is the case made in store the raw payload.
  3. Resolve identifiers first. Every 404 you prevent is a credit kept.
  4. Pace, do not overspend. A 429 is free, so backoff is cheaper than the fetch it defers. Spread a large refresh across a schedule rather than firing it all at once, which the nightly backfill guide walks through end to end, and let a cached read or a stale_fallback keep a synchronous path moving in enrich on signup.

Check the balance, not your assumptions

Before a large run, read the balance rather than guessing at it. GET /v1/credits returns it against the same auth header as everything else:

curl -s https://api.triguna.ai/v1/credits -H "ApiKey: $TRIGUNA_KEY"

Signup gives you five free credits with no card, which is enough to run the estimate and the reconciliation against real traffic before you commit to a number. The full billing contract, including every free status code, lives in the billing reference.

Next

costbillingpractices

Start with one request

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