Charts
Ask in plain words and get a finished chart card, with the right chart, key figures, a title that states the finding and the source of every value.
POST /v1/charts turns a question in plain words into a chart card that already made the
data-visualization decisions: which chart, what to measure, how to aggregate it (totals, shares, weekly
grouping, a moving average, a base-100 index), the key figures on top, a title that states the finding
("The blue dollar rose 4.1% this week"), annotations, and the source of every value. You can also send your
own data and get the same card.
- The chart that tells it. One of 12 types, picked for the data. Force another and it's respected while the data allows it.
- Numbers you can check. A figure (a price, a rate, a poll) is found verbatim in what was published and comes with the articles it came from. It is never invented.
- Ready to publish. An iframe that adapts to its width, a PNG at 2× and an SVG, in four themes, public by id. From the API, the MCP server or the dashboard's playground.
- Fast. A card is drawn in under a millisecond on our side. Coverage charts come back in about a second; charts with figures, in 2 to 5 seconds.
From a query
curl https://api.typesearch.ai/v1/charts \
-H "Authorization: Bearer $TYPESEARCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "blue dollar this week" }'It answers 201 with the card:
{
"id": "chart_8k2m9q4x7w1p3n5z",
"object": "chart",
"type": "line",
"theme": "light",
"locale": "en",
"chart": {
"type": "line",
"title": "The blue dollar rose 4.1% this week",
"subtitle": "Selling rate, in pesos · Sep 19–25",
"x": { "kind": "time", "granularity": "day" },
"y": { "format": "currency", "currency": "ARS", "label": "Selling" },
"series": [
{
"name": "Selling",
"points": [
{ "x": "2026-09-19", "y": 1215, "sources": [2] },
{ "x": "2026-09-24", "y": 1250, "sources": [1] },
{ "x": "2026-09-25", "y": 1265, "sources": [0, 1] }
]
}
],
"kpis": [
{ "label": "Latest", "value": 1265, "change": { "value": 4.1, "percent": true } },
{ "label": "High", "value": 1265 },
{ "label": "Low", "value": 1215 }
]
},
"compatible_types": ["line", "area", "bar", "kpi", "table"],
"plan": {
"recipe": "figure_trend",
"type": "line",
"confidence": 0.92,
"alternatives": [
{ "type": "area", "probability": 0.3 },
{ "type": "bar", "probability": 0.22 },
{ "type": "kpi", "probability": 0.14 }
],
"topic": "the blue dollar",
"needs": { "queries": ["blue dollar this week"], "mode": "fast", "days": 7, "min_points": 3, "max_results": 50 }
},
"sources": [
{
"url": "https://diario.example/economia/dolar-blue-hoy",
"title": "El dólar blue cerró a 1.265 pesos",
"source": "Diario Ejemplo",
"published_at": "2026-09-25T18:10:00Z"
},
{
"url": "https://noticias.example/mercados/cotizacion",
"title": "Cotización del dólar: el blue tocó su máximo del mes",
"source": "Noticias Ejemplo",
"published_at": "2026-09-24T17:02:00Z"
},
{
"url": "https://cronica.example/economia/blue-arranca-la-semana",
"title": "El blue arranca la semana en 1.215 pesos",
"source": "La Crónica Demo",
"published_at": "2026-09-19T15:40:00Z"
}
],
"embed_url": "https://api.typesearch.ai/embed/chart_8k2m9q4x7w1p3n5z",
"image_url": "https://api.typesearch.ai/embed/chart_8k2m9q4x7w1p3n5z.png",
"svg_url": "https://api.typesearch.ai/embed/chart_8k2m9q4x7w1p3n5z.svg",
"svg": null,
"created_at": "2026-09-26T14:02:11.482Z",
"expires_at": "2026-12-25T14:02:11.482Z",
"cached": false,
"usage": { "cost_usd": 0.0019, "mode": "fast", "queries": 1, "duration_ms": 2840 },
"warnings": []
}chart: what is drawn. The series, the key figures on top (kpis), the annotations, how each axis is formatted, and atitlethat states the finding (unless you send your owntitle).plan: what was measured (recipe), the chart it chose, andalternatives: other types that fit, which you can switch to for free withPATCH.compatible_types: every type this data supports.sourcesand each point'ssources: the articles behind the chart, and the indexes insourceswhere each value was published.embed_url,image_urlandsvg_url: public by id, no key needed. See Embed it and PNG and SVG.usage.cost_usd: what the request was billed. See Pricing.
Everything you know from search narrows a chart too: days, published_after, published_before,
countries, languages, include_domains, exclude_domains and timezone. When the query doesn't name the
things to compare with "vs", pass them in compare (2 to 5).
What you can ask
The plan reads your query and picks a recipe: what to measure, the chart that tells it, the days to look
at and the cheapest mode that gets the data (mode: "auto", the default).
recipe | Ask for | What it measures | Chart | Days | Mode |
|---|---|---|---|---|---|
coverage | "coverage of the Budget" | Articles that mention the topic, per day, over the whole period. A clear peak is marked with that day's headline. | area | 30 | ultra |
coverage_compare | "Milei vs Bullrich coverage over time" | The same count for 2 to 5 names, one line each. | line | 21 | ultra |
share_of_voice | "Milei vs Bullrich vs Kicillof coverage" | Each name's share of the articles. | donut | 30 | ultra |
tone | "tone of the coverage of the IMF" | Positive, neutral and negative articles, relative to your query. | donut | 14 | fast |
tone_compare | "tone of Milei vs Bullrich coverage" | The same split, for each name. | stacked_bar | 30 | fast |
outlets | "which outlets cover lithium" | Articles per outlet. | bar_horizontal | 30 | ultra |
timeline | "timeline of the dam case" | The milestones of a story: the busiest day of each week, and the story most outlets told that day. | timeline | 60 | fast |
figure_trend | "blue dollar this week" | A figure the articles publish (a price, a rate, a poll), day by day. | line, or kpi for "today" | 7 | fast |
figure_compare | "inflation" with compare: ["Chile", "Peru"] | The latest published value of a figure, for each name. | bar | 14 | fast |
answers | Any query, plus one typed question in questions | How the answers split across the articles. | donut | 7 | fast |
index_prices | "prices" with index | Price per product, cheapest first (up to 12). | bar_horizontal | whole index | fast |
index_scatter | "price and rating" with index | Price against rating. Each dot's size is its number of reviews. | scatter | whole index | fast |
index_availability | "availability" with index | Products in stock, out of stock and on pre-order. | donut | whole index | fast |
index_brands | "brands" with index | Products per brand. | donut | whole index | fast |
- Days. The defaults above apply unless a date in the query ("this week", "in August"),
days,published_afterorpublished_beforesets the period. - Mode. Set
modeto fix it. Inauto, a figure chart that doesn't find enough figures in headlines and standfirsts reads the best articles (normal), and is billed in that mode. - Words first. When the query says it ("coverage", "tone", "which outlets", "timeline", "price", "vs"),
the plan follows the words (
plan.confidence: 1). Otherwise it is decided once and remembered for 7 days. - Custom index. With
index(idx_…), the chart is built from the structured data each page of your custom index declares: price, rating, reviews, availability, brand.
A typed question makes an answers chart: how the articles answer it. One question per chart, with the
same format as in structured output:
{
"query": "the 2027 Budget",
"questions": {
"stance": {
"type": "choice",
"instructions": "What is the article’s stance on the budget?",
"criteria": { "supportive": null, "critical": null, "neutral": "Reports without taking a side." }
}
}
}How figures are found
A chart with figures (figure_trend, figure_compare) never makes a number up:
- Verbatim. Numbers are found as written in the headlines, standfirsts and excerpts of the articles, with their currency or unit. Each one is then only confirmed, or discarded, as the value you asked for.
- One value per day. Each point is the median of what was published that day, so two outlets with slightly different figures don't draw a zigzag. In a comparison, each name gets its latest published value.
- A source for every point. Each point's
sourceslists indexes in the response'ssources: the articles where that value was published. Show them in a tooltip or a footnote.
{ "x": "2026-09-25", "y": 1265, "sources": [0, 1] }If a publisher later asks not to be shown, its sources leave the card when it is served, and a point left without a source is hidden.
From your own data
Send data instead of query and you get the same card: we pick the chart (with type: "auto"), sort,
summarize, add the key figures and write a title (unless you send title). It costs $0.30 per 1,000
charts. source sets the line in the footer.
Series
curl https://api.typesearch.ai/v1/charts \
-H "Authorization: Bearer $TYPESEARCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"series": [
{
"name": "Subscribers",
"points": [
{ "x": "2026-06-01", "y": 18200 },
{ "x": "2026-07-01", "y": 19650 },
{ "x": "2026-08-01", "y": 21400 },
{ "x": "2026-09-01", "y": 23900 }
]
}
],
"y": { "label": "Subscribers", "format": "number" }
},
"source": "Diario Ejemplo, internal data"
}'xis a date (2026-09-01) for a time axis, a number for a numeric one (a scatter), or text for categories. To say it yourself, setdata.x.kindtotime,categoryornumber.data.yformats the values:format(number,percentorcurrency, withcurrency: "USD"),unit,decimals,label, andhigher_is_better: falsewhen lower is better (a price): rankings then put the lowest first.- For a scatter,
data.x_measureformats the x axis, each point'srsizes its dot andlabelnames it. roleon a series (positive,neutral,negative,other) colors it as tone.highlightnames the category to stand out.
Events
A list of dated events makes a timeline:
{
"data": {
"events": [
{ "date": "2026-07-30", "label": "Technical report on the dam leaks", "detail": "Diario Ejemplo" },
{ "date": "2026-08-06", "label": "The province halts construction" },
{ "date": "2026-08-19", "label": "The builder files an injunction", "end": "2026-08-22" }
]
},
"title": "The dam case, in three milestones"
}Columns and rows
{
"data": {
"columns": [
{ "key": "model", "label": "Model" },
{ "key": "price", "label": "Price", "format": { "format": "currency", "currency": "USD" } },
{ "key": "rating", "label": "Rating", "format": { "decimals": 1 } }
],
"rows": [
{ "model": "Ridgeline Trail 4", "price": 109, "rating": 4.6 },
{ "model": "Canyon Grip GTX", "price": 119, "rating": 4.4 }
]
}
}| Limit | |
|---|---|
| Series | 12, with up to 1,000 points each |
| Events | 50 |
| Columns | 8 |
| Rows | 100 |
With type: "auto", events make a timeline; columns and rows, a table; a numeric x, a scatter; dates,
a line (a kpi for one or two points); several series of categories, a stacked_bar; percentages that
add up to 100 in up to 6 parts, a donut; more than 7 categories, a bar_horizontal; fewer, a bar.
Chart types
type: "auto" (the default) picks the one that tells the data best. The other 12:
type | When auto picks it | What the data needs |
|---|---|---|
line | A figure over time, or several names over time. | 3 points in time or more. |
area | Coverage over time: a volume. | 3 points in time or more. |
bar | A few categories, or a figure compared across names. | Up to 12 categories. |
bar_horizontal | Rankings: outlets, prices, long names or more than 12 categories. | One series of categories. |
stacked_bar | Each category split into parts: tone by name. | 2 series or more, positive values, up to 12 categories. |
pie | Parts of a whole. | Positive values, 2 to 30 parts. |
donut | Parts of a whole, with the total in the center: share of voice, tone, brands, availability. | Positive values, 2 to 30 parts. |
scatter | Two figures against each other: price and rating. | A numeric x, 5 points or more. |
funnel | Stages that shrink. | One series of 3 to 7 categories that never grows. |
timeline | The milestones of a story. | Dated events. |
kpi | One figure with its change: "today", "now". | One point or more. |
table | Rows and columns. | Always possible. |
Forcing a type ("type": "donut") works while the data allows it. When it doesn't, the card is drawn with
the closest type that fits and says so in warnings with chart_type_adjusted. compatible_types lists
the types this data supports, best first.
The rules we apply
What someone who makes charts for a living would do, applied to every card. The same data always gives the same card.
- Long daily series become weeks (past 45 days) or months (past 8 months), adding up counts and keeping
the last value of a figure (
grouped). - No dual axes. Lines on very different scales (more than 25 times apart) are compared as an index, base
100 (
indexed). - Noisy daily series of three weeks or more get a 7-point moving average, with the raw series faint
underneath (
chart.smoothing). - Pies and donuts show at most 6 slices, with the rest in "Others", always last. Rankings show at most 12 bars; lines, areas and stacked bars, up to 6 series.
- Rankings sort biggest first, or lowest first when lower is better: a price, inflation, country risk.
- Long category names turn vertical bars into a horizontal ranking, so every label fits.
- A log scale only on lines, and only when the values span more than 200 times. Never on bars.
- Key figures on top: total, peak and daily average for coverage; latest (with its change), high and low for a figure.
- The title states the finding: "Budget coverage peaked on Sep 10", "Milei gets 42% of the coverage", "The priciest models are not the best rated".
Themes
theme | Looks | For |
|---|---|---|
light | White and warm grays. The default. | Apps, dashboards, docs. |
dark | Near black. | Dark interfaces. |
editorial | Warm paper, print ink, serif figures. | Media and blogs. |
electric | Deep blue gradient. | Slides and landing pages. |
The first series is always the typesearch electric blue, and the rest are told apart with color blindness
too. Set theme when you create the chart, switch it for free with PATCH, or show one view in another theme
with ?theme= on the embed or the image.
Embed it
embed_url is a page made to go in an iframe. It adapts to its width (under 480 px it switches to a compact
layout), shows tooltips on hover and touch, weighs a few KB and loads nothing from third parties: the fonts
are ours.
<iframe
src="https://api.typesearch.ai/embed/chart_8k2m9q4x7w1p3n5z"
title="The blue dollar rose 4.1% this week"
style="width: 100%; border: 0"
height="440"
loading="lazy"
></iframe>The page tells its parent the height it needs, on load and on every resize, with
{ type: "typesearch:chart:resize", id, height }. Listen for it once and every chart on the page fits
without scrollbars:
<script>
window.addEventListener('message', (event) => {
if (event.origin !== 'https://api.typesearch.ai') return;
const data = event.data;
if (!data || data.type !== 'typesearch:chart:resize') return;
for (const frame of document.querySelectorAll('iframe')) {
if (frame.contentWindow === event.source) frame.style.height = `${data.height}px`;
}
});
</script>data.id is the chart id, if you'd rather match by it. Add ?theme=dark (or editorial, electric) to show
it in another theme without making a new chart.
Embeds, images and SVGs are cached for 5 minutes, so a change made with PATCH shows up within that time.
Each IP can load up to 600 per minute.
PNG and SVG
image_url(….png): the card at twice the resolution, for chat, email or slides. In Markdown:.svg_url(….svg): the vector card.?size=compactgives either one in the narrow layout, for phones and stories, and?theme=in another theme:https://api.typesearch.ai/embed/chart_8k2m9q4x7w1p3n5z.png?size=compact&theme=electric.
Change a chart
PATCH /v1/charts/{id} switches the type, the theme, the title, the subtitle or the locale (en
or es). It redraws from the same data, without searching again, and it's free. Pick the type from
compatible_types. Without a title of your own, the title is rewritten for the new type and language.
curl -X PATCH https://api.typesearch.ai/v1/charts/chart_8k2m9q4x7w1p3n5z \
-H "Authorization: Bearer $TYPESEARCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "type": "area", "theme": "editorial" }'It answers with the updated chart. The embed_url, image_url and svg_url stay the same.
List, get and delete
GET /v1/charts: your charts, newest first,limitat a time (up to 100). Whenhas_moreistrue, passnext_beforeasbeforefor the next page.GET /v1/charts/{id}: one chart, with its data, plan and sources.DELETE /v1/charts/{id}: deletes it ({ "id": "chart_…", "object": "chart", "deleted": true }). Its embed, image and SVG stop working.
Every key of your organization sees the same charts. Stored charts expire after 90 days on pay as you go and
never on the monthly credit plan: expires_at says when, or null. See the
reference.
See the plan first
plan_only: true returns only the plan: the recipe, the chart type, its alternatives and what it would
search (plan.needs: queries, mode, days, minimum points). It doesn't search or draw, and it's free. chart
and id come back null.
Without storing it
store: false stores nothing: there is no id and no URLs, and the SVG comes in the response, in svg. It
costs the same. To get the SVG with a stored chart too, add include_svg: true.
Errors and warnings
| Status | code | When |
|---|---|---|
| 400 | invalid_request | Neither query nor data, or both; a field out of range; a PATCH with nothing to change. errors lists each field. |
| 402 | insufficient_credits, spend_limit_reached | No credit left, or the key reached its monthly limit. |
| 404 | chart_not_found | No chart with that id for your organization: it never existed, it was deleted or it expired. |
| 422 | insufficient_data | The search didn't find enough data for this chart. Not billed. |
insufficient_data says how many points it needed and how many it found. Try more days, another mode or a
broader query:
{
"type": "urn:typesearch:error:insufficient_data",
"status": 422,
"code": "insufficient_data",
"detail": "Not enough data for this chart: it needs 3 and found 1. Try more days, another mode or a broader query. Not billed.",
"request_id": "req_Vt4mQ8zK1pXa"
}Warnings don't stop the chart. They come in warnings, each with a code and a message:
code | What happened |
|---|---|
chart_type_adjusted | The data doesn't fit the type you asked for: it was drawn with the closest one that does. |
grouped | A long daily series was grouped by week, month or year. |
indexed | Series on very different scales were compared as an index, base 100. |
Pricing
A chart from a query costs its search, in the mode it needed and per query, plus $0.50 per 1,000 charts. With one query:
| Mode | Per 1,000 charts | Used by default for |
|---|---|---|
ultra | $1.50 | Coverage, share of voice, outlets. |
fast | $1.90 | Tone, timelines, figures, answers, custom index. |
normal | $2.70 | Figures, when headlines and standfirsts aren't enough. |
deep | $6.10 | Only when you set it. |
A chart that compares names searches one query per name: share of voice between three names in ultra
costs three searches at $1.00 per 1,000, plus one add-on. From your own data, a chart costs $0.30 per
1,000. usage.cost_usd says what each one cost.
Free:
- Every view of an embed, image or SVG.
GET, list,PATCHandDELETE.- The same request within 10 minutes: the same chart comes back with
cached: true. If only the search came from the cache, you pay just the add-on. - Failures, including
422 insufficient_data. plan_only.
The list is in the OpenAPI document, in x-pricing.per_1000_charts:
{ "query_addon": 0.5, "from_data": 0.3 }. For reference, Tako's Search, which
returns chart cards, lists $7 per 1,000. See Costs and caching.
From the MCP server
The MCP server's create_chart tool makes the same card from a query, so an agent
can answer with a chart:
{ "query": "Milei vs Bullrich vs Kicillof coverage", "theme": "dark" }It takes query, type, theme, days, compare, countries, languages and index, and returns the
title, subtitle, image_url (to show), embed_url, key_figures, the values drawn in data, events,
sources and cost_usd. Same key, same prices.