Endpoints

Usage history

Every billable run your credential made — what it asked, what it cost, how it went — with a free replayable snapshot of each search.

GET/v1/usage/history

Your recent runs

Every billable run this credential made, newest first.

Light rows only — the query, when, what it cost, how it went. The detail endpoint carries the request, the resolved plan and the replayable snapshot (searches only; an agent run's results stay free to re-read on its own surface instead). Cursor-paginated like every other list on /v1; there is no page or offset parameter.

Scope: dsp.usage.read

Use it when you are auditing what a credential ran and spent, or revisiting a past search without paying for it again. Outcome: the credential's runs, newest first, each expandable into the original request and a stored snapshot that replays for free.

Every metered call is recorded as a run: the query, when it happened, what it cost, and how it went. Each charged page of a search is its own run.

  • This route lists your credential's own runs, newest first.
  • /v1/usage/history/{run_id} expands one row into the original request, the resolved plan, and a stored page-1 snapshot you can re-read without re-running, and without re-paying.
  • /v1/usage/metrics buckets the same runs per day.

All three routes are free and require dsp.usage.read.

Whose runs you see#

Who "you" are follows the credential:

CredentialSees
API keyThe runs that key made
Dashboard sessionThe signed-in user's runs, plus the runs of every API key that user created, including revoked ones

Other members' runs and keys are never included.

Request#

cursorstring | null

Opaque cursor from a previous page.

limitintegerdefault: 25

≥ 1, ≤ 100.

action_typearray of object | null

Only runs of these billable actions. Repeatable — pass it once per action to filter to a family, e.g. all five agent tiers.

fromstring | null
tostring | null
  • action_type filters to one or more billable actions. Repeat the parameter to name a family, e.g. action_type=dsp.agent.run.high&action_type=dsp.agent.run.xhigh; omit it for everything.
  • from/to bound the window.
  • Pagination is the same cursor-and-nothing-else contract as every list on /v1. See Pagination.

Response#

attributionarray of Attribution
as_ofstring | null
domainstring | null
quotestring | null

RESERVED for per-fact citation. Always null in v1.

source_corpusSourceCorpus

Where content came from. Closed set — it appears in attribution[]. The first four are the GTM entity graph. The rest are the document corpora behind the non-entity categories, each a distinct index with its own refresh cadence — which is the reason they are named separately rather than lumped under one label: attribution[].as_of means nothing if two corpora updated a month apart share a name.

urlstring | null

RESERVED for per-fact citation. Always null in v1.

columnsarray of object | null

The answer table's columns, in order, when a run carries extra columns beyond the entity's own fields -- a job-opening link, a value joined from another dataset. Each entry is {name, description, source}; the value for each sits on every row in results keyed by the column name. Null means a plain entity table. Additive, so it breaks no existing client.

coverageCoverage | null

How much of the addressable set this answer represents. Null means coverage was not measured for this request -- fast and auto do not enumerate -- and never that coverage was zero. Additive, so it breaks no existing client.

auditedbooleandefault: false

Whether the query was checked against the executed filter for dropped requirements. Reported because an empty constraints_unmet only means something if something looked.

constraints_unmetarray of string

Requirements in your query that the search could not express, in your own words. Non-empty means part of the question was not answered -- most often because it needs a dataset this search cannot reach (job postings, employment history, patents, news, funding dates). When this is non-empty, universe_exhausted is withheld: 'we returned everything that matches' cannot be true of a question that was never asked.

criteriaarray of object

Per criterion: evaluated, passed, success_rate, and whether it was *proven* by the query or *judged* by a model. Without this a thin result leaves the caller guessing which requirement thinned it.

enumeratedinteger

How many of them this answer carries. ≥ 0.

enumeration_recallnumber | null

enumerated / universe. Null when the universe was not exact, because a ratio over an estimate is not a recall figure. Note this measures coverage of the *resolved query*, not of the user's intent -- a wrong predicate can be exhaustively enumerated. ≥ 0, ≤ 1.

linkagearray of object

One entry per other dataset this answer joined against: corpus, join_key, observable, of_constrained, ratio, is_lower_bound. ratio is the fraction of your matching accounts that dataset can even see -- measured at 5-60% depending on the dataset and how large the companies are, and never 100%. Non-empty therefore means universe_exhausted is withheld by construction: we returned everything reachable, and a dataset cannot see the rest.

linkage_coveragenumber | null

The joins combined, as a product rather than a minimum: two datasets seeing 27% and 24% of your accounts jointly see about 6.5%, not 24%. Null when no join was performed or when any leg's coverage is unknown.

linkage_coverage_is_lower_boundbooleandefault: false

True when a key set was capped or coverage was estimated rather than measured, making linkage_coverage a floor. Narrowing the question makes it exact.

similarityarray of object

Narrowings done by ranking rather than by predicate. Each entry carries the concept, how many ids it resolved to, the k asked for, whether the list was cut at that ceiling, and the fraction of candidates that could be compared at all. Non-empty means universe_exhausted is withheld on purpose: a cosine is a degree, so a top-k ranking has no complete state, and 2.9% of companies carry no embedding to compare.

truncated_bystring | null

Which limit stopped the enumeration short, when one did.

universeinteger

Records matching the resolved query, counted, not sampled. ≥ 0.

universe_exhaustedbooleandefault: false

Every matching record was returned and the count agrees. The claim an unbounded-corpus search engine cannot make.

universe_is_exactboolean

False when the count hit a cap. Always serialized: a capped count presented as exact is the same lie as an unmeasured one.

degradationarray of Degradation
detailstring
impact"recall" | "precision" | "freshness" | "fields"
reasonDegradationReason

Why an answer is less than it should be. Closed, because a client that cannot branch on the reason can only show the user a shrug. These are the honest constraints — partial vector coverage, smry mapped as keyword so it cannot be scored lexically, a federated corpus being unreachable — surfaced at runtime instead of in a footnote.

source_corpusSourceCorpus | null
has_moreboolean
metaResponseMeta

Present on every 2xx, including the ones that are not paginated.

as_ofstring
credential"api_key" | "session"default: api_key

Which credential produced this response. session means the dashboard playground ran it as the signed-in user rather than as an API key — so it is billed to the user, subject to interactive rate limits, and carries no integration compatibility guarantee. Additive and defaulted, so it breaks no existing client.

credits_chargednumber | null

What this request cost, in credits, to at most three decimals. 0 when it cost nothing: a Basic or AI search that found nothing, rows this search already paid for (a cursor sent again, or a request repeated with the same Idempotency-Key on the same UTC day), or a test key. A request without a cursor or a repeated key is a new search, so keep the first page rather than asking for it again. null on a free endpoint.

environment"live" | "test"

Echoed on every response so a developer can never mistake a test-key result for a live one.

request_idstring
next_cursorstring | null

Opaque and signed. Invariant: has_more == (next_cursor is not None). There is no page or offset parameter — not accepted, not deprecated, never present.

resolved_searchResolvedSearch | null
categoryEntityCategory

*What* to search, and what a returned row is. One parameter carries both, because they are the same fact: a search of patent returns patents. The alternative — a source/vertical selector beside a narrower category — lets a caller ask for patents from the people index, a combination that has no meaning and would need rejecting at runtime. A closed set that cannot express the contradiction is better than a validator that catches it. Values are exactly prism's own row types, so category in the request and results[].category in the response are always the same string. company and person are the GTM index; the rest are separate corpora with their own filters, freshness and coverage. Not everything applies everywhere: see SearchRequest, which rejects the GTM-only parameters rather than ignoring them. job_opening is a document corpus like patent/news_article/etc — a posting, not an entity — but its records carry a domain (the posting company's) all the same. It is deliberately not in GTM_CATEGORIES (that set is about the entity/document distinction, and a job posting is still a document), but it *is* in DOMAIN_FILTERABLE_CATEGORIES, which exists specifically to separate those two axes.

conflictsarray of object
filtersFilters
filters_source"caller" | "parser" | "caller+parser" | "none"default: none
mode"fast" | "auto" | "deep"

How hard to work on the query. The distinction is *what the words mean*, not how many results come back: - fast — match my words. The query is matched by meaning (vector retrieval, falling back to keywords where vectors are unavailable); no LLM in the path, so nothing is extracted into filters. - auto — understand my intent. The parser extracts filters. - deep — reason about it. Multi-step, slowest, most expensive. fast was very nearly defined as "filters only, no LLM", which would have made query — the primary parameter — silently inert. A parameter that does nothing is worse than one that is missing.

overridesarray of object
parser_versionstring
querystring | null
semantic_querystring | null
semantic_scope"company" | "person" | null
sortarray of object
resultsarray of HistoryRunSummary
action_typestring

The billable action, one of the dsp.* strings mint prices — searches by mode, similar, entities, relationships, and agent runs by effort tier — so history rows join cleanly against spend.

actor_typestring

user (a session) or api_key.

credits_chargednumber

What the ledger charged, in credits to at most three decimals. A call is charged once its response is built, so a run that failed before that reads zero; one that failed after keeps its charge, as the ledger does. 0 for rows already paid for: a cursor sent again, or a request repeated with the same Idempotency-Key on the same UTC day. A pending run shows what it has cost so far.

environmentstring
error_codestring | null

Why a failed run failed. incomplete: the request never finished, so whatever it was charged stands. unsettled: an agent run no one read after its hold expired, which cost nothing.

idinteger
latency_msinteger
occurred_atstring
querystring | null

What makes the run recognisable: the search text, joined anchor values for /similar, joined ids for /entities/*, the company pair for /relationships, the run's query for agent runs.

result_countinteger | null
status"pending" | "ok" | "failed"
suggestionSearchSuggestion | null

A better way to have asked this question -- a different category (kind: search), or agentic search via POST /v1/agents (kind: agentic). Advisory: these results came from the search that was asked for, whatever this says. Null when there is nothing worth suggesting, which is the common case. Additive, so it breaks no existing client.

categoryEntityCategory | null

The category to search instead. Set iff kind is search.

kind"search" | "agentic"

search — a different category fits. agentic — no single search fits; use POST /v1/agents.

messagestring

One ready-to-show sentence.

totalinteger

≥ 0.

total_is_estimateboolean

Always serialized, never defaulted away. A field clients can forget exists is a field that renders an estimate as exact.

Rows are deliberately light — identity and outcome, not payloads:

  • id is the run id. It is what you send to the detail route, and what the dashboard's replay uses (/search?run=<id> on the Usage page). Rows are retained for 90 days.
  • query is what makes a run recognisable: the search text, the joined anchor values for a /v1/similar run, the joined ids for /v1/entities/*, the company pair for /v1/relationships, the run's query for an agent run.
  • status is pending from the moment a run is charged until it finishes, then ok or failed. A run still pending long after any request could take is reported as failed, with error_code incomplete for a request that never finished, or unsettled for an agent run whose hold lapsed before anyone read the finished run.
  • credits_charged is what the ledger charged, in credits to at most three decimals, so history joins cleanly against the ledger rather than contradicting it. A call is charged only once its response is ready, so a run that failed before that reads zero; one that failed after keeps its charge, as the ledger does. Rows already paid for read zero, and a pending run shows what it has cost so far.
  • action_type is the same closed set of thirteen dsp.* strings the billing system prices — the three search modes, dsp.similar, dsp.entities, dsp.relationships, dsp.relationships.ask, dsp.org_chart, and the five dsp.agent.run.* effort tiers (see Credits & usage) — and actor_type says which kind of credential made the run (user or api_key).

One run in detail#

curl "https://api.sproutsdata.ai/v1/usage/history/{run_id}" \
  -H "X-API-Key: $SPROUTS_API_KEY"

run_id is the id from a list row. Unknown, expired, or someone else's run ids all answer the same way: 404 history.not_found.

Path parameters

run_idintegerrequired
action_typestring

The billable action, one of the dsp.* strings mint prices — searches by mode, similar, entities, relationships, and agent runs by effort tier — so history rows join cleanly against spend.

actor_typestring

user (a session) or api_key.

credits_chargednumber

What the ledger charged, in credits to at most three decimals. A call is charged once its response is built, so a run that failed before that reads zero; one that failed after keeps its charge, as the ledger does. 0 for rows already paid for: a cursor sent again, or a request repeated with the same Idempotency-Key on the same UTC day. A pending run shows what it has cost so far.

environmentstring
error_codestring | null

Why a failed run failed. incomplete: the request never finished, so whatever it was charged stands. unsettled: an agent run no one read after its hold expired, which cost nothing.

idinteger
latency_msinteger
metaResponseMeta

Present on every 2xx, including the ones that are not paginated.

as_ofstring
credential"api_key" | "session"default: api_key

Which credential produced this response. session means the dashboard playground ran it as the signed-in user rather than as an API key — so it is billed to the user, subject to interactive rate limits, and carries no integration compatibility guarantee. Additive and defaulted, so it breaks no existing client.

credits_chargednumber | null

What this request cost, in credits, to at most three decimals. 0 when it cost nothing: a Basic or AI search that found nothing, rows this search already paid for (a cursor sent again, or a request repeated with the same Idempotency-Key on the same UTC day), or a test key. A request without a cursor or a repeated key is a new search, so keep the first page rather than asking for it again. null on a free endpoint.

environment"live" | "test"

Echoed on every response so a developer can never mistake a test-key result for a live one.

request_idstring
occurred_atstring
querystring | null

What makes the run recognisable: the search text, joined anchor values for /similar, joined ids for /entities/*, the company pair for /relationships, the run's query for agent runs.

requestRequest

The original request body, minus the cursor — what a re-run posts.

resolved_searchobject | null
result_countinteger | null
snapshotobject | null
snapshot_expires_atstring | null
snapshot_stateSnapshot State
status"pending" | "ok" | "failed"

Three fields carry the substance the list omits:

  • request — the original request body, minus the cursor: exactly what a re-run would post.
  • resolved_search — how the request was interpreted, same shape as on the envelope.
  • snapshot — the page-1 response envelope as it was returned. Reading it here is free; it is a record, not a re-run. Snapshots are kept for 30 days (snapshot_expires_at), shorter than the 90-day row.

When snapshot is null, snapshot_state says why:

snapshot_stateMeaning
presentThe only state where snapshot is non-null
expiredOlder than 30 days
omitted_too_largeOver the storage cap
continuationA later page of a search: only the first page is stored
absentNothing to store: a pending or failed run, or an action that has no snapshot

Daily metrics#

curl "https://api.sproutsdata.ai/v1/usage/metrics" \
  -H "X-API-Key: $SPROUTS_API_KEY"

Requests, the ok/failed/pending split, and credits per day per action: the same runs the list shows, pre-bucketed. The window defaults to the last 30 days, like /v1/keys/usage.

fromstring | null
tostring | null
bucketsarray of HistoryMetricsBucket
action_typestring
creditsnumber

Credits, to at most three decimals.

daystring
failedinteger
okinteger
pendingintegerdefault: 0
requestsinteger
metaResponseMeta

Present on every 2xx, including the ones that are not paginated.

as_ofstring
credential"api_key" | "session"default: api_key

Which credential produced this response. session means the dashboard playground ran it as the signed-in user rather than as an API key — so it is billed to the user, subject to interactive rate limits, and carries no integration compatibility guarantee. Additive and defaulted, so it breaks no existing client.

credits_chargednumber | null

What this request cost, in credits, to at most three decimals. 0 when it cost nothing: a Basic or AI search that found nothing, rows this search already paid for (a cursor sent again, or a request repeated with the same Idempotency-Key on the same UTC day), or a test key. A request without a cursor or a repeated key is a new search, so keep the first page rather than asking for it again. null on a free endpoint.

environment"live" | "test"

Echoed on every response so a developer can never mistake a test-key result for a live one.

request_idstring
period_fromstring
period_tostring
total_creditsnumber

Credits, to at most three decimals.

total_failedinteger
total_okinteger
total_pendingintegerdefault: 0
total_requestsinteger

Errors#

StatusCodeSummary
401auth.unauthorizedNo usable credential (cause deliberately unspecified)
403auth.browser_originA secret key was used from a browser
403auth.insufficient_scopeThe credential does not hold the required scope
422pagination.cursor_invalidThe cursor could not be read (e.g. a filter changed mid-pagination)
422request.invalidA field failed validation (extra.errors has the detail)
503credits.unavailableThe billing service could not confirm the charge; retry with the same Idempotency-Key
503upstream.unavailableThe retrieval engine is unreachable (not charged)