Developers
Indexer API
Read API over the finalized Shear indexer: routes, query parameters and response shapes.
The indexer reads Shear accounts and transactions at finalized commitment and stores a projection in PostgreSQL. This HTTP API serves that projection. On-chain state stays authoritative: the database never supplies settlement prices or custody balances, and every row records the finalized slot it was read at.
How it works#
- Transactions. The indexer pages
getSignaturesForAddressfor the program at finalized commitment and fetches each transaction. It decodes events only from successful transactions, and only from log lines written while Shear is the active program in the invocation stack. Event-shaped lines from other programs are ignored. - Accounts. Each pass reads every program account with
getProgramAccountsat finalized commitment and stores one snapshot per address, with the context slot and a SHA-256 of the raw data. An older read never overwrites a newer snapshot. An account that disappears keeps its last snapshot and is markedclosed. - Cursor. The transaction cursor advances only after every transaction in the batch has been fetched. A pruned or missing transaction stops indexing until the RPC serves it.
- Governance. With
GOVERNANCE_REALMset, each pass also reads the SHEAR realm from SPL Governance (GOVERNANCE_PROGRAM_ID,GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZwby default): the realm, its governances with their native treasuries, their proposals and the voter records. These snapshots carry the governance program’s ID and follow the same slot andclosedrules. - Loop. The service waits 15 seconds between passes by default (
POLL_INTERVAL_MS). When the keeper is enabled, it runs after each indexing pass; see keeper.
The service needs a Solana RPC that retains the transaction history being indexed, the cluster genesis hash, the program ID and a PostgreSQL database. The governance routes also need GOVERNANCE_REALM.
Conventions#
- Integers
- 64- and 128-bit values (u64, i64, u128) are decimal strings, such as
"12050000000". 8-, 16- and 32-bit values such asbump,bidCount,pendingTickets,otmBps,auctionSecondsandoracleExponentare JSON numbers. - Units
- Base units as on chain: 8 decimals for TSLAx, shares and calls; 6 for USDC and USD marks; 9 for multipliers. See units.
- Names and keys
- Field names are camelCase forms of the Rust names. Public keys are base58 strings.
- Enums
- An object with one key, such as
"phase": { "Active": {} }. - Byte arrays
calendarHashis an array of 32 numbers.- Slots
- Snapshot and transaction slots are decimal strings.
/readyand/operations/runreport the live RPC slot as a number. - Times
- On-chain times such as
expiry,auctionEnd,openandcloseare Unix seconds as decimal strings. Database timesobservedAt,blockTimeandupdatedAtare ISO 8601 strings in UTC. - Chain
chainis the genesis hash the service is configured with. Every query is scoped to it.
Values in the examples below are illustrative.
Routes#
| Method | Path | Query | Returns |
|---|---|---|---|
| GET | /health | — | Process liveness. |
| GET | /ready | — | Database, chain identity and program checks. |
| GET | /vaults | — | Live Vault snapshots. |
| GET | /rounds | limit | Live Round snapshots. |
| GET | /accounts/:address | — | One snapshot of any type, closed accounts included. |
| GET | /events | limit, owner, round | Decoded events with signature and slot. |
| GET | /stake-config | — | Each vault’s bidder stake mint and amount. |
| GET | /bidders | owner, vault, staked | Bidder records: approval and stake. |
| GET | /governance | — | The SHEAR realm, its governances and treasuries, and whether each vault’s admin is one of them. |
| GET | /governance/proposals | state, limit | Proposals, newest first. |
| GET | /governance/voters/:owner | — | A wallet’s voting power and voter records. |
| GET | /jobs | — | The 100 most recently updated keeper jobs. |
| POST | /operations/run | — | One indexer and keeper cycle. Operator route with a bearer token. |
Read routes need no authentication and never sign transactions. Wallet actions such as deposits, bids and claims are built with the SDK and signed by their owners; the service holds no user keys.
Health and readiness#
GET /health#
Returns 200 while the process runs.
{ "status": "ok" }GET /ready#
Checks the database connection, that the RPC genesis hash matches the configured chain, and that the configured program account is executable. With the keeper enabled it also checks that the Pyth receiver and Wormhole programs are deployed. Any failure returns 503.
{
"status": "ready",
"chain": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
"slot": 447774012,
"program": "GG8Db3XMAfAaa4RT38e5uaeFSDjztEyaATpR6B3AxMcn"
}{
"status": "unavailable",
"message": "Database, chain identity, or deployed program validation failed"
}GET /vaults#
All Vault accounts of the configured program that still exist, ordered by address. No parameters. Each item is an account snapshot:
| Field | Type | Meaning |
|---|---|---|
| id | string | <chain>:<address>. |
| chain | string | Configured genesis hash. |
| program | string | Program ID. |
| address | string | Account address. |
| kind | string | Vault, Round, Position, Session, Bidder, StakeConfig, Ticket or Bid. |
| slot | string | Finalized context slot of the read that produced data. |
| data | object | Decoded account. Fields as in accounts. |
| dataHash | string | SHA-256 of the raw account data, hex. |
| observedAt | string | Time the row was last written. |
| closed | boolean | True once the account no longer exists on chain. |
[
{
"id": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d:9Q5nHgxW9864oE6q8JLjp6GioUkkQF1f3EsUcSgVYru9",
"chain": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
"program": "GG8Db3XMAfAaa4RT38e5uaeFSDjztEyaATpR6B3AxMcn",
"address": "9Q5nHgxW9864oE6q8JLjp6GioUkkQF1f3EsUcSgVYru9",
"kind": "Vault",
"slot": "447774012",
"data": {
"admin": "FyYA2nnz9XfzAN3kcbHT3MVw31VGoxffz6m9REJRSKve",
"underlying": "XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB",
"shareMint": "E61GhHfpx832wdG5BneKCzsujGP8au7FfKcaUyvv3NR7",
"custody": "JAVt8HP5aphQPnxisZhXedRHeRCREdcQL2mzUZKDDELe",
"premium": "7N1ieKGMaJXfhuZnJd5wSWx343iahVLZRT9EXDafd98j",
"bump": 254,
"config": {
"otmBps": 500,
"maxConfidenceBps": 100,
"auctionSeconds": 300,
"reservePremiumMicro": "1000000"
},
"premiumIndex": "39000000000000000",
"roundIndex": "1",
"active": true,
"pendingAdmin": "11111111111111111111111111111111"
},
"dataHash": "33112ee14ee469c3eb52fe90322ec81dd404a0093d565a6d71ce77cbc8124e3b",
"observedAt": "2026-10-01T15:02:11.482Z",
"closed": false
}
]GET /rounds#
| Parameter | Type | Default | Range |
|---|---|---|---|
| limit | integer | 25 | 1 to 100 |
Round snapshots of the configured program, ordered by snapshot slot, highest first. Each indexing pass refreshes every live account to the same slot, so rows usually tie: sort by data.index to order rounds. Round accounts are never closed, so past rounds stay in the set. To read one round, derive its address and use /accounts/:address.
[
{
"id": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d:HYjrczoD6N7A1Bh5KXYqCyFChNGwF1d3Lz9ZVN6fLrCN",
"chain": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
"program": "GG8Db3XMAfAaa4RT38e5uaeFSDjztEyaATpR6B3AxMcn",
"address": "HYjrczoD6N7A1Bh5KXYqCyFChNGwF1d3Lz9ZVN6fLrCN",
"kind": "Round",
"slot": "447774012",
"data": {
"vault": "9Q5nHgxW9864oE6q8JLjp6GioUkkQF1f3EsUcSgVYru9",
"index": "1",
"bump": 255,
"phase": { "Active": {} },
"expiry": "1790971200",
"auctionEnd": "1790602500",
"strike": "262629629",
"entryMultiplier": "1000000000",
"offered": "12050000000",
"sold": "12050000000",
"clearing": "3900000",
"premiumTotal": "469950000",
"optionMint": "FRCSNi9nFyLrHXoAhnnhxLpzTiuA5HBCPEbVif2Q9XL8",
"bidCount": 2,
"claimedBids": 1,
"bids": [
{
"owner": "J2jvoZVGZGWv6Y7rVDaUh69YRZEGyBHq17mf4F4YgSac",
"quantity": "8000000000",
"limit": "4250000",
"allocation": "8000000000",
"charge": "312000000"
},
{
"owner": "uTUaJqR2k8J9Z3TrAnAQv664eWvJZRFYC8Umhz7fr9V",
"quantity": "6000000000",
"limit": "3900000",
"allocation": "4050000000",
"charge": "157950000"
}
],
"pendingTickets": 1,
"settlement": "0",
"closeMultiplier": "0",
"observedAt": "0",
"oraclePublish": "0",
"oraclePrice": "0",
"oracleExponent": 0,
"reserveTotal": "0",
"claimedOptions": "0",
"snapshotNav": "0",
"snapshotSupply": "0",
"fallback": false
},
"dataHash": "f998fe06afa0cfbe73e0449dc2b1698309e1b5714960f027b2858312b152c275",
"observedAt": "2026-10-01T15:02:11.482Z",
"closed": false
}
]bids always has 16 entries. The example omits the 14 unused ones, which carry owner 11111111111111111111111111111111 and zero amounts. data.observedAt is the round’s on-chain observation time; the row’s observedAt is the database write time.
In this round 120.5 TSLAx backed 120.5 calls at a strike of 262.629629 USD. Two bids for 140 calls cleared at the lower limit, 3.90 USDC per call, for 469.95 USDC of premium.
GET /accounts/:address#
One snapshot by address, of any account type, including closed accounts. address must be a base58 string of 32 to 44 characters. Returns 404 with {"error":"account_not_indexed"} when the indexer has no row for it.
{
"id": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d:21Df9aJXgcq78P62V4QDHWkPB5t9YCBD3cHYVg5kVPgF",
"chain": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
"program": "GG8Db3XMAfAaa4RT38e5uaeFSDjztEyaATpR6B3AxMcn",
"address": "21Df9aJXgcq78P62V4QDHWkPB5t9YCBD3cHYVg5kVPgF",
"kind": "Position",
"slot": "447774012",
"data": {
"vault": "9Q5nHgxW9864oE6q8JLjp6GioUkkQF1f3EsUcSgVYru9",
"owner": "FPP21sbqhr2LPjSnJkw5NBetPubeFG4PsQFBxHj8noTq",
"shares": "2500000000",
"pendingWithdraw": "0",
"premiumIndex": "0",
"premiumCredit": "0",
"premiumFraction": "0"
},
"dataHash": "97fb5f8538b89f6c1accfd19836b65a73b61fbc2e0cbf84bb858a0fffa3f1592",
"observedAt": "2026-10-01T15:02:11.482Z",
"closed": false
}This position holds 25 shares and was last checkpointed before round 1 finalized. Its claimable premium is 2,500,000,000 × 39,000,000,000,000,000 / 10^18 = 97,500,000 micro-USDC, or 97.50 USDC.
GET /events#
| Parameter | Type | Default | Filter |
|---|---|---|---|
| limit | integer | 25 | 1 to 100 rows. |
| owner | base58 string | — | Events whose owner field equals this wallet. |
| round | base58 string | — | Events whose round field equals this round. |
Ordered by transaction slot, then log index, newest first. owner and round combine with AND. VaultInitialized, SessionRegistered, RoundStarted, AuctionFinalized, ExpiryObserved, ExpiryDisputed, RoundSettled, OracleFailureSettled and RoundClosed have no owner field and never match an owner filter.
| Field | Type | Meaning |
|---|---|---|
| id | string | <chain>:<signature>:<logIndex>. |
| transactionId | string | <chain>:<signature>. |
| logIndex | number | Index of the log line in the transaction. |
| name | string | Event name, such as BidPlaced. |
| data | object | Event fields in camelCase. See events. |
| transaction.signature | string | Transaction signature. |
| transaction.slot | string | Transaction slot. |
| transaction.blockTime | string | null | Block time, ISO 8601. |
[
{
"id": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d:3g1L6drRfeVmrEPdFuV5sHFVaHMnE24T5DpTMycT1HqazZWXhr56dTp8A8yiRVMsy4meTLaJxocyXgBHYzCV7HAe:7",
"transactionId": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d:3g1L6drRfeVmrEPdFuV5sHFVaHMnE24T5DpTMycT1HqazZWXhr56dTp8A8yiRVMsy4meTLaJxocyXgBHYzCV7HAe",
"logIndex": 7,
"name": "AuctionFinalized",
"data": {
"round": "HYjrczoD6N7A1Bh5KXYqCyFChNGwF1d3Lz9ZVN6fLrCN",
"sold": "12050000000",
"clearing": "3900000",
"premium": "469950000"
},
"transaction": {
"signature": "3g1L6drRfeVmrEPdFuV5sHFVaHMnE24T5DpTMycT1HqazZWXhr56dTp8A8yiRVMsy4meTLaJxocyXgBHYzCV7HAe",
"slot": "447121705",
"blockTime": "2026-09-28T13:35:41.000Z"
}
},
{
"id": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d:52hc5aVxXsS8KCg7NsULoqVxt3ZstfhVdv3MfF9NM7jnLxjPPsAHTuASirrv3XPZMBTr3hN1GmWrhPtSEeUzF1rh:9",
"transactionId": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d:52hc5aVxXsS8KCg7NsULoqVxt3ZstfhVdv3MfF9NM7jnLxjPPsAHTuASirrv3XPZMBTr3hN1GmWrhPtSEeUzF1rh",
"logIndex": 9,
"name": "BidPlaced",
"data": {
"round": "HYjrczoD6N7A1Bh5KXYqCyFChNGwF1d3Lz9ZVN6fLrCN",
"owner": "uTUaJqR2k8J9Z3TrAnAQv664eWvJZRFYC8Umhz7fr9V",
"quantity": "6000000000",
"limit": "3900000"
},
"transaction": {
"signature": "52hc5aVxXsS8KCg7NsULoqVxt3ZstfhVdv3MfF9NM7jnLxjPPsAHTuASirrv3XPZMBTr3hN1GmWrhPtSEeUzF1rh",
"slot": "447121180",
"blockTime": "2026-09-28T13:32:07.000Z"
}
}
]GET /stake-config#
The StakeConfig snapshot of each vault that has one: the mint new bidder stakes are paid in and the amount each locks. No parameters. See bidder stake.
[
{
"address": "4nB6fPq3sX9q2tJdN1o1yWm6a9qS1vB3KQfD3qJg8vTq",
"kind": "StakeConfig",
"slot": "447774012",
"data": {
"vault": "9Q5nHgxW9864oE6q8JLjp6GioUkkQF1f3EsUcSgVYru9",
"mint": "GvJRrJo7TQTRM9ALWDkRurGBWi9qk8U66xLrJTMYS2d",
"amount": "1000000000000",
"bump": 253
},
"closed": false
}
]GET /bidders#
| Parameter | Type | Default | Filter |
|---|---|---|---|
| owner | base58 string | — | Records of this wallet. |
| vault | base58 string | — | Records of this vault. |
| staked | boolean | — | true: records holding a stake. false: records without one. |
Bidder snapshots ordered by address; filters combine with AND. A wallet can bid when allowed is true or when stake is above 0 with unlockAt "0". While openBids is above 0, the stake can’t be withdrawn.
[
{
"address": "9MVeDqwaoCGoFmyCYFufdkMH191yuNz1g64VTKkUocoT",
"kind": "Bidder",
"data": {
"vault": "9Q5nHgxW9864oE6q8JLjp6GioUkkQF1f3EsUcSgVYru9",
"owner": "2HMn5D3UYSR4XiZAm5g6MaHTY9vtgHtnXTK7dvbvEQmF",
"allowed": false,
"stake": "1000000000000",
"unlockAt": "0",
"openBids": 1
},
"closed": false
}
]Governance routes#
Served from the SPL Governance snapshots of the configured realm. Without GOVERNANCE_REALM, /governance returns 404 governance_not_configured, and before the first pass it returns 404 governance_not_indexed. Account fields are SPL Governance’s, in camelCase; enums are numbers. See governance.
GET /governance#
The realm, its governances, and each vault’s admin. Each governance carries nativeTreasury, the account that signs what its proposals run, and that account’s balance in lamports. adminIsGovernance is true when a vault’s admin is one of those treasuries.
{
"programId": "GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw",
"realm": { "address": "Ergw4cXCsXnSjcPnbP1orhHFxnYygCzVt5vJGsfX7Xyh", "kind": "Realm", "data": { "name": "Shear", "communityMint": "GvJRrJo7TQTRM9ALWDkRurGBWi9qk8U66xLrJTMYS2d" } },
"governances": [
{ "address": "FQmSXhi4huziUs2Qnr1oBoEkketZC3SNjbABSXPrJn47", "kind": "Governance", "data": { "nativeTreasury": "HQD1DesTPxS6mxZLpXm6i3BwHnJc72HnFdUuJcByVw7J", "nativeTreasuryLamports": 1995000000 } }
],
"vaults": [
{ "address": "9Q5nHgxW9864oE6q8JLjp6GioUkkQF1f3EsUcSgVYru9", "admin": "HQD1DesTPxS6mxZLpXm6i3BwHnJc72HnFdUuJcByVw7J", "pendingAdmin": "11111111111111111111111111111111", "adminIsGovernance": true }
]
}GET /governance/proposals#
| Parameter | Type | Default | Filter |
|---|---|---|---|
| state | string | — | State name: Draft, SigningOff, Voting, Succeeded, Executing, Completed, Cancelled, Defeated, ExecutingWithErrors or Vetoed. |
| limit | integer | 25 | 1 to 100 rows. |
Proposal snapshots, newest draft first. data.stateName is the state as a name; data.state keeps SPL Governance’s number.
GET /governance/voters/:owner#
A wallet’s voting power: the SHEAR it has deposited in the realm, summed over its voter records, as a decimal string in 6-decimal atoms. A wallet with no record has "0".
{
"owner": "4wf4SW6X6PApKP9ra3e3txFFC5ZkGc2JTLNJ5mUA7GD6",
"votingPower": "600000000000000",
"records": [ { "address": "FnWdALw1Zj9uNxagwHu15x1vrdLseq8NZLUaMxdgjWcg", "kind": "TokenOwnerRecord", "data": { "governingTokenDepositAmount": "600000000000000" } } ]
}GET /jobs#
The 100 most recently updated keeper jobs for the configured chain. Signed transaction bytes and keys are never returned. No parameters. See keeper.
| Field | Type | Meaning |
|---|---|---|
| id | string | SHA-256 of chain:vault:round:action, hex. |
| vault | string | Vault address. |
| round | string | Round address, or session:<close> for a session registration. |
| action | string | startRound, finalizeAuction, observeExpiry, challengeExpiry, settleRound, timeoutSettle, closeRound, registerSession or processTicket:<ticket>. |
| status | string | preparing, signed, submitted, completed, retryable or awaiting_timeout. |
| attempts | number | Times the job was claimed. |
| signatures | string[] | null | Signatures of the signed batch. Jobs that post a Pyth update can hold several. |
| result | object | null | { "finalized": true } or { "recovered": true } when completed; an object with lastSignature and slot while submitting. |
| error | string | null | Last failure message. |
| updatedAt | string | Last update, ISO 8601. |
[
{
"id": "2d73201bd8b91bf89da3b303ad56a67873f65bc5b4cab17be356f9ae91530ea2",
"vault": "9Q5nHgxW9864oE6q8JLjp6GioUkkQF1f3EsUcSgVYru9",
"round": "HYjrczoD6N7A1Bh5KXYqCyFChNGwF1d3Lz9ZVN6fLrCN",
"action": "finalizeAuction",
"status": "completed",
"attempts": 1,
"signatures": ["3g1L6drRfeVmrEPdFuV5sHFVaHMnE24T5DpTMycT1HqazZWXhr56dTp8A8yiRVMsy4meTLaJxocyXgBHYzCV7HAe"],
"result": { "finalized": true },
"error": null,
"updatedAt": "2026-09-28T13:36:02.117Z"
}
]POST /operations/run#
Runs one fixed cycle: the readiness checks, one indexing pass, then the keeper if it is enabled. The body must be exactly {}, within 1,024 bytes. The route accepts no transaction bytes, account lists, prices or timestamps. While a cycle is already running, it returns {"status":"busy"}.
curl -X POST "$SHEAR_API/operations/run" \
-H "Authorization: Bearer $OPERATIONS_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"indexed": { "transactions": 0, "slot": 447774012, "decoded": 10, "unknown": 0 },
"actions": [
{ "vault": "9Q5nHgxW9864oE6q8JLjp6GioUkkQF1f3EsUcSgVYru9", "status": "wait_expiry", "until": 1790971200 }
]
}indexed counts new transactions, decoded accounts and unrecognized accounts. Each actions item names a vault and either a keeper action with its job id and status (processTickets carries a tickets array of job results instead), or a waiting status: wait_auction, wait_expiry, wait_timeout, dispute_window, market_closed, insufficient_session_time, weekly_round_already_completed or waiting_for_initial_deposits. A vault that fails reports status: "failed" with a message. Without the keeper, actions is empty.
Errors#
| Status | Body | When |
|---|---|---|
| 400 | {"error":"invalid_request","message":"Request does not match the API schema"} | A path or query value fails its schema, /rounds or /events gets an unknown query parameter, or the POST body is not {}. |
| 401 | {"error":"unauthorized"} | Missing or wrong bearer token on /operations/run. |
| 404 | {"error":"account_not_indexed"} | /accounts/:address has no row for the address. |
| 500 | {"error":"internal_error","message":"The requested operation failed; inspect the service logs"} | Any other failure. |
| 503 | {"status":"unavailable","message":"Database, chain identity, or deployed program validation failed"} | A /ready check failed. |
| 503 | {"error":"operations_disabled"} | /operations/run without a configured token. |