On this page

For AI agents: a documentation index is available at /docs/llms.txt. Append .md to any page URL for markdown, or send Accept: text/markdown.

Render agent artifacts

An artifact is a small, versioned JSON envelope your agent attaches to a tool call to describe a visual it showed a user. Agent Analytics validates the envelope and redraws that visual as a card in the session view, so a reviewer sees what the user saw instead of the JSON behind it. Amplitude draws the card with built-in renderers that follow the viewer's theme, so you never ship UI code and Amplitude never executes yours.

Emit artifacts when the visual was the answer and the raw payload doesn't tell you whether the answer was any good, such as a chart your agent generated or a set of choices it offered. If you only need the underlying numbers for analysis, you don't need artifacts: tool content already lands as event properties you can query in charts and cohorts.

Artifact rendering is off by default. Contact your Amplitude representative to enable it for your organization.

How it works

  1. Your agent produces a visual for its user, as it does today.
  2. Alongside it, your code attaches an artifact envelope to the tool call's input or output.
  3. Agent Analytics recognizes the envelope, validates it, and renders a card in both the conversation view and the trace panel.
  4. The raw tool payload stays visible next to the card. Amplitude never replaces or hides it.

If a payload doesn't validate, Agent Analytics shows a short reason next to the raw output instead of a card. It never guesses at your data or renders a partial result.

The envelope

Every artifact is a JSON object under a single reserved key. The key is deliberately vendor-neutral and nothing in the payload grammar is Amplitude-specific, so the same builder can emit to another platform that adopts this shape — you change the key, not your code.

json
{
  "$agent_artifact": {
    "v": 1,
    "spec": "https://amplitude.com/docs/amplitude-ai/agent-analytics/render-artifacts",
    "type": "chart",
    "title": "Monthly payouts",
    "caption": "August was the highest month.",
    "scope": { "label": "Aug 2026" },
    "locale": "en-IN",
    "payload": {}
  }
}

Two conventions apply everywhere:

  • unit is a display string, not a concept. Send "INR", "%", "ms", "req/s", or anything else. Amplitude appends it to values as-is and never interprets it. Number formatting comes from locale.
  • Colors are semantic, never literal. Send primary, success, or danger and Amplitude maps them to theme colors that work in light and dark mode, and that stay distinguishable for color-blind readers. Hex values, fonts, and sizes aren't part of the contract.

Attach the envelope to a tool call

Artifacts travel on the tool call that produced them. Pass the envelope as the tool's output, or as input when the artifact is the input, as with a question you asked the user.

const artifact = {
  $agent_artifact: {
    v: 1,
    type: "chart",
    title: "Monthly payouts",
    payload: {
      shape: "categorical",
      kind: "bar",
      unit: "INR",
      items: [
        { label: "Aug 2026", value: 37279.7, color: "danger" },
        { label: "Jul 2026", value: 23925, color: "primary" },
      ],
    },
  },
};

session.trackToolCall("render_payouts_chart", latencyMs, true, {
  input: JSON.stringify({ range: "last-3-months" }),
  output: JSON.stringify(artifact),
});

Tool content comes from the input and output options. Amplitude ignores other spellings without an error, so your artifact never arrives. That includes toolInput and toolOutput, which are the names of the resulting event properties. If you rely on patch() for zero-code instrumentation, note that auto-extracted tool calls carry no input or output, so artifacts need an explicit trackToolCall. Refer to Track tool calls in the SDK reference.

Emit from any ingest path

The envelope is the same on every path. What changes is only where you put it: artifacts ride the tool call's existing content fields, so any path that carries tool content carries artifacts.

AI SDK

Pass the envelope as the output (or input) option, as in the examples above. Amplitude serializes it into the [Agent] Tool Output property.

HTTP API or a standard Amplitude SDK

Set the [Agent] Tool Output property directly on an [Agent] Tool Call event. Refer to Set up Agent Analytics for the full event contract and the ID fields every event needs.

bash
curl -X POST https://api2.amplitude.com/2/httpapi \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "YOUR_API_KEY",
    "events": [{
      "event_type": "[Agent] Tool Call",
      "user_id": "user-123",
      "event_properties": {
        "[Agent] Session ID": "ticket-42",
        "[Agent] Turn ID": 2,
        "[Agent] Trace ID": "trace-abc",
        "[Agent] Agent ID": "support-bot",
        "[Agent] Tool Name": "render_refund_summary",
        "[Agent] Tool Success": true,
        "[Agent] Tool Input": "{\"order_id\":\"A-1\"}",
        "[Agent] Tool Output": "{\"$agent_artifact\":{\"v\":1,\"type\":\"summary\",\"title\":\"Refund total\",\"payload\":{\"headline\":{\"value\":42.5,\"label\":\"Refunded\",\"unit\":\"USD\"}}}}"
      }
    }]
  }'

Both properties take a JSON string, so serialize the envelope before assigning it.

OTLP

Put the envelope in whichever result attribute your instrumentation already uses on the tool span. Amplitude reads the first one present, in this order:

Tool input follows the same pattern, from gen_ai.tool.call.arguments or input.value. The tool name comes from gen_ai.tool.name or tool.name. Refer to Set up Agent Analytics for the OTLP endpoint and headers.

Artifact types

chart

Describe a chart by its data shape, which Amplitude validates, plus an optional kind that hints at the picture you drew.

kind is a free string: bar, column, pie, donut, line, area, or anything else you call your chart. Amplitude draws a kind that has its own renderer that way. Anything else renders as the shape's default presentation with a visible note saying so, so a reviewer always knows they're looking at a substitute. Shapes that have no dedicated renderer yet (steps, points, matrix) render as a labeled table and carry the same note.

Optional chart fields: unit, xLabel, yLabel, and highlightedIndex (for categorical, to emphasize one item).

json
{
  "$agent_artifact": {
    "v": 1,
    "type": "chart",
    "title": "Monthly cash flow",
    "locale": "en-IN",
    "payload": {
      "shape": "series",
      "kind": "line",
      "unit": "INR",
      "xLabel": "Month",
      "yLabel": "Amount",
      "series": [
        {
          "label": "Cash-in",
          "color": "success",
          "points": [
            { "x": "Apr 2026", "y": 222389 },
            { "x": "May 2026", "y": 42810 }
          ]
        },
        {
          "label": "Cash-out",
          "color": "danger",
          "points": [
            { "x": "Apr 2026", "y": 61453 },
            { "x": "May 2026", "y": 32580 }
          ]
        }
      ]
    }
  }
}

summary

One number card at whatever density you need: a bare headline, a headline with a comparison, or a full overview with supporting metrics and facts. Send a headline; every other field is optional.

json
{
  "$agent_artifact": {
    "v": 1,
    "type": "summary",
    "title": "Net cash flow",
    "scope": { "label": "Sep 2026" },
    "payload": {
      "headline": {
        "value": 25008,
        "label": "Net cash flow",
        "unit": "SAR",
        "tone": "positive"
      },
      "delta": { "value": 80192, "direction": "up", "comparedTo": "vs August" },
      "metrics": [{ "label": "Entries", "value": 41, "tone": "neutral" }],
      "facts": [
        { "label": "All-time balance", "value": 918220, "tone": "positive" }
      ],
      "suggestions": ["Show entries for this month"]
    }
  }
}

tone is positive, negative, or neutral. It carries direction, not judgment. suggestions are the follow-ups your agent offered; they render as labels, because the session view is a record of what happened, not a place to continue the conversation.

options

A choice you presented to the user. Because the user has already answered by the time anyone reviews the session, the choices render as labels.

json
{
  "$agent_artifact": {
    "v": 1,
    "type": "options",
    "payload": {
      "question": "Which quarter should I show?",
      "options": [
        { "id": "2026-q1", "label": "Jan – Mar 2026" },
        { "id": "2026-q2", "label": "Apr – Jun 2026" }
      ],
      "reason": "too_many_results"
    }
  }
}

Attach this one to the tool's input, since the question is what your tool received. reason is your own short code for why the agent asked; it's stored for analysis and not rendered.

table

json
{
  "$agent_artifact": {
    "v": 1,
    "type": "table",
    "title": "Spend by region",
    "payload": {
      "columns": ["Region", "Sessions", "Spend"],
      "rows": [
        ["EU", 1200, 4517.5],
        ["US", 880, 91000]
      ]
    }
  }
}

Cells are strings or numbers; numbers format by locale and align right. Every row needs exactly one cell per column. Amplitude rejects a row of any other length instead of padding it, because padding would misstate your data.

html

For a visual the other types genuinely can't express, send self-contained markup.

json
{
  "$agent_artifact": {
    "v": 1,
    "type": "html",
    "title": "Custom SLA gauge",
    "payload": {
      "html": "<html><head><style>…</style></head><body>…</body></html>"
    }
  }
}

Amplitude enforces all of these:

  • Static markup only: Amplitude rejects script tags, and the rendering frame can't execute scripts at all. Interactivity belongs in the declarative types.
  • Fully self-contained: inline your CSS and data. Amplitude blocks external requests for images, fonts, stylesheets, and APIs, so a document that assumes the network renders broken. Use data: URIs for images.
  • Bring your own styling: the frame isolates your document, so Amplitude's theme doesn't reach inside it.
  • 100,000 characters maximum.

Amplitude shows an html artifact collapsed, with a button to render it, then draws it in a sealed frame that can't reach the surrounding page.

Prefer a declarative type wherever one fits: those cards follow the viewer's theme, work with screen readers, and appear in exports. html trades all of that for freedom of layout.

Limits

Amplitude rejects oversized payloads instead of truncating them, because a shortened chart would show the wrong picture.

Privacy

Artifact payloads travel on your tool content, so they follow the same content settings as the rest of your instrumentation, including your redaction configuration. Two things to know:

  • Redact before you serialize: redaction that rewrites text can corrupt JSON mid-payload, which makes the artifact unparseable and the card disappear. Remove or mask values as you build the envelope.
  • Markup carries data in more places than text: for html, check attributes and inline styles, not only visible strings.

If your organization uses metadata_only content mode, Amplitude doesn't capture tool content, so artifacts don't render.

Troubleshooting

Was this helpful?