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

# Save AI Charts to a Dashboard

> Add a 'Save and add to dashboard' button to your own chat UI: persist a chart the agent created and pin it to a dashboard

This guide walks through the full flow for letting your users keep the charts
an Upsolve agent builds in chat:

1. Call the [chat endpoint](/api-reference/endpoint/chat-with-agent) from your own UI and capture the generated chart from the stream.
2. Show a **"Save and add to dashboard"** button next to the rendered chart.
3. On click, persist the chart with [Save Chart from Chat](/api-reference/endpoint/save-chart-from-chat), then pin it to a dashboard with [Add Charts to Dashboard](/api-reference/endpoint/add-charts-to-dashboard).

<Note>
  Charts the agent creates in chat are **ephemeral** — they exist only in the
  response stream and the agent's session memory. Until you call Save Chart
  from Chat, nothing is stored, and the chart is gone when the conversation
  ends.
</Note>

## Prerequisites

* **A project user token** for the end user, minted server-side via
  [Get Project User Token](/api-reference/endpoint/get-project-user-token) —
  see [Backend Setup](/ai-agent-builder/deploy-agents/backend-setup). All
  three endpoints in this guide accept it.
* **An agent ID** — the agent your users chat with.
* **A dashboard ID** — the target dashboard. For a fixed ("hardcoded")
  dashboard, copy its ID once from the hub, or look it up with
  [List Workspace Dashboards](/api-reference/endpoint/list-workspace-dashboards).

## Step 1: Chat and capture the chart

Call the chat endpoint and watch the stream for a `tool-result` event whose
`toolName` is `generateChart`. Its `payload.result.chartConfig` is the chart —
keep the whole object in state, untouched.

```typescript theme={null}
// See the Chat with an Agent reference for the full streaming parser.
if (
  event.type === "tool-result" &&
  event.payload?.toolName === "generateChart" &&
  event.payload?.result?.chartConfig
) {
  setGeneratedChart({
    chartConfig: event.payload.result.chartConfig, // save this verbatim
    chartData: event.payload.result.chartData, // rows, for rendering a preview
  });
}
```

<Warning>
  Pass `chartConfig` through **exactly as received**. It normally includes a
  `sqlOverride` field carrying the SQL the agent wrote — that is what the
  saved chart uses to load data on a dashboard. If your tool result carries the
  query on the sibling `sql` field instead of on the config, forward it on the
  request's top-level `sql` field. Without SQL from either source, the save
  endpoint rejects the request.
</Warning>

## Step 2: The "Save and add to dashboard" button

When a chart arrives, render it (using `chartData`) alongside a save button:

```tsx theme={null}
function SaveAndAddToDashboardButton({
  chartConfig,
  agentId,
  dashboardId,
  projectUserToken,
}: {
  chartConfig: object;
  agentId: string;
  dashboardId: string; // your hardcoded target dashboard
  projectUserToken: string;
}) {
  const [state, setState] = useState<"idle" | "saving" | "saved" | "error">(
    "idle"
  );

  const handleClick = async () => {
    setState("saving");
    try {
      // 1. Persist the chart
      const saveRes = await fetch(
        "https://api.upsolve.ai/v1/api/chart/save-from-chat",
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${projectUserToken}`,
          },
          body: JSON.stringify({ chartConfig, agentId }),
        }
      );
      const saveJson = await saveRes.json();
      if (saveJson.status !== "success") {
        throw new Error(saveJson.error?.message ?? "Failed to save chart");
      }
      const chartId = saveJson.data.chartId;

      // 2. Add it to the dashboard
      const addRes = await fetch(
        "https://api.upsolve.ai/v1/api/dashboard/add-charts",
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${projectUserToken}`,
          },
          body: JSON.stringify({
            targetDashboardId: dashboardId,
            charts: [{ chartId }],
          }),
        }
      );
      const addJson = await addRes.json();
      if (addJson.status !== "success") {
        throw new Error(addJson.error?.message ?? "Failed to add chart");
      }

      setState("saved");
    } catch (err) {
      console.error(err);
      setState("error");
    }
  };

  return (
    <button onClick={handleClick} disabled={state === "saving"}>
      {state === "idle" && "Save and add to dashboard"}
      {state === "saving" && "Saving…"}
      {state === "saved" && "Added to dashboard ✓"}
      {state === "error" && "Failed — try again"}
    </button>
  );
}
```

## Step 3 (reference): the two calls, as cURL

Persist the chart:

```bash theme={null}
curl -X POST "https://api.upsolve.ai/v1/api/chart/save-from-chat" \
  -H "Authorization: Bearer <project_user_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "chartConfig": { /* toolResult.result.chartConfig, verbatim */ },
    "agentId": "<agent_id>"
  }'
```

```json theme={null}
{
  "status": "success",
  "data": {
    "chartId": "b3f7…",
    "name": "Monthly Revenue 2025"
  }
}
```

Add it to the dashboard:

```bash theme={null}
curl -X POST "https://api.upsolve.ai/v1/api/dashboard/add-charts" \
  -H "Authorization: Bearer <project_user_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "targetDashboardId": "<dashboard_id>",
    "charts": [{ "chartId": "b3f7…" }]
  }'
```

The chart appears appended below the dashboard's existing charts, full width.
Users can resize and reposition it from the dashboard UI afterwards.

## How the pieces fit

| Concern            | Behavior                                                                                                                                                                                              |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Why `agentId`?** | The save endpoint links the chart to the agent's data connection and project, so the chart can query data when rendered on any dashboard. Recommended — pass the same ID you chat with.               |
| **Deep copy**      | Add Charts to Dashboard copies the saved chart into the dashboard (new chart ID in the response's `addedChartIds`). The original stays in the chart library and can be added to other dashboards too. |
| **Edit behavior**  | Saved chat charts are marked AI-generated; in the Upsolve hub their "Edit" action reopens the agent chat rather than the manual chart editor.                                                         |
| **Repeat clicks**  | Each click saves a new chart and adds a new tile. Disable the button after success (as above) unless you want duplicates.                                                                             |
| **Auth**           | Both endpoints also accept an Upsolve API key (`Authorization: Bearer up_…`) for server-side flows — e.g. if you proxy the save through your backend.                                                 |

## Troubleshooting

<AccordionGroup>
  <Accordion title="400: No SQL found for this chart">
    You extracted a partial config (for example, re-built the object from the
    rendered chart) instead of forwarding `toolResult.result.chartConfig`
    verbatim, and no query was supplied on the top-level `sql` field. Forward
    the config as received, or pass the tool call's `sql` on the request body.
  </Accordion>

  <Accordion title="404: Agent … not found in this organization">
    The `agentId` belongs to a different organization than the token you are
    calling with, or the ID is not the agent's ID (check the agent URL in the
    hub).
  </Accordion>

  <Accordion title="400: Connection belongs to a different project than the agent">
    You passed both `agentId` and an explicit `connectionId`, but the connection
    lives in a different project than the agent. Pass a `connectionId` from the
    agent's project, or omit it to use the agent's own connection.
  </Accordion>

  <Accordion title="422: Could not resolve a data connection for this chart">
    The chart needs a connection to run its SQL on a dashboard. Pass `agentId`
    (preferred — its data model supplies the connection) or an explicit
    `connectionId`. If you pass an `agentId` whose data model has no connection,
    attach a connection to the data model or pass `connectionId` directly.
  </Accordion>
</AccordionGroup>
