Ava Protocol AVS API

Public REST API for the Ava Protocol AVS aggregator — workflow creation, execution monitoring, smart-wallet management, secrets, tokens, node/trigger evaluation, and operator listing. RFC 7807 errors, cursor pagination, JWT bearer auth. The TypeScript SDK (@avaprotocol/sdk-js) is a thin wrapper over this wire protocol.

OpenAPI Specification

ava-protocol-avs-openapi-original.yml Raw ↑
openapi: 3.0.3

# AVS Aggregator REST API — public partner-facing surface.
#
# Source of truth for the REST API: Go handler interfaces and SDK types are
# generated from this spec via `make rest-gen`. See
# avs-infra/API_REST_IMPLEMENTATION_PLAN.md for the full design rationale.
#
# Conventions:
#   - All field names are camelCase (request bodies, response bodies, query
#     parameters, template variables).
#   - Resources are plural nouns; IDs in path; bodies in JSON.
#   - Custom actions use colon-suffix routes (Google AIP-136):
#       POST /workflows/{id}:pause, POST /workflows:simulate, etc.
#   - Errors follow RFC 7807 (application/problem+json).
#   - Pagination uses opaque cursors via `?before=` / `?after=` query params
#     and a uniform `{ data, pageInfo }` envelope.

info:
  title: Ava Protocol AVS API
  version: '1.0.0'
  description: |
    Public REST API for the Ava Protocol AVS aggregator. Exposes workflow
    creation, execution monitoring, smart-wallet management, and related
    operations.

    Authentication is a single credential type — a JWT bearer token — obtained
    either via the wallet-signing flow (`POST /auth:exchange`) or out-of-band
    via the operator-run `create-api-key` CLI. Every request must include
    `Authorization: Bearer <jwt>`.

servers:
  - url: https://gateway.avaprotocol.org/api/v1
    description: Production gateway
  - url: https://gateway-staging.avaprotocol.org/api/v1
    description: Staging gateway
  - url: http://localhost:8080/api/v1
    description: Local dev

# Health check lives at root, outside /api/v1/. Documented here so the spec is
# self-describing; the actual route is mounted by the server above /api/v1/.
tags:
  - name: Health
    description: Liveness / readiness probes
  - name: Auth
    description: Token issuance and credential management
  - name: Workflows
    description: Workflow CRUD and lifecycle actions
  - name: Executions
    description: Workflow execution history and status
  - name: Wallets
    description: Smart-wallet derivation and operations
  - name: Secrets
    description: User/workflow/org secret storage
  - name: Tokens
    description: ERC-20 metadata lookup
  - name: Nodes
    description: Stand-alone node execution
  - name: Triggers
    description: Stand-alone trigger evaluation
  - name: Operators
    description: Connected operator status (read-only)

security:
  - bearerAuth: []

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        JWT bearer token. Obtained via `POST /auth:exchange` (wallet
        signature flow) or via the operator-run `create-api-key` CLI
        (long-lived, server-to-server). Send on every request as
        `Authorization: Bearer <jwt>`.

  parameters:
    PageBefore:
      name: before
      in: query
      description: Cursor — return items immediately before this position (backward pagination).
      schema:
        type: string
    PageAfter:
      name: after
      in: query
      description: Cursor — return items immediately after this position (forward pagination).
      schema:
        type: string
    PageLimit:
      name: limit
      in: query
      description: Max items to return. Default 20; server-enforced ceiling applies.
      schema:
        type: integer
        format: int32
        minimum: 1
        maximum: 200
        default: 20
    ChainIdQuery:
      name: chainId
      in: query
      description: |
        The chain to operate on (a single value). Omit to use the aggregator
        default (the request's JWT `aud` chain, then the gateway default).
      schema:
        type: integer
        format: int64

  schemas:
    # -------------------------------------------------------------------
    # Common scalars
    # -------------------------------------------------------------------

    ChainId:
      type: integer
      format: int64
      description: |
        Numeric chain ID (e.g. 11155111 for Sepolia, 8453 for Base). On
        chain-aware trigger/node configs this is required and must be a
        configured chain; on query/filter params it is optional.
      example: 11155111

    EthereumAddress:
      type: string
      pattern: '^0x[a-fA-F0-9]{40}$'
      description: Lowercase or checksummed hex EOA / contract address.
      example: '0x82F2Dd9a552a69f2ceD7Ff2D05c43aB8430158FB'

    Hex:
      type: string
      pattern: '^0x[a-fA-F0-9]*$'
      description: Arbitrary-length hex-encoded byte string.

    Ulid:
      type: string
      pattern: '^[0-9A-HJKMNP-TV-Z]{26}$'
      description: ULID identifier (26-char Crockford base32).
      example: '01JG2FE5MDVKBPHEG0PEYSDKAC'

    Timestamp:
      type: string
      format: date-time
      description: RFC 3339 timestamp.

    # -------------------------------------------------------------------
    # Pagination envelope (shared by every list endpoint)
    # -------------------------------------------------------------------

    PageInfo:
      type: object
      required:
        - hasNextPage
        - hasPreviousPage
      properties:
        hasNextPage:
          type: boolean
        hasPreviousPage:
          type: boolean
        startCursor:
          type: string
          description: Cursor for the first item in the current page; pass to `before` for the previous page.
        endCursor:
          type: string
          description: Cursor for the last item in the current page; pass to `after` for the next page.

    # -------------------------------------------------------------------
    # RFC 7807 problem+json error shape
    # -------------------------------------------------------------------

    Problem:
      type: object
      description: |
        RFC 7807 problem+json. Returned as `application/problem+json` on any
        4xx/5xx response. `type` and `title` describe the error class; `detail`
        is human-readable; `instance` is a per-request identifier suitable for
        log correlation.
      required:
        - type
        - title
        - status
      properties:
        type:
          type: string
          format: uri
          description: URI identifying the problem type.
          example: 'https://docs.avaprotocol.org/errors/workflow-not-found'
        title:
          type: string
          description: Short, human-readable summary.
          example: 'Workflow not found'
        status:
          type: integer
          format: int32
          description: HTTP status code (echoed for clients that surface only the body).
          example: 404
        detail:
          type: string
          description: Human-readable explanation specific to this occurrence.
          example: 'No workflow with id 01JG2FE5MDVKBPHEG0PEYSDKAC for owner 0xabc...'
        instance:
          type: string
          description: URI / opaque ID identifying this specific occurrence (e.g., request id).
          example: 'req_01JG2FE5MFKTH0754RGF2DMVY7'
        code:
          type: string
          description: |
            Machine-readable error code. Stable across releases; clients can
            switch on this for programmatic handling. Mirrors the gRPC-era
            ErrorCode enum vocabulary.
          example: 'WORKFLOW_NOT_FOUND'

    # -------------------------------------------------------------------
    # Health
    # -------------------------------------------------------------------

    HealthStatus:
      type: object
      required:
        - status
        - version
      properties:
        status:
          type: string
          enum: [ok, degraded, starting]
        version:
          type: string
          description: |
            Aggregator binary version (e.g., `v3.2.0`). Always set —
            SDK clients use this to stamp the canonical EIP-191 auth
            message so the signed `Version` field reflects the
            gateway the user actually authenticated against.
        chainId:
          $ref: '#/components/schemas/ChainId'
          description: EigenLayer registration chain ID.

    # -------------------------------------------------------------------
    # Auth
    # -------------------------------------------------------------------

    AuthExchangeRequest:
      type: object
      required:
        - ownerAddress
        - signature
        - message
      properties:
        ownerAddress:
          $ref: '#/components/schemas/EthereumAddress'
        signature:
          $ref: '#/components/schemas/Hex'
          description: EIP-191 personal_sign signature of `message`.
        message:
          type: string
          description: |
            The plain-text message that was signed. Must use the canonical
            format with the EigenLayer registration chain ID (NOT the workflow
            target chain). SDKs generate this locally.

    AuthExchangeResponse:
      type: object
      required:
        - token
        - expiresAt
      properties:
        token:
          type: string
          description: JWT bearer token.
        expiresAt:
          $ref: '#/components/schemas/Timestamp'
        subject:
          $ref: '#/components/schemas/EthereumAddress'
          description: The EOA the token is bound to (echoed for clients).

    # -------------------------------------------------------------------
    # Workflow domain — Workflow envelope + Triggers + Nodes + Edges
    #
    # A Workflow is a DAG: one Trigger fires it, Nodes are executed in
    # topological order following Edges, and the run is recorded as an
    # Execution (see executions section in a later commit).
    #
    # Trigger and Node are discriminated unions, keyed by `type`. Each
    # variant has its own typed `config` shape. The discriminator lets
    # SDKs and OpenAPI codegen produce exhaustive switch handling.
    # -------------------------------------------------------------------

    WorkflowStatus:
      type: string
      enum:
        - enabled
        - disabled
        - running
        - completed
        - failed
      description: |
        Lifecycle status. `enabled` means actively monitored; `disabled` is
        paused. `running`, `completed`, `failed` are terminal-ish states
        emitted during/after execution.

    TriggerType:
      type: string
      enum:
        - manual
        - fixedTime
        - cron
        - block
        - event
      description: |
        Discriminator field for the Trigger union. Mirrors the proto
        `TriggerType` enum but without the `TRIGGER_TYPE_` prefix.

    NodeType:
      type: string
      enum:
        - ethTransfer
        - contractWrite
        - contractRead
        - graphqlQuery
        - restApi
        - customCode
        - branch
        - filter
        - loop
        - balance
        - await
      description: |
        Discriminator field for the Node union. Mirrors the proto
        `NodeType` enum but without the `NODE_TYPE_` prefix.

    Lang:
      type: string
      enum:
        - javascript
        - json
        - graphql
        - handlebars
      description: |
        Language/format of an inline payload (e.g., custom code source,
        manual trigger data). Mirrors the proto `Lang` enum minus the
        `LANG_` prefix. Wire values are lowercase.

    # ---- Trigger configs ----

    ManualTriggerConfig:
      type: object
      description: User-initiated trigger; no chain context.
      required: [lang]
      properties:
        data:
          description: Arbitrary structured payload returned in the trigger output.
          # google.protobuf.Value equivalent; OpenAPI allows any JSON.
          additionalProperties: true
        headers:
          type: object
          additionalProperties: { type: string }
          description: HTTP headers (for webhook testing).
        pathParams:
          type: object
          additionalProperties: { type: string }
          description: Path parameters (for webhook testing).
        lang:
          $ref: '#/components/schemas/Lang'

    FixedTimeTriggerConfig:
      type: object
      description: Fires at one or more absolute Unix-epoch milliseconds.
      required: [epochs]
      properties:
        epochs:
          type: array
          items:
            type: integer
            format: int64
          minItems: 1

    CronTriggerConfig:
      type: object
      description: Fires on one or more cron schedules.
      required: [schedules]
      properties:
        schedules:
          type: array
          items:
            type: string
            description: 'Standard cron expression (e.g., `* * * * *`).'
          minItems: 1
        timezone:
          type: string
          description: 'IANA timezone (e.g., `UTC`, `America/New_York`). Default UTC.'

    BlockTriggerConfig:
      type: object
      description: Fires every N blocks on the target chain.
      required: [interval, chainId]
      properties:
        interval:
          type: integer
          format: int64
          minimum: 1
          description: Fire every N blocks.
        chainId:
          $ref: '#/components/schemas/ChainId'
          description: Chain to watch blocks on. Required — a workflow carries no chain to inherit.

    EventTriggerQuery:
      type: object
      description: Single ethereum.FilterQuery — one subscription per query.
      properties:
        addresses:
          type: array
          items: { $ref: '#/components/schemas/EthereumAddress' }
          description: Contract addresses to filter events from. Empty matches any contract.
        topics:
          type: array
          items:
            type: string
            nullable: true
          description: |
            Topic filters (`topics[0]` is the event signature, `topics[1..]`
            are indexed parameter values). `null` means wildcard at that
            position.
        maxEventsPerBlock:
          type: integer
          format: int32
          description: Safety ceiling per query per block. Exceeded → task cancelled.
        contractAbi:
          type: array
          items:
            additionalProperties: true
          description: Contract ABI entries (JSON form) for event decoding.
        conditions:
          type: array
          items: { $ref: '#/components/schemas/EventCondition' }
          description: Filters applied to decoded event data.
        methodCalls:
          type: array
          items: { $ref: '#/components/schemas/EventMethodCall' }
          description: Method calls used to enrich decoded event data (e.g., `decimals`).

    EventCondition:
      type: object
      description: Predicate evaluated against decoded event data.
      required: [fieldName, operator, value]
      properties:
        fieldName: { type: string }
        operator:
          type: string
          enum: [eq, ne, gt, gte, lt, lte, contains]
        value:
          type: string
          description: |
            Value to compare against, encoded as a string. The operator
            parses it according to `fieldType` (e.g. `int256` / `uint256`
            → big.Int, `address` → checksummed hex, `bool` →
            "true"/"false"). Matches the proto `EventCondition.value`,
            which is also a string.
        fieldType: { type: string }

    EventMethodCall:
      type: object
      required: [methodName]
      properties:
        methodName: { type: string }
        callData:
          $ref: '#/components/schemas/Hex'
        applyToFields:
          type: array
          items: { type: string }
        methodParams:
          type: array
          items:
            type: string
            description: Handlebars template; resolves against decoded event data.

    EventTriggerConfig:
      type: object
      description: Fires when matching on-chain events are observed.
      required: [queries, chainId]
      properties:
        queries:
          type: array
          items: { $ref: '#/components/schemas/EventTriggerQuery' }
          minItems: 1
        cooldownSeconds:
          type: integer
          format: int32
          minimum: 0
          description: |
            Seconds to wait after a fire before allowing the same task to
            trigger again. Default 300. 0 disables cooldown.
        chainId:
          $ref: '#/components/schemas/ChainId'
          description: Chain to watch events on. Required — a workflow carries no chain to inherit.

    # ---- Trigger union ----

    Trigger:
      type: object
      required: [type, name, config]
      properties:
        id: { type: string }
        name: { type: string }
        type: { $ref: '#/components/schemas/TriggerType' }
      discriminator:
        propertyName: type
        mapping:
          manual:    '#/components/schemas/ManualTrigger'
          fixedTime: '#/components/schemas/FixedTimeTrigger'
          cron:      '#/components/schemas/CronTrigger'
          block:     '#/components/schemas/BlockTrigger'
          event:     '#/components/schemas/EventTrigger'
      oneOf:
        - $ref: '#/components/schemas/ManualTrigger'
        - $ref: '#/components/schemas/FixedTimeTrigger'
        - $ref: '#/components/schemas/CronTrigger'
        - $ref: '#/components/schemas/BlockTrigger'
        - $ref: '#/components/schemas/EventTrigger'

    ManualTrigger:
      allOf:
        - type: object
          properties:
            type:
              type: string
              enum: [manual]
            config: { $ref: '#/components/schemas/ManualTriggerConfig' }

    FixedTimeTrigger:
      allOf:
        - type: object
          properties:
            type:
              type: string
              enum: [fixedTime]
            config: { $ref: '#/components/schemas/FixedTimeTriggerConfig' }

    CronTrigger:
      allOf:
        - type: object
          properties:
            type:
              type: string
              enum: [cron]
            config: { $ref: '#/components/schemas/CronTriggerConfig' }

    BlockTrigger:
      allOf:
        - type: object
          properties:
            type:
              type: string
              enum: [block]
            config: { $ref: '#/components/schemas/BlockTriggerConfig' }

    EventTrigger:
      allOf:
        - type: object
          properties:
            type:
              type: string
              enum: [event]
            config: { $ref: '#/components/schemas/EventTriggerConfig' }

    # ---- Node configs ----

    MethodCall:
      type: object
      description: One call to a contract method (used by ContractWrite + ContractRead).
      required: [methodName]
      properties:
        methodName: { type: string }
        callData:
          $ref: '#/components/schemas/Hex'
        applyToFields:
          type: array
          items: { type: string }
        methodParams:
          type: array
          items:
            type: string
            description: Handlebars template for method args.

    ETHTransferNodeConfig:
      type: object
      required: [destination, amount, chainId]
      properties:
        destination: { $ref: '#/components/schemas/EthereumAddress' }
        amount:
          type: string
          description: Amount in wei (decimal string for big-int safety). Special value `max` withdraws the entire balance.
        chainId:
          $ref: '#/components/schemas/ChainId'
          description: Chain to execute on. Required — a workflow carries no chain to inherit.

    ContractWriteNodeConfig:
      type: object
      required: [contractAddress, chainId]
      properties:
        contractAddress: { $ref: '#/components/schemas/EthereumAddress' }
        callData: { $ref: '#/components/schemas/Hex' }
        contractAbi:
          type: array
          items:
            additionalProperties: true
        methodCalls:
          type: array
          items: { $ref: '#/components/schemas/MethodCall' }
        isSimulated:
          type: boolean
          description: When true, use Tenderly simulation instead of sending a real UserOp.
        value:
          type: string
          description: ETH value to send with the call (wei, decimal string).
        gasLimit:
          type: string
          description: Custom gas limit (decimal string).
        chainId:
          $ref: '#/components/schemas/ChainId'
          description: Chain to execute on. Required — a workflow carries no chain to inherit.

    ContractReadNodeConfig:
      type: object
      required: [contractAddress, chainId]
      properties:
        contractAddress: { $ref: '#/components/schemas/EthereumAddress' }
        contractAbi:
          type: array
          items:
            additionalProperties: true
        methodCalls:
          type: array
          items: { $ref: '#/components/schemas/MethodCall' }
        chainId:
          $ref: '#/components/schemas/ChainId'
          description: Chain to read from. Required — a workflow carries no chain to inherit.

    GraphQLQueryNodeConfig:
      type: object
      required: [url, query]
      properties:
        url:
          type: string
          format: uri
        query: { type: string }
        variables:
          type: object
          additionalProperties: { type: string }

    RestAPINodeConfig:
      type: object
      required: [url, method]
      properties:
        url:
          type: string
          format: uri
        method:
          type: string
          enum: [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS]
        headers:
          type: object
          additionalProperties: { type: string }
        body: { type: string }
        options:
          type: object
          description: |
            Generic options bag for backend features on terminal
            RestAPI nodes. `summarize: true` opts a SendGrid /v3/mail/send
            or Telegram /sendMessage node into the aggregator's
            context-memory summarizer, which composes a subject + HTML
            body from the workflow's execution context and injects them
            into the outgoing request. Without this field set, the
            aggregator falls back to the deterministic summarizer (no
            LLM polish).
          properties:
            summarize:
              type: boolean
              description: |
                When true on a terminal SendGrid or Telegram node,
                ComposeSummarySmart runs at execution time and fills
                in the empty content.value / text slot with an
                AI-generated body. No-op on non-notification URLs.
          additionalProperties: true

    CustomCodeNodeConfig:
      type: object
      required: [lang, source]
      properties:
        lang: { $ref: '#/components/schemas/Lang' }
        source: { type: string }

    BranchCondition:
      type: object
      required: [id, expression]
      properties:
        id: { type: string }
        type:
          type: string
          enum: [if, elseIf, else]
        expression:
          type: string
          description: JavaScript-evaluated boolean expression.

    BranchNodeConfig:
      type: object
      required: [conditions]
      properties:
        conditions:
          type: array
          items: { $ref: '#/components/schemas/BranchCondition' }
          minItems: 1

    FilterNodeConfig:
      type: object
      required: [inputVariable, expression]
      properties:
        inputVariable:
          type: string
          description: Template path for the source array (e.g., `{{custom_code1.data}}`).
        expression:
          type: string
          description: JavaScript predicate evaluated per item.

    LoopNodeConfig:
      type: object
      description: |
        Iterates over an input array, running an inner Node per item. The
        runner node is one of the chain-aware or chain-agnostic node types;
        a chain-aware runner must specify its own required `chainId` (there is
        no inheritance from the loop or workflow).
      required: [inputVariable, runner]
      properties:
        inputVariable:
          type: string
          description: Template path for the iterable (e.g., `{{settings.addressList}}`).
        iterVar:
          type: string
          default: 'value'
          description: Name of the per-iteration variable (defaults to `value`).
        runner:
          $ref: '#/components/schemas/Node'

    BalanceNodeConfig:
      type: object
      required: [address, chain]
      properties:
        address: { $ref: '#/components/schemas/EthereumAddress' }
        chain:
          type: string
          description: 'Chain name or numeric ID (e.g., `ethereum`, `base`, `1`, `8453`).'
        includeSpam: { type: boolean }
        includeZeroBalances: { type: boolean }
        minUsdValueCents:
          type: integer
          format: int64
          description: Filter out tokens with USD value below this many cents.
        tokenAddresses:
          type: array
          items: { $ref: '#/components/schemas/EthereumAddress' }
          description: Restrict to these tokens. Empty = fetch all.

    AwaitNodeConfig:
      type: object
      description: |
        Pauses the workflow until a wake arrives (durable execution). Two mutually
        exclusive flavors: the external-signal flavor (human approval — set `channel`,
        e.g. a Telegram approve/reject), or the chain-event flavor (cross-chain — set
        `chainEvent` to pause until an operator observes that on-chain event, e.g. a
        bridge arrival on another chain). Exactly one flavor must be configured.
      properties:
        channel:
          type: string
          description: 'External-signal flavor — signal channel: `telegram` or `api`.'
        approvers:
          type: array
          items: { type: string }
          description: External-signal flavor — authorized approver identities. Empty = the workflow owner.
        prompt:
          type: string
          description: External-signal flavor — message shown to the approver.
        chainEvent:
          allOf: [{ $ref: '#/components/schemas/EventTriggerConfig' }]
          description: |
            Chain-event flavor — the on-chain event to wait for (a mid-workflow
            EventTrigger). An operator covering `chainEvent.chainId` watches it and
            resumes the execution when it fires. Mutually exclusive with `channel`.
        timeoutSeconds:
          type: integer
          format: int64
          description: Safety bound; 0 = server default (the wait is never unbounded).

    SignalExecutionRequest:
      type: object
      required: [decision]
      properties:
        decision:
          type: string
          enum: [approve, reject]
          description: The approver's decision.
        payload:
          type: object
          additionalProperties: true
          description: Optional structured data delivered as the await step's output.

    # ---- Node union ----

    Node:
      type: object
      required: [type, id, config]
      properties:
        id: { type: string }
        name: { type: string }
        type: { $ref: '#/components/schemas/NodeType' }
      discriminator:
        propertyName: type
        mapping:
          ethTransfer:   '#/components/schemas/ETHTransferNode'
          contractWrite: '#/components/schemas/ContractWriteNode'
          contractRead:  '#/components/schemas/ContractReadNode'
          graphqlQuery:  '#/components/schemas/GraphQLQueryNode'
          restApi:       '#/components/schemas/RestAPINode'
          customCode:    '#/components/schemas/CustomCodeNode'
          branch:        '#/components/schemas/BranchNode'
          filter:        '#/components/schemas/FilterNode'
          loop:          '#/components/schemas/LoopNode'
          balance:       '#/components/schemas/BalanceNode'
          await:         '#/components/schemas/AwaitNode'
      oneOf:
        - $ref: '#/components/schemas/ETHTransferNode'
        - $ref: '#/components/schemas/ContractWriteNode'
        - $ref: '#/components/schemas/ContractReadNode'
        - $ref: '#/components/schemas/GraphQLQueryNode'
        - $ref: '#/components/schemas/RestAPINode'
        - $ref: '#/components/schemas/CustomCodeNode'
        - $ref: '#/components/schemas/BranchNode'
        - $ref: '#/components/schemas/FilterNode'
        - $ref: '#/components/schemas/LoopNode'
        - $ref: '#/components/schemas/BalanceNode'
        - $ref: '#/components/schemas/AwaitNode'

    ETHTransferNode:
      allOf:
        - type: object
          properties:
            type: { type: string, enum: [ethTransfer] }
            config: { $ref: '#/components/schemas/ETHTransferNodeConfig' }

    ContractWriteNode:
      allOf:
        - type: object
          properties:
            type: { type: string, enum: [contractWrite] }
            config: { $ref: '#/components/schemas/ContractWriteNodeConfig' }

    ContractReadNode:
      allOf:
        - type: object
          properties:
            type: { type: string, enum: [contractRead] }
            config: { $ref: '#/components/schemas/ContractReadNodeConfig' }

    GraphQLQueryNode:
      allOf:
        - type: object
          properties:
            type: { type: string, enum: [graphqlQuery] }
            config: { $ref: '#/components/schemas/GraphQLQueryNodeConfig' }

    RestAPINode:
      allOf:
        - type: object
          properties:
            type: { type: string, enum: [restApi] }
            config: { $ref: '#/components/schemas/RestAPINodeConfig' }

    CustomCodeNode:
      allOf:
        - type: object
          properties:
            type: { type: string, enum: [customCode] }
            config: { $ref: '#/components/schemas/CustomCodeNodeConfig' }

    BranchNode:
      allOf:
        - type: object
          properties:
            type: { type: string, enum: [branch] }
            config: { $ref: '#/components/schemas/BranchNodeConfig' }

    FilterNode:
      allOf:
        - type: object
          properties:
            type: { type: string, enum: [filter] }
            config: { $ref: '#/components/schemas/FilterNodeConfig' }

    LoopNode:
      allOf:
        - type: object
          properties:
            type: { type: string, enum: [loop] }
            config: { $ref: '#/components/schemas/LoopNodeConfig' }

    BalanceNode:
      allOf:
        - type: object
          properties:
            type: { type: string, enum: [balance] }
            config: { $ref: '#/components/schemas/BalanceNodeConfig' }

    AwaitNode:
      allOf:
        - type: object
          properties:
            type: { type: string, enum: [await] }
            config: { $ref: '#/components/schemas/AwaitNodeConfig' }

    # ---- Workflow envelope ----

    Edge:
      type: object
      required: [id, source, target]
      properties:
        id: { type: string }
        source:
          type: string
          description: Node or trigger ID where this edge starts.
        target:
          type: string
          description: Node ID where this edge ends.

    InputVariables:
      type: object
      additionalProperties: true
      description: |
        Free-form key-value bag of values used to resolve `{{variable.path}}`
        template references inside trigger and node configs. Conventional
        well-known keys: `settings.runner` (smart wallet address),
        `settings.chainId` (chain id). camelCase keys; back-compat support
        for snake_case keys exists during the migration window.

    Workflow:
      type: object
      required: [id, owner, smartWalletAddress, trigger, nodes, status]
      properties:
        id: { $ref: '#/components/schemas/Ulid' }
        name: { type: string }
        owner: { $ref: '#/components/schemas/EthereumAddress' }
        smartWalletAddress: { $ref: '#/components/schemas/EthereumAddress' }
        trigger: { $ref: '#/components/schemas/Trigger' }
        nodes:
          type: array
          items: { $ref: '#/components/schemas/Node' }
        edges:
          type: array
          items: { $ref: '#/components/schemas/Edge' }
        inputVariables: { $ref: '#/components/schemas/InputVariables' }
        status: { $ref: '#/components/schemas/WorkflowStatus' }
        startAt:
          type: integer
          format: int64
          description: Unix-epoch milliseconds — workflow is inert before this time.
        expiredAt:
          type: integer
          format: int64
          description: Unix-epoch milliseconds — workflow is inert after this time.
        maxExecution:
          type: integer
          format: int64
          description: Cap on how many times this workflow may execute. 0 = unlimited.
        

# --- truncated at 32 KB (83 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/ava-protocol/refs/heads/main/openapi/ava-protocol-avs-openapi-original.yml