> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formo.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Update Chart

> Update an existing chart on a dashboard board. Modify the chart query, type, title, or visualization settings through the Formo API.



## OpenAPI

````yaml PUT /v0/boards/{boardId}/charts/{chartId}
openapi: 3.1.0
info:
  title: Formo Public API
  description: >-
    REST API for managing Formo projects, analytics, alerts, boards, charts,
    contracts, segments, and AI chat.


    **Auth.** Every endpoint on `api.formo.so` requires a workspace API key with
    the appropriate scopes (see `x-api-scopes`). The one exception is `POST
    /v0/raw_events`, which runs on `events.formo.so` and authenticates with the
    project SDK write key instead.


    **Response shape.** Successful responses return the resource directly (or `{
    data: [...], total, page, size, has_more }` for paginated lists). HTTP
    status carries success/failure; there is no envelope wrapping success
    bodies.


    **Errors.** Non-2xx responses from `api.formo.so` use the `Error` envelope,
    with two exceptions: some rate-limit rejections reply in plain text, and
    `POST /v0/raw_events` returns a plain `{ "error": "..." }` object. Treat the
    HTTP status as authoritative and parse defensively: `{ error: { code,
    message, doc_url, param?, details? } }`. Branch on the machine-readable
    `code` (see `ErrorCode` enum) and follow `doc_url` to the matching section
    of the [errors reference](https://docs.formo.so/api/errors).


    **Idempotency.** Pass an `Idempotency-Key` header on write requests to
    alerts, boards, charts, contracts, segments, and import to make retries
    safe; the response is cached for 24 h and replayed on duplicate keys.
    Profile writes and `POST /v0/query` do not support it.
  version: 0.1.0
  contact:
    name: Formo
    url: https://formo.so
servers:
  - url: https://api.formo.so
    description: API Server (boards, alerts, contracts, segments, profiles, query, import)
  - url: https://events.formo.so
    description: Events Server (event ingestion)
security:
  - WorkspaceApiKey: []
tags:
  - name: Alerts
    description: Manage project alerts and notifications
  - name: Boards
    description: Manage dashboard boards
  - name: Charts
    description: Manage charts within boards
  - name: Contracts
    description: Manage blockchain contract monitoring
  - name: Segments
    description: Manage user segments
  - name: Profiles
    description: Wallet profiles and import
  - name: Query
    description: >-
      Execute SQL queries and call pre-built analytics endpoints (KPIs, top
      pages, lifecycle, retention, revenue). Requires the query:read scope.
  - name: Events
    description: Event ingestion API (events.formo.so)
paths:
  /v0/boards/{boardId}/charts/{chartId}:
    put:
      tags:
        - Charts
      summary: Edit chart
      operationId: editChart
      parameters:
        - name: boardId
          in: path
          required: true
          schema:
            type: string
        - name: chartId
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateChartRequest'
      responses:
        '200':
          description: Chart updated
          content:
            application/json:
              schema:
                type: string
                description: Chart ID
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 255
      description: >-
        Optional unique value (e.g. a UUID v4) that lets you safely retry
        POST/PUT/PATCH/DELETE requests. The first request runs normally;
        subsequent requests with the same key replay the stored response (status
        + body) for 24 hours, so retries can never double-create or
        double-charge. Two concurrent requests with the same key return `409
        IDEMPOTENCY_IN_PROGRESS`. Generate a fresh key per logical operation.
  schemas:
    UpdateChartRequest:
      type: object
      description: Request body for updating an existing chart.
      properties:
        chartId:
          type: string
          description: ID of the chart to update.
        projectId:
          type: string
          description: Project the chart belongs to.
        query:
          type: string
          minLength: 1
          description: >-
            SQL query that powers the chart.


            - **`funnel`**: pass `"SELECT 1"`; the actual query is
            auto-generated from `steps`.

            - **`user_paths`**: omit it; the query is generated from
            `settings.anchors`.

            - **`retention`**: omit it; data is fetched automatically.

            - **All other types**: required; must be a valid SQL string.
        chart_type:
          type: string
          enum:
            - table
            - number
            - funnel
            - bar
            - line
            - area
            - pie
            - stacked
            - user_paths
            - retention
          description: >-
            Visualization type. Determines which other fields are required:


            | `chart_type` | Extra required fields |

            |---|---|

            | `table` | `query` |

            | `number` | `query` (must return 1 row × 1 column) |

            | `bar` | `query`, `x_axis`, `y_axis` (≥ 1) |

            | `line` | `query`, `x_axis`, `y_axis` (≥ 1) |

            | `area` | `query`, `x_axis`, `y_axis` (≥ 1; exactly 1 if `group_by`
            set) |

            | `pie` | `query`, `y_axis` (exactly 1) |

            | `stacked` | `query`, `x_axis`, `y_axis` (exactly 1), `group_by` |

            | `funnel` | `steps` (≥ 2), `query` placeholder `"SELECT 1"` |

            | `user_paths` | `settings.anchors` (≥ 1); query is generated |

            | `retention` | `settings.entryFilter` (key required; `null` means
            any event) |
        title:
          type: string
          minLength: 1
          description: Display name shown on the chart and board.
        description:
          type: string
          description: Optional description.
        x_axis:
          type: string
          description: >-
            Column name for the X axis. Required for `bar`, `line`, and
            `stacked`.
        y_axis:
          type: array
          items:
            type: string
          description: |-
            Column name(s) used as Y axis metrics.

            - `bar` / `line`: at least 1 element required.
            - `pie` / `stacked`: exactly 1 element required.
        group_by:
          type: string
          description: Column to group / stack series by. Required for `stacked`.
        steps:
          type: array
          items:
            $ref: '#/components/schemas/FunnelStep'
          minItems: 2
          description: >-
            Ordered list of funnel steps. Required for `funnel` (minimum 2
            steps).


            Each element is a `FunnelStep`; add property predicates in its
            canonical `filters` array (e.g. `"filters": [{ "field": "rdns",
            "op": "eq", "value": "io.metamask" }]`).
        settings:
          $ref: '#/components/schemas/ChartSettings'
      required:
        - chartId
        - projectId
        - chart_type
        - title
    FunnelStep:
      type: object
      description: >-
        A single funnel or user-path step. Event-property predicates are stored
        in `filters`, using the same canonical `{field, op, value}` envelope as
        every other filter surface. Funnel steps (not user-path anchors or
        retention filters) may also carry `events`, an OR group: the step then
        matches any listed event. `type`/`event` stay the step's primary event
        and are always part of the group; `filters` apply to the whole group.
      properties:
        type:
          type: string
          enum:
            - event
            - track
            - decoded_log
          description: >-
            `event`: built-in page/connect/transaction events; `track`: custom
            tracked events; `decoded_log`: decoded smart-contract events.
        event:
          type: string
          minLength: 1
          description: >-
            Event name (e.g. `page`, `connect`, `transaction`, or a custom track
            event name).
        events:
          type: array
          maxItems: 10
          items:
            $ref: '#/components/schemas/FunnelStepEvent'
          description: >-
            Optional OR group (funnel steps only, max 10). When present and
            non-empty the step matches any of these events, e.g.
            `[{"type":"track","event":"Swap
            Initiated"},{"type":"track","event":"Limit Order Placed"}]`. The
            primary `type`/`event` is always included.
        filters:
          type: array
          items:
            $ref: '#/components/schemas/StepFilterCondition'
      required:
        - type
        - event
      additionalProperties: true
    ChartSettings:
      type: object
      description: >-
        Chart-type-specific configuration. The fields that apply depend on
        `chart_type`:


        - **funnel**: `funnelType`, `conversionWindow`, `breakdown`

        - **user_paths**: `anchors`, `maxSteps`, `nodesPerStep`,
        `conversionWindow`, `filters`

        - **retention**: `entryFilter`, `retentionFilter`,
        `retentionUserFilters`, `retentionSignalType`, `retentionLabelSignal`


        Fields are optional at the schema level except where the selected chart
        type requires them. User Paths require at least one `anchors` entry.
        Retention requests must explicitly include `entryFilter`; use `null` for
        any event.
      properties:
        funnelType:
          type: string
          enum:
            - closed
            - open
          default: closed
          description: >-
            **Funnel only.** `closed`: users must complete steps in strict order
            with no intervening events. `open`: users may complete steps in
            order but other events may occur between steps.
        conversionWindow:
          $ref: '#/components/schemas/ConversionWindow'
          description: >-
            **Funnel & user_paths.** Maximum time from Step 1 for a user to
            complete all steps.
        breakdown:
          type: string
          enum:
            - device
            - browser
            - os
            - location
            - referrer
            - ref
            - utm_source
            - utm_medium
            - utm_campaign
            - utm_term
            - utm_content
            - builder_codes
          description: >-
            **Funnel only.** Split each funnel bar by this dimension. The top
            categories are shown individually; the rest are collapsed into
            'Others'.
        anchors:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/FunnelStep'
          description: >-
            **user_paths (required).** Ordered path anchors. The first entry
            starts the flow; the last entry ends it. A single entry creates an
            open-ended flow.
        maxSteps:
          type: integer
          minimum: 2
          maximum: 5
          default: 3
          description: >-
            **user_paths.** Maximum number of steps to show in the flow (2 to
            5). Values above 5 are clamped to 5.
        nodesPerStep:
          type: integer
          minimum: 2
          maximum: 8
          default: 5
          description: >-
            **user_paths.** Maximum number of unique event nodes visible per
            step (2 to 8). Values above 8 are clamped to 8.
        filters:
          type: string
          description: >-
            **user_paths.** JSON-encoded string of additional filters applied to
            the path query.
        retentionFilter:
          oneOf:
            - $ref: '#/components/schemas/FunnelStep'
            - type: 'null'
          description: >-
            **retention.** Event that qualifies a returning visit as 'retained'.
            If `null`, any event counts as a return.
        entryFilter:
          oneOf:
            - $ref: '#/components/schemas/FunnelStep'
            - type: 'null'
          description: >-
            **retention (required).** Event that places a user into the cohort.
            If `null`, any event counts as cohort entry. The key must be present
            even when its value is `null`.
        retentionUserFilters:
          type: array
          items:
            $ref: '#/components/schemas/RetentionUserFilter'
          description: >-
            **retention.** Zero or more user-segment filters that narrow the
            cohort (e.g. only desktop users, only users from a specific UTM
            source).
        retentionSignalType:
          type: string
          enum:
            - event
            - label
          default: event
          description: >-
            **retention.** `event` (default): cohort and retention are driven by
            events. `label`: driven by a label value over time (see
            `retentionLabelSignal`); when `label`, the event fields are ignored.
        retentionLabelSignal:
          $ref: '#/components/schemas/RetentionLabelSignal'
          description: >-
            **retention.** The label predicate when `retentionSignalType` is
            `label`.
        retentionCohortLabelFilters:
          type: array
          description: '**retention.** Label predicates that restrict an event-based cohort.'
          items:
            $ref: '#/components/schemas/RetentionCohortLabelFilter'
    Error:
      type: object
      description: >-
        Standard error envelope returned by every public API endpoint for any
        non-2xx response. The HTTP status code carries success/failure; the body
        provides a machine-readable `code`, a human-readable `message`, and a
        `doc_url` pointing at the matching section of the docs so agents can
        fetch context on the fly.
      properties:
        error:
          type: object
          required:
            - code
            - message
            - doc_url
          properties:
            code:
              $ref: '#/components/schemas/ErrorCode'
            message:
              type: string
              description: >-
                Human-readable error description. Wording may change between
                releases, so branch on `code`, not `message`.
            doc_url:
              type: string
              format: uri
              description: >-
                Link to the matching section of the errors reference at
                https://docs.formo.so/api/errors.
            param:
              type: string
              description: >-
                When the error pertains to a specific request field, the dotted
                path to that field (e.g. `body.trigger_filters.0.value`).
            details:
              type: object
              additionalProperties: true
              description: >-
                Code-specific extra context. For `INVALID_VALIDATION_REQUEST`
                this is a `{ fieldPath: message }` map of every Zod validation
                failure.
      required:
        - error
    FunnelStepEvent:
      type: object
      description: >-
        One alternative inside a funnel step's OR group. `filters` uses the same
        canonical `{field, op, value}` envelope as `FunnelStep.filters` and
        binds to this member only: `(A AND filtersA) OR (B AND filtersB)`.
      properties:
        type:
          type: string
          enum:
            - event
            - track
            - decoded_log
        event:
          type: string
          minLength: 1
        filters:
          type: array
          items:
            $ref: '#/components/schemas/StepFilterCondition'
          description: Member-scoped predicates, AND-joined within this member.
      required:
        - type
        - event
    StepFilterCondition:
      type: object
      description: A canonical filter on a funnel, flow, retention, or user-path step.
      properties:
        field:
          type: string
          description: Column or property targeted by this filter.
        op:
          type: string
          enum:
            - eq
            - neq
            - gt
            - lt
            - gte
            - lte
            - in
            - nin
            - startsWith
            - endsWith
            - contains
            - notEmpty
            - isEmpty
          description: Canonical comparison operator token.
        value:
          oneOf:
            - type: string
            - type: number
            - type: boolean
            - type: array
              minItems: 1
              items:
                oneOf:
                  - type: string
                    pattern: ^[^|]*$
                  - type: number
            - type: 'null'
          description: >-
            Value to compare against. Omit for `notEmpty` and `isEmpty`. For
            `in` / `nin`, pass a non-empty array or a pipe-delimited string.
            Array string members cannot contain a literal `|`, which is reserved
            as the Tinybird membership separator.
      required:
        - field
        - op
    ConversionWindow:
      type: object
      description: >-
        Time window within which a user must complete all funnel steps (measured
        from Step 1). Defaults to 2 hours if omitted.
      properties:
        value:
          type: integer
          minimum: 1
          description: Number of time units.
        unit:
          type: string
          enum:
            - hour
            - day
            - week
          description: Time unit. `week` = 7 days.
      required:
        - value
        - unit
    RetentionUserFilter:
      type: object
      description: >-
        A user-level retention cohort filter using the canonical `{field, op,
        value}` envelope.
      properties:
        value:
          oneOf:
            - type: string
            - type: number
          description: The value to compare against. Omit for `notEmpty` and `isEmpty`.
        field:
          type: string
          description: >-
            The user property to filter on (e.g. `device`, `browser`, `os`,
            `location`, `utm_source`, `utm_medium`, `utm_campaign`).
        op:
          type: string
          enum:
            - eq
            - neq
            - in
            - nin
            - gt
            - gte
            - lt
            - lte
            - notEmpty
            - isEmpty
          description: >-
            Comparison operator. Only the canonical terse tokens are accepted
            (the retired long forms
            `greater`/`greaterOrEqual`/`less`/`lessOrEqual` are rejected).
            `notEmpty` ("is not empty") and `isEmpty` ("is empty") are
            value-less existence checks on a string user property; the `value`
            is ignored. Substring operators (`startsWith` / `endsWith` /
            `contains`) are not supported on retention user filters.
      required:
        - field
        - op
    RetentionLabelSignal:
      type: object
      description: >-
        The label predicate for label-based retention. The cohort is wallets
        grouped by the week they FIRST crossed this predicate on their label
        value; a wallet is retained in a later week if its latest value as of
        that week's end still satisfies it (as-of / carry-forward, evaluated
        against label history).
      properties:
        field:
          type: string
          description: >-
            The label tag to evaluate (the value of `tag_id` set via `POST
            /v0/profiles/:address/labels`).
        op:
          type: string
          enum:
            - gt
            - gte
            - lt
            - lte
            - eq
          default: gt
          description: >-
            Comparison operator. Numeric operators coerce both sides via
            toFloat64OrZero, so a numeric op on a non-numeric value yields no
            match (not an error).
        value:
          type: string
          description: >-
            The threshold/value to compare against. Always a string; numeric ops
            coerce it.
        chain_id:
          type: string
          default: ''
          description: Optional chain scope. Empty string matches across all chains.
      required:
        - field
        - op
        - value
    RetentionCohortLabelFilter:
      type: object
      description: >-
        A label predicate that restricts an event-based retention cohort. Uses
        the canonical filter envelope.
      properties:
        field:
          type: string
          description: The label tag to evaluate.
        op:
          type: string
          enum:
            - eq
            - neq
            - contains
            - gt
            - gte
            - lt
            - lte
          default: eq
        value:
          type: string
        chain_id:
          type: string
          default: ''
          description: Optional chain scope. Empty string matches across all chains.
      required:
        - field
        - op
        - value
    ErrorCode:
      type: string
      description: >-
        Stable, enumerated error codes. New codes may be added in any release;
        clients should treat unknown codes as the closest matching HTTP status
        family.
      enum:
        - INTERNAL_SERVER_ERROR
        - INVALID_VALIDATION_REQUEST
        - UNAUTHORIZED
        - BAD_REQUEST
        - FORBIDDEN
        - NOT_FOUND
        - CONFLICT
        - INVALID_CHAIN_ID
        - CONTEXT_LIMIT_EXCEEDED
        - SERVICE_UNAVAILABLE
        - TOO_MANY_REQUESTS
        - IDEMPOTENCY_IN_PROGRESS
        - INVALID_IDEMPOTENCY_KEY
  responses:
    BadRequest:
      description: >-
        The request was rejected. `code` is either `INVALID_VALIDATION_REQUEST`
        (Zod schema mismatch; `details` carries a `{ fieldPath: message }` map)
        or `BAD_REQUEST` (semantic validation failure outside Zod, e.g.
        mismatched IDs, business-rule violations). Branch on `code`, not status,
        to tell the two apart.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            validation:
              summary: Zod schema mismatch
              value:
                error:
                  code: INVALID_VALIDATION_REQUEST
                  message: Invalid request data
                  doc_url: https://docs.formo.so/api/errors#invalid_validation_request
                  details:
                    body.name: String must contain at least 1 character(s)
            semantic:
              summary: Semantic validation failure
              value:
                error:
                  code: BAD_REQUEST
                  message: Target board must be different from the current board
                  doc_url: https://docs.formo.so/api/errors#bad_request
    Conflict:
      description: >-
        The request conflicts with current resource state, or an
        `Idempotency-Key` request with the same key is currently in flight.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: IDEMPOTENCY_IN_PROGRESS
              message: >-
                A request with this Idempotency-Key is already in progress.
                Retry shortly.
              doc_url: https://docs.formo.so/api/errors#idempotency_in_progress
  securitySchemes:
    WorkspaceApiKey:
      type: http
      scheme: bearer
      description: >-
        Workspace API key (e.g. `formo_xxx`). Create one in the Formo dashboard
        under Team Settings > API.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.