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
- Your agent produces a visual for its user, as it does today.
- Alongside it, your code attaches an artifact envelope to the tool call's
inputoroutput. - Agent Analytics recognizes the envelope, validates it, and renders a card in both the conversation view and the trace panel.
- 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.
{
"$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": {}
}
}
| Field | Required | Notes |
|---|---|---|
v | yes | Contract version. Always 1. |
spec | no | URL of the contract this payload follows. The key is vendor-neutral, so spec is how a payload says whose shape it is. Amplitude ignores it when rendering. |
type | yes | One of chart, summary, options, table, html. |
title | by type | Required for chart, table, and html; optional for summary and options. |
caption | no | One line of narrative under the card. |
scope | no | { label, dateRange? }, the period or population the data covers. Amplitude renders it beside the title, so a month's numbers never read as all-time. |
locale | no | BCP-47 tag. Drives number formatting. |
generatedAt, id | no | Provenance. Amplitude stores these but doesn't render them. |
payload | yes | Per-type body. Refer to Artifact types. |
Two conventions apply everywhere:
unitis 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 fromlocale.- Colors are semantic, never literal. Send
primary,success, ordangerand 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.
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:
| Attribute | Convention |
|---|---|
gen_ai.output.messages | OpenTelemetry GenAI |
gen_ai.tool.call.result | OpenTelemetry GenAI |
output.value | OpenInference |
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.
shape | Payload | Use for |
|---|---|---|
categorical | items: [{ label, value, color? }] | Bars, columns, pies, donuts |
series | series: [{ label, color?, points: [{ x, y }] }] | Lines and areas over an ordered x |
steps | steps: [{ label, value }] | Funnels, waterfalls |
points | points: [{ x, y, size? }], where x and y are numbers | Scatter, bubble |
matrix | xLabels, yLabels, values: number[][] | Heatmaps, grids |
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).
{
"$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.
{
"$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.
{
"$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
{
"$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.
{
"$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.
| Limit | Value |
|---|---|
Chart items (categorical) | 12 |
| Chart series | 4, up to 100 points each |
| Steps | 12 |
| Table | 8 columns × 50 rows |
| Summary metrics / facts / suggestions | 12 / 12 / 8 |
| Options | 12 |
html | 100,000 characters |
| Whole payload | 256 KB |
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
| Symptom | Cause |
|---|---|
| Raw JSON, no card | The envelope key is missing or misspelled, or artifact rendering isn't enabled for your organization. |
| Nothing at all, no card and no raw content | Tool content isn't reaching Amplitude. Confirm you're passing input/output (not toolInput/toolOutput) and that your content mode captures tool content. |
| "Artifact not rendered" with a reason | Amplitude recognized the envelope, but the payload failed validation. The reason names the field. Fix it and re-emit. |
| A note about a substituted presentation | Your kind or shape has no dedicated renderer yet, so Amplitude drew the closest honest equivalent. The data is intact. |
An html card that looks broken | The document loads something external, or relies on scripts. Inline everything; remove scripts. |
Was this helpful?