Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.alterscope.org/llms.txt

Use this file to discover all available pages before exploring further.

List endpoints return one page at a time and hand you a cursor to fetch the next. There is no offset/page-number paging — cursors are stable under inserts, so you never skip or double-read a row while paging a live feed. Two cursor conventions exist in the API, depending on the endpoint:
  • Envelope cursor — most list endpoints (e.g. GET /v2/yield/opportunities) return paging state inside meta: an opaque cursor plus a has_more boolean.
  • Replay cursor — the event replay endpoint (GET /v2/events/replay) walks forward by event ID and signals the end with an empty next_cursor.
Both are documented below. Always treat the cursor as opaque: don’t parse it, build it, or persist it across schema versions — pass back exactly what the previous response gave you.

Envelope cursor (meta.cursor + meta.has_more)

GET /v2/yield/opportunities is the reference example. The response wraps its rows in the standard envelope and carries paging state in meta:
{
  "data": [ /* page of opportunities */ ],
  "meta": {
    "total": 412,
    "cursor": "eyJpZCI6MTAwfQ==",
    "has_more": true,
    "limit": 50
  }
}
FieldMeaning
meta.cursorOpaque cursor for the next page. Pass it back as page[cursor].
meta.has_moretrue while more pages remain; false on the last page.
meta.limitThe page size that was applied.
meta.totalTotal rows matching your filters across all pages.

Page size

Set the page size with page[limit]. For /v2/yield/opportunities the default is 50 and the maximum is 200; a value above the maximum is clamped down rather than rejected. Page-size defaults and caps vary by endpoint — the Endpoints group in the sidebar documents each one. Request the next page by passing page[cursor] set to the previous response’s meta.cursor.
Other list families that use the envelope cursor (for example point-in-time snapshots) expose the same cursor / has_more pair in meta. The field names are identical; only the per-endpoint limit default and cap differ.

Loop until has_more is false

Keep fetching while meta.has_more is true, feeding meta.cursor back as page[cursor] each time:
cursor=""
while : ; do
  url="https://api.alterscope.org/v2/yield/opportunities?page[limit]=200"
  [ -n "$cursor" ] && url="${url}&page[cursor]=${cursor}"

  page=$(curl -s "$url" -H "Authorization: Bearer sk_live_...")

  echo "$page" | jq '.data[]'

  has_more=$(echo "$page" | jq -r '.meta.has_more')
  cursor=$(echo "$page" | jq -r '.meta.cursor')
  [ "$has_more" = "true" ] || break
done
Each page counts as one request against your rate limit. Use the largest limit the endpoint allows to minimize round-trips, and watch X-RateLimit-Remaining to throttle before you hit 429.

Replay cursor (next_cursor)

GET /v2/events/replay recovers events you missed during a WebSocket disconnect. It is cursor-based on event ID — not timestamp ranges. Walk forward by passing the last event’s ID as since_event_id, and stop when next_cursor comes back empty.
ParameterDefaultNotes
since_event_idA ULID event ID. The walk returns events after this one. Omit it for the first page. A non-ULID value returns 400 INVALID_CURSOR.
limit100Page size, capped at 1000. Out-of-range or non-numeric values fall back to the default.
typeOptional single event-type filter (e.g. peg.depeg.start). An unknown type returns 400 INVALID_EVENT_TYPE.
subject_idOptional filter to a single subject (e.g. a specific vault or feed).
The replay window is tier-gated. A cursor older than your tier’s window returns 410 REPLAY_WINDOW_EXPIRED, and the Free tier — which has no replay window — returns 404 REPLAY_NOT_AVAILABLE. Per-tier replay windows are in Rate limits & tiers. The response keeps its fields at the top level (it is not wrapped under data/meta):
{
  "events": [
    {
      "event_id": "01HXY2K8ZQ9F3M7VN0ABCDEFGH",
      "type": "peg.depeg.start",
      "severity": "high",
      "chain": "ethereum",
      "protocol": "morpho",
      "subject_id": "0x...",
      "ts": "2026-05-31T12:00:00Z",
      "data": { /* event-specific payload */ }
    }
  ],
  "next_cursor": "01HXY2K8ZQ9F3M7VN0ABCDEFGH",
  "window_days": 7
}

Loop until next_cursor is empty

Pass next_cursor back as since_event_id until it comes back empty:
cursor=""
while : ; do
  url="https://api.alterscope.org/v2/events/replay?limit=100"
  [ -n "$cursor" ] && url="${url}&since_event_id=${cursor}"

  page=$(curl -s "$url" -H "Authorization: Bearer sk_live_...")

  echo "$page" | jq '.events[]'

  cursor=$(echo "$page" | jq -r '.next_cursor')
  [ -n "$cursor" ] && [ "$cursor" != "null" ] || break
done
Replay is for recovery, not for live streaming. For ongoing updates, subscribe over WebSockets or webhooks and use replay only to backfill the gap after a reconnect.

Filtering

Filters narrow a list before it’s paged. The set below is what GET /v2/yield/opportunities accepts — other endpoints expose their own filters, documented in the Endpoints group. Filter parameters use the filter[...] bracket form; comma-separate values for the multi-value filters.
ParameterExampleEffect
filter[venue]aave,morphoRestrict to one or more protocols.
filter[chain]ethereum,stellarRestrict to one or more chains.
filter[asset]ETH,USDCRestrict to opportunities involving these assets.
filter[strategy]lending,vault_managedRestrict to one or more strategy types.
filter[riskBand]1,2Restrict to specific risk bands.
filter[maxRiskBand]2Cap the risk band (anything above is excluded).
filter[tier]1,2Restrict to data-tier 1, 2, or 3.
filter[directionality]market_neutralRestrict by directionality.
filter[minApy] / filter[maxApy]5 / 50Bound net APY (percent).
filter[minLiquidityScore]60Minimum liquidity score.
filter[minTvl]1000000Minimum TVL in USD.
searchETHFull-text search across name and asset.
curl -s "https://api.alterscope.org/v2/yield/opportunities?filter[chain]=ethereum&filter[venue]=morpho&filter[minApy]=5&page[limit]=50" \
  -H "Authorization: Bearer sk_live_..."
Filters apply before pagination, so meta.total reflects the filtered set, not the whole catalog. Apply the same filters on every page of a paging loop — changing them mid-loop invalidates the cursor.

Sorting

GET /v2/yield/opportunities accepts a single sort parameter. Prefix the field with - for descending order. The default sort is -tvlUsd (largest TVL first).
curl -s "https://api.alterscope.org/v2/yield/opportunities?sort=-riskAdjustedScore" \
  -H "Authorization: Bearer sk_live_..."
Sort and pagination compose: the cursor encodes your sort order, so paging stays consistent as long as you keep the same sort (and filters) across the loop.

See also

API reference overview

Base URL, auth, and the response envelope.

Rate limits & tiers

Per-tier request rates, quotas, and replay windows.

Errors

The standard error envelope and status codes.

Webhooks

Push delivery for live events.