AIXBT Docs

Recipe Specification

Declarative YAML pipelines for multi-step analysis workflows

Formal YAML schema reference for AIXBT recipes (v1.1). Recipes run on all surfaces: CLI, MCP, and REST API. Examples below use CLI syntax, but the YAML is the same everywhere.

1. Overview

A recipe is a declarative YAML file that defines a multi-step data pipeline. Recipes fetch data from the AIXBT API (and optionally external providers for enrichment), iterate over results, apply transforms to control output size, and yield execution to agents for inference. The CLI handles all deterministic work. The recipe author defines what data to gather and when to involve an agent.

2. Recipe Structure

Top-level fields:

FieldTypeRequiredDescription
namestringyesRecipe identifier
versionstringnoRecipe version (defaults to "1.0", number also accepted and coerced to string)
descriptionstringnoHuman-readable description (defaults to "")
estimatedTokensnumber | nullnoAuthor estimate of output token count. Informational only.
paramsobjectnoParameter definitions (see Parameters)
requiredOneOfstring[]noExactly one of these params must be provided (see Parameters)
stepsarrayyesStep definitions (must be non-empty)
hintsobjectnoStructural hints for data consumers (see Hints)
analysisobjectnoAnalysis instructions for the consuming agent (see Analysis)

Minimal valid recipe:

name: example
version: "1.0"
steps:
  - id: projects
    type: api
    action: projects
    params:
      limit: 5

3. Parameters

Runtime parameters, provided via --<name> <value> flags:

FieldTypeRequiredDescription
type"string" | "number" | "boolean"yesParameter data type
requiredbooleannoWhether the caller must provide this parameter
descriptionstringnoHuman-readable description
defaultstring | number | booleannoDefault value if not provided

Parameters are referenced in steps via {params.<name>}. A required parameter with no default causes an error if omitted. Template resolution skips params that were not provided: "{params.tickers}" resolves to nothing if --tickers was not passed, so the API param is omitted from the request.

params:
  chain:
    type: string
    required: true
    description: Blockchain to scan
  limit:
    type: number
    default: 10

envelope (sampled-step budget lever)

Recipes with sampled steps (see Sample) can declare an envelope param — the caller-facing budget lever for the total tokens spent on sampled steps per item:

params:
  envelope:
    type: number
    description: Total token budget for sampled steps; per-step budgets scale to envelope/N.
    default: 150000
  • It is an ordinary number param — not part of requiredOneOf — so it never affects the exactly-one validation.
  • The engine divides envelope by N (the number of items processed) and scales each sampled step's tokenBudget proportionally, preserving the ratios the author declared and respecting each step's ceiling. See the two-lever model under Sample.
  • Default 150k covers direct callers (CLI, Indigo, Kimi, MCP). The Violet/Ultraviolet materialise path raises it to 500k for large-context answer models. Authors set the per-step ceilings + weights; callers (or the platform) set envelope.

requiredOneOf

For params where the user must provide exactly one of several alternatives, use requiredOneOf at the top level:

params:
  projectIds: { type: string, description: "Comma-separated project IDs" }
  tickers: { type: string, description: "Comma-separated ticker symbols (e.g. SOL,ETH)" }
  names: { type: string, description: "Comma-separated project names" }
  address: { type: string, description: "Token contract address" }
requiredOneOf: [projectIds, tickers, names, address]

Rules:

  • The list must contain at least 2 param names
  • Each name must be a defined param
  • Params in the list must not have required: true (conflicts with oneOf semantics)
  • At runtime, zero provided → error. Two or more provided → error.

The step passes all params through; template resolution skips the ones not provided:

- id: projects
  type: api
  action: projects
  params:
    projectIds: "{params.projectIds}"
    tickers: "{params.tickers}"
    names: "{params.names}"
    address: "{params.address}"
aixbt recipe run project_deep_dive --tickers SOL
aixbt recipe run project_deep_dive --names "solana"
aixbt recipe run project_deep_dive --address 0x...

4. Step Types

Every step requires a type field: either api or agent.

API Steps (type: api)

FieldTypeRequiredDescription
idstringyesUnique step identifier
type"api"yesLiteral string identifying this as an API step
actionstringyesAction name (e.g., projects, intel, price)
sourcestringnoProvider name. Defaults to "aixbt".
paramsobjectnoParameters with template support
transformTransformBlocknoTransform applied to the response data
projectionstringnoProjection profile (e.g. signal.compact, project.broad) that reshapes AIXBT signal/project rows into a compact, model-visible shape before select runs. Either a literal profile name or a {params.X} template (see Caller-selectable projection). AIXBT-source steps only. See Projection Profiles.
allowedProjectionsstring[]noClosed menu a template ({params.X}) projection may resolve to. Required (non-empty) when projection is a template; forbidden on a literal projection. Every entry must be a real profile valid for the step's action. See Caller-selectable projection.
forstringnoIteration modifier (see Iteration with for:)
fallbackstringnoMessage for agent when step is skipped due to missing/insufficient provider key
- id: projects
  type: api
  action: top-projects-climbing
  params:
    limit: 10

- id: price_data
  type: api
  action: chart
  source: market
  params:
    geckoId: bitcoin
    limit: 30
  fallback: "Use publicly available price data instead."

Agent Steps (type: agent)

Yield execution to an external agent for inference, analysis, or decision-making.

FieldTypeRequiredDescription
idstringyesUnique step identifier
type"agent"yesLiteral string identifying this as an agent step
contextstring[]yesStep IDs whose data to include
instructionsstringyesDetailed instructions for the agent
returnsobjectyesMap of field names to type strings
forstringnoIf present, creates a parallel agent step (see below)

Pauses execution and yields to an external agent. The returns type strings: "string", "number", "boolean", "string[]", "object".

- id: picks
  type: agent
  context:
    - recent_intel
    - top_projects
    - clusters
  instructions: |
    Select 3-5 projects with the strongest recent intel reinforcements.
    Focus on projects with rising momentum and multiple intel reinforcements.
  returns:
    projectIds: "string[]"
    rationale: "string"

Iteration with for:

The for: modifier works on both API and agent steps. It iterates over an array, executing the step for each item.

The for value is a bare reference (no curly braces) to an array-valued step result. Within a for: step, {item} and {item.field} reference the current iteration item.

API step with for: executes an API call per item:

- id: momentum
  type: api
  for: projects.data
  action: momentum
  params:
    id: "{item.id}"
    start: "-7d"
    includeClusters: "false"

When individual items fail in a step with for: and fallback, those items degrade gracefully instead of failing the step. See Fallback Mechanism.

Agent step with for: creates a fan-out pattern where the agent processes items in parallel:

- id: analyze
  type: agent
  for: projects.data
  context:
    - projects
    - momentum
    - intel
  instructions: |
    For each project, analyze its momentum trajectory and intel reinforcements.
    Produce a brief assessment of risk and opportunity.
  returns:
    assessment: "string"
    risk_level: "string"

Transforms on for: API steps run per-iteration.

5. Provider System

The action/source pair determines which provider handles a step.

Virtual Providers

Virtual providers are provider-agnostic entry points that route to the best concrete backend. Use these in recipes for portability and smart routing.

source valueProviderWhat it wraps
"market"MarketCoinGecko/GeckoTerminal + DexPaprika: on-chain price, pools, OHLCV
"security"SecurityGoPlus: token and address security analysis
"defi"DeFiDeFiLlama: TVL, protocols, emissions, yields

Dotted Syntax

For shared actions that exist on multiple backends, use dotted syntax to force a specific one:

- id: price_data
  type: api
  action: token-ohlcv
  source: market.coingecko    # forces CoinGecko instead of default DexPaprika

Without the dot, the virtual provider picks its default backend.

Concrete Providers

You can also use concrete providers directly when you need a specific backend or actions not exposed through virtual providers:

source valueProvider
omitted or "aixbt"AIXBT API (default)
"coingecko"CoinGecko / GeckoTerminal
"dexpaprika"DexPaprika
"defillama"DeFiLlama
"goplus"GoPlus

Tier Requirements

Each action has a minimum tier (free, demo, or pro). If the configured key's tier is too low, the step degrades gracefully. Add a fallback field to control the message the agent receives. See Fallback Mechanism.

6. Fallback Mechanism

The fallback field on API steps (including those with for:) provides graceful degradation when a provider key is missing or the tier is insufficient. The string you write becomes an instruction to the agent running the recipe, telling it what to do instead.

API steps: the agent receives a message prefixed with context:

Step "<id>" was skipped: no <source> API key configured. <your fallback text>
- id: price_data
  type: api
  action: chart
  source: market
  params:
    network: ethereum
    address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
    timeframe: day
    limit: 30
  fallback: "Use publicly available price data instead."

The agent sees: Step "price_data" was skipped: no market API key configured. Use publicly available price data instead.

for: steps: items that succeed return data normally. Items that fail degrade gracefully, and the agent receives a collated note listing which items need alternative handling:

<your fallback text> for: <item1>, <item2>, <item3>
- id: tvl
  type: api
  for: projects.data
  action: protocol
  source: defi
  params: { protocol: "{item.name}" }
  fallback: "Look up TVL data using available tools."

If 3 of 10 items fail, the agent sees: Look up TVL data using available tools for: Uniswap, Aave, Compound

Without fallback, a provider unavailability (missing key, tier too low) still degrades gracefully rather than failing the recipe, but you lose control over the instruction.

7. Transforms

Transforms reduce and reshape data before it reaches agents, controlling token consumption.

Transform Block

FieldTypeDescription
selectstring[]Field projection: keep only these fields
sampleSampleTransformWeighted random sampling

At least one of select or sample should be present (both can be used together).

Execution order: sampleprojectionselect. Sampling runs first (on the full rows, so weight fields are available even if later dropped); a step's projection profile then reshapes each row; select narrows the projected shape last. Without a projection, the order is sampleselect.

Select

Selects specified fields from each item. Supports dot notation for nested fields.

transform:
  select:
    - id
    - name
    - metrics.usd
    - metrics.volume

Produces: { id: "...", name: "...", metrics: { usd: ..., volume: ... } }. Nested structure is preserved.

Sample

Weighted random sampling that controls output size by item count or token budget.

FieldTypeRequiredDescription
countnumberno*Fixed number of items to sample
tokenBudgetnumberno*Per-step token ceiling and relative weight, not a fixed allocation. When a recipe declares an envelope param and runs over multiple items (N), each step's budget is scaled to envelope / N while preserving the ratios between steps and never exceeding the declared tokenBudget ceiling. With no envelope, the declared value is used as-is.
guaranteePercentnumbernoFraction (0-1) of top-weighted items always included (default: 0.3)
guaranteeCountnumbernoFixed number of top-weighted items always included, clamped to the sample's targetCount. At high N the scaled budget can shrink the sample below the requested guarantee, so the effective guarantee never exceeds what the budget admits.
weight_bystringnoField path for custom weights (dot notation supported)

Rules:

  • At least one of count or tokenBudget is required
  • If both are specified, count takes precedence
  • guaranteePercent and guaranteeCount are mutually exclusive
  • Guaranteed items are selected first (top by weight), then remaining slots are filled by weighted random sampling without replacement
  • guaranteeCount is clamped to both the available item count and the sample's targetCount — an absolute guarantee cannot force more items than the (possibly scaled-down) budget allows
  • Default weighting (when weight_by is omitted): favors recent, high-activity items

Two-lever budget model. Sampled steps have two independent budget levers:

  • Author lever — the per-step tokenBudget sets a ceiling and a relative weight. Two sampled steps with 50000 each split any shared envelope equally; 60000 vs 40000 split it 60/40.
  • Caller lever — the envelope recipe param (see Parameters) sets the total budget across the sampled steps for a single item, divided by N (the number of items being processed). At low N the per-step ceilings apply unchanged; at high N every sampled step scales proportionally to envelope / N (with a small per-item floor) so a wide fan-out stays bounded instead of multiplying the per-step ceilings by N.
transform:
  sample:
    tokenBudget: 25000
    guaranteePercent: 0.3
    weight_by: spikingScore
  select:
    - id
    - name
    - description
    - spikingScore

8. Projection Profiles

A projection profile is a named, model-visible shape for AIXBT signal or project data. Instead of hand-authoring a long transform.select on every signal/project step, set projection: <profile> on the step. The engine reshapes each row into that profile's compact form, dropping the largest fields (full cluster lists, activity ledgers, raw CoinGecko blobs, embedded intel). A transform.select can still narrow further.

Projection is AIXBT-only: it applies to steps whose source is aixbt (the default). It is not valid on external-provider steps or raw-path actions.

Where projection runs

Projection slots between sample and select (see Transforms):

sampleprojectionselect

  • sample runs first on the full rows, so weighting still sees pre-projection fields (e.g. observationCount).
  • projection then reshapes each surviving row to the profile's shape.
  • select, if present, narrows the projected shape further.

Because of this order, a select can only keep fields the profile emits; see the select rule.

Signal profiles

Valid on the intel and signals actions. Every signal profile carries the same base scalars (id, detectedAt, reinforcedAt, description, projectName, projectId, category, observationCount, hasOfficialSource, sentiment, plus metrics when enrichment attached it). They differ only in how they represent clusters, citations, and the activity ledger:

ProfileCluster / citation / activity shapeDropsFetch default
signal.compactclusterCount (count only)clusters, activity, citations, headlineactivity: none
signal.clusteredfull clusters[] (cluster identities)clusterCount, activity, citations, headlineactivity: none
signal.citedclusterCount + citations[]clusters, activity, headlineactivity: none
signal.timelineminimal row: id, detectedAt, description, category onlyeverything elseactivity: none
signal.activityfull clusters[] + full activity[] ledgercitations, headlineactivity: observations

Project profiles

Valid on the project, projects, and top-projects-climbing actions (each profile lists the subset it allows). All four compact coingeckoData to { apiId, symbol, slug, categories }, always dropping image and contractAddress. Every other top-level project field (name, rank, the score fields, momentumContext, metrics, tokens, and so on) passes through unchanged:

ProfilecoingeckoDataEmbedded signals/intelValid actionsFetch default
project.broadcompact (no description)droppedprojects, top-projects-climbingintelActivity: none
project.marketcompact (no description)droppedproject, projects, top-projects-climbingintelActivity: none
project.narrowcompact, keeps descriptiondroppedproject, projectsintelActivity: none
project.leaderboardcompact (no description)kept, each compacted to the signal.compact shapeprojectsintelActivity: none

Fetch defaults (activity mode)

Each profile also sets a fetch default (the "Fetch default" column above) so the API skips data the shape discards. These are defaults only: an explicit params.activity or params.intelActivity on the step overrides them. So projection: signal.compact with params: { activity: all } keeps the compact shape but still fetches the full ledger.

Caller-selectable projection

Instead of fixing the profile, a step can let the caller choose it at invocation by templating the field — projection: "{params.<name>}" — bounded by a closed allowedProjections menu:

params:
  detail:
    type: string
    default: signal.clustered      # must be a member of allowedProjections

steps:
  - id: feed
    type: api
    action: intel
    projection: "{params.detail}"
    allowedProjections: [signal.clustered, signal.activity, signal.cited]

The engine resolves the template once per run, then checks the result against allowedProjections. Pass --detail signal.activity to fetch the activity ledger; omit it and the param default (signal.clustered) applies. A value outside the menu — e.g. --detail signal.compact — aborts the run with an error naming the rejected value and the allowed set, so a caller (or planner) can self-correct in one shot.

Authoring rules (enforced by aixbt recipe validate):

  • A template projection requires a non-empty allowedProjections; every entry must be a real profile valid for the step's action.
  • The referenced param must exist, be type: string, and default to a member of allowedProjections (so an un-passed param still resolves in-menu).
  • A literal projection must not carry allowedProjections (there is no menu to constrain).
  • allowedProjections requires a template projection; a menu with no template — or on a raw-path action — is rejected.
  • Only the whole-field {params.X} form is allowed: no partial strings (signal.{params.x}), no {item.*}.

select must match the profile

Because projection runs before select, and select can only keep fields that already exist, a transform.select on a projected step may only name fields the profile emits. Naming a field the profile dropped silently yields nothing, and there is no validation error for it.

Common pitfalls:

  • On signal.compact / signal.cited, select clusterCount, not clusters: those profiles emit the count, not the list. Use signal.clustered if you need full cluster identities.
  • signal.cited keeps citations only if your select includes citations.
  • signal.timeline emits only id, detectedAt, description, category; selecting anything else drops it.
  • Embedded signals survive only under project.leaderboard; selecting signals on project.broad / narrow / market yields nothing.

A step with a projection and no select passes the whole profile shape through.

The built-in recipe registry is guarded by a test that enforces this select-matches-profile rule. Saved (user-authored) recipes are not, and a mismatched select fails silently, so follow the rules above when authoring your own.

Validation

aixbt recipe validate rejects:

  • an unknown profile name;
  • a profile used on an action it does not support (the error lists the allowed actions);
  • a projection on a raw-path action (e.g. /v2/...);
  • a projection on a non-aixbt source;
  • a template projection ({params.X}) without a non-empty allowedProjections, or with a menu entry that is unknown or invalid for the action;
  • a literal projection that carries allowedProjections, or an allowedProjections menu with no template projection;
  • a malformed projection template (anything other than a whole-field {params.X} — e.g. partial strings or {item.*});
  • a referenced projection param that is missing, not type: string, or whose default is not in allowedProjections.

Examples

# Broad intel scan: compact signals, cluster breadth as a count.
- id: intel
  type: api
  action: intel
  params: { reinforcedAfter: "-48h", limit: 50 }
  projection: signal.compact

# Verification-focused: keep citations (must be named in select).
- id: catalysts
  type: api
  action: intel
  projection: signal.cited
  transform:
    select: [description, category, citations]

# Project list: compact CoinGecko, no embedded signals.
- id: top_projects
  type: api
  action: projects
  params: { limit: 10 }
  projection: project.broad

9. Variable Templating

Template expressions use {...} syntax and are resolved in action, params, for, fallback, and analysis fields. They are not resolved in hints (passed through verbatim).

Expression Reference

ExpressionResolves to
{params.name}Value of recipe parameter name
{step_id}Step result data (shorthand for {step_id.data})
{step_id.data}Step result data (explicit)
{step_id.field}Shorthand for {step_id.data.field}
{step_id.data.nested.path}Nested field access
{step_id.data[*].field}Pluck: extract field from every array item
{step_id[*].field}Shorthand pluck (equivalent to above)
{item}Current for: iteration item
{item.field}Property of the current for: item

Relative Time Expressions

Bare relative time strings are converted to ISO 8601 timestamps at execution time:

ExpressionMeaning
-30m30 minutes ago
-24h24 hours ago
-7d7 days ago
params:
  detectedAfter: "-48h"                    # direct, resolved at execution
  start: "{params.lookback}"               # template: if lookback="-7d", resolved to ISO timestamp

Type Preservation

When an entire param value is a single template expression ("{step_id.field}"), the resolved type is preserved (arrays stay arrays, numbers stay numbers). When the expression is part of a larger string ("prefix {value} suffix"), it's interpolated as a string.

10. Segment Boundary Rule

Recipes are divided into segments by agent steps. This is the most important structural constraint to understand when writing recipes.

How segments work

Segment 0:  [api_step_1] → [api_step_with_for] → [agent_step_1]
                                                        | yield
Segment 1:  [api_step_2] → [api_step_3]
             ^ can access: agent_step_1 output + own segment steps
             x cannot access: api_step_1, api_step_with_for

An agent step ends a segment. The next segment begins after the agent step, and can access:

  1. The preceding agent step's output (the returns data provided at resume)
  2. Steps within its own segment that precede it

It cannot access steps from earlier segments (before the agent step). This is enforced at parse time.

Carry-forward context

When a recipe uses --agent, each spawned agent subprocess only sees the current yield's data. It has no memory of prior segments. If a downstream agent step or the analysis block references data from a pre-yield step, that data would be missing.

The engine solves this automatically: at each yield point, it scans downstream agent context arrays and analysis.context to identify which pre-yield step results are needed, bundles them into the yield output, and restores them on resume. Just reference the step ID in a context array.

Segment 0:  [projects] → [intel] → [picks (agent)]
                                          | yield (carries: projects, intel)
Segment 1:  [details] → [final (agent, context: [projects, picks])]
                                    ^ projects restored from carry-forward

Without --agent (yield/resume mode), the orchestrating agent already has full conversation context across all yields. Carry-forward data appears in the yield output as a hint about which prior steps matter downstream, but the agent doesn't need to pass it back. It can reference prior data from its own context.

Best practice: Always include accurate context arrays on agent steps and the analysis block, even if you only use yield/resume mode today. This ensures your recipes work correctly with --agent and makes the data dependencies explicit.

The segment boundary rule still applies to API step params references. An API step in segment 1 cannot template from an API step in segment 0. Carry-forward only transports data for agent context arrays and analysis.context.

11. Hints and Analysis Blocks

Hints

Passed through verbatim to the consuming agent; the CLI does not act on hints. They tell the agent how to interpret the data.

FieldTypeDescription
combinestring[]Step IDs whose data represents the same entities
keystringShared field that relates the combined datasets
includestring[]Step IDs to include as reference data alongside combined
hints:
  combine:
    - projects
    - momentum
  key: id
  include:
    - clusters

Analysis

Instructions for the consuming agent's final analysis. Template expressions ({params.*}) are resolved at execution time.

FieldTypeDescription
instructionsstringMain analysis instructions
contextstring[]Step IDs whose data is needed for final analysis. Triggers carry-forward across yield boundaries in --agent mode. In yield/resume mode, indicates which prior data the agent should reference.
outputstringOutput format directive
analysis:
  instructions: |
    Analyze the momentum patterns for {params.chain} projects.
    Focus on divergences between momentum score and price action.
  context: [projects, momentum]
  output: markdown

12. Recipe Writing Guide

Composition patterns

Single-segment (no agent): Pure data pipeline. All steps execute and the result is returned directly. Good for data collection and enrichment without inference.

steps:
  - id: protocols
    type: api
    action: protocols
    source: defi
    transform:
      sample:
        count: 20
      select: [name, tvl, chain, category]
  - id: chains
    type: api
    action: chains
    source: defi

Agent gate (two segments): Broad data fetch → agent picks → deep enrichment on picks. The most common pattern.

steps:
  # Segment 0: broad scan
  - id: intel
    type: api
    action: intel
    params: { reinforcedAfter: "-48h", limit: 50 }
  - id: top_projects
    type: api
    action: top-projects-climbing
    params: { limit: 10 }
  - id: picks
    type: agent
    context: [intel, top_projects]
    instructions: "Select 3-5 standout projects..."
    returns: { projectIds: "string[]" }
  # Segment 1: targeted enrichment
  - id: details
    type: api
    action: projects
    params: { projectIds: "{picks.projectIds}" }
  - id: narrative
    type: api
    for: details.data
    action: intel
    params:
      projectIds: "{item.id}"
      detectedAfter: "-30d"
    transform:
      sample: { tokenBudget: 25000, guaranteePercent: 0.3 }
      select: [description, detectedAt, category]

Multi-provider enrichment: Combine AIXBT data with virtual providers in a single pipeline.

steps:
  - id: projects
    type: api
    action: projects
    params: { limit: 10, hasToken: "true" }
  - id: tvl
    type: api
    for: projects.data
    action: protocol
    source: defi
    params: { protocol: "{item.name}" }
    fallback: "TVL data unavailable for this project."
    transform:
      select: [name, tvl, chainTvls]

Appendix: Validation

Run aixbt recipe validate <file> to check a recipe for errors without executing it.

On this page