Every API here is available over the APIs.io API and to AI agents over MCP.
{"openapi":"3.1.0","info":{"title":"SnowSignals daas API","version":"1.0.0","description":"Metered, prepaid Phase-Event data.\n\n**Auth:** pick one method per account in the credentials tab (default **url**):\n\n- **url**: pass your key as the `apiKey` query parameter: `?apiKey=<key>` (infura/etherscan style). The key is the whole credential, with no secret and no signature.\n- **nonce**: `Authorization: ApiKey base64(key:nonce:proof)`, `proof` = SHA256 of `` `${secret}:${nonce}` `` (hex, truncated to 19 chars). `nonce` must be **strictly increasing** per key (replay defense), so a single key allows **one in-flight request at a time**. Batch via the composer (multi-TF/multi-currency in one call) or use multiple keys for parallelism.\n\nThe two methods are mutually exclusive: a request that does not match your account's method is `401`.\n\n**Clocks (nonce method):** most clients use `Date.now()` as the nonce. Because the rule is *strictly increasing*, a **fast clock is a one-way trap**: a nonce sent from a clock running ahead is stored, and once your clock is corrected every nonce you can produce is lower, so the key returns `401` until an operator resets it. Check `GET /v1/api/time` (public, unmetered) and offset your nonce by the difference. A nonce need not be a timestamp; any strictly increasing series works.\n\n**Pricing:** the billable unit is the **atom** (1 atom = base rate = 2314 micro-USD). A request serves rows (one per currency×TF) and consumes `rows × mult(n)` atoms, where `n` = rows served: `n=1 → ×1.25`, `2..5 → ×1.15`, `n≥6 → ×1.00`. Example: `tf=all` (6 TFs, 1 currency) = 6 rows × 2314 × 1.00 = 13884 micro-USD.\n\n**Errors:** `402` out of credits (no partial serve), `423` refund in progress, `429` per-key rate limit (with `Retry-After`), `400` invalid currency/tf. The public visualizer + backdata download are separate surfaces, not part of the paid API.","contact":{"name":"SnowSignals","url":"https://snowsignals.io"},"termsOfService":"https://snowsignals.io/terms"},"x-apisguru-categories":["financial","analytics"],"servers":[{"url":"https://snowsignals.io/v1"}],"components":{"securitySchemes":{"UrlKey":{"type":"apiKey","in":"query","name":"apiKey","description":"url method (default): pass your key as `?apiKey=<key>`. The key is the whole credential."},"ApiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"nonce method: `ApiKey base64(key:nonce:proof)` where `proof = SHA256(\"secret:nonce\")` hex truncated to 19 chars (see the API description for the signing scheme)."}},"schemas":{"PhaseReading":{"type":"object","properties":{"ts":{"type":"string","format":"date-time","example":"2026-07-14T17:00:00.000Z"},"phase":{"type":"string","enum":["establishing_bull","establishing_bear","running_reentry_bull","running_reentry_bear","running_first_bull","running_first_bear","consolidating_bull","consolidating_bear","running_post_fe_bull","running_post_fe_bear","breaking_bull","breaking_bear"],"example":"establishing_bull"},"label":{"type":"string","description":"Human display label for `phase` (the full map is at GET /v1/api/phases). Derived from `phase`; do not key logic on it.","example":"Establishing Bull"}}},"Notification":{"type":"object","properties":{"id":{"type":"string","example":"1287"},"category":{"type":"string","enum":["support","billing"],"example":"support"},"title":{"type":"string","example":"Support replied to your ticket"},"snippet":{"type":"string","nullable":true,"example":"#42 · Cannot log in"},"link":{"type":"string","nullable":true,"description":"In-app deep target.","example":"/support"},"createdAt":{"type":"string","format":"date-time","example":"2026-07-15T01:22:03.145Z"}}},"NotificationPage":{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/Notification"},"description":"Up to 10 notifications, newest first."},"nextCursor":{"type":"string","nullable":true,"description":"Opaque cursor for the next page, or null when no older rows remain."}}}}},"security":[{"UrlKey":[]},{"ApiKey":[]}],"paths":{"/api/phase/resolution-stats":{"get":{"security":[],"summary":"Public, unmetered: how each phase historically resolves, to help interpret a /phase read.","description":"Serves the same generated research artifact as the MCP `phase_resolution_stats` tool. Returns the successor-phase transition matrix, the trend-hold continuation funnel, and per-phase reward-vs-drawdown (MFE/MAE), each with provenance (`statsVersion`, `generatedAt`, window). The model is built from BTC history over a fixed research window rather than per-currency live data, so it takes no query parameters. Use it to interpret a `/api/phase/{boundary,updates}` reading.","responses":{"200":{"description":"The phase-resolution artifact, served verbatim, plus a `legend`. `schemaVersion` pins the shape and the stat sections sit under provenance. `legend` (added at serve time) declares the units, how each transition edge is counted, the MFE/MAE horizon, and the pooling/scope. The on-site 'How it works' charts render the same artifact.","content":{"application/json":{"schema":{"type":"object","description":"Generated phase-resolution research artifact (served verbatim) + serve-time legend.","properties":{"schemaVersion":{"type":"integer","description":"Artifact shape version.","example":3},"statsVersion":{"type":"string","description":"Dated version of the generated stats.","example":"2026-09-01.2"},"generatedAt":{"type":"string","format":"date-time","example":"2026-09-01T10:40:30.000Z"},"provenance":{"type":"object","description":"How the model was derived: source product, currency (BTC), and the research window."},"legend":{"type":"object","description":"Units and definitions for interpreting the stat sections: a code↔enum↔label vocabulary crosswalk, plus transitionMatrix / trendHoldFunnel / rewardDrawdown / pooling / scope notes."}},"additionalProperties":true},"example":{"legend":{"vocabulary":[{"label":"Establishing","code":"EST","enum":"establishing_bull | establishing_bear","statsLabel":"Establishing"},{"label":"Running-First","code":"RF","enum":"running_first_bull | running_first_bear","statsLabel":"Running-First"},{"label":"Consolidating","code":"CON","enum":"consolidating_bull | consolidating_bear","statsLabel":"Consolidating"},{"label":"Re-entry","code":"REE","enum":"running_reentry_bull | running_reentry_bear","statsLabel":"Re-entry"},{"label":"Post-FE","code":"PFE","enum":"running_post_fe_bull | running_post_fe_bear","statsLabel":"Post-FE"},{"label":"Breaking","code":"BRK","enum":"breaking_bull | breaking_bear","statsLabel":"Breaking"}],"howToRead":"n is an occurrence count: how many times the phase showed up in the window. It tells you the phase is common or rare, nothing about whether the pattern holds, so read the transitions (what tends to follow what) at any n. Read the mfe/mae figures as rough ranges rather than targets. They land closest to face value on the 4h and 1d, where steady price action forms clean moving-average channels, and carry more noise on the faster timeframes. Every timeframe is worth reading; the short ones just move quicker and rougher.","transitionMatrix":"nodes.nBull/nBear = number of phase occurrences in that direction over the window. An edge value is the integer percent (0–100) of transitions LEAVING the source phase-state, row-normalized over that node's outgoing transitions. Edge id is 'SRC>DEST'; a _same/_flip suffix marks whether the successor keeps the side or flips it.","trendHoldFunnel":{"_about":"Per trend channel; integer percents.","commit":"% of channels that reach a Running phase.","hold":"% of committed channels that close favorably vs entry.","reach":"% of committed channels that reach a Breaking phase.","exitWins":"of channels reaching Breaking, % where exiting at the FIRST Breaking beats holding to the channel's end."},"rewardDrawdown":"Measured per phase occurrence from the price at the phase's opening boundary to the phase's end. mfe = average max FAVORABLE excursion (% of entry price; up for bull, down for bear), floored at 0; mae = average max ADVERSE excursion. *Min/*Max give the range across occurrences; n = sample size. Horizon = the phase's own duration (entry boundary → phase end), not a fixed bar count.","pooling":"pooled_1h_2h and pooled_4h_1d group adjacent timeframes for larger samples; standalone 1h/2h/4h/1d are also provided.","scope":"Derived from BTC only, 2022-01-01 → 2026-07-01, on closed boundaries. 15m and 1w are excluded. A research model of phase behavior: not per-asset live expectancy; other currencies and the excluded timeframes are not represented."}}}}},"429":{"description":"Per-IP rate limit exceeded (Retry-After header)."}}}},"/api/notifications":{"get":{"summary":"Read-only notification feed (unmetered). Overview = 10 most recent per category; drill-down = next 10 of one category.","security":[{"UrlKey":[]},{"ApiKey":[]}],"parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string","enum":["support","billing"]},"description":"Omit for the per-category overview; set to drill into one category (with `cursor`). `security` and `marketing` are not readable over the API; see the 400 response."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Opaque pagination cursor from a previous `nextCursor` (drill-down only)."}],"responses":{"200":{"description":"Without `category`: an overview object keyed by category, each `{ rows: Notification[≤10], nextCursor }`. With `category`: a single `{ rows: Notification[≤10], nextCursor }` page. `nextCursor` is null when no older rows remain. Never marks anything read. Only categories the notification routing matrix permits on the `api` channel are present. `security` is NEVER included (an API key is a weaker credential than a session, so account-takeover events are not readable through one) and neither is `marketing`; a category whose `api` toggle you have switched off in your notification settings is omitted rather than returned empty.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","description":"Overview (no `category`). Keys are present only for permitted categories: never `security`, and never a category disabled on the `api` channel in your settings.","properties":{"support":{"$ref":"#/components/schemas/NotificationPage"},"billing":{"$ref":"#/components/schemas/NotificationPage"}}},{"$ref":"#/components/schemas/NotificationPage"}]},"example":{"support":{"rows":[{"id":"1287","category":"support","title":"Support replied to your ticket","snippet":"#42 · Cannot log in","link":"/support","createdAt":"2026-07-15T01:22:03.145Z"}],"nextCursor":null},"billing":{"rows":[],"nextCursor":null}}}}},"400":{"description":"Invalid cursor; unknown category; `category=security` (never available over the API); or a category you have disabled on the `api` channel. Disabled/unavailable categories fail loud rather than returning an empty page, so 'hidden by policy' is never mistaken for 'nothing to show'."},"401":{"description":"Missing or invalid API key credential."},"429":{"description":"Per-key rate limit exceeded (Retry-After header)."}}}},"/api/phases":{"get":{"security":[],"summary":"Public, unmetered metadata: the 12-value phase enum + labels + enabled TFs/currencies, plus an about-the-model summary and a definition card per phase.","responses":{"200":{"description":"Metadata. `about` is the model-provenance summary; `cards` gives one definition card per phase (role, market-terms meaning, typical successors).","content":{"application/json":{"schema":{"type":"object","properties":{"phases":{"type":"array","items":{"type":"string"}},"labels":{"type":"object","additionalProperties":{"type":"string"}},"tfs":{"type":"array","items":{"type":"string"}},"currencies":{"type":"array","items":{"type":"string"}},"about":{"type":"string","description":"Model-provenance summary (Moving Average Graphical Analysis)."},"cards":{"type":"array","description":"One definition card per phase.","items":{"type":"object","properties":{"base":{"type":"string"},"code":{"type":"string"},"label":{"type":"string"},"variants":{"type":"object","properties":{"bull":{"type":"string"},"bear":{"type":"string"}}},"role":{"type":"string"},"definition":{"type":"string"},"successors":{"type":"string"}}}}}},"example":{"phases":["establishing_bull","establishing_bear","running_reentry_bull","running_reentry_bear","running_first_bull","running_first_bear","consolidating_bull","consolidating_bear","running_post_fe_bull","running_post_fe_bear","breaking_bull","breaking_bear"],"labels":{"establishing_bull":"Establishing Bull","establishing_bear":"Establishing Bear","running_reentry_bull":"Running Re-entry Bull","running_reentry_bear":"Running Re-entry Bear","running_first_bull":"Running First Bull","running_first_bear":"Running First Bear","consolidating_bull":"Consolidating Bull","consolidating_bear":"Consolidating Bear","running_post_fe_bull":"Running Post-FE Bull","running_post_fe_bear":"Running Post-FE Bear","breaking_bull":"Breaking Bull","breaking_bear":"Breaking Bear"},"tfs":["15m","1h","2h","4h","1d","1w"],"currencies":["BTC","ETH","GRAM","SOL","TRX"],"about":"TrendVane, SnowSignals' market-phase product, labels phases using a method called Moving Average Graphical Analysis, which uses measurement of fixed coordinates on a plotted graph. The author Keny Ruyter (aka Snowkid) developed the method and nomenclature. On each timeframe the model tracks a fast and a slow moving average of closed-candle price. Where they cross sets the directional bearing (bull/bear); the space between them, the \"channel\", is measured as it forms, widens, flattens, expands, or collapses. Then, the phase model collects these into probabilistic channel states, which are called phases. Each has an inverse, Bull or Bear:\n- Establishing: fresh crossover; trend not yet proven.\n- Running-First: channel established; first real leg underway.\n- Consolidating: channel flattened; a pause where the next data decides.\n- Re-entry: a second push in the trend direction after a consolidation.\n- Post-FE: (Post Flatted Expansion) a flattened channel re-expands into a continuation leg; rarest, highest-magnitude state.\n- Breaking: channel collapsing; the decisive reverse-or-continue point.\nEach phase is computed deterministically from closed candles, independently per timeframe and currency. If you want the backtested numbers to read at face value, use the 4h and 1d, where price action holds steady enough to form clean moving-average channels. Faster timeframes flip more often and swing rougher, 15m most of all, but they compute the same way and are just as valid to read. The short ones trade some stability for speed; if that's the trade you want, the live updates track the bar as it forms. Use cases: \n- Multi timeframe analysis\n- Higher timeframe wisdom\n- Current market regime\n- Confluence for your TA\n- Decision making the old fashioned way","cards":[{"base":"establishing","code":"EST","label":"Establishing","variants":{"bull":"establishing_bull","bear":"establishing_bear"},"role":"deciding a side","definition":"A fresh crossover where the trend is not yet proven; price typically alternates bull/bear for several rounds before committing to a side. The first Establishing of a cycle follows a counter-trend event.","successors":"Most often commits to a first leg (→ Running-First, same side); otherwise never commits and flips back to the opposite side."},{"base":"running_first","code":"RF","label":"Running-First","variants":{"bull":"running_first_bull","bear":"running_first_bear"},"role":"first real leg","definition":"The first real move just above Establishing's noise floor. Channels are short and often pull back; the trend usually decides its direction here.","successors":"Commonly relaxes back to same-side Establishing (the leg gives up and re-decides) or matures into a Consolidating pause; can also fail and flip."},{"base":"consolidating","code":"CON","label":"Consolidating","variants":{"bull":"consolidating_bull","bear":"consolidating_bear"},"role":"the pause","definition":"The trend is established; this state defines the flow; a pause between pushes. Longer consolidations tend to alternate with shorter running legs.","successors":"Most often the pause breaks (→ Breaking, the most common exit); otherwise takes another swing (→ Re-entry) or collapses back to Establishing."},{"base":"running_reentry","code":"REE","label":"Re-entry","variants":{"bull":"running_reentry_bull","bear":"running_reentry_bear"},"role":"another push","definition":"A second push in the trend direction after a consolidation decided to continue; puts more time on the clock, and higher-timeframe agreement can extend it.","successors":"Usually settles into another Consolidating pause, or breaks out of the push (→ Breaking)."},{"base":"running_post_fe","code":"PFE","label":"Post-FE","variants":{"bull":"running_post_fe_bull","bear":"running_post_fe_bear"},"role":"continuation leg","definition":"A flattened channel's moving averages re-expand into a fresh continuation leg (Post Flatted Expansion). The rarest state, and historically the widest favorable-to-adverse excursion of the set.","successors":"One more Consolidating pause before a final break, or straight to Breaking."},{"base":"breaking","code":"BRK","label":"Breaking","variants":{"bull":"breaking_bull","bear":"breaking_bear"},"role":"decision node","definition":"The decisive point where the trend reverses or consolidates into a continuation, typically volatile, and can move sharply counter-trend.","successors":"Most often resets to Establishing on the opposite side; can flip into a fresh opposite-direction trend, extend into another consolidation, or (rarely) continue cleanly (→ Post-FE)."}]}}}}}}},"/api/time":{"get":{"summary":"Server clock in epoch ms; public, unmetered. Use it to keep your nonce ahead.","description":"Compare to your local `Date.now()` and offset your nonce by the difference.\n\nA clock running **fast** is a one-way trap: the nonce it sends is stored, and once corrected every nonce you can produce is lower than the stored one, so the key `401`s until an operator resets it. Checking here first is how you avoid needing that.","security":[],"responses":{"200":{"description":"Current server time.","content":{"application/json":{"schema":{"type":"object","properties":{"serverTime":{"type":"integer","format":"int64","description":"Server clock, milliseconds since the Unix epoch (UTC)."}}},"example":{"serverTime":1784110769783}}}}}}},"/user/balance":{"get":{"summary":"The account's spendable/refundable balance + atom display (session or ApiKey auth).","responses":{"200":{"description":"Balance view. Amounts are micro-USD (1e-6 USD).","content":{"application/json":{"schema":{"type":"object","properties":{"spendable_micro_usd":{"type":"integer","description":"Balance available to spend on metered requests.","example":4958060},"refundable_micro_usd":{"type":"integer","description":"Amount currently in a refund/withdrawal hold.","example":0},"spendPaused":{"type":"boolean","description":"True while a refund is settling (requests return 423).","example":false},"base_rate_micro_usd":{"type":"integer","description":"Price of one atom (base rate).","example":2314},"atoms":{"type":"integer","description":"Spendable balance expressed in whole atoms (floor(spendable / base rate)).","example":2142},"multiplier_tiers":{"type":"array","description":"The request-composition multiplier tiers. A request pays the factor for the tier its row count falls in; a null max_rows is the open-ended top tier. Same tiers the meter charges through, so you can price a call from here.","items":{"type":"object","properties":{"max_rows":{"type":"integer","nullable":true,"description":"Top of the tier (rows), or null for no upper bound.","example":5},"factor":{"type":"number","description":"Multiplier applied at this tier.","example":1.15}}}},"deposit_addresses":{"type":"array","description":"Your standing per-chain deposit addresses. You set these up from the dashboard.","items":{"type":"object","properties":{"chain":{"type":"string","example":"base"},"address":{"type":"string","example":"0x1234abcd...ef90"}}}}}},"example":{"spendable_micro_usd":4958060,"refundable_micro_usd":0,"spendPaused":false,"base_rate_micro_usd":2314,"atoms":2142,"multiplier_tiers":[{"max_rows":1,"factor":1.25},{"max_rows":5,"factor":1.15},{"max_rows":null,"factor":1}],"deposit_addresses":[{"chain":"base","address":"0x1234abcd...ef90"}]}}}},"401":{"description":"Unauthorized."}}}},"/user/usage":{"get":{"summary":"The indexed usage log (id-cursor paginated via `before`).","parameters":[{"name":"limit","in":"query","schema":{"type":"integer"}},{"name":"before","in":"query","schema":{"type":"integer"},"description":"id cursor from a prior page's nextBefore."}],"responses":{"200":{"description":"Usage rows (newest first) + nextBefore cursor.","content":{"application/json":{"schema":{"type":"object","properties":{"rows":{"type":"array","items":{"type":"object","properties":{"createdAt":{"type":"string","format":"date-time","example":"2026-07-14T17:11:45.732Z"},"endpoint":{"type":"string","description":"The metered endpoint (or `/mcp:<tool>` for MCP calls).","example":"/v1/api/phase/boundary"},"currencies":{"type":"string","description":"Comma-joined currencies served.","example":"BTC"},"tfs":{"type":"string","description":"Comma-joined timeframes served.","example":"1h"},"rows":{"type":"integer","example":1},"multiplier":{"type":"number","description":"Row-count pricing multiplier applied.","example":1.25},"debitMicroUsd":{"type":"integer","example":1446}}}},"nextBefore":{"type":["integer","null"],"description":"id cursor for the next (older) page, or null when no older rows remain.","example":4}}},"example":{"rows":[{"createdAt":"2026-07-14T17:11:45.732Z","endpoint":"/v1/api/phase/boundary","currencies":"BTC","tfs":"1h","rows":1,"multiplier":1.25,"debitMicroUsd":1446},{"createdAt":"2026-07-14T17:11:45.724Z","endpoint":"/v1/api/phase/updates","currencies":"BTC,ETH,GRAM,SOL,TRX","tfs":"15m,1h,2h,4h,1d,1w","rows":30,"multiplier":1,"debitMicroUsd":34710}],"nextBefore":4}}}},"401":{"description":"Unauthorized."}}}},"/user/deposit-now":{"post":{"summary":"Prod the system to check for a deposit (session or ApiKey auth).","description":"Ask the deposit watcher to scan your standing deposit addresses now, so a deposit you have just sent is picked up sooner instead of waiting for the next routine scan. It arms a watch on every standing address and enabled token, and returns what was armed. If the account has no standing address on file, the armed list comes back empty. This is the REST twin of the MCP deposit_poll tool; both run the same arming path. Unmetered.","responses":{"200":{"description":"What was armed. Empty when the account has no standing deposit address on file.","content":{"application/json":{"schema":{"type":"object","properties":{"armed":{"type":"array","items":{"type":"object","properties":{"chain":{"type":"string","example":"BASE"},"address":{"type":"string","example":"0x1234abcd...ef90"},"token":{"type":"string","example":"USDC"}}}}}},"example":{"armed":[{"chain":"BASE","address":"0x1234abcd...ef90","token":"USDC"},{"chain":"BASE","address":"0x1234abcd...ef90","token":"USDT"}]}}}},"401":{"description":"Unauthorized."}}}},"/api/phase/boundary":{"get":{"summary":"Metered Phase-Event read: boundary (the last closed-boundary phase, deterministic truth), per currency×TF.","parameters":[{"name":"currency","in":"query","schema":{"type":"string"},"description":"Comma list or 'all'. GET /v1/api/phases for the live enabled currency list. Default: all."},{"name":"tf","in":"query","schema":{"type":"string"},"description":"Comma list or 'all'. Enabled: 15m, 1h, 2h, 4h, 1d, 1w. Default: all."}],"responses":{"200":{"description":"Phase readings (currency → timeframe) + metering meta.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","description":"currency → timeframe → reading (null when that timeframe has no reading yet).","additionalProperties":{"type":"object","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PhaseReading"},{"type":"null"}]}}},"stale":{"type":"object","description":"Present ONLY inside the upstream composition window just after a minute boundary, when the exchange candle for the new minute has not yet settled and the returned phase may still reflect the previous minute. Absent means the data is settled. Poll again after pollAfterMs.","properties":{"reason":{"type":"string","description":"Human-readable explanation of why the data may be stale."},"pollAfterMs":{"type":"integer","description":"Milliseconds until the composition window closes; poll again after this.","example":8200}}},"meta":{"type":"object","properties":{"rows":{"type":"integer","description":"Rows served (n = |currencies| × |tfs|).","example":2},"debitMicroUsd":{"type":"integer","description":"Amount billed for this request (rows × base rate × mult(rows)).","example":2661},"cached":{"type":"boolean","description":"Whether the serve was a Redis cache hit. Billing is cache-independent.","example":false},"endpoint":{"type":"string","enum":["boundary","updates"],"description":"Which serve semantic produced this response.","example":"boundary"}}}}},"example":{"data":{"BTC":{"1h":{"ts":"2026-07-14T17:00:00.000Z","phase":"establishing_bull","label":"Establishing Bull"}}},"meta":{"rows":1,"debitMicroUsd":1446,"cached":false,"endpoint":"boundary"}}}}},"400":{"description":"Invalid currency or tf."},"402":{"description":"Out of credits."},"423":{"description":"Account spending is paused (a refund is settling)."},"429":{"description":"Per-key rate limit exceeded (Retry-After header)."}}}},"/api/phase/updates":{"get":{"summary":"Metered Phase-Event read: updates (the intra-bucket phase on the timeframe's sub-cadence), per currency×TF.","parameters":[{"name":"currency","in":"query","schema":{"type":"string"},"description":"Comma list or 'all'. GET /v1/api/phases for the live enabled currency list. Default: all."},{"name":"tf","in":"query","schema":{"type":"string"},"description":"Comma list or 'all'. Enabled: 15m, 1h, 2h, 4h, 1d, 1w. Default: all."}],"responses":{"200":{"description":"Phase readings (currency → timeframe) + metering meta.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","description":"currency → timeframe → reading (null when that timeframe has no reading yet).","additionalProperties":{"type":"object","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PhaseReading"},{"type":"null"}]}}},"stale":{"type":"object","description":"Present ONLY inside the upstream composition window just after a minute boundary, when the exchange candle for the new minute has not yet settled and the returned phase may still reflect the previous minute. Absent means the data is settled. Poll again after pollAfterMs.","properties":{"reason":{"type":"string","description":"Human-readable explanation of why the data may be stale."},"pollAfterMs":{"type":"integer","description":"Milliseconds until the composition window closes; poll again after this.","example":8200}}},"meta":{"type":"object","properties":{"rows":{"type":"integer","description":"Rows served (n = |currencies| × |tfs|).","example":2},"debitMicroUsd":{"type":"integer","description":"Amount billed for this request (rows × base rate × mult(rows)).","example":2661},"cached":{"type":"boolean","description":"Whether the serve was a Redis cache hit. Billing is cache-independent.","example":false},"endpoint":{"type":"string","enum":["boundary","updates"],"description":"Which serve semantic produced this response.","example":"updates"}}}}},"example":{"data":{"BTC":{"1h":{"ts":"2026-07-14T17:00:00.000Z","phase":"establishing_bull","label":"Establishing Bull"}}},"meta":{"rows":1,"debitMicroUsd":1446,"cached":false,"endpoint":"updates"}}}}},"400":{"description":"Invalid currency or tf."},"402":{"description":"Out of credits."},"423":{"description":"Account spending is paused (a refund is settling)."},"429":{"description":"Per-key rate limit exceeded (Retry-After header)."}}}}}}