{"slug": "analytics-api-how-to-serve-governed-metrics-to-any-consumer", "title": "Analytics API: How to Serve Governed Metrics to Any Consumer", "summary": "A developer built an analytics API backed by a semantic layer that serves governed metrics to any consumer, including AI agents. The API provides a single query interface for metrics like revenue and churn, handling query generation, caching, and access control. The project also includes an MCP charts tool that renders interactive charts from query results for AI agents.", "body_md": "You have metrics. Revenue, active users, churn, usage per customer. They live in your data warehouse. Now you need to serve them to multiple consumers: your product's frontend, your customers' integrations, an AI agent, a scheduled report generator.\n\nThe first instinct is custom API endpoints. One endpoint for revenue by region. Another for churn by plan. Another for the customer dashboard. Each endpoint has its own SQL query, its own response format, its own maintenance burden. By the twentieth endpoint, you're running a bespoke analytics service.\n\nAn analytics API backed by a [semantic layer](https://bonnard.dev/glossary/semantic-layer) gives you one query interface that serves every consumer. Define metrics once. Query them from anywhere.\n\nAn analytics API is a programmatic interface for querying metrics. Instead of connecting to a database and writing SQL, consumers call an API endpoint with the metrics they want and get structured results back.\n\n```\ncurl https://analytics.example.com/v1/query \\\n  -H \"Authorization: Bearer bon_pk_...\" \\\n  -d '{\n    \"measures\": [\"orders.total_revenue\", \"orders.order_count\"],\n    \"dimensions\": [\"orders.region\"],\n    \"timeDimensions\": [{\n      \"dimension\": \"orders.created_at\",\n      \"granularity\": \"month\",\n      \"dateRange\": [\"2026-01-01\", \"2026-03-31\"]\n    }],\n    \"orderBy\": { \"orders.total_revenue\": \"desc\" }\n  }'\n```\n\nThe response is structured JSON:\n\n```\n{\n  \"data\": [\n    { \"orders.region\": \"EMEA\", \"orders.total_revenue\": 142000, \"orders.order_count\": 340, \"orders.created_at\": \"2026-01-01\" },\n    { \"orders.region\": \"APAC\", \"orders.total_revenue\": 98000, \"orders.order_count\": 220, \"orders.created_at\": \"2026-01-01\" }\n  ]\n}\n```\n\nEvery consumer uses the same interface. The dashboard frontend, the customer's API integration, the scheduled report, and the AI agent all query the same endpoint with the same metric definitions. The analytics API handles query generation, execution, caching, and access control.\n\nCustom endpoints work at small scale. They break at medium scale:\n\n**Metric drift.** `/api/revenue`\n\nand `/api/dashboard/revenue`\n\nboth return \"revenue\" but use different SQL queries. One gets updated. The other doesn't.\n\n**N+1 endpoint problem.** Every new metric or dimension combination is a new endpoint. Revenue by region. Revenue by plan. Revenue by region AND plan. Revenue by region AND plan AND month. The combinatorial explosion is real.\n\n**No caching strategy.** Each endpoint hits the warehouse on every request. Response times grow with data volume. You end up building a caching layer per endpoint.\n\n**No access control.** Each endpoint implements its own auth. Customer A's key works on all endpoints or none. Per-tenant, per-metric access control is custom code.\n\nA shared query interface handles all of this. But there's a gap that shows up once AI agents become consumers: an agent that fetches rows still has to present them. A wall of JSON tells the user nothing. Agents need to chart query results, not just fetch them.\n\nFetching rows is half the job. The other half is rendering them so a person can read the answer. That's where `@bonnard/mcp-charts`\n\nfits: a `visualize`\n\ntool you add to your MCP server that renders interactive charts from your query results.\n\n```\nnpm install @bonnard/mcp-charts\njs\nimport { addCharts } from \"@bonnard/mcp-charts\";\n\n// runSql runs against your warehouse and returns typed rows\naddCharts(server, { runSql });\n```\n\n`addCharts`\n\nregisters a `visualize`\n\ntool (and a `visualize_read_me`\n\ncompanion) on your server. Bonnard never touches your database. Your `runSql`\n\ndoes, against Postgres, BigQuery, Snowflake, Databricks, DuckDB, or any source you wire up.\n\n`visualize`\n\nwith a query.`runSql`\n\nreturns rows.`ui://`\n\nwidget inside Claude or ChatGPT.The agent gets line, bar, and area charts for trends, scatter for distributions, funnel and waterfall for staged data, pie for shares, and a table when a table is the right answer. Axes, formatting, and gap-fill are decided from the typed schema, so the same query produces the same chart every time.\n\nOne query interface for the numbers. One `visualize`\n\ntool for the picture.\n\n| Approach | Metric governance | Multi-tenant | Caching | Maintenance |\n|---|---|---|---|---|\nCustom REST endpoints |\nNone (per-endpoint SQL) | DIY | DIY | High (per endpoint) |\nGraphQL API |\nSchema-level | DIY | Per-resolver | Medium-high |\nDirect warehouse access |\nNone | Manual | None | Low (but dangerous) |\nBI tool API (Looker, Metabase) |\nTool-specific | Tool-specific | Tool-specific | Medium |\nBonnard |\nInherits your `runSql` definitions |\nInherits your `runSql`\n|\nInherits your warehouse | Low (a few lines on your server). Adds interactive charts via the MCP `visualize` tool, rendered in Claude/ChatGPT from query results |\n\n**You need one when:**\n\n**You don't need one when:**\n\nOnce your analytics API can fetch rows, add a `visualize`\n\ntool so agents can chart them:\n\n```\nnpm install @bonnard/mcp-charts\njs\nimport { addCharts } from \"@bonnard/mcp-charts\";\n\naddCharts(server, { runSql });\n```\n\nThat registers the `visualize`\n\ntool on your MCP server. Agents call it with a query, your `runSql`\n\nreturns rows, and Bonnard renders an interactive chart inside Claude or ChatGPT. Adapters ship for Postgres, BigQuery, Snowflake, Databricks, and DuckDB, or pass your own `runSql`\n\n.\n\nFor the full walkthrough: [Add Interactive Charts to Your MCP Server](https://bonnard.dev/blog/mcp-charts). For the design choices behind the tool: [How Bonnard Builds Agent-Friendly MCPs](https://bonnard.dev/blog/how-bonnard-builds-agent-friendly-mcps).\n\nSource is on [GitHub](https://github.com/bonnard-data/mcp-charts).\n\nAn analytics API is a programmatic interface for querying business metrics. Instead of writing SQL against a database, consumers call an API with the measures, dimensions, and filters they want. The API handles query generation, caching, and access control.\n\nA generic REST API exposes resources (users, orders, invoices). An analytics API exposes metrics (revenue, churn rate, active users) with aggregation, filtering, and time dimensions built in. The query interface is designed for analytical questions, not CRUD operations.\n\nIf AI agents query your data, an analytics API is the governed path. The alternative is giving agents direct database access (dangerous) or text-to-SQL (inconsistent). An analytics API backed by a semantic layer gives agents governed access to metric definitions. See [What Is an Agentic Semantic Layer?](https://bonnard.dev/blog/what-is-agentic-semantic-layer).\n\nGraphQL works for analytics APIs but you end up building the aggregation, caching, and access control layer yourself. A semantic layer provides these out of the box with a purpose-built query interface for analytical workloads.", "url": "https://wpnews.pro/news/analytics-api-how-to-serve-governed-metrics-to-any-consumer", "canonical_source": "https://dev.to/maxbonnard/analytics-api-how-to-serve-governed-metrics-to-any-consumer-1ko4", "published_at": "2026-07-16 15:25:33+00:00", "updated_at": "2026-07-16 15:38:10.874662+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "ai-agents", "artificial-intelligence", "large-language-models"], "entities": ["Bonnard", "Postgres", "BigQuery", "Snowflake", "Databricks", "DuckDB", "Claude", "ChatGPT"], "alternates": {"html": "https://wpnews.pro/news/analytics-api-how-to-serve-governed-metrics-to-any-consumer", "markdown": "https://wpnews.pro/news/analytics-api-how-to-serve-governed-metrics-to-any-consumer.md", "text": "https://wpnews.pro/news/analytics-api-how-to-serve-governed-metrics-to-any-consumer.txt", "jsonld": "https://wpnews.pro/news/analytics-api-how-to-serve-governed-metrics-to-any-consumer.jsonld"}}