---
title: "One chat-edit turn"
method: POST
path: "/dashboards/{dashboard_id}/chat"
tags: ["Dashboard v3"]
---

# One chat-edit turn

`POST /dashboards/{dashboard_id}/chat`

## Request

- Base URL: `https://app.mammoth.io/api/v2`
- URL: `https://app.mammoth.io/api/v2/dashboards/{dashboard_id}/chat`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-KEY` and API key in header `X-API-SECRET` and API key in header `X-WORKSPACE-ID`

Language → design controls: one guarded LLM turn applied to the draft canvas (append + async re-bake when a real change lands; an honest no-op resolves with just the message). Returns a job_id; poll /jobs/{id} for {message, changed, sequence, canvas, layout_intent, options}.

## Path parameters

- `dashboard_id` integer, required

## Request body

- ChatEditSpec: One chat-edit turn
  - `params` ChatEditParams, required
    - `prompt` string: The edit request. May be empty only when the turn carries attachments: the chat then asks what they are for
    - `scope` object, nullable: Scoped-edit context ({kind, title, focus}) — the guard layers contain the edit to the selected object
    - `selected_fig` integer, nullable: The selected chart's data-fig index, when one is selected
    - `active_page_id` string, nullable: The editor's current active page id — the edit targets THAT page. Absent (old clients) falls back to the persisted active page; an id that resolves to no page fails the edit rather than landing on another page
    - `target_tile_id` string, nullable: The selected added tile's stable id — resolves the target tile by identity (surviving a reorder/recompose that stales its position) instead of its positional figIndex; falls back to the index when absent or unmatched
    - `conversation_history` object[], nullable: The client-held session transcript ({role, content} turns) — wins over the persisted row transcript when provided
    - `client_turn_id` string, nullable: The FE's per-turn token — stamped on BOTH persisted messages of the turn so the client reconciles its optimistic prompt and binds the AI annotation by id, never by role+content
    - `change` union: The FE-computed 'what changed' chips (round-26 §3.1) — persisted VERBATIM on the assistant row so the change summary derives from the server transcript on reload, with no client-held history. The BE does not diff; it stores what the FE sends (`scope` is persisted the same way as the turn's `scopes`).
      - object[]
      - object
    - `attachments` string[], nullable: Refs (`att:<sha>`) of what the user attached to this message — pictures, documents or text kept by POST /dashboards/{id}/chat/attachments. Something to READ, never data
    - `attachment_action` AttachmentAction
      - `kind` 'purpose' | 'apply_look' | 'apply_brief', required
      - `purpose` string, nullable
      - `card` AttachmentCard
        - `instruction` string, nullable
        - `signals` AttachmentSignals
          - `primary` string, required
          - `accents` string[]
          - `ground` string, nullable
          - `fontName` string, nullable
        - `left_out` LeftOut[], nullable
          - `item` string, required: What the picture or brief has, in a few words.
          - `reason` string, required: Why this board can't carry it: 'no churn column in this data'.
        - `colours` AttachmentColour[], nullable
          - `hex` string, required
          - `name` string, required
        - `must_cover` BriefAsk[], nullable
          - `ask` string, required: What the brief asks for, in its own few words.
          - `lands_as` string, required: Where it lands on this board: a section, slide, tile or KPI by name. Empty when the data can't carry it.
          - `carried` boolean, required: False when this data can't carry the ask.
          - `reason` string: When not carried: why ('no cost column, so it's left out').
        - `call_out` BriefAsk[], nullable
          - `ask` string, required: What the brief asks for, in its own few words.
          - `lands_as` string, required: Where it lands on this board: a section, slide, tile or KPI by name. Empty when the data can't carry it.
          - `carried` boolean, required: False when this data can't carry the ask.
          - `reason` string: When not carried: why ('no cost column, so it's left out').
        - `number_checks` NumberCheck[], nullable
          - `brief_says` string, required: The figure alone, as the brief states it: 'fell 15%', '2.1M'.
          - `about` string, required: What it measures, in a few words: 'weekend views'. No figure.
          - `data_shows` string, nullable: The data's own figure for the same thing, copied from the FACTS given (with its unit or %). Null when the facts don't carry it.
      - `save_context` boolean

## Response `201`

Document created, URL follows

- ObjectJobSchema
  - `status_code` integer, nullable
  - `job_id` integer, nullable: Created job id
  - `failure_reason` string, nullable: Failure reason if status is failure

## Other responses

- `202`: Successfully Submitted
- `400`: Bad request syntax or unsupported method
- `403`: Request forbidden -- authorization will not help

---

[API](https://skmtc.dev/mammoth/apis/mammoth-analytics.md) · [All operations](https://skmtc.dev/mammoth/apis/mammoth-analytics/llms.txt) · [OpenAPI document](https://skmtc.dev/mammoth/apis/mammoth-analytics/revisions/17af8131167d?raw)
