Skip to content

Credits & usage

Trace bills the public API in credits. The model is deliberately simple: you pay for company records, once per company, with a free re-access window. Separately, a high-ceiling rate limiter protects the service from abnormal request volume. The two are independent — credits are the product control, the rate limiter is operational backpressure.

The unit of billing is a company record:

  • 1 credit per net-new company returned, flat. A call that returns 50 companies charges up to 50 credits.
  • Re-access within 30 days is free. Once your org has been charged for a company, returning that same company again within 30 days costs nothing. The window is keyed by (your org, company).
  • Duplicates within one response are charged once. The same company can’t bill twice in a single call.

Which calls are billable:

Call What’s charged
GET /v1/companies/{identifier} 1 credit for the company returned
GET /v1/companies (list/search) 1 credit per company on the returned page
GET /v1/companies/{identifier}/partnerships 1 credit per partner company returned
GET /v1/companies/{identifier}/partnerships/graph 1 credit per company node in the graph (the queried company itself is free)

The 30-day window applies to all of these, so a company you already paid for — whether you first saw it via a lookup, a search page, or a partnership result — is free on subsequent calls within the window.

When your org’s balance can’t cover a call, the request fails with HTTP 402 and no data is returned. The response body:

{
"error": "credit_exhausted",
"message": "Insufficient credits to complete this request.",
"requested": 50,
"shortfall": 12
}

requested is how many credits the call needed; shortfall is how many you were short. Top up your balance in the Trace app (Settings → Usage & Credits) and retry.

Authenticated /v1 traffic is governed by a per-key operational limiter — a safety valve against abnormal volume, set well above normal usage:

  • 600 requests per minute per API key
  • 100,000 requests per day per API key

These are infrastructure limits, not a product quota. Normal integrations never hit them. Successful responses carry no X-RateLimit-* headers — they only appear when you’ve been throttled.

The unauthenticated open endpoints under /v1/open/* have no API key, so they are rate-limited by client IP instead (a lower ceiling than the authenticated per-key limits above).

When you exceed a limit you get HTTP 429 with a Retry-After header (seconds to wait) plus the standard rate-limit headers:

{
"error": "operational_rate_limit_exceeded",
"message": "Too many requests. Please wait before retrying.",
"retry_after_seconds": 30
}
Header Meaning
Retry-After Seconds to wait before retrying.
X-RateLimit-Limit Per-minute request ceiling.
X-RateLimit-Remaining Requests left in the current minute window.
X-RateLimit-Reset Unix timestamp (seconds) when the minute window resets.
X-RateLimit-Limit-Day Per-day request ceiling.
X-RateLimit-Remaining-Day Requests left in the current day window.
X-RateLimit-Reset-Day Unix timestamp (seconds) when the day window resets.

These two failures mean different things and call for different handling:

  • 402 (credit_exhausted) — you’re out of credits. Retrying won’t help until you add credits. Stop, top up in the app, then resume. The shortfall field tells you how many you need.
  • 429 (operational_rate_limit_exceeded) — you’re sending too fast. Back off and retry. Wait the number of seconds in Retry-After (fall back to retry_after_seconds in the body), then retry. For polling loops, prefer exponential backoff with jitter rather than a tight retry.

A simple backoff loop:

backoff.py
import time
import httpx
def get_with_backoff(client, url, headers, max_retries=5):
for attempt in range(max_retries):
resp = client.get(url, headers=headers)
if resp.status_code == 429:
wait = int(resp.headers.get("Retry-After", "60"))
time.sleep(wait)
continue
if resp.status_code == 402:
raise RuntimeError("Out of credits — top up before retrying.")
resp.raise_for_status()
return resp.json()
raise RuntimeError("Rate limited after retries")