thirdfy-app.json reference
The manifest format for a protocol on Thirdfy. Reads over MCP or REST, writes as contract calls, and the rules every write must pass.
thirdfy-app.json describes one protocol. It lists the protocol's actions, and each read action describes what it returns. The machine-readable schema (JSON Schema 2020-12) is at https://api.thirdfy.com/schemas/thirdfy-app.v1.json.
App fields
| Field | Meaning |
|---|---|
schemaVersion | 1. |
appId | Your stable id: lowercase letters, digits, _ and -, starting with a letter or digit, up to 64 characters. |
kind | data, data_agent or venue. Use venue for a protocol with writes. |
title, summary | Summary is at most 200 characters. |
category | The shelf in the directory, such as earn, spot, derivatives, sentiment or news. |
icon, docsUrl | Optional URLs. |
upstream | { transport, url?, auth }. See below. |
pricing | { model }: none for a free read. |
redistribution | { displayValues, displayRaw, attribution? }: whether agents' users may be shown your values and your raw reply, and the credit line to show. |
manifestVersion | A positive integer. It must grow with every submission. |
contracts | Writes only: { "<chainId>": { "<alias>": "0x…" } }. The only addresses a write may call or approve. |
actions[] | Your actions. |
Reads
A read gets data from your service. Two transports:
mcp_remote: your remote MCP server.upstream.urlis anhttpsURL. Each action names the MCPtoolto call ininvoke.invoke.argumentscan add fixed arguments (literals only).rest: your HTTP API.upstream.urlis anhttpsbase URL. Each action givesinvoke.method(GETorPOST), apathunder that base with whole-segment{param}placeholders, andqueryandbodyfields ({param}or a literal).
Each read action has:
| Field | Meaning |
|---|---|
action | Snake case, for example get_acme_pool_stats. Start with a read verb: get_, list_, fetch_, search_, show_ or check_. |
readOnly | true. |
billingTier, credits | The tier is one of the read_* tiers for a read (read_offchain for data your service serves) and write or write_heavy for a write. credits must equal the tier's default. Thirdfy sets that figure, and the submission check tells you the expected value, so the 0 in the examples is a placeholder. |
description | What an agent reads to decide to call it. At most 500 characters. Reviewed by Thirdfy. |
inputSchema | JSON Schema of the params. Only these params reach your service. |
outputSchema | JSON Schema of the typed reading. Required. |
invoke.typed | Maps each typed field to a path in your reply. Without it the whole reply is the reading. |
fixtureParams | Read-only params Thirdfy uses for its checks and its hourly health probe. |
signals[] | Optional. Named measures with typed fields, a freshness and the roles a strategy may give them (direction, filter, context). |
ttlMs | How long Thirdfy may cache a reading (0 to 3,600,000). |
A read does one fixed thing. Tools that run code or call whatever their input names (a generic execute, a free endpoint_path) cannot be listed.
Thirdfy sends no Thirdfy key to a manifest it has not certified, and keys it holds never appear in the catalog.
Writes
A write is an on-chain call to your contracts, declared as data (upstream.transport: contract_call, auth: none, no url). Thirdfy encodes the call from the action's write block, checks it, and sends it from the agent's wallet.
write field | Meaning |
|---|---|
route | How the owner's wallet policy fences the call: erc4626_self, pinned_call or router_adapter. See below. |
domain | spot, bridge, perps, earn, prediction or internal. |
chains | Chain ids. Each needs an entry in contracts. |
params | What the agent supplies: { <name>: { type, role? } }. Types are fixed size: address, bool, bytes32, uintN, intN. An agent-supplied address needs a role: token, market or spend_token. |
approvals | Optional ERC-20 approvals sent before the call. Exact amounts only. |
call | { contract, function, args, pins, value?, maxValueWei? }. |
opensPosition, exit | A write that opens a position names the write of this app that closes it. |
risk, maxAmountUsd | Risk level, and the most one call may move. Thirdfy may lower maxAmountUsd when it lists you. |
The three routes
| Route | Use it for | Owner signature |
|---|---|---|
erc4626_self | A standard ERC-4626 vault: exactly deposit, withdraw or redeem, with receiver and owner pinned to the agent's wallet. | Not needed. |
pinned_call | Any other function. Your contracts become a group in the owner's wallet policy, with the function and its pinned arguments fixed. | The owner signs your protocol into the policy once. |
router_adapter | Opaque calldata through a Thirdfy router. | Listed, but not executable until the router ships. |
Pins: $self and $owner
args binds every input of the function to $param.<name>, $self (the agent's wallet), $owner (the owner's payout address) or a literal. pins turn into conditions of the wallet policy rule: a pinned path must equal $self (or $owner).
Every recipient-like input must be pinned. Names such as recipient, to, receiver, onBehalfOf, owner and beneficiary count, also inside longer names such as feeRecipient. A pinned input cannot also take agent input. Funds can only come back to the agent's wallet.
Approvals
approvals entries are { token, spender, amount }, where token is $param.<name> or an alias and spender is an alias from contracts. The amount is $param.<name> or a decimal. An approval is for the exact amount of the call and never unlimited: an amount of 2^128 or more is refused.
What a write may not do
The checks refuse a write when:
- a function input is unbound, or an arg names no input;
- a recipient-like address is not pinned;
- an input is an array,
bytesorstring; - the function is a multicall wrapper (
multicall,execute,aggregate,batch), because the wallet policy cannot see inside it; - a payable function has no
maxValueWei, or a non-payable one sends value; - an approval is unlimited;
- an address that is neither zero nor in
contractsappears as a literal; - the name is not
<appId>_<verb>(for exampleacme_deposit) or starts with a read verb; - the billing tier is not a write tier;
- a write that opens a position has no
exit, or theexitis not a write of this app.
The app around it has kind: "venue", upstream: { "transport": "contract_call", "auth": "none" }, pricing: { "model": "none" } and contracts: { "8453": { "vault": "0x…", "usdc": "0x…" } }, where the aliases vault and usdc are the ones the action uses. The 0 in credits is a placeholder; see billingTier, credits above. The submission checks tell you if a field is missing.
fixtureParams
Every write needs fixtureParams: example params Thirdfy uses to build the call, simulate it and run the policy proof. Reads use them for the check call and the health probe. Use harmless values and read-only params.
exit
A write that opens a position (a deposit, a swap into a token, a new order) must name, in exit, the write of the same app that closes it. An agent that opens a position through you must always have a way out, and Thirdfy tests it.
After a check fails
The submission page lists each finding with its path in the manifest. Fix the manifest, raise manifestVersion and submit again.