{"openapi":"3.1.0","info":{"title":"Ventura","version":"0.1.0","summary":"USD yield on Solana, filtered by rules the caller sets.","description":"Every endpoint is public and takes no key. A wallet address is the only identifier any of them accepts, nothing about a caller is stored, and signing never leaves the caller: transactions come back unsigned and simulated.\n\nFacts are refreshed every 15 minutes and re-fetched before any transaction is built. Every fact carries the time it was read and the upstream it came from, and a fact that could not be read is null rather than a guess — the rules treat it as failing.","license":{"name":"See the repository"}},"servers":[{"url":"https://api.getventura.net"}],"paths":{"/sources":{"get":{"summary":"Every source, with its live facts and the age of each one","description":"Unranked on purpose: which source is right depends on what the caller refuses to hold.","responses":{"200":{"description":"The sources, their reviewed profiles in summary, and live facts.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/sources/{id}":{"get":{"summary":"One source's whole reviewed profile","description":"Evidence links, incidents, issuer, eligibility, redemption terms, and the live facts.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The profile and its facts.","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"No such source."}}}},"/sources/{id}/history":{"get":{"summary":"What the facts cron recorded, newest first","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"hours","in":"query","schema":{"type":"number","default":24}},{"name":"limit","in":"query","schema":{"type":"integer","default":200,"maximum":500}}],"responses":{"200":{"description":"One row per source per cron run, within the retention window.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/exposure/{wallet}":{"get":{"summary":"What a wallet holds, and which of these rules it breaks","description":"Positions of all three shapes — share tokens, lending deposits that are not wallet tokens, and held tokens priced at what selling them quotes — with what each depends on. Unpriced holdings are reported as unpriced, never as zero.","parameters":[{"name":"wallet","in":"path","required":true,"description":"Any Solana address, base58. Nothing about it is stored.","schema":{"type":"string"}},{"name":"preset","in":"query","description":"conservative, balanced or max_yield. Individual rules are overridden in the body.","schema":{"type":"string","enum":["conservative","balanced","max_yield"],"default":"conservative"}},{"name":"jurisdiction","in":"query","description":"ISO 3166-1 alpha-2 of where the holder lives. Left out, every source fails the eligibility rule, because each one names jurisdictions it is not available in — unless declared_eligible says the holder already answered for themselves.","schema":{"type":"string","minLength":2,"maxLength":2}},{"name":"accredited","in":"query","description":"Whether the holder is a qualified, professional or accredited investor.","schema":{"type":"boolean","default":false}},{"name":"declared_eligible","in":"query","description":"The holder declared they do not live anywhere a source excludes, without naming the country. It stands in for jurisdiction against the exclusion lists only: a qualified-investor country cannot be checked without knowing which one it is, and accredited is unaffected. Both left out, eligibility fails as unknown.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Positions, shared dependencies, concentration, and the facts used.","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"summary":"The same, with individual rules overridden","parameters":[{"name":"wallet","in":"path","required":true,"description":"Any Solana address, base58. Nothing about it is stored.","schema":{"type":"string"}},{"name":"preset","in":"query","description":"conservative, balanced or max_yield. Individual rules are overridden in the body.","schema":{"type":"string","enum":["conservative","balanced","max_yield"],"default":"conservative"}},{"name":"jurisdiction","in":"query","description":"ISO 3166-1 alpha-2 of where the holder lives. Left out, every source fails the eligibility rule, because each one names jurisdictions it is not available in — unless declared_eligible says the holder already answered for themselves.","schema":{"type":"string","minLength":2,"maxLength":2}},{"name":"accredited","in":"query","description":"Whether the holder is a qualified, professional or accredited investor.","schema":{"type":"boolean","default":false}},{"name":"declared_eligible","in":"query","description":"The holder declared they do not live anywhere a source excludes, without naming the country. It stands in for jurisdiction against the exclusion lists only: a qualified-investor country cannot be checked without knowing which one it is, and accredited is unaffected. Both left out, eligibility fails as unknown.","schema":{"type":"boolean","default":false}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"rules":{"type":"object","description":"Rules to override on the preset: excluded_risk_types, timelock, require_nav_redemption, max_exit_bps, min_withdrawable_multiple, min_oracle_providers, max_per_source_pct, max_per_dependency_pct, max_evidence_age_days, max_live_data_age_minutes, block_open_incidents, include_reward_apy. An unknown key is refused rather than ignored."}}}}}},"responses":{"200":{"description":"As above.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/rebalance/{wallet}":{"get":{"summary":"What would have to leave for the wallet to fit the rules","description":"A holding is only proposed for sale on a rule it is known to break — never on a fact that could not be read, and never because live data went stale. Being over a concentration limit is a trim to the cap, not an exit, and a trim the allocation could only park as idle USDC is given back with a note.","parameters":[{"name":"wallet","in":"path","required":true,"description":"Any Solana address, base58. Nothing about it is stored.","schema":{"type":"string"}},{"name":"preset","in":"query","description":"conservative, balanced or max_yield. Individual rules are overridden in the body.","schema":{"type":"string","enum":["conservative","balanced","max_yield"],"default":"conservative"}},{"name":"jurisdiction","in":"query","description":"ISO 3166-1 alpha-2 of where the holder lives. Left out, every source fails the eligibility rule, because each one names jurisdictions it is not available in — unless declared_eligible says the holder already answered for themselves.","schema":{"type":"string","minLength":2,"maxLength":2}},{"name":"accredited","in":"query","description":"Whether the holder is a qualified, professional or accredited investor.","schema":{"type":"boolean","default":false}},{"name":"declared_eligible","in":"query","description":"The holder declared they do not live anywhere a source excludes, without naming the country. It stands in for jurisdiction against the exclusion lists only: a qualified-investor country cannot be checked without knowing which one it is, and accredited is unaffected. Both left out, eligibility fails as unknown.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Exits with their reasons, what stays, and where the proceeds would go.","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"summary":"The same, with individual rules overridden","parameters":[{"name":"wallet","in":"path","required":true,"description":"Any Solana address, base58. Nothing about it is stored.","schema":{"type":"string"}},{"name":"preset","in":"query","description":"conservative, balanced or max_yield. Individual rules are overridden in the body.","schema":{"type":"string","enum":["conservative","balanced","max_yield"],"default":"conservative"}},{"name":"jurisdiction","in":"query","description":"ISO 3166-1 alpha-2 of where the holder lives. Left out, every source fails the eligibility rule, because each one names jurisdictions it is not available in — unless declared_eligible says the holder already answered for themselves.","schema":{"type":"string","minLength":2,"maxLength":2}},{"name":"accredited","in":"query","description":"Whether the holder is a qualified, professional or accredited investor.","schema":{"type":"boolean","default":false}},{"name":"declared_eligible","in":"query","description":"The holder declared they do not live anywhere a source excludes, without naming the country. It stands in for jurisdiction against the exclusion lists only: a qualified-investor country cannot be checked without knowing which one it is, and accredited is unaffected. Both left out, eligibility fails as unknown.","schema":{"type":"boolean","default":false}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"rules":{"type":"object","description":"Rules to override on the preset: excluded_risk_types, timelock, require_nav_redemption, max_exit_bps, min_withdrawable_multiple, min_oracle_providers, max_per_source_pct, max_per_dependency_pct, max_evidence_age_days, max_live_data_age_minutes, block_open_incidents, include_reward_apy. An unknown key is refused rather than ignored."}}}}}},"responses":{"200":{"description":"As above.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/allocate/preview":{"post":{"summary":"Where an amount would go under these rules","description":"Deterministic and explained: dollars per source, the reason each one is there, every reason the others are not, what stays idle and why, and a timestamp for every fact used. No optimiser and no scoring. Given a wallet, what it already holds fills its sources' caps first, so the allocation tops up what is short instead of what is already full.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amountUsd"],"properties":{"amountUsd":{"type":"number","exclusiveMinimum":0,"maximum":10000000},"wallet":{"type":"string","description":"The wallet this money is going into. Pass it: a cap is a share of everything its owner holds, not of the amount being moved today, so without it the limits are measured against the wrong total and a source that is already full is offered more."},"preset":{"type":"string","enum":["conservative","balanced","max_yield"],"default":"conservative"},"rules":{"type":"object","description":"Rules to override on the preset."},"jurisdiction":{"type":"string","minLength":2,"maxLength":2,"nullable":true},"accredited":{"type":"boolean","default":false},"declared_eligible":{"type":"boolean","default":false,"description":"The holder declared they do not live anywhere a source excludes, in place of naming the country."}}}}}},"responses":{"200":{"description":"The allocation, the rules it was computed under, and the facts used.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"A malformed amount or an unknown rule."}}}},"/tx/build":{"post":{"summary":"Unsigned transactions for an allocation's legs","description":"Each leg is built from the protocol's own API and simulated here before it is returned; the caller signs and sends. No key is held, and a leg that fails to build leaves the others signable — a 422 carries both. Amounts are decimal strings: USDC for a deposit or a lending withdrawal, the token for a held source, or \"all\" to close a position.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["wallet","legs"],"properties":{"wallet":{"type":"string"},"legs":{"type":"array","items":{"type":"object","required":["source_id","action","amount"],"properties":{"source_id":{"type":"string"},"action":{"type":"string","enum":["deposit","withdraw"]},"amount":{"type":"string"}}}},"jurisdiction":{"type":"string","minLength":2,"maxLength":2,"nullable":true},"accredited":{"type":"boolean","default":false},"declared_eligible":{"type":"boolean","default":false,"description":"The holder declared they do not live anywhere a source excludes, in place of naming the country."}}}}}},"responses":{"200":{"description":"A base64 unsigned transaction per leg, with what its simulation consumed.","content":{"application/json":{"schema":{"type":"object"}}}},"403":{"description":"A deposit the holder may not make under the source's own terms."},"422":{"description":"At least one leg failed to build. The ones that did are in the response."}}}},"/rpc":{"post":{"summary":"Solana JSON-RPC, with a method allowlist","description":"So a caller can send what it signed without holding an endpoint key. Reads, simulate and send only.","responses":{"200":{"description":"Whatever the upstream node answered.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/events":{"get":{"summary":"Public usage counts, rolled up by day","responses":{"200":{"description":"Counts by event name. Nothing in them is about anyone.","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"summary":"Count one use","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"usd":{"type":"number","minimum":0}}}}}},"responses":{"204":{"description":"Counted."},"400":{"description":"A name that is not one of ours."}}}}}}