---
title: "POST /v1/workflows/analyze-funds"
method: POST
path: "/v1/workflows/analyze-funds"
---

# POST /v1/workflows/analyze-funds

`POST /v1/workflows/analyze-funds`

Analyze the fund requirements of an inline workflow definition without saving or executing it. Same response shape as the workflow-scoped variant, with `workflowId` empty. Read-only — does not consume CU.

## Request body

- union
  - object
  - AnalyzeFundsDefinitionParams
    - `defaultWalletAddress` string — DefaultWalletAddress overrides the wallet used when a node has no explicit connector or payload wallet field. When empty, the org's default wallet is used (same fallback as the saved-workflow path).
    - `definition` WorkflowDefinition, required — Definition is the inline workflow graph to analyze. Same shape as a saved workflow's definition. Unlike the ephemeral-run endpoint there is no trigger restriction — analysis is read-only and method-agnostic.
      - `blockExpansions` object — Set on run snapshots only (not workflow DB)
      - `inputSchema` WorkflowInput[]
        - `description` string
        - `key` string
        - `required` boolean
        - `type` string — "string", "number", "boolean", "object", "array"
      - `nodes` object, required
      - `sensitivePropKeys` string[] — Legacy: kept for old runs; no longer populated for new workflows
      - `triggerNodeIds` string[] — TriggerNodeIDs lists the node IDs that are trigger (root) nodes. Every workflow declares this — single-trigger workflows ship ["root"] (the legacy node id), multi-trigger workflows list every trigger node id. Treating single-trigger as a forest-of-1 removes the two-path branching throughout the BE + FE; older rows without the field are backfilled by migration 000282 and the field-missing path stays as a read-side safety net (see FindTriggerNodeIDs) but is no longer exercised by saves. "root" is also a runtime alias for "the trigger that fired this run" — {{root.X}} variable references resolve to the firing trigger regardless of which trigger fired. Don't repurpose the literal "root" as a trigger id on a multi-trigger workflow.
      - `variableDefs` VariableDef[] — VariableDefs is a snapshot of the workflow's declared variables at run creation time. The canonical source lives on the workflows row (Workflow.VariableDefs column). Snapshotted into the run definition so the worker can resolve {{$vars.x}} lookups and route variable-action writes to the right scope without an extra DB round trip.
        - `default` unknown
        - `description` string
        - `lifetime` 'persist' | 'reset'
        - `name` string
        - `type` 'number' | 'text' | 'boolean' | 'list' | 'object'

## Response `200`

OK

- AnalyzeFundsSuccessResponse
  - `code` integer
  - `data` AnalyzeFundsResponse
    - `balancesFetched` boolean
    - `requirements` AnalyzeFundsRequirement[]
      - `amountIsHumanReadable` boolean
      - `amountIsPartial` boolean — AmountIsPartial means RequiredAmount understates the true total for a reason OTHER than a runtime expression: an operand was dropped, or the amount is per-iteration inside a loop. Floor semantics apply (the number is a lower bound) but NO claim about a later step computing the remainder is true.
      - `amountRuntimeDetermined` boolean — AmountRuntimeDetermined is true when the leg's amount is wired to a runtime expression ({{...}}) rather than a literal, and could not be constant-folded. The leg's IDENTITY (chain + token) is fully resolved — only the SIZE is unknown until the workflow runs. Distinct from Unresolved, which means the identity itself could not be determined. When multiple legs sharing the same dedup key (wallet + chain + token) are merged (addOrMerge), this flag is true if ANY contributing leg was runtime-determined — even when another contributing leg supplied a literal amount. It is never cleared just because a literal amount is also present. FLOOR SEMANTICS: when this is true AND RequiredAmount is non-empty, the two fields are BOTH meaningful together: RequiredAmount is the sum of only the literal contributing legs — a known LOWER BOUND on the true requirement, not the total (the runtime-determined leg's own contribution is never invented). Treat RequiredAmount as "at least X" in this state. isInsufficient MAY still be computed against this floor and MAY be true — a balance below the floor is a genuine shortfall. A balance at or above the floor is UNKNOWN sufficiency, not proven sufficient.
      - `amountUnit` string — AmountUnit declares the unit of the amount when the analyzer KNOWS it, rather than inferring it from the string's shape. "raw" — smallest units (wei-like). ONLY the constant-fold sets this: it computes the integer itself, so it is the one producer that knows. "" — unknown. Consumers MUST print the value verbatim and MUST NOT rescale it by decimals. Deliberately has no "human" value yet. Action payloads don't carry unit metadata, so claiming "human" would be the same guess in a new coat.
      - `balance` AnalyzeFundsBalance
        - `amount` string — raw units (wei)
        - `amountIsPartial` boolean — AmountIsPartial mirrors funds.Requirement.AmountIsPartial: true when RequiredAmount understates the true total for a reason OTHER than a runtime expression — a dropped operand on a unit mismatch, or a per-iteration amount inside a loop. Same FLOOR semantics as AmountRuntimeDetermined (the amount is a known lower bound, and IsInsufficient=true against it is a genuine shortfall), but unlike that flag there is no runtime expression that a later step resolves — no claim is made about anything computing the remainder.
        - `amountRuntimeDetermined` boolean — AmountRuntimeDetermined mirrors funds.Requirement.AmountRuntimeDetermined: true when at least one contributing leg's amount is wired to a runtime expression and could not be resolved statically. When this is true and the requirement has no known amount at all, IsInsufficient stays false (nothing to compare against — "unknown", not "known and sufficient"). When this is true AND a RequiredAmount IS present (merged with a literal-amount leg), that amount is only a known FLOOR — the literal legs' sum, not the true total — and IsInsufficient IS computed against it. IsInsufficient=true there is a genuine shortfall. But IsInsufficient=false is still NOT proof of sufficiency: it only means the balance clears the known floor, not the (partially unknown) total. Consumers must key off AmountRuntimeDetermined AND AmountIsPartial, not IsInsufficient alone, to know whether "sufficient" can be asserted — both flags carry the identical FLOOR semantics described above.
        - `amountUsd` number
        - `chainId` integer
        - `decimals` integer
        - `isInsufficient` boolean — amount < requiredAmount
        - `isLow` boolean — gas only: balance below LowGasThreshold
        - `symbol` string
        - `tokenAddress` string
        - `walletAddress` string
      - `chainId` integer
      - `chainName` string
      - `decimals` integer
      - `isNative` boolean
      - `nodeId` string
      - `nodeName` string
      - `nodeType` string
      - `reason` string
      - `receivedDisplay` ReceivedLegDisplay — ReceivedDisplay is DISPLAY-ONLY context describing what the SAME node receives, attached only when this sent leg has no RequiredAmount of its own. See the ReceivedLegDisplay doc for the boundary it must not cross.
        - `amount` string — Amount is the received leg's resolved amount: a literal from the payload, or the exact constant-fold of an upstream literal-only convert-to-wei node. Never estimated.
        - `amountUnit` string — AmountUnit declares the unit of the amount when the analyzer KNOWS it, rather than inferring it from the string's shape. "raw" — smallest units (wei-like). ONLY the constant-fold sets this: it computes the integer itself, so it is the one producer that knows. "" — unknown. Consumers MUST print the value verbatim and MUST NOT rescale it by decimals. Deliberately has no "human" value yet. Action payloads don't carry unit metadata, so claiming "human" would be the same guess in a new coat.
        - `chainId` integer
        - `chainName` string
        - `decimals` integer
        - `isHumanReadable` boolean — IsHumanReadable mirrors Requirement.AmountIsHumanReadable for Amount: true for values like "0.5", false for raw smallest-unit integers.
        - `symbol` string
      - `requiredAmount` string
      - `source` 'requiredTokens' | 'fundsMovement.sent' | 'fundsMovement.received' | 'requiresGas'
      - `symbol` string
      - `tokenAddress` string
      - `unresolved` boolean
      - `unresolvedReason` 'chainId' | 'tokenAddress' | 'parseError' | 'template'
      - `walletAddress` string
    - `unresolved` Requirement[]
      - `amountIsHumanReadable` boolean
      - `amountIsPartial` boolean — AmountIsPartial means RequiredAmount understates the true total for a reason OTHER than a runtime expression: an operand was dropped, or the amount is per-iteration inside a loop. Floor semantics apply (the number is a lower bound) but NO claim about a later step computing the remainder is true.
      - `amountRuntimeDetermined` boolean — AmountRuntimeDetermined is true when the leg's amount is wired to a runtime expression ({{...}}) rather than a literal, and could not be constant-folded. The leg's IDENTITY (chain + token) is fully resolved — only the SIZE is unknown until the workflow runs. Distinct from Unresolved, which means the identity itself could not be determined. When multiple legs sharing the same dedup key (wallet + chain + token) are merged (addOrMerge), this flag is true if ANY contributing leg was runtime-determined — even when another contributing leg supplied a literal amount. It is never cleared just because a literal amount is also present. FLOOR SEMANTICS: when this is true AND RequiredAmount is non-empty, the two fields are BOTH meaningful together: RequiredAmount is the sum of only the literal contributing legs — a known LOWER BOUND on the true requirement, not the total (the runtime-determined leg's own contribution is never invented). Treat RequiredAmount as "at least X" in this state. isInsufficient MAY still be computed against this floor and MAY be true — a balance below the floor is a genuine shortfall. A balance at or above the floor is UNKNOWN sufficiency, not proven sufficient.
      - `amountUnit` string — AmountUnit declares the unit of the amount when the analyzer KNOWS it, rather than inferring it from the string's shape. "raw" — smallest units (wei-like). ONLY the constant-fold sets this: it computes the integer itself, so it is the one producer that knows. "" — unknown. Consumers MUST print the value verbatim and MUST NOT rescale it by decimals. Deliberately has no "human" value yet. Action payloads don't carry unit metadata, so claiming "human" would be the same guess in a new coat.
      - `chainId` integer
      - `chainName` string
      - `decimals` integer
      - `isNative` boolean
      - `nodeId` string
      - `nodeName` string
      - `nodeType` string
      - `reason` string
      - `receivedDisplay` ReceivedLegDisplay — ReceivedDisplay is DISPLAY-ONLY context describing what the SAME node receives, attached only when this sent leg has no RequiredAmount of its own. See the ReceivedLegDisplay doc for the boundary it must not cross.
        - `amount` string — Amount is the received leg's resolved amount: a literal from the payload, or the exact constant-fold of an upstream literal-only convert-to-wei node. Never estimated.
        - `amountUnit` string — AmountUnit declares the unit of the amount when the analyzer KNOWS it, rather than inferring it from the string's shape. "raw" — smallest units (wei-like). ONLY the constant-fold sets this: it computes the integer itself, so it is the one producer that knows. "" — unknown. Consumers MUST print the value verbatim and MUST NOT rescale it by decimals. Deliberately has no "human" value yet. Action payloads don't carry unit metadata, so claiming "human" would be the same guess in a new coat.
        - `chainId` integer
        - `chainName` string
        - `decimals` integer
        - `isHumanReadable` boolean — IsHumanReadable mirrors Requirement.AmountIsHumanReadable for Amount: true for values like "0.5", false for raw smallest-unit integers.
        - `symbol` string
      - `requiredAmount` string
      - `source` 'requiredTokens' | 'fundsMovement.sent' | 'fundsMovement.received' | 'requiresGas'
      - `symbol` string
      - `tokenAddress` string
      - `unresolved` boolean
      - `unresolvedReason` 'chainId' | 'tokenAddress' | 'parseError' | 'template'
      - `walletAddress` string
    - `workflowId` string
    - `workflowVersion` integer
  - `message` string
  - `requestId` string

## Other responses

- `400` — Invalid definition
- `403` — Missing org context

---

[API](https://skmtc.dev/b3os/apis/b3os-workflow-api.md) · [All operations](https://skmtc.dev/b3os/apis/b3os-workflow-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/b3os/b3os-workflow-api/revisions/a8c7af3c66a1/schema)
