Every endpoint the API serves, with its parameters, errors and webhooks. Generated from the specification rather than written beside it, so it cannot describe a version of the API that no longer exists.
102 operations across 14 groups, and 2 webhook events. Plans and pricing.
Base URL https://aspern.org/api. Authenticate with X-API-Key, issued from your account page. Ratings and this document need no key.
| Setting | Value | Why it matters |
|---|---|---|
| Base URL | https://aspern.org/api | Every path below already begins with /v1, do not put it here too. |
| Auth header | X-API-Key | Not Authorization. |
| Auth scheme / prefix | none | The key is sent raw. A "Bearer" prefix makes the key unreadable. |
| Spec path | /v1/openapi.json | Relative to the base URL above. |
curl -s https://aspern.org/api/v1/vaults?agentManaged=true&limit=5 \ -H "X-API-Key: $ASPERN_KEY"
Errors carry the reason. A 402 means the call is outside your plan and names what would cover it. A 429 is one of two different things, told apart by its error field and described under The contract below: going too fast, or a spent allowance. Neither is ever returned as an empty result, an empty list from this API means we looked and found nothing.
Every response states what it cannot see. Coverage, sample size and the mechanism behind a figure travel with the figure, because a number read without them is the failure this platform exists to prevent.
Base URL https://aspern.org/api. The machine-readable contract is https://aspern.org/api/v1/openapi.json, and /v1/openapi.json, /openapi.json and /.well-known/openapi.json at the site root lead to it. The key goes in X-API-Key. The document says which contract it is in info.x-aspern-environment (always production here), info.x-aspern-build and info.x-contract-hash; every response repeats the first two in X-ASPERN-Environment, X-ASPERN-Build and in meta. There is no demo contract on this host: a client holding anything else is not holding ours.
/v1 the contract only grows: new routes, new optional parameters, new fields, new warning codes. It never removes or renames a route or a field, changes a field’s type or unit, or changes what a status code means.x-lifecycle: deprecated in the contract, with its replacement, announced in the changelog, and served for at least 180 days. A removal happens only in a new major version, and /v1 keeps answering through that period.x-lifecycle. The published contract is checked every day against the approved list of operations, and every change of its hash is recorded with what was added or removed; a build that would drop an operation without that record does not ship.A token bucket, which refills continuously and so has no window edge to exploit. With a key: 20 requests a second sustained, and a burst of 60. Without one: 60 a minute and a burst of 60. A plan may carry its own. Every response states where you stand in RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy, so a client can pace itself rather than discover the limit.
rate_limited: Sent faster than the limit. Wait the number of seconds in Retry-After and continue; nothing is lost and nothing is billed.quota_exceeded: The monthly allowance is spent. Retrying does not help until the period resets or the plan changes; /v1/plans, /v1/meta and /v1/account stay reachable.Only a response with a status below 400, and never a 304. A failed call, a refusal of either kind and a poll that found nothing new all cost nothing. The allowance is a hard stop, not a meter: nothing is billed past it, and there is no overage charge to be surprised by.
limit and offset, and every list states its paging in meta.pagination: total, hasMore, nextOffset and orderedBy. Follow nextOffset until it is null. The order is total, with a unique tie-breaker, so pages neither skip nor repeat a row. Each route below states its own cap.
Where the rows sit depends on the route, and the schema says which. On /v1/vaults data is the list itself. On a route that returns context beside its rows, such as /v1/operators or /v1/changes, data is an object and the rows are under items. Paging is in meta on both.
Three ways, for three questions. Has this changed? Send back the ETag in If-None-Match; an unchanged answer is a 304 with no body, and is free. What changed? /v1/changes is the feed: a vault that stopped being exitable, one that died, one that reopened, a new observation, filterable by kind, by age and by the capital behind it. What was it then? asOf on /v1/crowding returns the book as it stood rather than as it is.
There is no general since parameter on the list routes, and none is implied: a client wanting every row that changed since a time uses the ETag or the feed.
Reported, never refused. The call succeeds as if the parameter were absent, and warnings carries parameter_ignored naming it, so a filter you meant to apply cannot fail silently.
Every route listed below is in the API. These are on the site and not yet reachable with a key:
Every change to the published API, and whether it could break a client. Within /v1 none may.
What we think of a strategy. Free, the argument is worth more to us published than sold.
/v1/vaultsThe vault universe, filterable.
A vault absent here is one we do not observe, not one that does not exist. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| q | string | optional | Name or address fragment. |
| chain | string | optional | e.g. hyperliquid, solana, base. |
| platform | string | optional | A platform id as /v1/platforms lists it and every vault carries in `platformId`, e.g. `morpho` or `morpho-v2`. The display name is also accepted, case-insensitively. |
| agentManaged | boolean | optional | Restrict to machine-managed. `machineManaged` is accepted as a synonym. |
| minTvl | number | optional | Floor on stated size. |
| earned | string | optional | in-line | beating | short — what a holder ACTUALLY earned against what the venue advertises. Matches only the vaults where the two are COMPARABLE. The answer’s `warnings` carry that population with its id, its definition and a count taken when you asked, because a figure typed into a description goes wrong silently. A vault whose venue quotes nothing, whose share price never moves, or whose history is too short is excluded rather than counted as in-line, and the answer carries a warning saying so. |
| minOwnShare | number | optional | Floor on the share of the vault its LEADER holds, 0 to 1. Readable on some venues only, and the answer’s `warnings` carry the count with its population id, so a floor here excludes every other venue, where the figure is ABSENT rather than zero, and the answer carries a warning saying so. What the chain shows is who holds the shares, not whose money they are: a large holding is evidence of alignment, never proof. |
| maxRisk | number | optional | Ceiling on the published risk score. Scored for a minority of open vaults; the rest are excluded by this filter because they have no score, which is not the same as scoring well. |
| closed | boolean | optional | Include vaults we have recorded as shut. Excluded by default. |
| lifecycle | string | optional | live | dead | unlaunched | unknown. `dead` reaches the failures in one call rather than past eleven hundred rows of survivors, and implies closed=true. `unlaunched` is a vault nobody ever deposited into, which is not a failure. |
| format | string | optional | 'csv' returns a spreadsheet rather than JSON. Needs export.bulk, so this otherwise-free route answers 402 for that one value. |
| limit | number | optional | Default 50, capped at 100, or 1000 with export.bulk. |
| offset | number | optional |
/v1/operators/{slug}/checkThe operator check: who runs the money, and how much of it is their own. Free, and needs no key.
The question that follows the vault check, in an answer rather than the 533KB document /v1/operators/{slug} returns. `ownShare` is readable on Hyperliquid and Drift only — 584 of 3,394 open vaults, 17% — because those two publish the leader’s holding; elsewhere it is ABSENT, not zero, and `cannotSee` says which. The median is taken over the strategies we can read and describes those, not the manager. And what the chain shows is who holds the shares, not whose money they are: the address running a vault can be a treasury or a custodian holding for others, so a large own-share is evidence of alignment and never proof. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path | Operator slug. |
/v1/vaults/{slug}/checkThe vault check: what the record shows about one vault before putting money in. Free, and needs no key.
The counterpart of /v1/agents/{address}/check, for capital rather than counterparties. The headline is REALISED against ADVERTISED — what a holder actually earned set against what the venue says the vault yields — and it ships with its own `comparability`, because a realised figure drawn from too short or too sparse a window is not a comparison and must never be read as one. Rates are decimal fractions: 0.0432 is 4.32% a year. `cannotSee` lists what we could not establish for this vault, in its own field rather than as an absence a reader has to notice. Answered at the PUBLIC tier whatever key is sent, so the same URL never means two different things. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path | The vault slug, or its contract address — either resolves. Case is ignored, and an address held by two vaults is refused rather than guessed. |
/v1/vaults/{slug}One vault: metrics, value series, positions, execution quality, and what evidence exists for it.
The `available` block is free at every tier and states what this platform HOLDS for the vault, fills, positions, funding accrual, liquidation prices, whether or not the key may read it. An absence and a paywall are different facts: a 402 from /v1/funding or /v1/stress means the data exists and the tier does not carry it. `performance.execution` carries turnover, taker share and fee basis points where the vault has traded enough for them to mean anything, and null where it has not. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path | The vault slug, or its contract address — either resolves. Case is ignored, and an address held by two vaults is refused rather than guessed. |
/v1/operatorsOperator cohorts, survivors and failures kept in.
Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| limit | number | optional | Default 50, capped at 200. |
| offset | number | optional |
/v1/operators/{slug}One operator and its whole book.
Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path |
/v1/assetsInstruments and who holds them.
Free.
/v1/indexThe machine-managed capital index, with its constituents.
Requires `index.read` — PUBLIC or PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| from | string | optional | ISO date, inclusive. |
| to | string | optional | ISO date, inclusive. |
/v1/benchmark/{symbol}A benchmark series to compare against.
We keep a series for a SHORT list of symbols, not for everything that has a price. A 404 here means we hold no series for that symbol, never that the symbol is unknown, and the body lists the ones we do hold. Our own verifier read nine symbols out of /v1/vaults, got 404 on all nine, and reported a documented route that answers nothing. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| symbol | string | in path | A symbol we keep a benchmark series for. The 404 body lists them. |
/v1/metaCounts, capabilities your key holds, and the standing limitations.
Free.
/v1/platformsEvery platform we hold vaults for, with the stable id /v1/vaults?platform= filters by.
The id is stable and is what every vault carries as `platformId`; the name is a display label and may change. A platform with no open vault is still listed, with its count at zero. Free.
/v1/assets/denominationsWhat the capital is denominated in, and whether that thing is worth what its name claims.
Only a dollar claim can be checked here: there is no ether or bitcoin reference price, so those are unpriced rather than sound. A dollar-named token priced far from a dollar is a ticker collision rather than a depeg, and its price is refused. Free.
What each vault and operator actually did, read from the chain rather than reported to us.
/v1/vaults/{slug}/waterfallGross to net, and whether it was earned by being right or by being paid to wait.
Measured over the window funding can be measured over, not the vault’s lifetime. Requires `waterfall.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path | The vault slug, or its contract address — either resolves. Case is ignored, and an address held by two vaults is refused rather than guessed. |
/v1/vaults/{slug}/executionTaker share, fee rate and how it gets filled.
Answers the analytical envelope like its siblings: `status = available` carries data, and a vault with too few fills carries `data: null`, a reasonCode, and how many fills it has against how many the measure needs. It used to answer a bare error object with a 200, which made it the one route a client had to special-case. Requires `fills.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path | The vault slug, or its contract address — either resolves. Case is ignored, and an address held by two vaults is refused rather than guessed. |
/v1/vaults/{slug}/tradesHit rate, payoff and holding period, rebuilt from the venue’s own open and close labels.
A high win rate beside a payoff below one is the signature of selling optionality. Requires `trades.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path | The vault slug, or its contract address — either resolves. Case is ignored, and an address held by two vaults is refused rather than guessed. |
/v1/forced-exitsWhat the venue did against a book’s will, and when several went at once.
Observed, not modelled, the only thing here that can contradict the stress ladder. `truncated` means the field walk hit its ceiling, so the field list is partial rather than complete. Requires `forced.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
/v1/agents/{address}One address: what it claims to be, what the chain records it doing, and how the two were joined.
Claims and behaviour are listed separately and never blended — a registration is somebody saying what they are, a payment is the chain recording what they did. 326,023 addresses hold an ERC-8004 identity and about five hundred have ever appeared on a rail that moves money, so `economicallyActive` is false for almost all of them. An operator that registers with one key and transacts with another appears as a registration with no behaviour, and this cannot tell that from an identity that has never done anything. `declaresX402` is true where at least one of its registrations says so; false means none of the cards we read mention x402, which is a fact about the cards and not about whether it sells anything. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| address | string | in path | The address to profile. |
/v1/agents/{address}/checkThe counterparty check: what the chain says about an address before you pay or hire it.
Dimensions with their evidence class, never a score. Independent means a counterparty this address has never paid back; only the two-way loop is tested — and `independence` now measures what that caveat has always only admitted, by finding payers that share an obscure third party with another payer of the same address. x402 counts a payment only where the recipient lists a paid resource in the x402 catalogue, so a plain token transfer is not a sale. An address never paid on a rail we read answers 200 with that observation. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| address | string | in path | An EVM address (0x…), a Cardano payment address (addr1…) or a Solana address. Another chain’s address answers 422 chain_not_read, naming what it is. |
/v1/agents/{address}/profileAgent economic profile: four readings of one economic subject on one page — revenue quality, delivery reliability, economic dependencies and operating health.
A composition of the figures the single-address check already carries, from the same nightly row and the same functions, so it cannot disagree with the endpoints it summarises. THERE IS NO SCORE ON TOP and there will not be one: a composite would be the figure every reader quotes and none can check, and it would average a measured quantity with an unmeasured one until neither is visible. Each block keeps its own counts and its own denominator, a block we have not computed is null rather than a row of zeros, and `cannotSee` names what no block here can reach — revenue off our rails, dependencies that are not payments, and whether delivered work was any good. Requires `business.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| address | string | in path | An EVM address (0x…), a Cardano payment address (addr1…) or a Solana address. Another chain’s address answers 422 chain_not_read, naming what it is. |
/v1/serviceIs a declared service endpoint answering, what does it offer, and who declares it.
Three facts kept apart: what an identity DECLARES, what a probe OBSERVED, and the readings behind it. A failed probe is an availability fact and never evidence of bad faith — one bad day and a dead service are identical in a single reading, which is why the history comes with it. Drift between declared and observed is computed only where both are known, because a probe that read no capabilities is silence rather than an empty offering. We probe what registered identities declare; anything else is outside our reading rather than absent. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| url | string | required | The absolute http(s) URL of the endpoint. |
/v1/identifyWhich party is this, from whatever you hold: a transaction hash, a URL, a hostname or an ERC-8004 registration number.
One party or none, NEVER a pick between several — where more than one matches they are all listed and `address` is null, because naming one would be an identification the evidence does not support. A transaction’s payee is the Transfer event’s recipient, never the transaction’s `to`, which is the token contract. A link through a hostname or a declared endpoint is the subject’s own claim and never our verification of control. An identifier we cannot resolve is not evidence against it. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| q | string | required | An address, a transaction hash, an http(s) URL, a hostname, or an ERC-8004 registration number, optionally as `chain:id`. |
/v1/evidenceEvery edge we hold about one subject, each carrying where it came from.
The same party lives in half a dozen tables here, each with its own route and shape. This is the one answer to “what do you know, and how do you know it”. Every edge carries `source`, `observedAt` (when the world was in that state), `asOf` (when WE read it — never the same field, because a fresh read of a stale fact is not a fresh fact), a `confidence` that names what it is a confidence IN, its own `coverage` sentence, and the `method` that established it. Edges are NEVER summed: `declared` is the subject speaking, `observed` is our reading, `derived` is arithmetic over the others, and folding them together rebuilds the reputation score this exists to replace. It infers no identity — a shared host or funder is emitted as exactly that, because being the same party is a conclusion no join supports. What we looked for and did not find is listed, so a thin subject cannot read as a complete picture. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| subject | string | required | An address, or the http(s) URL of an endpoint. |
/v1/claim/challengeA sentence to sign, proving you control an address.
A signature proves you hold the private key for that address, as of now. It does NOT prove who you are, does NOT prove you own whatever business is behind it, and authorises no payment or transaction. The challenge is derived from the address, a ten-minute window and a server secret rather than stored, so there is no nonce to create, expire or leak; a signature is therefore valid for at most twice that window, which is stated rather than hidden. EVM only: a Cardano or Solana key signs differently and is not supported here yet. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| address | string | required | The 0x address you are proving control of. |
/v1/claim/verifyCheck a signature against the challenge for that address.
A failed proof answers 200 with `ok: false`, not an error status: nothing went wrong with the request, we asked a question and the answer was no, and an error would make a correct refusal look like a malfunction. A valid signature from a DIFFERENT key is reported as such rather than as invalid, because which of the two it is matters. Nothing is stored yet: proving control does not currently change what any page shows. Free.
/v1/evaluateOne answer about a proposed action: a decision, a limit, and the rule that decided.
The decision depends on the AMOUNT, the ACTION, the policy you name and how much we actually read, which is why the same subject can be `allow` at one size and `abstain` at another. `allow` means nothing we could check objects, up to `maxAmountUsd`, and is never a statement that the action is safe: nothing here sees what it is for or what was agreed. `review` means something we READ does not match. `abstain` means we did not read enough to have an opinion, which is OUR gap and must never be read as approval. `unsupported` means the policy has no rule for this action. A rule whose evidence we do not hold appears in `missingEvidence`, lowers `coverage`, and is never counted as an objection. An unknown `policy` is refused rather than silently replaced with the default. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| action | string | required | pay, connect_mcp, call_tool or allocate_capital. |
| subject | string | required | The address, endpoint URL or vault slug the action is about. |
| amountUsd | number | optional | What you are about to commit. Left out, the amount rules report that they did not run. |
| chain | string | optional | The chain it settles on. |
| resource | string | optional | The endpoint being bought, where it differs from the subject. |
| policy | string | optional | A version or slug from /v1/policies. Defaults to conservative-v1. |
/v1/policiesEvery policy a decision can be made under, with every rule.
Published in full, including to the parties being evaluated: a rule nobody can read is a rule nobody can correct, and the population we evaluate is the one best placed to tell us when a rule is wrong. A decision carries the policy version it was made under, so an answer given today can be reproduced after the rules change. Free.
/v1/agents/dependenciesWhat several agents rest on AT ONCE, with a shared counterparty kept strictly apart from a dependency.
Finding that three agents paid the same address is one join and tells you almost nothing: on our rails one address is paid by 71 agents across 4,100 payments and another by three agents once each. Four tests must all hold before the word dependency is used — repeated (more than one payment), one-way (it never pays them back, because money moving both directions is trade between peers), sustained (spanning more than a day, so a burst is not read as an arrangement) and shared (more than one of the agents asked about). All four are published on every party so you can draw the line somewhere else. Anything short is a `shared-counterparty` with the failed test named, listed as a LEAD. The concentration line counts dependencies only. A party just ONE agent pays is left out entirely; that is free on /v1/agents/{address}/check. Most shared infrastructure — a cloud region, a model provider on a card, one operator’s keys — leaves no payment at all, so what this finds is a FLOOR on the concentration that exists. Requires `business.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
/v1/x402/endpointsWhich endpoints an address sells, or which ones match some text. The seller’s own catalogue entry, never our reading of it.
A listing is not evidence that the endpoint answers, nor that the price is still what it says: /v1/test-purchase knocks on it. `priceUsd` is the price the seller published and `priceRaw` is the figure in the asset’s own base units, which are different claims and are both carried. Nothing matching means nothing in the catalogue WE read matches, which is our reach rather than evidence that the seller publishes nothing. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| payee | string | optional | An address, to list what it sells. |
| q | string | optional | Part of a URL, matched case-insensitively. |
| limit | number | optional | Default 20, capped at 100. |
/v1/x402/disagreementsWhich catalogued endpoints ask to be paid at an address their own listing does not name. The counts are free; the list needs a plan.
A disagreement is between two statements by the SAME party — their catalogue listing and their live endpoint — so it is theirs to explain and never ours to resolve. It is not evidence of fraud: a seller may have moved their payee and not relisted. Measured only over the endpoints WE have knocked on, which is our reach rather than the catalogue, and `comparable` is the denominator because an endpoint naming no payee cannot disagree. `items` is NULL without a plan rather than empty, since an empty list would say we found none; any SINGLE endpoint is free wherever it is asked about. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| limit | number | optional | Rows to return once a plan is held. Default 50, capped at 200. |
/v1/test-purchaseWalk the whole purchase of an x402 endpoint without paying: what it charges now, whether its own listing agrees, what the record says about its payee, and whether the price held between the decision and the signature.
Each step can STOP the purchase and the first one that does is the answer, which is what separates this from a report. An unanswered question is never rounded up to a passed one: the verdict is `cannot-tell`, not `would-proceed`. We knock only on endpoints already in the x402 catalogue we read, because fetching a URL because a request named it would make us a prober-for-hire pointed at somebody else’s host, and at most one live handshake a minute per resource, shared by everybody. Nothing here is a promise about what arrives: only paying and reading the answer establishes that, which is the half that needs your own wallet. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| resource | string | required | The http(s) endpoint to rehearse buying. It must be one the x402 catalogue already holds. |
| policy | string | optional | A policy version or slug to decide under. Defaults to conservative-v1. |
| fill.* | string | optional | A value for a slot the seller’s own URL declares, e.g. `fill.doi=10.1000/xyz` for a resource listed as `…?doi={doi}`. Only declared slots are accepted; a name the template does not contain is refused rather than ignored. Values are percent-encoded and the result is verified to reach the same origin and path shape as the catalogue entry, so a value can never choose the host. A filled rehearsal is always a live knock and is NOT recorded into the endpoint history, because two different questions in one run series would look like a price that moved. |
/v1/knockKnock on an x402 endpoint and get what it served. Without a payment this reads its terms; with an authorisation you signed yourself, it carries that to the seller and hands you the answer.
WE NEVER HOLD YOUR KEY. You sign; we carry the envelope, because a seller’s host sends no CORS headers to a browser and that failure is indistinguishable from the endpoint being down. A 200 means the seller ACCEPTED the authorisation, NOT that money has settled: settlement needs somebody to submit it on chain, and `settlement` beside the answer is the seller saying so rather than us. A 402 on a knock that CARRIED a payment is the seller refusing what you signed, reported as `rejected` because it is a different fact from the first 402, and nothing was spent. `accepts` carries every set of terms the seller offered and `offer` the one a browser wallet can meet, which is often not the first: where none can be, `fault` names why in a sentence. We knock only on endpoints already in the x402 catalogue we read, because fetching a URL because a request named it would make us a prober-for-hire pointed at somebody else’s host; at most one UNPAID handshake a minute per endpoint, shared by everybody, while a paid one is never made to wait on a signature that would expire; and never on our own host, because what we sell has one set of terms and one door. An answer bigger than 512 KB is handed back as text rather than parsed, since half a list drawn as a whole one is a figure we could not stand behind. The seller’s answer is yours and is not stored; what we keep is that the knock happened and how it went. `describe: true` adds `blocks`, the same reading our own pages draw, always as a stranger’s payload: no unit added, no tone inferred, nothing drawn that would claim their numbers are parts of one whole. Free.
/v1/agents/{address}/businessDoes this agent have a business: six questions about its customers, its revenue, its deliveries, its costs and its identity.
Nearly every figure behind this has been computed nightly for weeks and published nowhere a reader would find it; what this adds is the assembly and four refusals the data invites and does not support. An ADDRESS is not a customer: one customer can hold twenty and twenty can share a funder, so the independence figure is a floor. A PAYMENT is not a delivery: the busiest agents settle on rails that carry money without carrying a job, and "no delivery record" there is a statement about our reach, not about the agent. A SHARED FUNDER is not an identity, so a link is published as demonstrated or as merely co-occurring and the second is never promoted. A COST we cannot see is not a cost that is not there. There is deliberately NO SCORE over the six, because a number would average "thousands paid this" against "we hold no delivery record", and the second is about us. Requires `business.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| address | string | in path | The agent’s address. |
/v1/exits/screenWhich vaults have ever actually paid an amount like yours, in one withdrawal or across a day.
THREE ANSWERS AND THE MIDDLE ONE IS THE FINDING: `paid-in-one` is a single settled withdrawal at least this large; `paid-across-a-day` is that much leaving in one day and never in one go, which is a different fact rather than a weaker version of the first; `never-asked-this-size` is not a refusal and not a failure, because most vaults have never been asked. A vault with no withdrawal history at all is counted in NEITHER and reported separately, since unread is not untested. Declared liquidity is a level at an instant and what has left is a flow over time; the two are never divided by one another here and no ratio between them is returned. Nothing in it is a forecast. Requires `action.check` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| amountUsd | number | required | The size you are weighing, in US dollars. Required: "can I get out" has no answer without one. |
| limit | number | optional | How many vaults to list, 1 to 100. The counts cover the whole population either way. |
/v1/vaults/{slug}/exit-confidenceWhat evidence says about getting a PARTICULAR amount out of this vault, as six readings rather than a score.
The register beside this says what has LEFT; this asks the question a holder has, which is about them and about a size. Six components, each with its own verdict, and the answer is the WORST of them rather than an average: a queue in front of you is not offset by a long history, it happens before your turn. An `unknown` outranks a `supports`, because a gap we could not read must not be counted as a pass; `not-asked` marks a component whose input you did not send and `not-applicable` one this venue does not have at all; neither is a gap in our record and neither lowers the summary. A withdrawable figure is a FLOOR on venues whose vaults lend into markets holding their own free liquidity, and the component says so. NOTHING HERE IS A GUARANTEE of withdrawal and the word does not appear: a vault that has paid everybody for a year can stop tomorrow. Requires `action.check` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path | The vault slug, or its contract address — either resolves. Case is ignored, and an address held by two vaults is refused rather than guessed. |
| amountUsd | number | optional | What you are thinking of taking out, in dollars. Left out, the sizing component reports `not-asked` rather than passing. |
/v1/check-actionOne check before an agent signs: the counterparty, the calldata, the seller’s listing, the endpoint and the policy, and — the point — what they say about EACH OTHER.
Its five parts are each free on their own routes. What this adds is the comparison between them, which is where the losses live: the bytes paying an address you never checked, a policy allowing a payment for a path that is not served, a listing naming a payee the calldata does not. Every cross-check names BOTH readings that disagree and none of them is an accusation — the record can be wrong. A cross-check can only make the verdict worse, never better, because a comparison is not evidence that something the policy objected to is fine. SEND `calldata`, `chain` AND `from` AND IT IS ALSO SIMULATED: one `eth_call` at the latest block, where the node executes the transaction and throws the result away, so nothing is signed, broadcast or spent. A revert is the strongest finding here, because every other reading argues about whether you SHOULD send it and this one is that it will not work. It is NOT a state diff: a transaction that succeeds can still do something other than what you wanted. It runs against the current block, so one that succeeds here can revert when it lands two blocks later, and a node that refuses is reported as our gap rather than as a failure. Parts that did not run are listed under `ran` with why, because a check that did not happen must never look like one that passed. Requires `action.check` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
/v1/preflightIs there anything against THIS payment: the amount, the chain and the endpoint, against what the address has actually done.
Three verdicts and the third is never a weak yes: nothing-against-it, look-first, and cannot-say, which is what a chain we do not read or a proposal with nothing to check returns. It never says a payment is safe — it cannot see what the payment is for or what the two parties agreed. A listed price is the seller’s own published figure and can be stale; an amount above anything an address has received is unusual rather than wrong, and absence on a chain is not evidence against it. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| to | string | required | The address about to be paid. |
| amountUsd | number | optional | What is about to be sent, in dollars. Left out, the amount checks say they were not run rather than passing. |
| chain | string | optional | The chain it is about to be sent on, e.g. base. |
| resource | string | optional | The http(s) URL of the endpoint being bought, where there is one. This is the sharpest check available: its catalogue entry carries the seller’s own price and payee, and where we have probed the endpoint, what IT demanded in its own 402 is compared against both — with every disagreement kept rather than resolved. `againstEndpoint.price.againstQuote` answers in one word whether the amount is an exact-match, below-quote, above-quote or unknown against that live demand, and BELOW IS NOT A PASS: less than what the endpoint is asking does not buy the call unless its terms allow a partial payment, which we cannot see. |
/v1/agents/{address}/historyWhat one agent’s record looked like on each of the last N days.
PAID: one cent a call over x402, the median price of this rail, where payment is enabled on the deployment. The check itself stays free. This is a record we keep rather than one the chain gives away, and it begins on the day we started keeping it (2026-09-22), which the answer states rather than implying with an empty series. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| address | string | in path | An EVM or Cardano address. |
| days | number | optional | How far back, 1 to 365. Default 90. |
/v1/counterparty/bulkCheck up to fifty addresses in one call, each with its observations, and what the SET shares underneath.
PAID TWO WAYS, and they buy different things. One cent a call over x402 where payment is enabled, priced per call rather than per address, buys the per-address rows. The `shared` block — the parties that two or more of these addresses pay — needs `dependency.shared`, on Analyst and above: it is the one answer no per-address row can contain and no public figure holds, because the input is your own shortlist. Without it the block says so and names the capability rather than being absent. An address on a chain we do not read carries `readable: false` rather than a row of zeros. Free.
What a shock costs you: look-through exposure, liquidation distance, forced selling.
/v1/riskPrice a holding you already have: look-through exposure and what a move costs you.
Losses are pro rata on vault equity; a waterfall or first-loss tranche will not lose pro rata. Requires `risk.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
{
"holdings": [
{
"vault": "hyperliquidity-provider-hlp-dfc24b",
"usd": 1000000
}
],
"shocks": [
-0.1,
-0.2
]
}/v1/stressWho is liquidated as one instrument moves, and at what named price.
Only the named instrument moves, so every figure is a floor. Requires `stress.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| instrument | string | required | |
| scale | string | optional | 'sigma' places rungs by distance rather than round numbers. |
| sigmas | string | optional | Comma-separated rungs, in standard deviations, replacing the defaults. |
| horizonDays | number | optional | Default 30. |
/v1/cascadeThe forced selling that follows a first round, and where it lands.
Impact from quoted depth reads about four times below the volatility model; the response says which was used. `converged` false means the simulation hit its round ceiling with liquidations still arriving, so the totals are a floor rather than the end of the cascade. Requires `cascade.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| instrument | string | required | |
| shock | number | required | Fractional move, -0.95 to 0.95. |
| horizonDays | number | optional | Default 30. Sets the volatility window behind the impact model. |
/v1/correlationWhether vaults are the same trade once the market is removed.
Every figure carries its overlapping days and an interval; an indistinguishable pair says so. `sameTrade` IS A BOOLEAN WHOSE FALSE MEANS THREE THINGS, and which one it is has to be read off `residual`: residual null means we could not compute it, `residual.distinguishable` false means we could not tell the correlation apart from zero at all, and only a populated, distinguishable residual below the threshold means the pair is measurably NOT one trade. Counting every false as “different trades” folds two kinds of ignorance into a finding, which is the one reading this field must not be given. `agentManagedOnly` echoes your own request rather than describing the data: true is the agent-managed cohort, false the whole universe. Requires `correlation.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| vaults | string | optional | Comma-separated slugs, two to twelve. Omit for the cohort. |
| agentManaged | boolean | optional | Restrict the cohort to machine-managed vaults. |
| limit | number | optional | Default 20, capped at 50. |
/v1/vaults/{slug}/exitsWhat actually left this vault, beside what the venue says could.
A LEVEL and a FLOW, kept apart and never divided by one another: `declaredWithdrawableUsd` is what could be taken out at this instant, and every figure under `windows` is a sum over a period. We record withdrawals that SETTLED, so this does not see an attempt that reverted, a queue or notice period somebody waited through, or a gate that refused them before they submitted — which means an empty register is “nobody withdrew” OR “nobody could” and cannot be read as either. `largestEverUsd` is the most useful line: somebody actually got that much out, on a day we can name, which is evidence about the past and never a promise about today. AND WHERE THERE IS NO CAPACITY FIGURE AT ALL, `capacityRefusal` says WHY, which is the field to read before concluding we have a gap: `whose` is `the-contract` where the answer does not exist to be read and asking again returns the same thing, `ours` where the work is still ours, and `neither` where the exit IS read and simply does not run through this contract. On the vaults whose strategy walk ended in attested accounting it carries `postedValue`: a party POSTS what the vault is worth rather than deriving it, and `selectorReturnedThePosting` is true where what `maxWithdraw` answered IS that posted figure — byte for byte on the largest of them. It is never false, because a figure that does not match may be our own unit assumption rather than the contracts disagreeing. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path | The vault slug, or its contract address — either resolves. Case is ignored, and an address held by two vaults is refused rather than guessed. |
| exitAmountUsd | number | optional | A position to weigh the exit against, in US dollars. With it the answer carries `exitCapacity`: whether that much is inside what the vault declares withdrawable RIGHT NOW, and separately whether a single withdrawal at least that large has ever been recorded. Two readings, never averaged, and `unknown` where the evidence is not there. Without it `exitCapacity` still describes the capacity itself and the four amount-dependent fields are null. |
/v1/vaults/{slug}/redemptionWhat a withdrawal does to the strategy behind it, and whether it could be paid at all.
The venue returns at most a hundred depositors, so holder counts are floors. For a lending vault the binding constraint is liquidity rather than margin: scenarios carry exceedsLiquidity and unmetUsd, and a null withdrawableUsd means unread rather than unlimited. Three flags about one withdrawal, and none is a verdict: `emptiesVault` leaves nothing behind; `breachesMaintenance` is computed only where the withdrawal is neither zero nor emptying, so its false can mean the test did not apply; `lockedByUtilisation` is NULL where idle or TVL is unread and false where it was measured and the vault is not locked. Requires `redemption.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path | The vault slug, or its contract address — either resolves. Case is ignored, and an address held by two vaults is refused rather than guessed. |
| exitAmountUsd | number | optional | OPTIONAL, and the answer is useful without it. With a position named, `exitCapacity` weighs that size against what the vault declares withdrawable at the last reading. `largestEverUsd` is NULL here rather than zero because this route does not read flow history; /v1/vaults/{slug}/exits answers that half and keeps the two apart. |
/v1/changesWhat changed: which vault stopped being exitable, which died, which raised a new observation.
The feed began when it began, nothing before that was missed, it was not yet running. A change on a vault holding under $50,000 is not announced. `wasLocked` describes the state BEFORE the change being reported, not after it. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| kind | string | optional | exit.locked | exit.freed | vault.died | vault.reopened | observation.raised |
| days | number | optional | How far back. Default 30. |
| minUsd | number | optional | Floor on the capital behind the change. |
| limit | number | optional | Default 50. |
| offset | number | optional |
/v1/liquidityWhich lending vaults cannot be withdrawn from today, and how long that has been true.
Read for Euler, Morpho and Lista. A vault absent here is one whose liquidity we do not read, never one with none locked, and the history begins the day we started keeping it, because a market’s past free liquidity needs an archive node nobody offers free on BNB. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| locked | boolean | optional | Only the vaults nothing can be withdrawn from. |
| minUsd | number | optional | Floor on capital. |
| days | number | optional | How much history to return per vault. Default 30. |
| limit | number | optional | Default 50. |
/v1/liquidity/unmovedHow long each book has sat fully lent, and whether anybody acted.
ANSWERS EVERY CALLER, and the capability named here is what the naming part costs rather than what the route needs to reply. The totals are complete at any tier; the books, their curators and what each is lent against come with a plan or one call at a time over x402 — send a PAYMENT-SIGNATURE on this same URL. A public caller gets 200 with two rows and `withheld` counting the rest, never a 402, so a generated client should not expect one. Times the consequence, not the control: we do not hold LTV or caps, so this measures how long a depositor could not leave rather than when a parameter was changed. A duration marked "atLeast" began before our readings did, and the register starts the day we started keeping it. Requires `reactivity.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| minUsd | number | optional | Floor on the capital stuck. |
| collateral | string | optional | Restrict to one collateral. |
| limit | number | optional | Default 50. |
/v1/exposure/nettingWhere longs and shorts cancel, and where they only appear to.
`crossesMargin` is true where any instrument in the book shares a margin account with another, so the offsets are not independent. False means none of the instruments we read do, which is not a statement about instruments we do not read. Requires `positions.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
/v1/operators/{slug}/exposureOne operator’s book across every vault it runs.
`offsetCrossesMargin` is true where an offsetting position sits in a different margin account, so the two do not net in a liquidation even though they net on paper. Requires `positions.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path |
/v1/coverage/capitalHow much of the market each vault reading actually covers, by vault count AND by capital.
FREE on purpose: this is a statement about OUR limits, and a coverage claim nobody can audit is marketing. NEITHER figure is "the coverage": `byCount` is the share of open vaults with the reading, `byCapital` the share of the capital in them, and they differ by a lot because more than half the vault COUNT is one venue’s small automated vaults. `byCapital` is null where no capital was read at all. Says what we CAN read and never whether a vault is any good; which vaults are missing a reading is the paid screen. Free.
/v1/vaults/risk-dimensionsSix risks across every vault we score, and where the one word reads better than the vault does.
NOTHING HERE IS A NEW MEASUREMENT: the levels are grouped from components the risk scorer already published, and the controller level is read from the controller columns and is NOT part of `band`. The population finding and every denominator are FREE; the LIST of which vaults it applies to needs `dimensions.read`, and `locked` says so rather than returning an empty list that looks like no matches. A vault never scored is counted in `denominators.neverScored` and is absent from the population, because unscored is our coverage and not a reading. On this route the evidence dimension counts components only and `coverage.cannotSeeCount` is 0 for every vault, meaning not computed here — the per-vault /v1/vaults/{slug}/check computes it properly and is free. Free.
/v1/vaults/earnedWhat depositors actually kept, against what their vault advertised.
NOT THE VAULT’S RETURN, which is what a unit held throughout would have earned. This is what the people in it kept, read from the depositor register and set against the venue’s own quote scaled to how long they have held. THE CAVEAT IS PART OF THE FIGURE: a depositor who withdrew everything is not in the register, and the ones who left after losing money are exactly the ones a return figure most needs, so this reads BETTER than the truth rather than worse. `gapPoints` is in percentage POINTS, not per cent: a depositor cannot lose more than they put in. Only vaults whose advertised rate is inside the band this platform will stand behind are listed, so a venue quoting two thousand per cent cannot lead the list with its own arithmetic. The aggregate and the six widest gaps are free; the rest of the list needs `earned.detail`, and `withheld` says how many are not here. Free.
/v1/capital/portfolioRead the vaults you hold TOGETHER: what they share, and an exit that is not the sum of their exits.
It cannot verify that you hold any of this, and nothing here is advice: no allocation percentage and no expected return, ever. The exit is reported in four figures that are never added up — declared, behind a queue, floor-only and unread — because a portfolio that is 40% readable is not a portfolio that is 40% liquid. Where you state no amount for a holding, every dollar figure counts only the holdings you priced and says so rather than summing part of a portfolio and calling it the portfolio. The exit split, the coverage and the bands are FREE, as is every per-vault reading behind them; a plan buys what the holdings SHARE. Requires `liquidity.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| holdings | string | required | slug:usd,slug:usd — the amount after each slug is optional. At most 50. |
/v1/capital/deploymentHow much of the capital we track we can watch being used, by kind of vault.
Knowing how much a vault holds and knowing what it does with it are different readings. Every dollar counted sits in a contract anybody can check; what is missing is our reading. And where positions ARE read, a hedge on a centralised venue is invisible to us, so a one-sided book may be flat. Free.
The market around a position, crowding, correlation, who leads and who follows.
/v1/crowdingWho is in the same trade, and what share of the venue that is.
`coverageSuspect` IS FALSE FOR TWO DIFFERENT REASONS and `coverageRatio` tells them apart: a ratio we measured and found ordinary, or no ratio at all because the venue publishes no open interest to compare against. Reading false as “coverage is sound” hands our ignorance to a reader as a clean bill of health, in the one case that most deserves the caveat. Where `coverageRatio` is null the share figures beside it describe what WE can see and not what the market holds. A ratio above 2 is the suspect case: gross counts both sides of a book and a venue’s open interest counts one, so approaching 2 is ordinary and past it something is being counted twice. Requires `crowding.summary` — PUBLIC or PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| instrument | string | optional | Restrict to one instrument. |
| members | boolean | optional | Include cluster membership. Needs a plan. |
| asOf | string | optional | Read the book as it stood. Pins to the newest snapshot at or before this instant, and the reply says which one it landed on. |
/v1/crowding/historyWhether a crowd is building or dispersing.
Requires `crowding.timeseries` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| instrument | string | optional | |
| days | number | optional |
/v1/fundingWhat holding a perpetual costs or pays, and who is on each side.
Annualised simple. A vault collecting today can be paying tomorrow. `longsPay` has THREE states: true, false, and NULL where the question has no answer, either because we hold no rate or because the rate is exactly zero and neither side pays. It was two-state until 2026-09-27, when a quarter of recent instrument rows turned out to be exactly zero and were being published as shorts paying. `instrumentsWithoutRate` counts what every total leaves out. Requires `funding.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| limit | number | optional | Default 25. |
/v1/marketsWhether an instrument is still a market, and how much notional is marked in ones that are not.
Requires `markets.read` — PUBLIC or PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
/v1/lead-followPairs whose trades follow one another sooner than chance accounts for.
It does not say copying. A shared vendor, one operator, or the same obvious level all look identical from a timestamp. `sdFloored` says the standard deviation used was the floor rather than the sample’s own, which happens on thin samples and makes the z understate. `exceedsAllShifts` is true when no shifted recount reached the observed value, the strongest this permutation test can say, and it is still bounded by `pPermutationFloor`. `truncated` means the field walk hit its ceiling, so the field list is partial. Requires `leadfollow.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| days | number | optional | Default 7. |
Series over time. Every point is a measurement taken then, never a backfill of what we believe now.
/v1/history/capitalDaily measured capital, split by who decides.
`reconstructed: true` marks a day REBUILT from stored value history rather than observed on the day itself, and it is a different measurement wearing the same name: a rebuilt day carries today’s cohort and classification, so it is survivorship-biased backwards. Compare a run of reconstructed days with a run of observed ones and the shape of the series is partly our own construction. Requires `crowding.summary` — PUBLIC or PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| from | string | optional | ISO date. Defaults to a year back. |
| to | string | optional | ISO date. Defaults to today. |
Commit to a decision before executing, reveal it afterwards, and let anyone check the two match.
/v1/resolveSettle a condition about a vault from our records, or decline to.
Deterministic and recomputable. A window we do not fully span is refused rather than answered on the part we hold. No collateral backs it. `spansQuestion` false means the observations do not cover the window asked about, AND is also what you get when there are no observations at all; `observations` is the field that separates them. Free.
{
"kind": "max_drawdown_below",
"vault": "hyperliquidity-provider-hlp-dfc24b",
"from": 1735689600000,
"to": 1751328000000,
"threshold": 0.1
}/v1/vaults/{slug}/assessmentA hash-committed assessment, in the shape ERC-8004’s Reputation Registry points at.
ASPERN does not publish on chain; the call arguments are returned for whoever does. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| slug | string | in path | The vault slug, or its contract address — either resolves. Case is ignored, and an address held by two vaults is refused rather than guessed. |
| methodology | string | optional | Build the document at an older methodology version, so a hash issued under it stays checkable. /v1/meta lists what this build can produce. |
Whether a sequence is intact, checkable without trusting us.
/v1/registryPre-register a strategy before it runs. Free.
Free.
/v1/registryDeclarations already signed.
`strategyLodged` is true where a sealed strategy was committed with the declaration, and false where the declaration stands alone — not a judgement on either. `isDemonstration` marks a record WE wrote to show the format: it is not a claim by anybody and must never be counted as a declaration, and some sit inside anchored roots because they were committed before being labelled. `proofChecked` IS FALSE FOR TWO UNRELATED REASONS and `proofState` says which: `not-anchored` means the declaration has not reached a batch yet, which is a wait; `failed` means it IS anchored and the Merkle proof does not check out against the recorded root, which is evidence that something is wrong with a record we published. On a surface whose whole point is verifiability those must never read the same, so treat `proofChecked: false` alone as unusable and read `proofState`. A proof is published only where it verifies: `proof` is null in both false cases. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| address | string | optional | |
| management | string | optional | |
| limit | number | optional |
/v1/commitCommit to a decision before executing it.
Free.
/v1/revealReveal it afterwards; we match it against the chain.
Free.
/v1/attestation/{digest}Whether a sequence is intact. A gap voids the grade permanently.
Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| digest | string | in path |
Watches you arm, and the webhooks they fire.
/v1/watchesYour alerts, where they deliver, and what has fired.
With no webhook registered a firing is recorded and readable but reaches nothing. Requires `crowding.members` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
/v1/watchesArm an alert, or change its threshold.
The threshold is a fraction, not a percentage: 0.9 means 90%. The one exception is `unresolved`, which is a number of days. Changing a threshold re-arms, so the next crossing fires. `rests` is the only kind here about somebody ELSE’s dependency rather than your own exposure: at 0.5 it fires when more than half of what an agent spends goes to a single party, which nothing in that agent’s own record will show you. Requires `crowding.members` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
/v1/payment/inspectRead the transaction you are about to sign, against what you say you intend.
It reads three calls and refuses to guess at any other: decoding unknown calldata without the ABI means guessing where each argument begins, and a confident wrong answer about where money goes is worse than none. `undecodable` is NOT a pass. Amounts are atomic units, never dollars, because the token owns its decimals. Anything you leave out of `expect` is reported as not compared rather than as agreeing. Nothing is stored. Free.
/v1/receiptsRecord what you did, so it can be checked later.
The reference is yours and is stored WITHOUT being verified: a transaction hash we have not read is your evidence, not ours. `outcome` comes back null and stays null until something reads what actually happened, so a receipt nobody has checked never looks like one that settled. Readable only by the key that wrote it. Free.
/v1/watch/subjectBe told when the answer about an address changes.
The first run records a reading and reports nothing, because there is nothing yet to compare against. An unknown dimension or policy is refused rather than dropped: a watch that quietly ignores half of what you asked for is worse than no watch. One watch per subject per key; calling again edits it. Free.
/v1/watches/{id}Disarm an alert.
Requires `crowding.members` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| id | string | in path | Watch id. |
Your balance, quota, plans and this document.
/v1/freshnessHow old every figure on this platform is, and which readers have stopped.
A job that has NEVER run is not a job that ran long ago — neverRun says the first, an old timestamp the second. Late is measured against each job’s own cadence, with two intervals of grace, because one threshold cannot describe an hourly job and a daily one. `hasNoCode` marks a scheduled job with no runner — it cannot run and nothing else here would say so — but it is read from a list the scheduler writes, and where that is missing the list is empty and every job comes back false. `codeCheckRan` says whether it was written: false makes every `hasNoCode` on the answer unusable rather than merely absent, because the reassuring value is the default. `lastOk` is the last RUN’s outcome and says nothing about one now in flight. `overCeiling` and `nearCeiling` are false for THREE reasons and only one is good news: the job ran inside its ceiling, or no ceiling is configured for it, or it has never recorded a duration. Read `ceilingMinutes` and `neverRun` beside them before treating false as a pass. Free.
/v1/openapi.jsonThis document. Free, so it can be read before deciding whether to buy a key.
Free.
/v1/plansPrices and quotas. No key needed.
Free.
/v1/populationsWhat every population key in meta.coverage.population means. Free, so a key can be resolved without scraping a page.
Keys marked decomposed narrow stage by stage at /reconciliation, where every row that falls out is named along with the reason. Not decomposed is an honest state rather than a gap: inventing an intermediate stage nobody measures would imply a count that was never taken. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| key | string | optional | Resolve one key instead of listing all. An undeclared key answers 404, never an empty list. |
/v1/lineageWhat every lineage block on this API means, field by field, and why several of them are null.
A vault read from a venue HTTP API has no block number and a document assembled from four readers has no single source event id. Those fields are null with a reason rather than filled with something plausible. Free.
/v1/proofThe capital and payments a reader can re-derive themselves, and the gaps, weighted by capital.
Nothing is corrected from these checks: a disagreement has more than one possible owner. Hyperliquid is absent by nature — an order book has no contract holding the capital to be asked. Two claims of different strength travel here: the confirmed total was asked at the head with no block recorded and cannot be repeated, while sample.rows name the block and can be. Twenty sample rows are complete at every tier; the rest are listed with the vault and contract withheld and need positions.read. Free.
/v1/examplesIdentifiers each parameterised route currently answers to, read from the data as the request is served.
Derived, never listed: a hand-kept fixture list is correct on the afternoon it is written. A name here is one the endpoint answered to a moment ago and not a promise that it will forever, because these populations change as vaults open and instruments stop being held. Free by design — a catalogue of valid inputs that itself needs a paid capability leaves the caller who most needs it still guessing. Free.
/v1/checksEvery claim we make, written so a consumer can test it without calling us again.
A check marked executable:false cannot be repeated by a machine and says why on the row. Both families in that state today are our gap rather than the source’s: the withdrawable calls were made at the head with no block recorded, and most outside comparisons were obtained by a person reading a page. Open at every tier by design — a proof somebody has to buy before they can test it is a receipt. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| family | string | optional | Run one group only. The families and their sizes are in every response, so this needs no separate lookup. |
/v1/differencesWhere our figures disagree with a public source, and the stated reason.
The ratio is not an error rate: most pairs are different units over different windows. A row with state=open is a difference we have not explained, published rather than left out. Free.
/v1/limitsWhat is withheld from an unkeyed caller, per surface, and what a plan opens.
A short list is not necessarily all of the data. This says how many rows come whole, how many follow with one field withheld and which field that is, so a caller never has to infer a limit by hitting it. `charging` is whether money is being taken for this surface right now, which is a setting and not a fact about your account. Free.
/v1/accountYour balance, quota and deposit address.
Free.
/v1/agents/railsHow many registered agent identities ever appear on a rail that moves money.
A floor: the registry side is the identity token’s holder and the rail side is whoever paid, so an operator using two keys reads as absent. Three rails are read — ACP, the Olas mech marketplace, x402. Free.
/v1/agents/changesWhat moved in the agent record, across every agent, since a timestamp.
ONE ROW PER FIELD THAT MOVED, not per address: an agent whose dealings and concentration both moved is two facts, and folding them would make you diff our answer to find out what we meant. /v1/changes is the VAULT feed and has always carried only a handful of agent events; what moves daily is the record itself, on roughly one agent in five. A day with no row for an address is a day it did not move, never a day we did not look. The feed begins at `from`, the first day held, and asking for anything earlier answers from there rather than pretending nothing happened. Requires `agents.changes` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| since | string | optional | An ISO date or timestamp. The last seven days if omitted. |
| address | string | optional | One address, where you want its movement rather than everybody’s. |
| limit | number | optional | Default 1,000, capped at 5,000. `capped` says the page was full. |
| offset | number | optional |
/v1/counterparty/agentsFind agents by their record: rail, independent payers, dealings, ACP completion, whether their service answers, whether they pay their payers back.
Every filter is a floor or an exclusion on a measured dimension, and the order is by one named dimension; there is no composite "best". Only addresses that have been paid are listed. A Cardano seller is listed by the enterprise address of its payment credential. Free.
| Parameter | Type | In | What it does |
|---|---|---|---|
| rail | string | optional | x402, acp, mech or masumi: what kind of dealing. |
| network | string | optional | base, polygon, ethereum, arbitrum, optimism, gnosis or cardano: where it settled. Composes with `rail`, and means the address HAS dealt there, not that it only deals there. |
| service | string | optional | What it was paid FOR, matched exactly against the service names the ACP protocol recorded and the descriptions sellers published. Their words, not our classification, so the spelling is theirs: /v1/counterparty lists the commonest. |
| minIndependent | number | optional | At least this many counterparties it never paid back. |
| minDealings | number | optional | At least this many dealings. |
| minCompletion | number | optional | Completed ACP jobs over those that ENDED, as a decimal fraction 0 to 1. A conditional rate: one agent reads 0.9776 while 38.8% of its 9,894 jobs carry no phase we could decode, so it describes a third of that agent’s work. Pair it with minOutcomeCoverage. |
| minOutcomeCoverage | number | optional | The share of ALL this agent’s ACP jobs that reached an outcome at all, 0 to 1 — what minCompletion covers. Excludes every agent with no ACP jobs. |
| minUsd | number | optional | At least this much priced value, USD. |
| registered | boolean | optional | Holds a registration in a registry we read. |
| serving | boolean | optional | A declared callable service answered our probe. |
| noTwoWay | boolean | optional | Pays none of its payers back. |
| notConcentrated | boolean | optional | No single counterparty above half of ten or more dealings. |
| spreadSuppliers | boolean | optional | It pays somebody, and no single party takes more than half of what it SPENDS. The other direction from notConcentrated: an agent with forty payers and one supplier passes that one and is one supplier away from stopping. A seller with too few suppliers to have a share passes, because too few to measure is not evidence of concentration; a seller with NO suppliers does not, because nothing to measure is a different thing — that was 2,723 of the 3,121 rows this used to return. `supplierSpread=diversified` is the filter for the measured ones alone. |
| supplierSpread | string | optional | diversified, concentrated, insufficient-data or none. The precise form of `spreadSuppliers`, which admits a seller with too few suppliers to have a share — deliberately, since too few to measure is not evidence of concentration, but indistinguishable in a list from one that was measured and found spread. `diversified` is the measured one. `none` means it pays nobody on a rail we read, which `insufficient-data` does not: the share is null for both and the supplier COUNT is what tells them apart. |
| activeDays | number | optional | Paid within this many days. |
| q | string | optional | Name fragment, from its registrations. |
| sort | string | optional | independent (default), dealings, usd, completion, recent, paidBack, largestShare, counterparties, serving, suppliers, largestSupplierShare or spent. The last three order by what an agent rests on rather than by who rests on it. |
| dir | string | optional | desc (default, largest first) or asc. A missing value sorts last either way. |
| limit | number | optional | Default 50, capped at 200. |
| offset | number | optional |
/v1/counterpartyWho gets paid on the agent rails, by how many parties that stand apart from them, and how many registered identities were ever paid at all.
Rebuilt nightly from the rail tables. A dealing is an x402 sale or an ACP job; the two are counted in one unit because both name the other party, and mech deliveries, which name no payer, are kept beside. Longer payment loops are not read yet. Free.
/v1/agents/claimsWhat agent cards claim about the other registries they are in, and whether the claim holds.
Three states and they never merge: found, absent on a registry we read over its whole life, and unread — which is our coverage gap rather than their claim. A self-claim, naming the registry the card is already in, is counted separately and is not a link. `selfClaim` is a card naming the registry it is already in: true means the claim points at itself and establishes no link. Free.
/v1/agents/workEvery rail this platform reads, counted from each rail’s own table.
The units differ per rail and are named rather than added together: a channel is not a payment, a job is not a payment, and a registration is not either. A rail with no span carries no event time we can trust — the Solana registries are read by scanning the program, so the only date on a row is when we read it. Free.
/v1/agents/deliveryWho delivered and who did not, provider by provider, on the ACP job book.
Rates are over jobs that REACHED an outcome. A job still in negotiation has not failed, and one we never saw a phase for is a gap in our reading rather than an event in the market; both are reported beside the rate rather than folded into it. Requires `delivery.read` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
/v1/agents/payersWho pays on x402, separated by observable behaviour rather than by label.
No class claims “agent”. They separate behaves-like-a-program from we-cannot-tell. Payments are counted, never summed across tokens. Free.
/v1/dependenciesHow much capital stands behind each owner, curator and guardian key.
Ranked by dollars behind a key, not vaults per key — nine vaults holding a billion and nineteen holding half of that are different findings. Ownership renounced to the zero address is its own state and never folded into unknown: one means nobody can change the vault, the other means we could not read it. The named keys need a plan; the aggregate does not. `rolesOverlap` is true where more than one of owner, curator and guardian is held by the same party. False means the roles we could read are held apart, which is not proof they are different people. Free.
/v1/rwaReal-world exposure by class, never summed across classes.
A tokenised Treasury bill and a USDC balance are both “RWA exposure” in a press release and nothing alike in a risk report; on our numbers they differ by a factor of thirty. The classes are separate figures and there is no total. The holdings list needs a plan; the class totals do not. `attributed` is whether an exposure could be placed with a named issuer or instrument; false means unattributed in our reading, never that nobody holds it. Free.
/v1/tradingFour perpetual venues, every open GMX position, and what attributable agents owe.
The exposure figures are NOT one measurement and there is deliberately no total — GMX, dYdX and Avantis publish open interest, Ostium is summed notional over a capped sample and is a floor. Leverage is size over collateral computed here; the venue’s own leverage field disagrees on two positions in five and the ratio is published rather than either being called correct. Named positions need a plan; the shape of the book does not. Free.
/v1/lifecycleSix agent-commerce rails on one seven-rung ladder.
The rungs are never added together: a registration, a request and a settlement answer different questions, and a total mixing them lets a rail with a chatty request event outrank one where money moves. An empty cell says which of three things it is — never happened, cannot happen on this rail, or we do not read it. Free.
/v1/revenueWhat agent work is advertised at, and what happens to the money afterwards.
A published price is not a sale. The treasury residual is our own reach and not agent behaviour: revenue moved to an exchange, to an unlinked treasury address, or onto a chain we do not read look identical from here, so it is unexplained rather than withdrawn. Two of the four outcomes are bounded by our coverage rather than by behaviour. Free.
/v1/x402/evidencex402 payments by what can be shown about them: route, facilitator, candidate.
An authorised EIP-3009 transfer proves a transfer, not an x402 payment. No tier is protocol-confirmed: the HTTP 402 receipt never reaches a chain. Free.
/v1/collateral/{symbol}Which vaults lend against one collateral, and at what size.
PAID. The vault answer carries the TOTALS free - how many vaults share the exposure and how much they hold - and this is which ones they are, the same split the per-market rows already make. The denominator is THE VAULTS WE TRACK and never a market: nobody publishes how much of an asset is pledged across all of lending, so this says who falls together rather than how large the fall would be. A vault lending against this asset on a venue we do not walk is absent from the crowd rather than counted at zero, and a slice named `idle` is the absence of an exposure and joins no crowd. Requires `crowding.members` — PRO or ENTERPRISE. Without it the call answers 402 with the amount and a deposit address.
| Parameter | Type | In | What it does |
|---|---|---|---|
| symbol | string | in path | The asset symbol as the venue writes it, e.g. wstETH. Case is ignored. |
/v1/coverage/measuresFive coverage measures, each with its own numerator, denominator and population.
They are not comparable with one another and are never summed. Position notional is divided by notional, never by capital: a levered book cannot inflate a capital figure. Free.
/v1/perimeterVenue-level readings behind the coverage perimeter.
The perimeter itself is a page, because its coverage claims are checked against the database at read time; a cached copy would be exactly the drift it exists to catch. Free.
/v1/evidence/regressionsReadings we had at the previous check and do not have now, per fact, with the vaults named.
FREE, for the reason /v1/coverage/capital is: this is a statement about OUR reliability, and one nobody can audit is marketing. A REGRESSION IS NOT A FAULT — a vault that stopped publishing a figure has broken nothing of ours, and a reading withdrawn because it was wrong is a good day — but it may never be silent, which is what this route exists to prevent. `checked: false` means the comparison has not run or could not be read and is NOT a clean result: `lost: []` beside it says nothing. Vaults that CLOSED are not counted, because a closed vault takes its readings with it. It compares the last two checks and not the whole history, so it says what changed rather than how long something has been missing. Free.
We POST to your endpoint when a watch fires. The signing scheme below is also shipped as verifyWebhook in our published package, the same function we sign with, so this description cannot drift into a scheme subtly different from what you receive.
watch.firedA watch you armed has crossed its threshold.
Sent to every active endpoint registered for this event, at most once per watch per crossing. **Verifying.** Each request carries `x-aspern-signature: t=<unix-seconds>,v1=<hex>`, where the hex is HMAC-SHA256 over the exact string `<t>.<raw request body>` keyed by your webhook secret. Sign the BYTES you received: re-serialising the parsed JSON produces a different signature and the delivery becomes unverifiable. **Replay.** Reject a timestamp more than 300 seconds from your own clock. Compare the digest with a constant-time comparison. **Retries.** A non-2xx is retried with exponential backoff from one minute, doubling, capped at six hours, with up to 20% jitter added, never subtracted, so a retry is never earlier than its backoff. A 4xx other than 408 and 429 is treated as permanent and not retried: you understood and refused. **Ordering and duplicates.** Delivery is at-least-once and not ordered. Use the `id` to make your handler idempotent.
{
"id": "whd_2f8a1c",
"event": "watch.fired",
"createdAt": "2026-08-27T09:14:02.117Z",
"watch": {
"id": "wch_91b2",
"kind": "drawdown",
"target": "morpho-steakhouse-usdc",
"threshold": 0.05
},
"observed": {
"value": 0.062,
"at": "2026-08-27T09:12:00.000Z"
},
"subject": {
"slug": "morpho-steakhouse-usdc",
"name": "Steakhouse USDC",
"chain": "ethereum"
}
}webhook.testA test delivery, sent when you ask for one.
Identical in shape and signing to a real event, so a receiver that verifies this one verifies all of them. Sent on demand from the account page, the only way to find out your endpoint rejects our signature before a real alert depends on it.
{
"id": "whd_test_01",
"event": "webhook.test",
"createdAt": "2026-08-27T09:00:00.000Z"
}