Signal manifest
thirdfy-app.json v1. One open format that says what each data read returns, what it measures, how fresh it is, what it costs and how a strategy may use it.
Category: Market data · signals
Agents should not trade on data they cannot inspect. The signal manifest describes every data read on Thirdfy in one machine-readable format. It says what a read measures, which typed fields it returns, their units, how fresh the reading is, what one call costs, and which roles a strategy may give it.
Live. The production catalog (https://api.thirdfy.com/api/v1/agent/actions/catalog) serves apps[] and each action's app, signals[], credits, dataCategory and outputSchema. The MCP signals toolset (mcp.thirdfy.com) and the CLI thirdfy-agent signals command (@thirdfy/agent-cli 0.2.79 and later) read the same manifest. Every change is additive: no catalog field was removed or renamed.
The model: app, actions, signals
There is one format, thirdfy-app.json (schema version 1).
- An app is one provider or data agent, such as CoinMarketCap or Allora.
- An app has actions. These are ordinary catalog actions, such as
get_cmc_fear_greed. - A read-only action has signals. A signal is one named measure with typed fields, a universe, a freshness and allowed roles.
One action can carry several signals. For example, get_cmc_derivatives_metrics provides cmc.market_funding, cmc.open_interest_change and cmc.liquidations.
Thirdfy publishes the manifest inside the action catalog. Consumers read it from there. They do not have to guess meaning from action names.
App fields
| Field | Type | Meaning |
|---|---|---|
schemaVersion | 1 | Format version. |
appId | string | Stable id, lowercase (^[a-z0-9][a-z0-9_-]{0,63}$). For a built-in provider it equals the provider id (cmc, nansen, allora). |
kind | enum | data (a data provider), data_agent (an agent that sells its output, such as Allora), venue or internal. |
title | string | Display name. |
summary | string | At most 200 characters. Shown before a client loads the detail. |
category | string | Marketplace shelf: sentiment, derivatives, forecasts, flows, news. |
icon, docsUrl | URL | Optional. |
upstream.transport | enum | How Thirdfy reaches the provider: native, mcp_remote, rest, x402 or webhook. |
upstream.url, upstream.auth | string | Where and how Thirdfy authenticates upstream, for example thirdfy_held_key. Provider keys stay with Thirdfy. The public catalog never shows url or auth. |
redistribution.displayValues | boolean | A consumer may show the typed values to its users. |
redistribution.displayRaw | boolean | A consumer may show the provider's raw reply. |
redistribution.attribution | string | The credit line to show wherever the data appears. Optional. |
pricing.model | enum | per_read, subscription or none. |
manifestVersion | integer | Bumped on every change. |
actions[] | array | See below. |
Action fields
| Field | Type | Meaning |
|---|---|---|
action | string | The catalog action name in snake case (get_cmc_fear_greed). The kebab-case spelling resolves to the same action. |
readOnly | boolean | Only a read-only action carries signals. |
billingTier | string | The credit tier of one call. |
credits | number | Credits per call at that tier. |
outputSchema | JSON Schema | JSON Schema 2020-12 of the output. Required for any app that is not built in. |
signals[] | array | The signals this read provides. It may be empty. |
Signal fields
| Field | Type | Meaning |
|---|---|---|
id | string | <appId>.<measure>, such as cmc.fear_greed. Stable once published: strategies and studies key on it. |
title | string | Display name. |
summary | string | At most 200 characters. |
measures | string | What it measures: sentiment, crowding, price_forecast, regime, positioning, … |
scope | enum | market (one reading for the whole market) or asset (one reading per asset). |
universe | "market", "any" or string | market for market scope. For asset scope: any asset the action accepts, or a list of tickers such as ["BTC"]. |
fields | object | { name: { path, type, unit?, range?, values?, description? } }. See Fields and paths. |
asOf.path | string | Where the observation time is. When the provider sends no time, this is freshness.fetchedAt. |
cadenceSec | number | How often the provider refreshes the reading. Optional. |
freshnessSec | number | The oldest a reading may be and still be used. Optional. |
horizonHours | number | The horizon a forecast speaks to. Optional. |
roles | string | The roles a strategy may give it. See Roles. |
rules | string | The rule types that can read it: threshold_veto, regime_gate, event_blackout, forecast_gap, positioning. |
attribution, docsUrl | string | Optional. |
Fields and paths
type is one of number, string, enum, boolean, datetime, array or json.
rangeis allowed only on numbers, for example[0, 100].valuesis allowed only on enums, for example["low", "medium", "high"].jsonmarks a raw block the provider owns. It is read as context and never compared.
A path points into the action output's data object. It is a dotted key path. [] steps into every item of an array.
- A typed reading is always under
typed:typed.index,typed.rows[].fundingRate. - Allora forecasts are typed natively, so their paths start at
results[]. - A read with no typed reading yet points at the raw block (
payload, orresultfor a CoinMarketCap brief). Such a signal iscontextonly.
Units live in field names and in unit. usd is US dollars. percent means 6.26 is 6.26%. percentage_points is a change in points. bps is basis points. percentile is 0 to 100. index is a unitless score. count is a whole number of items, such as traders. percent_per_interval and fraction_per_interval are funding rates per funding interval. Dates are ISO-8601 UTC.
Roles
| Role | What it may do |
|---|---|
direction | Pick the side of a trade (long or short). |
filter | Veto an entry. A filter never opens a trade on its own. |
context | Inform only. It never changes a decision on its own. |
A signal lists the roles it supports. A strategy picks one of them. A consumer such as a hosted runtime may approve fewer roles than the manifest allows.
Typed reads
The main CoinMarketCap and Nansen reads return a typed reading next to the raw reply. These are the typed reads today:
get_cmc_fear_greed,get_cmc_global_metrics,get_cmc_derivatives_metrics,get_cmc_upcoming_eventsget_cmc_market_regime,get_cmc_derivatives_crowding,get_cmc_perp_contract_analysisget_nansen_perp_screener
Every other CoinMarketCap and Nansen read has freshness but no typed yet. Its raw reply is in payload, in result for a composed CoinMarketCap brief, or in answer for the Nansen research agent. Composed briefs name the credit line sourceAttribution. Allora forecasts are typed natively: each item of results[] has its own timestamp and freshness.
| Field | Meaning |
|---|---|
payload | The provider's raw reply, unchanged. Kept for compatibility. |
typed | The typed reading. Display strings become numbers ("+6.26%" becomes 6.26, "385.08 B" becomes 385080000000). It is null when the reply had a shape the parser did not recognize. |
parseIssues | string[]. One entry per field that could not be read, such as "typed.index: missing". Empty when every field was read. |
freshness | { fetchedAt, ttlMs, cacheHit }. fetchedAt is when Thirdfy fetched the data upstream. A cache hit keeps the original time. ttlMs is how long Thirdfy caches it (0 means not cached). |
attribution | The credit line to show, when the provider requires one. |
A read never fails because of an unexpected reply. typed has null fields and parseIssues says why. Check parseIssues and the age of asOf before you act on a value.
Credits and billing tiers
Each call is billed in Thirdfy credits at the action's billing tier. The catalog shows billingTier and credits on every action.
| Tier | Credits per call | Examples |
|---|---|---|
read_offchain | 2.5 | get_cmc_fear_greed, get_cmc_derivatives_metrics, get_nansen_perp_screener, get_allora_market_forecast |
read_compose | 10 | Composed CoinMarketCap briefs: get_cmc_market_regime, get_cmc_derivatives_crowding, get_cmc_perp_contract_analysis, get_cmc_btc_etf_demand |
A cache hit is still a billed call. One call can feed several signals, so read once and evaluate every signal on that reply. See Credits and balance.
Redistribution and attribution
The app's redistribution block says what a consumer may show its users.
displayValues: true: show the typed values, with the attribution line.displayValues: false: use the values in decisions, but show only the outcome, such as "passed" or "vetoed". Nansen isfalse.displayRaw: false: do not show the provider's raw reply.
Show attribution wherever the data appears, for example "Powered by CoinMarketCap".
Discovery
REST
- Top level:
apps[], each withappId,kind,title,summary,category,docsUrl,transport,redistribution,pricing,manifestVersion,source,actions(the action names) andsignalIds. - Each action:
app { appId, manifestVersion },signals[],billingTier,credits,dataCategoryandoutputSchema. - The keyed agent list (
GET /api/v1/agent/actions) has the same per-action fields. It sendsoutputSchemaonly when you addinclude=outputSchema.
dataCategory groups data reads: market_data, signal, research and wallet.
MCP
Connect to https://mcp.thirdfy.com/mcp?toolsets=signals.
- The toolset adds only the read-only data reads that carry signals: 13 tools today. Every tool has
readOnlyHint: trueand a title. - Each tool has an MCP
outputSchema. A call returnstyped,parseIssues,freshnessandattributionasstructuredContent, so the model does not receive the raw payload. PassresponseFormat: "detailed"to get the full reply. - The tool description has one line per signal: what it measures, fields with units, refresh and max age, roles, credits and attribution.
- Tool
_meta["com.thirdfy/app"]carries the app id, manifest version, data category, billing tier, credits and signals. - Combine it with other toolsets:
?toolsets=signals,perps. - Without a key the tools are listed but do not run. A call returns
AGENT_KEY_REQUIRED. See MCP onboarding.
CLI
signals lists every app and signal: fields with paths and units, freshness, roles, rules, the read that returns it, credits and attribution. run prints the typed reading of a data read first in human mode.
Complete example: CoinMarketCap fear and greed
The CoinMarketCap manifest, trimmed to one action. The catalog serves the same data in a flatter shape: apps[] has the app fields with transport in place of upstream (upstream.url and upstream.auth stay private), and each catalog action carries its own signals[].
A call to get_cmc_fear_greed returns (example values, raw payload trimmed):
How a strategy reads it:
- Read
typed.index(theindexfield path) andtyped.asOf(theasOfpath). - If
parseIssuesis not empty orasOfis older thanfreshnessSec(1 hour), treat the signal as unavailable. - Use it only in an allowed role. Here that is
filter(for example, athreshold_vetothat skips new longs at 80 or above) orcontext. - Show "Powered by CoinMarketCap" wherever the value appears.
Bring your own provider
Through certification with Thirdfy. Thirdfy can now reach a provider through generic mcp_remote and rest adapters. Onboarding is not self-serve: a provider works with Thirdfy to certify its manifest before it goes live, and no third-party app is certified yet. The format below is final.
A provider that runs its own MCP server publishes one manifest. Thirdfy proxies the calls, holds the key, meters each read and checks every reply against outputSchema. The provider never sees who the end user is.
Checks before a manifest goes live:
- The manifest validates, and ids are unique.
- A signal id starts with its app id.
rangeappears only on numbers,valuesonly on enums.- A signal whose fields are all
jsoniscontextonly. - An app that is not built in declares
outputSchemaon every action. - Every path resolves on a sample reply that validates against
outputSchema. - Credits match the billing tier.
A Thirdfy admin then certifies the version. A change is always a new manifestVersion, never an edit.
Current signals
| App | Signals | Page |
|---|---|---|
CoinMarketCap (cmc) | 13 | CoinMarketCap |
Nansen (nansen) | 1 | Nansen |
Allora (allora) | 1 | Allora |
GMGN reads are in the catalog but have no manifest yet. See GMGN.