The Metabase API - A Developer's Reference Guide
The Metabase API is a REST API that exposes nearly every administrative and analytical operation available in the Metabase UI — creating dashboards, m...
The Metabase API: A Developer's Reference Guide
The Metabase API is a REST API that exposes nearly every administrative and analytical operation available in the Metabase UI — creating dashboards, managing users, executing queries, configuring permissions, and more — as HTTP endpoints that can be automated, scripted, and integrated into existing developer workflows. It is the foundation for treating Metabase as code: provisioning instances programmatically, managing configuration through CI/CD pipelines, and building integrations with other tools in your stack.
The API is available on all Metabase deployments — open source, Pro, and Enterprise — and is documented interactively at /api/docs on any running instance.
---
Authentication
Session Token Authentication
The Metabase API uses session tokens for authentication. Obtain a token by POSTing credentials to /api/session:
bash
curl -X POST \ -H "Content-Type: application/json" \ -d '{"username": "admin@yourcompany.com", "password": "your-password"}' \ https://your-metabase.com/api/session
Response:
json
{ "id": "session-token-string-here" }
Include the token in subsequent requests as the X-Metabase-Session header:
bash
curl -H "X-Metabase-Session: session-token-string-here" \ https://your-metabase.com/api/dashboard
API Key Authentication (Recommended for Automation)
For automated scripts and CI/CD pipelines, use API keys instead of session tokens. API keys don't expire and are tied to a specific user account.
Create an API key in Admin → Settings → Authentication → API Keys, then use it as the x-api-key header:
bash
curl -H "x-api-key: mb_your_api_key_here" \ https://your-metabase.com/api/dashboard
API keys are available in Metabase Pro and Enterprise. For open-source deployments, use session tokens.
Important: API Authentication in Automation
Never hardcode credentials in scripts. Use environment variables:
bash
export METABASE_API_KEY="mb_your_api_key_here" export METABASE_SITE_URL="https://your-metabase.com"
curl -H "x-api-key: $METABASE_API_KEY" \ "$METABASE_SITE_URL/api/dashboard"
---
API Structure and Conventions
The Metabase API follows REST conventions with a few quirks worth knowing:
Base URL
All API endpoints are under /api/:
https://your-metabase.com/api/{resource}
HTTP Methods
| Method | Usage |
|---|---|
GET | Read resources |
POST | Create resources or perform actions |
PUT | Update resources (full replacement) |
DELETE | Delete resources |
Response Format
All responses are JSON. Successful responses return the resource directly (not wrapped in a data key). Error responses include an errors or message field:
json
// Success { "id": 42, "name": "Sales Dashboard", ... }
// Error { "errors": { "name": "value must be a non-blank string" } }
Pagination
List endpoints return arrays. Some endpoints support limit and offset query parameters for pagination. Check individual endpoint documentation for pagination support.
---
Core API Endpoints
Databases
bash
<h1 class="text-4xl font-bold mb-6 text-slate-900">List all connected databases</h1> GET /api/database
<h1 class="text-4xl font-bold mb-6 text-slate-900">Get a specific database</h1> GET /api/database/:id
<h1 class="text-4xl font-bold mb-6 text-slate-900">Add a new database connection</h1> POST /api/database
<h1 class="text-4xl font-bold mb-6 text-slate-900">Update database connection settings</h1> PUT /api/database/:id
<h1 class="text-4xl font-bold mb-6 text-slate-900">Trigger a manual schema sync</h1> POST /api/database/:id/sync_schema
<h1 class="text-4xl font-bold mb-6 text-slate-900">Trigger a manual field values rescan</h1> POST /api/database/:id/rescan_values
Add a database connection:
bash
curl -X POST \ -H "x-api-key: $METABASE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Production DB", "engine": "postgres", "details": { "host": "your-db-host", "port": 5432, "dbname": "your_database", "user": "metabase_reader", "password": "your-password", "ssl": true } }' \ "$METABASE_SITE_URL/api/database"
Dashboards
bash
<h1 class="text-4xl font-bold mb-6 text-slate-900">List all dashboards</h1> GET /api/dashboard
<h1 class="text-4xl font-bold mb-6 text-slate-900">Get a specific dashboard (with all cards)</h1> GET /api/dashboard/:id
<h1 class="text-4xl font-bold mb-6 text-slate-900">Create a dashboard</h1> POST /api/dashboard
<h1 class="text-4xl font-bold mb-6 text-slate-900">Update a dashboard</h1> PUT /api/dashboard/:id
<h1 class="text-4xl font-bold mb-6 text-slate-900">Delete a dashboard</h1> DELETE /api/dashboard/:id
<h1 class="text-4xl font-bold mb-6 text-slate-900">Add a question card to a dashboard</h1> POST /api/dashboard/:id/cards
<h1 class="text-4xl font-bold mb-6 text-slate-900">Get dashboard cards</h1> GET /api/dashboard/:id/cards
Create a dashboard:
bash
curl -X POST \ -H "x-api-key: $METABASE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Customer Analytics", "description": "Key metrics for customer success", "collection_id": 5 }' \ "$METABASE_SITE_URL/api/dashboard"
Questions (Cards)
In Metabase's API, questions are called "cards":
bash
<h1 class="text-4xl font-bold mb-6 text-slate-900">List all questions</h1> GET /api/card
<h1 class="text-4xl font-bold mb-6 text-slate-900">Get a specific question</h1> GET /api/card/:id
<h1 class="text-4xl font-bold mb-6 text-slate-900">Create a question</h1> POST /api/card
<h1 class="text-4xl font-bold mb-6 text-slate-900">Update a question</h1> PUT /api/card/:id
<h1 class="text-4xl font-bold mb-6 text-slate-900">Delete a question</h1> DELETE /api/card/:id
<h1 class="text-4xl font-bold mb-6 text-slate-900">Execute a question and return results</h1> POST /api/card/:id/query
Execute a question and get results:
bash
curl -X POST \ -H "x-api-key: $METABASE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"parameters": []}' \ "$METABASE_SITE_URL/api/card/42/query"
Response includes the query results as a dataset:
json
{ "data": { "rows": [[1, "Acme Corp", 15000], [2, "Globex", 9500]], "cols": [ {"name": "id", "display_name": "ID", "base_type": "type/Integer"}, {"name": "name", "display_name": "Name", "base_type": "type/Text"}, {"name": "revenue", "display_name": "Revenue", "base_type": "type/Integer"} ] }, "row_count": 2 }
Running Ad-Hoc Queries
Execute a SQL query against a connected database without saving it as a question:
bash
POST /api/dataset
bash
curl -X POST \ -H "x-api-key: $METABASE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "database": 2, "native": { "query": "SELECT COUNT(*) as total_orders FROM orders WHERE created_at > now() - interval '"'"'7 days'"'"'" }, "type": "native" }' \ "$METABASE_SITE_URL/api/dataset"
Collections
Collections are folders for organizing questions and dashboards:
bash
<h1 class="text-4xl font-bold mb-6 text-slate-900">List all collections</h1> GET /api/collection
<h1 class="text-4xl font-bold mb-6 text-slate-900">Get items in a collection</h1> GET /api/collection/:id/items
<h1 class="text-4xl font-bold mb-6 text-slate-900">Create a collection</h1> POST /api/collection
<h1 class="text-4xl font-bold mb-6 text-slate-900">Move an item to a collection</h1> PUT /api/card/:id # update collection_id field PUT /api/dashboard/:id # update collection_id field
Users and Groups
bash
<h1 class="text-4xl font-bold mb-6 text-slate-900">List all users</h1> GET /api/user
<h1 class="text-4xl font-bold mb-6 text-slate-900">Create a user</h1> POST /api/user
<h1 class="text-4xl font-bold mb-6 text-slate-900">Update a user</h1> PUT /api/user/:id
<h1 class="text-4xl font-bold mb-6 text-slate-900">Deactivate a user</h1> DELETE /api/user/:id
<h1 class="text-4xl font-bold mb-6 text-slate-900">List all groups</h1> GET /api/permissions/group
<h1 class="text-4xl font-bold mb-6 text-slate-900">Create a group</h1> POST /api/permissions/group
<h1 class="text-4xl font-bold mb-6 text-slate-900">Add a user to a group</h1> POST /api/permissions/group/:group_id/membership
Create a user:
bash
curl -X POST \ -H "x-api-key: $METABASE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "first_name": "Jane", "last_name": "Smith", "email": "jane@yourcompany.com", "password": "temporary-password-123" }' \ "$METABASE_SITE_URL/api/user"
Permissions
bash
<h1 class="text-4xl font-bold mb-6 text-slate-900">Get the permissions graph (all permissions for all groups)</h1> GET /api/permissions/graph
<h1 class="text-4xl font-bold mb-6 text-slate-900">Update the permissions graph</h1> PUT /api/permissions/graph
The permissions graph is a nested JSON object representing all group permissions across all databases. Updating it requires sending the complete graph — partial updates are not supported.
---
Common Automation Patterns
Pattern 1: Provisioning a New Tenant
When a new customer signs up for your SaaS product, automatically provision their Metabase access:
javascript
async function provisionTenantAccess(tenant) { const baseUrl = process.env.METABASE_SITE_URL; const headers = { "x-api-key": process.env.METABASE_API_KEY, "Content-Type": "application/json", };
// 1. Create a group for this tenant const groupRes = await fetch(${baseUrl}/api/permissions/group, { method: "POST", headers, body: JSON.stringify({ name: Tenant: ${tenant.name} }), }); const group = await groupRes.json();
// 2. Create an admin user for the tenant const userRes = await fetch(${baseUrl}/api/user, { method: "POST", headers, body: JSON.stringify({ first_name: tenant.adminFirstName, last_name: tenant.adminLastName, email: tenant.adminEmail, password: generateTempPassword(), }), }); const user = await userRes.json();
// 3. Add the user to the tenant's group await fetch(${baseUrl}/api/permissions/group/${group.id}/membership, { method: "POST", headers, body: JSON.stringify({ user_id: user.id }), });
return { groupId: group.id, userId: user.id }; }
Pattern 2: Programmatic Dashboard Creation
Build a script that creates a standardized dashboard for each new product or team:
javascript
async function createStandardDashboard(name, collectionId, databaseId) { const baseUrl = process.env.METABASE_SITE_URL; const headers = { "x-api-key": process.env.METABASE_API_KEY, "Content-Type": "application/json", };
// 1. Create the dashboard const dashRes = await fetch(${baseUrl}/api/dashboard, { method: "POST", headers, body: JSON.stringify({ name, collection_id: collectionId, }), }); const dashboard = await dashRes.json();
// 2. Create a question const cardRes = await fetch(${baseUrl}/api/card, { method: "POST", headers, body: JSON.stringify({ name: ${name} - Orders Over Time, display: "line", dataset_query: { database: databaseId, type: "query", query: { "source-table": TABLE_ID, aggregation: [["count"]], breakout: [["field", DATE_FIELD_ID, { "temporal-unit": "week" }]], }, }, visualization_settings: {}, collection_id: collectionId, }), }); const card = await cardRes.json();
// 3. Add the question to the dashboard await fetch(${baseUrl}/api/dashboard/${dashboard.id}/cards, { method: "POST", headers, body: JSON.stringify({ cardId: card.id, row: 0, col: 0, size_x: 12, size_y: 6, }), });
return dashboard.id; }
Pattern 3: Extracting Query Results for External Use
Use the API to pull data from Metabase questions into another system (a data export, a Slack alert, a custom report):
javascript
async function getQuestionData(questionId, parameters = []) { const res = await fetch( ${process.env.METABASE_SITE_URL}/api/card/${questionId}/query, { method: "POST", headers: { "x-api-key": process.env.METABASE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ parameters }), } );
const result = await res.json(); const { rows, cols } = result.data;
// Convert to array of objects return rows.map((row) => Object.fromEntries(cols.map((col, i) => [col.name, row[i]])) ); }
// Usage const orders = await getQuestionData(42, [ { type: "date/range", value: "2024-01-01~2024-12-31", target: ["variable", ["template-tag", "date_range"]] } ]); // orders = [{ order_id: 1, customer: "Acme", amount: 500 }, ...]
---
Rate Limits and Error Handling
The Metabase API does not publish formal rate limits, but it's good practice to:
- Add delays between bulk operations (e.g., creating many users)
javascript
async function apiRequest(url, options, retries = 3) { for (let attempt = 0; attempt < retries; attempt++) { const res = await fetch(url, options);
if (res.ok) return res.json();
if (res.status === 429 || res.status === 503) { const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s await new Promise((r) => setTimeout(r, delay)); continue; }
const error = await res.json(); throw new Error(API error ${res.status}: ${JSON.stringify(error)}); }
throw new Error(Failed after ${retries} retries); }
---
Exploring the API
Every Metabase instance exposes interactive API documentation at:
https://your-metabase.com/api/docs
This is the most accurate reference for the version you're running — it reflects the exact endpoints and request shapes available on your instance. The API surface can change between Metabase versions, so always reference /api/docs on your specific deployment rather than relying on third-party documentation.
You can also discover the API surface by watching network traffic in your browser's DevTools while using the Metabase UI — every UI action maps to one or more API calls.
---
Summary
The Metabase REST API covers all major operations: database management, dashboard and question CRUD, query execution, user and group management, and permissions. Authentication is via API key (recommended for automation) or session token. Common use cases include provisioning new tenants, creating standardized dashboards programmatically, and extracting query results into external systems. The API is available on all Metabase plans and is self-documented at /api/docs on every running instance.