> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pegana.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Get an asset by symbol



## OpenAPI

````yaml https://api.pegana.xyz/openapi.json get /v1/assets/{symbol}
openapi: 3.1.0
info:
  title: Pegana API
  description: >-
    The peg-risk oracle for Solana. Read real-time peg state, history, alerts,
    and delivery health across the live mainnet asset universe spanning 6
    classes — LSTs, fiat / CDP / delta-neutral / yield-bearing / FX stables, and
    a leveraged synthetic. Some assets are flagged `monitoring_only` (beta):
    their alerts are live but the calibration is provisional. Public read
    endpoints require no API key but ARE rate-limited per IP (300/min global,
    10/min on /v1/audit.csv); exceeding the limit returns 429 with a Retry-After
    header. User-scoped /v1/me/* routes require a JWT obtained via Telegram
    Login. Every state transition emits a public receipt at /v1/audit/{id} with
    the methodology version, frozen inputs, and an on-chain SPL Memo commit (SAS
    deferred per ADR-0004 — SPL Memo gives 90% of the value). EVERY error
    response across the API is JSON — the envelope is `{error, message, asset?}`
    (the `ApiError` schema); branch on the stable `error` code, never on
    `message`. Malformed request bodies surface as `{error:"invalid_body"}`. Any
    endpoint may also return 500 `{error:"internal"}` on an unexpected server
    error. Value conventions: monetary and ratio amounts are exact decimal
    STRINGS (parse with a decimal library, never a float) with trailing zeros
    trimmed; USD aggregate fields (e.g. `market_cap`, `loop_exposure_usd`,
    `liquidatable_usd`) are rounded to the cent while price/discount signal
    fields keep full precision. All timestamps are RFC3339 UTC with millisecond
    precision and a `Z` suffix (`2026-07-24T15:33:14.545Z`). Every response
    carries an `x-request-id` header (quote it when reporting an issue) and the
    rate-limit budget as `RateLimit-Limit` / `RateLimit-Remaining` /
    `RateLimit-Reset` (mirrored as `X-RateLimit-*`); a 429 also sets
    `Retry-After`.
  contact:
    name: Rafael Souza
    email: rafael@pegana.xyz
  license:
    name: MIT
    identifier: MIT
  version: 1.0.0
servers:
  - url: https://api.pegana.xyz
    description: Production
security: []
tags:
  - name: Health
    description: Liveness and readiness probes
  - name: Assets
    description: Public read access to asset state, history, and metadata
  - name: Alerts
    description: Global feed of state transitions
  - name: Stats
    description: Aggregate counters and delivery health
  - name: Auth
    description: Telegram Login Widget → JWT
  - name: Me
    description: Authenticated user profile and preferences
  - name: Subscriptions
    description: User alert subscriptions
  - name: Webhooks
    description: User-managed Ed25519-signed webhooks
  - name: WebSocket
    description: Live state stream
  - name: audit
    description: >-
      Public receipts for alerts — methodology version, inputs frozen, replay
      bundles. All endpoints public, no auth, cacheable. ADR-0006 four-state
      response on /v1/audit/:id (200/202/404/410); ADR-0014 bounds (90-day max,
      100-row max, 50k-row cap).
  - name: methodology
    description: >-
      Public lifecycle status of the active methodology — version + status
      (active/deprecated/broken) + optional fix_url when broken. Consumed by
      web/app/api/methodology to power the home-page trust strip.
  - name: peg
    description: >-
      Peg-signal feed for tokens.xyz / aggregator consumption — a `data` array
      of per-asset signals (each carries its own `mint`). Public, no auth,
      cacheable. Honest beta status via `calibration` + `monitoring_only`;
      freshness via `updated_at` + `stale`.
paths:
  /v1/assets/{symbol}:
    get:
      tags:
        - Assets
      summary: Get an asset by symbol
      operationId: asset_detail
      parameters:
        - name: symbol
          in: path
          description: >-
            Asset symbol — case-insensitive; resolved to canonical casing (e.g.
            `jitoSOL`, `USDC`, `hyUSD`)
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Latest snapshot for the asset
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssetCard'
        '404':
          description: Asset symbol unknown or inactive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
components:
  schemas:
    AssetCard:
      type: object
      required:
        - symbol
        - name
        - mint
        - class
        - anchor
        - peg_target
        - decimals
        - monitoring_only
        - calibration
        - risk_side
      properties:
        anchor:
          $ref: '#/components/schemas/Anchor'
          description: >-
            Peg anchor — how to interpret the asset's fair value without knowing

            Pegana's internal taxonomy.


            - `"USD"` — fixed $1 target (fiat-backed, RWA, delta-neutral, CDP
              stablecoins). A discount from $1 IS the risk signal.
            - `"FX"` — fixed non-USD fiat exchange-rate peg (e.g. EURC→EUR,
              BRZ→BRL). Read `peg_target` for the specific currency. A discount
              from the FX rate IS the risk signal.
            - `"NAV"` — intrinsic redemption / net-asset value, NOT a fixed fiat
              rate (LSTs, yield-bearing stables, synthetic leverage). NAV itself
              moves; the discount is relative to it.

            Derived from `class`, NOT from `peg_target`. A yield-bearing stable
            can

            have `peg_target = "USD"` yet `anchor = "NAV"` because its fair
            value

            legitimately accrues above $1 over time.
        calibration:
          $ref: '#/components/schemas/Calibration'
          description: >-
            Consumer-facing calibration label, `provisional` | `calibrated` —
            the

            self-describing form of `monitoring_only` (`provisional` ⇔

            `monitoring_only=true`), in the SAME vocabulary the dispatcher emits
            on

            the webhook canonical event. Always present.
        circulating_supply:
          type:
            - string
            - 'null'
          description: >-
            Circulating supply of the mint (`rust_decimal`, serialized as a JSON

            string), scaled by `decimals`. This is a SIZE/CONTEXT signal — NOT a

            peg-safety signal — and is NEVER an input to `discount`/`state`/

            thresholds. Sourced from the on-chain SPL Mint account's `supply`

            field by the isolated `token_supply` indexer task on a slow (~10
            min)

            cadence (migration 0065). No freshness bound is enforced on this

            field the way `updated_at` is for `discount` — supply changes slowly

            by nature. `null` until the mint has been observed at least once —

            serialized as `null` (like `discount`), not omitted.
        class:
          type: string
        confidence:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/Confidence'
              description: >-
                Pyth oracle confidence bucket: `high` | `medium` | `low` |
                `unknown`.

                Scopes the PRICE-ORACLE confidence interval only (conf/price
                ratio), NOT

                market-quote / route-depth reliability. Omitted when no discount
                snapshot

                exists yet. Reflects the LATEST snapshot — an omitted value
                means "no

                current signal" and MUST NOT be rendered as a positive bucket;
                the client

                gates on `updated_at` freshness before showing it (hardening
                H1/H9).
        current_state_duration_secs:
          type:
            - integer
            - 'null'
          format: int64
          description: >-
            Seconds the asset has resided in its CURRENT committed FSM band
            (engine

            `band_since`). Omitted until the engine has stamped it. Read with
            `state`;

            `sustained` is the thresholded view (Spec 03).
        decimals:
          type: integer
          format: int32
        discount:
          type:
            - string
            - 'null'
        intrinsic_usd:
          type:
            - string
            - 'null'
        jitter_bps_24h:
          type:
            - string
            - 'null'
          description: >-
            Peg-jitter scalar over the trailing 24h (BSRV-03): peak-to-trough

            dispersion of the discount, `max(max_discount) - min(min_discount)`,

            in the same signed-fraction units as `discount`. A cheap stability

            measure (always >= 0) computed server-side once per scan instead of

            every client recomputing it from the averaged `series_24h`. Trailing

            24h, independent of the current-snapshot freshness bound. Omitted
            when

            no `discount_1m` rows exist in the window.
        market_cap:
          type:
            - string
            - 'null'
          description: >-
            Market capitalization in USD = `circulating_supply × market_usd` — a

            SIZE/CONTEXT signal for gauging relative scale across assets, NOT a

            peg-safety signal and NEVER an input to
            `discount`/`state`/thresholds.

            `None` whenever either factor is missing (no `circulating_supply`

            observed yet, or no fresh `market_usd` snapshot). Serialized as
            `null`

            (like `discount`), not omitted.
        market_usd:
          type:
            - string
            - 'null'
        mint:
          type: string
        monitoring_only:
          type: boolean
          description: >-
            True when the asset is actively monitored but excluded from the

            calibrated launch denominator. This is a calibration-status flag,
            not a

            health state: an asset can be `PEGGED` and `monitoring_only=true`.
        monitoring_reason:
          type:
            - string
            - 'null'
          description: |-
            Human-readable reason for `monitoring_only=true`. Omitted for fully
            calibrated assets.
        monitoring_since:
          type:
            - string
            - 'null'
          description: >-
            YYYY-MM-DD date when the asset entered monitoring-only status.
            Omitted

            for fully calibrated assets.
        name:
          type: string
        oracle_degraded:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/OracleDegraded'
              description: >-
                Advisory, API-synthesized, OUT-OF-HASH "amber" annotation from
                the

                independent Pyth oracle leg — sits BELOW the hard
                Pyth-confidence gate

                (500bps -> UNKNOWN). Computed purely from data already on this
                row

                (see `oracle_degraded.rs`); NEVER a receipt input and NEVER
                touches

                `discount`/`state`/thresholds. Only ever populated on the

                single-asset detail endpoints (`/v1/assets/{symbol}` + by-mint)
                — the

                `/v1/assets` list has no oracle asof-join, so this is always
                omitted

                there. Omitted (not `null`) whenever the asset carries no
                subscribed

                oracle feed, the tick is already hard-gated to UNKNOWN, or
                nothing

                amber fired.
        peg_target:
          type: string
        risk_score:
          type:
            - integer
            - 'null'
          format: int32
          description: >-
            Derived 0-100 peg-risk score — a single sortable/thresholdable
            number for

            integrators, ALONGSIDE (never replacing) `state`. Anchored to the
            committed

            `state` so it never disagrees with the classification (PEGGED ≤ 24,
            DRIFT

            25-49, DEPEG 50-74, CRITICAL 75-94, BLACK_SWAN 95-100), interpolated
            within

            the band by the effective discount magnitude. `null` when `state` is

            UNKNOWN/absent, no discount exists, or thresholds are the CDP
            CR-form —

            we never fabricate a score (ADR-0019). Deterministic + reproducible
            from

            the other published fields; NOT part of any receipt hash. See

            `crates/api-rs/src/risk_score.rs`.
          minimum: 0
        risk_side:
          $ref: '#/components/schemas/RiskSide'
          description: >-
            Which direction of `discount` is the risk signal, so a consumer
            reading

            the raw signed number knows how to interpret it. Derived from
            `anchor`:

            `"below"` for USD/FX anchors (a discount from peg is the risk),

            `"either"` for NAV anchors (premium and discount are both
            meaningful).
        series_24h:
          type:
            - array
            - 'null'
          items:
            type: number
            format: double
        sol_per_lst:
          type:
            - string
            - 'null'
          description: >-
            Stake-pool SOL per share for Sanctum LSTs (`rust_decimal`,
            serialized as

            a JSON string). Omitted for non-LST assets. Reflects the LATEST
            intrinsic

            snapshot — read together with `updated_at` for freshness. NAV
            decomposition

            is `intrinsic_usd = sol_per_lst × SOL/USD`.
        state:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/PegState'
        state_reason:
          type:
            - string
            - 'null'
          description: >-
            Advisory machine-readable reason for the snapshot's state.
            Explanatory

            only — never a verdict input and not part of any receipt hash.


            **Emitted today:**

            - `premium_sanity` — engine NAV-sanity override: smoothed discount
            exceeded
              the premium-sanity bound, state forced to UNKNOWN (intrinsic anchor
              suspect).
            - `stale_source` — API layer: feed is >15 min stale, state collapsed
            to
              UNKNOWN; no fresh discount snapshot exists.

            **Reserved — not yet emitted (planned for depth-gate /
            oracle-crosscheck

            work):** `no_oracle_crosscheck | intrinsic_unavailable |
            shallow_market |

            nominal`.


            Omitted when null (most rows).
        sustained:
          type:
            - boolean
            - 'null'
          description: >-
            True when the asset has held a committed ALERTING band (DRIFT+) long
            enough

            to be a sustained episode (a slow mechanism depeg, not a blip). A

            PEGGED/UNKNOWN asset is never `sustained`. Omitted until computed.
        symbol:
          type: string
        thresholds:
          description: >-
            Per-asset alert thresholds, served verbatim from `assets.thresholds`

            JSONB (BSRV-01). Two shapes pass through unchanged: bps-keyed

            `{"drift_bps","depeg_bps","critical_bps"}` for most assets, and the

            CDP collateral-ratio form `{"cr_drift","cr_depeg","cr_critical",

            "cr_black_swan"}` for hyUSD. This is the AUTHORITATIVE source the

            client should key band-gauge / chart threshold lines / "closest to

            breaking" sort off — replacing any hand-maintained client table. The

            column is re-synced to the engine's calibrated `assets.toml` values
            by

            migration `0042_resync_asset_thresholds`. Omitted only if the column

            is NULL (never, given the `NOT NULL` constraint) or fails to decode.
        updated_at:
          type:
            - string
            - 'null'
          format: date-time
        worst_abs_24h:
          type:
            - string
            - 'null'
          description: >-
            Worst (largest-magnitude) absolute discount observed over the
            trailing

            24h (BSRV-02), in the same signed-fraction units as `discount`

            (e.g. `0.0123` = 123 bps). Computed as

            `max(greatest(abs(min_discount), abs(max_discount)))` over the

            `discount_1m` aggregate — discount is signed, so the abs is required
            to

            catch the worst tick in EITHER direction. Trailing 24h, INDEPENDENT
            of

            the current-snapshot 15-min freshness bound on `discount`. Omitted
            when

            no `discount_1m` rows exist in the window.
    ApiError:
      type: object
      required:
        - error
        - message
      properties:
        asset:
          type:
            - string
            - 'null'
          description: The offending asset symbol, present only for asset-scoped errors.
        error:
          type: string
          description: Stable, machine-readable code. Branch on this, never on `message`.
        message:
          type: string
          description: >-
            Human-readable explanation. NOT stable — for humans/logs; do not
            parse.
    Anchor:
      type: string
      description: >-
        Peg anchor tells a consumer how to interpret the asset's fair value
        WITHOUT

        knowing Pegana's internal taxonomy:


        - `"USD"` — the asset targets exactly $1 (fiat-backed, RWA,
        delta-neutral,
          and CDP stablecoins). A discount from $1 IS the risk signal.

        - `"FX"` — the asset targets a fixed non-USD fiat exchange rate (e.g.
          EURC→EUR, BRZ→BRL). Read `peg_target` for the specific currency. A
          discount from the FX rate IS the risk signal.

        - `"NAV"` — the asset's fair value is its intrinsic redemption/net-asset
          value, which is NOT a fixed fiat rate (LSTs, yield-bearing stables,
          synthetic leverage). The discount is relative to NAV, and NAV itself
          moves.

        IMPORTANT: derived from `class`, NOT from `peg_target`. A yield-bearing

        stable can have `peg_target = "USD"` yet `anchor = "NAV"` because its

        price legitimately accrues above $1 over time.
      enum:
        - USD
        - FX
        - NAV
    Calibration:
      type: string
      description: >-
        Calibration status of an asset (and of an alert that asset emitted).


        - `provisional` — a "beta" asset: released and alerting publicly, but
        its
          thresholds are not yet validated over a full post-launch calibration
          window. Equivalent to `monitoring_only=true`. Read its alerts as
          experimental.
        - `calibrated` — a fully calibrated asset that counts in the launch
          denominator.

        This is the SAME vocabulary the dispatcher emits on the webhook
        canonical

        event (`"calibration":"provisional"|"calibrated"`), so a machine
        consumer

        reading both the webhook fan-out and the REST read surfaces sees one
        word

        for the concept instead of `calibration` there and `monitoring_only`
        here.
      enum:
        - provisional
        - calibrated
    Confidence:
      type: string
      description: >-
        Pyth oracle confidence bucket. Mirrors `pyth_confidence_label`. Scopes
        the

        PRICE-ORACLE confidence interval only (conf/price ratio) — NOT
        market-quote

        or route-depth reliability.
      enum:
        - high
        - medium
        - low
        - unknown
    OracleDegraded:
      type: object
      description: >-
        Advisory oracle-vs-market divergence annotation for a single asset.
        Never

        a receipt input; never part of the canonical hash; purely additive on
        the

        single-asset detail response. Omitted entirely (not `null`) via the

        `Option<OracleDegraded>` field's `skip_serializing_if` on `AssetCard`.
      required:
        - reason
      properties:
        age_secs:
          type:
            - integer
            - 'null'
          format: int64
          description: |-
            Seconds elapsed since the asof-joined oracle snapshot was published.
            `None` only when no oracle snapshot was found at all (in which case
            the whole annotation is `None` too — this field is always populated
            whenever `OracleDegraded` is `Some`).
        conf_bps:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            `oracle_conf / oracle_usd`, in bps. `None` when the oracle snapshot
            carried no confidence interval, or the computation overflowed.
        reason:
          type: string
          description: >-
            Machine-readable reason. Worst-first priority when multiple guards

            fire in the same tick: `oracle_divergence` >
            `oracle_confidence_wide`

            > `oracle_stale`.
        spread_bps:
          type:
            - integer
            - 'null'
          format: int32
          description: |-
            `|market_usd - oracle_usd| / oracle_usd`, in bps. `None` when
            `market_usd` was absent/non-positive, or the computation overflowed.
    RiskSide:
      type: string
      description: |-
        Which direction of the signed `discount` is the risk signal for this
        asset's anchor type.

        - `"below"` — a discount BELOW the peg/anchor is the risk (USD and FX
          anchors: the asset is worth less than the target, which is bad).
        - `"either"` — BOTH a discount and a premium are meaningful risk signals
          (NAV anchors: LSTs, yield-bearing stables, synthetic leverage — the
          intrinsic value moves, so deviation in either direction from NAV signals
          mispricing or liquidity stress).
      enum:
        - below
        - either
    PegState:
      type: string
      description: >-
        Class-aware peg state. Mirrors the engine `PegState` enum and the

        `peg_state` Postgres enum. Used for `state`, `from_state`, and
        `to_state`

        fields across the API.


        The state is CLASS-AWARE: an LST reading a −1.4% discount (normal
        unstaking

        spread) is `PEGGED`, while a fiat stable at far less would be `DRIFT`.
        Trust

        this value directly rather than imposing a naive discount cut.
      enum:
        - PEGGED
        - DRIFT
        - DEPEG
        - CRITICAL
        - BLACK_SWAN
        - UNKNOWN

````