# Metabase BI Connector

> Embed live Metabase dashboards and questions inside Odoo with short-lived signed JWTs - the secret never leaves your server.

- **What it is:** Embed live Metabase dashboards and questions inside Odoo with short-lived signed JWTs - the secret never leaves your server.
- **For:** Extra Tools
- **Odoo versions:** 16.0, 17.0, 18.0, 19.0, 20.0 - Community and Enterprise
- **Runs on:** Odoo.sh or Odoo you host yourself - not Odoo Online, which accepts no third-party apps
- **Price:** 89 EUR, one-time, on the Odoo Apps Store
- **Technical name:** bambooforge_metabase_connector
- **Run for real:** Tested for real against a current Metabase release: 1 database, 42 questions and 3 dashboards came into Odoo with 0 errors.
- **Not for:** The connector reads metadata only — database names and engines, question names/chart types and their database link, and dashboard names/collections — and writes the corresponding Odoo catalog records. It does not read or copy the rows inside your databases, nor the result data of any question. The Metabase API key and webhook secret are stored in admin-only fields.; In scope today: read-only import of Metabase databases, questions (cards) and dashboards into Odoo; scheduled reconciliation; dry-run, validation and rollback safety; schema introspection; field-mapping suggestions; a bundled mock for evaluation.; Out of scope / roadmap: any write-back to Metabase; webhook/push intake (the route is a neutralized stub); incremental list filtering by timestamp (v1 lists by cursor pagination); importing question result data or query definitions.
- **Manual:** https://docs.bambooforge.dev/bambooforge_metabase_connector/
- **Live demo:** https://demo20.bambooforge.dev/odoo/action-bambooforge_metabase_connector.action_metabase_control_tower
- **Store page:** https://apps.odoo.com/apps/modules/20.0/bambooforge_metabase_connector
- **Manual last updated:** 2026-10-15

---
# Metabase BI Connector

A read-only bridge that catalogs your Metabase business-intelligence assets
inside Odoo 20: import your Metabase databases, saved questions (cards) and
dashboards into a local, searchable catalog — over the official Metabase REST
API, with a resilient job queue, dry-run safety and full logging so you stay in
control.

This page is the complete manual. If you follow it top to bottom you can
install, connect, run your first import, automate it, and fix the common issues
**without contacting support**.

## Overview

The connector reads three Metabase asset types over the Metabase REST API and
keeps a local mirror of them inside Odoo. It is **import-only**: nothing is ever
written back to Metabase. It covers:

- **Databases** — every data source registered in Metabase, with its name and
  engine. Imported from `GET /api/database` into **metabase.database**.
- **Questions (cards)** — saved questions with their chart type (the Metabase
  `display` value) and a link to the parent Metabase database. Imported from
  `GET /api/card` into **metabase.question**.
- **Dashboards** — dashboards with their collection. Imported from
  `GET /api/dashboard` into **metabase.dashboard**.

Direction of sync: **Odoo ← Metabase** (read-only catalog). The connector never
creates, updates or deletes anything in Metabase; it only catalogs metadata
about your BI assets inside Odoo.

Databases are imported before questions, so each question can resolve its parent
database. Every remote call goes through a queue: nothing is written to Odoo
until a job runs, jobs retry with back-off on transient failures, and a circuit
breaker pauses an instance that keeps failing.

## Tested against the real thing

Automated tests run against a bundled mock on every release; the line below is the part no mock can give you - the connector pointed at the real platform, with real records.

Tested for real against a current Metabase release: 1 database, 42 questions and 3 dashboards came into Odoo with 0 errors.

## Requirements

- **Odoo**: 20.0, Community or Enterprise. Depends only on **base** and
  **mail**, which are always present.
- **Metabase**: a reachable Metabase server that supports API-key
  authentication (the `x-api-key` header) on its REST API. The connector
  reads `/api/database`, `/api/card` and `/api/dashboard`.
- **Python**: no extra libraries beyond a standard Odoo 20 install.
- **Odoo access**: any internal user can use the connector screens. The
  Metabase API key and webhook secret are stored in system-only fields, so only
  the **Settings / Administration** user can read or change them.
- **Network**: outbound HTTPS from the Odoo server to your Metabase server. By
  default the connector refuses internal/loopback/private hosts as an SSRF
  safeguard (see *Safety features*).

## Installation

1. Copy `bambooforge_metabase_connector` into your Odoo addons path.
2. Restart the Odoo service.
3. Open **Apps**, click **Update Apps List**, search for *Metabase*, and press
   **Activate / Install**.

No Metabase server is required to evaluate the connector: a mock Metabase REST
API ships inside the module, so you can install, explore and run the full import
flow against sample data before pointing it at a real server.

## Step 1 — Create an API key in Metabase

The connector authenticates with a single Metabase API key, sent on every
request as the `x-api-key` HTTP header. Create one in Metabase:

1. Sign in to Metabase as an administrator.
2. Open the gear menu and go to **Admin settings ▸ Authentication ▸ API keys**
   (on some builds: **Admin settings ▸ Settings ▸ API keys**).
3. Click **Create API key**.
4. Give it a name such as `Odoo` and assign it to a group that can read your
   databases, questions and dashboards. Read access is enough — the connector
   never writes.
5. Click **Create** and copy the generated key. It is shown once.

You will paste this key into Odoo in Step 2.

## Step 2 — Create the connection in Odoo

Open **Metabase Connector ▸ Configuration ▸ Instances** and create a record.

Key fields:

| Field | What to enter |
|---|---|
| Name | A label for this server, e.g. *Company Metabase*. Must be unique per company. |
| Base URL | Your Metabase server root, e.g. `https://metabase.example.com`. |
| Authentication | *API Key (x-api-key)* — the only supported method. |
| API Key | The key from Step 1. Sent as the `x-api-key` header on every request (visible to administrators only). |
| API Path | The API path prefix. Leave the default `api` unless your server differs; the client builds `<base_url>/api/database` and so on. |
| Timeout (seconds) | HTTP timeout per request. Default `30`. |
| Verify SSL | Keep on for production. Turn off only for self-signed test certificates. |
| Allow internal host | Off by default. Turn on only to reach a Metabase server on localhost or a private network (lowers the SSRF guard — see *Safety features*). |

Then click **Test Connection**. The connector lists your Metabase databases as
a lightweight auth + connectivity check; a green *Connected* state means the API
key and URL are correct. If it fails, the exact error is recorded on the form
(**Last Connection Error**) and in **Logs** (see *Troubleshooting*).

Tip: use **Quick Setup** (button on the instance) to seed default flows, field
mappings and a recommended schedule in one step.

## Step 3 — First import (dry-run, then live)

New instances start with **Dry-run** ON. In dry-run, import jobs *simulate*
writes: instead of changing data they produce **Validation Results** you can
review under **Metabase Connector ▸ Operations ▸ Validation Results**. This lets
you confirm what *would* happen before anything is written.

To run a first import:

1. On the instance, open the **Import** tab and click **Queue Database Import**
   (and/or **Queue Question Import**, **Queue Dashboard Import**). This enqueues
   jobs; it does not block the UI. Queueing questions imports databases first so
   each question resolves its parent database.
2. Jobs are processed by the **Metabase Queue Processor** scheduled action
   (every minute), or immediately if you run it manually from
   **Operations ▸ Queue Jobs**.
3. Review **Validation Results** while still in dry-run.
4. When satisfied, open the instance, turn **Dry-run** OFF, and run the imports
   again to write the catalog records for real.

Imported records land in **Metabase Connector ▸ Catalog**:
**Databases**, **Questions** and **Dashboards**. Each carries its Metabase
external ID, so re-imports update the same record instead of duplicating it. A
question shows its chart type and is linked to its database.

## Field mapping & customization

- **Field Mappings** (Configuration) map Metabase fields to the Odoo catalog
  fields per entity. Use **Suggest Mappings** on the instance to seed the
  core ones, then adjust.
- **Schema Fields** lists the discovered Metabase fields per entity. Run
  **Schema Introspection** on the instance to refresh it: it samples one live
  Metabase record per entity (falling back to the bundled mock when the API is
  unreachable) and flattens its shape next to the Odoo target fields.
- **Import limits** are per-entity dials on the instance — *Databases import
  limit* (default `100`), *Questions import limit* (default `200`),
  *Dashboards import limit* (default `200`) — capping how many remote IDs each
  import enqueues.

## Automation (scheduled actions & webhooks)

The module ships these scheduled actions (**Settings ▸ Technical ▸ Scheduled
Actions**):

| Scheduled action | Default | Purpose |
|---|---|---|
| Metabase Queue Processor | every 1 min | Processes queued import jobs. |
| Metabase Reconciliation | every 15 min | Re-lists enabled entities and queues imports for anything missing or previously failed. |
| Metabase Maintenance | every 1 hr | Recovers stale/locked jobs and trims old jobs and logs. |
| Metabase Flow Scheduler | every 5 min | Runs scheduled import flows. |
| Metabase Flow Metrics | every 1 hr | Aggregates flow-run metrics. |
| Metabase Auto Recover | every 15 min | Reopens a tripped circuit breaker once the server is healthy again. |

Turn on **Auto import** / **Auto reconcile** per entity on the instance (Import
tab) to let the scheduled actions keep the catalog current hands-free.

**Webhooks**: this connector is a pull-only importer, so there is **no webhook
intake**. A route exists at `/metabase/webhook` only so anything still pointed
at it gets a clear *not supported* reply (HTTP 501) instead of a 404; a genuine
push path is a roadmap item, not part of this read-only version. Keep the
catalog current with the scheduled actions above.

## Safety features

- **Dry-run mode** — simulate writes and review Validation Results before going
  live. Controlled by *Dry-run* on the instance (ON by default).
- **Pre-flight validation** — *Minimal / Standard / Strict* business-validation
  profiles gate risky writes. Controlled by *Validation* and *Business
  validation profile*.
- **Resilient queue** — every import action is a job with retry and exponential
  back-off (*Retry limit*, *Backoff policy*, *Backoff base/cap minutes*). HTTP
  calls additionally retry transient failures (*API retry limit*, default 2).
- **Circuit breaker** — after repeated failures (*Auto pause threshold*,
  default 5) an instance auto-pauses (*Tripped*); the Auto Recover action
  reopens it once the server responds again. Auth/configuration errors trip it
  immediately.
- **Rollback snapshots** — when *Rollback* is enabled, imports capture a
  snapshot so you can undo a batch from **Operations ▸ Rollback Snapshots**.
- **SSRF guard** — the connector refuses internal/loopback/private hosts (and
  cloud-metadata addresses such as `169.254.169.254`) unless *Allow internal
  host* is explicitly enabled.

## Troubleshooting

| Symptom | Cause and fix |
|---|---|
| Test Connection fails with 401 / 403 | Wrong or expired **API key**, or the key's group lacks read access. Re-create the key in **Admin settings ▸ Authentication ▸ API keys** and paste it again. The connector sends it as the `x-api-key` header. |
| "API Key (x-api-key) is required" | The **API Key** field is empty. Paste the key from Step 1 (it is an admin-only field). |
| "...is not allowed because it resolves to a non-public address" | Base URL points at localhost/private IP (SSRF guard). Enable *Allow internal host* on the instance (self-hosted/test only). |
| SSL errors on Test Connection | Self-signed certificate. Use a valid cert, or turn off *Verify SSL* for testing only. |
| Connection times out | Metabase is unreachable from the Odoo server or slow to answer. Check the Base URL, firewall/outbound rules, and raise *Timeout (seconds)* if needed. |
| Jobs stay in *Pending* | The Queue Processor is off or the instance is paused. Check the **Metabase Queue Processor** scheduled action is active and the instance is not *Paused*. |
| Instance shows *Tripped* | Circuit breaker tripped after repeated failures (or an auth error). Fix the server/credentials; Auto Recover reopens it, or click **Resume Sync**. |
| Questions imported without a database link | Databases were not imported first. Use **Queue Question Import** (it imports databases first) or run **Queue Database Import** before questions. |
| Records imported twice | Imports are keyed by the Metabase external ID, so this should not happen. If it does, check that two instances do not point at the same server. |
| Nothing happens after Import | You are in **Dry-run**. Review Validation Results, then turn dry-run off and re-run. |
| POST to `/metabase/webhook` returns 501 | Expected. This connector has no webhook intake; use the scheduled actions to keep the catalog current. |

For anything else, **Operations ▸ Logs** records every API call and error with a
timestamp.

## Frequently asked questions

**Which versions are supported?**
Odoo 20.0 on the Odoo side. On the Metabase side, any server whose REST API
supports API-key authentication via the `x-api-key` header and the
`/api/database`, `/api/card` and `/api/dashboard` endpoints. Validate your
exact build with the bundled mock first.

**Do I need a Metabase server to evaluate it?**
No. A mock Metabase REST API ships inside, so you can install, explore and demo
the full import flow before connecting a real server.

**How does authentication work?**
Create a Metabase API key under **Admin settings ▸ Authentication ▸ API keys**
and paste it into the instance. The connector sends it as the `x-api-key`
header on every request. There is no username/password and no token exchange.

**Does this write anything back to Metabase?**
No. The connector is read-only by design: it only lists and reads your
databases, questions and dashboards. It never creates, updates or deletes
anything in Metabase.

**Is there real-time / webhook sync?**
No. This is a pull-only importer; the `/metabase/webhook` route is a stub that
answers *not supported*. Use the scheduled **Reconciliation** and **Auto
import** to keep the catalog current. A push path is a roadmap item.

**Is it safe to run against production data?**
Yes. The connector never touches your Metabase data. On the Odoo side, dry-run
is ON by default, validation gates risky writes, and rollback snapshots let you
undo. You decide when to go live.

**What support and refund policy do I get?**
Every request is answered within 24 hours, setup help included. If you report a
bug within 2 months of purchase and it is not resolved within 15 days, you are
entitled to a full money-back refund.

## Data, privacy & limits

- The connector **reads metadata only** — database names and engines, question
  names/chart types and their database link, and dashboard names/collections —
  and writes the corresponding Odoo catalog records. It does **not** read or
  copy the rows inside your databases, nor the result data of any question. The
  Metabase API key and webhook secret are stored in admin-only fields.
- **In scope today**: read-only import of Metabase databases, questions (cards)
  and dashboards into Odoo; scheduled reconciliation; dry-run, validation and
  rollback safety; schema introspection; field-mapping suggestions; a bundled
  mock for evaluation.
- **Out of scope / roadmap**: any write-back to Metabase; webhook/push intake
  (the route is a neutralized stub); incremental list filtering by timestamp
  (v1 lists by cursor pagination); importing question result data or query
  definitions.

## Support & updates

- Support: [support@bambooforge.dev](mailto:support@bambooforge.dev) — answered within 24 hours, setup help
  included.
- Refund: report a bug within 2 months of purchase; if unresolved within 15
  days, full refund.
- Full source is included. Updates track the supported Odoo 20 / Metabase REST
  API line.

## Upgrading & version compatibility

This build targets **Odoo 20.0**. Each Odoo major series (16.0, 17.0, 18.0, 19.0, 20.0) has its
own dedicated build of this module — always install the build that matches your Odoo
version. Mixing a build with a different Odoo series is not supported.

### Patch upgrades (same series, e.g. 20.0.1.0.0 → later)

1. **Back up** your database and filestore first.
2. Replace the module folder with the newer build.
3. Restart Odoo with the module updated:
   
   ```
   ./odoo-bin -c your.conf -u bambooforge_metabase_connector -d your_db
   ```
4. Odoo applies any schema/data changes automatically. Your existing records and
   configuration are preserved.

### Cross-version migration (moving to a newer Odoo)

Upgrading Odoo itself is a database migration handled by Odoo's standard upgrade
tooling. When you migrate the database to the next Odoo series, install the matching
build of this module for that series. Data created by this module carries over with
the database migration.

After any upgrade the module's scheduled actions resume on their normal cadence —
no manual re-activation is required.

## Uninstallation

You can remove this module at any time from **Apps → (this module) → Uninstall**,
or from the command line. Uninstalling is clean and reversible by reinstalling —
but note what is and is not deleted.

### What is removed

- The module's own tables and every record in them (23 models, prefixed `metabase.*`) — this is the data this module created.
- The menus, actions, views and reports this module installed.
- Its scheduled actions (cron jobs) — they stop immediately on uninstall.
- Connection records, credentials, field mappings, queue jobs and sync logs stored in Odoo.

### What is preserved

- **Your remote platform is never touched.** Uninstalling only removes the Odoo-side
  connector; products, customers and orders on the external store/service are untouched.
- Records already imported into standard Odoo models (e.g. contacts, products, sales
  orders) remain — they are ordinary Odoo records once created.
- Attachments and chatter messages on standard records are kept.

As always, take a database backup before uninstalling in production.

## Changelog

### 20.0.1.0.2

- Built for Odoo 20: record rules and access rights rewritten onto Odoo 20's single ir.access model, the screens rebuilt on Owl 3, and every icon moved to the icon set Odoo 20 ships.

### 20.0.1.0.0

Current release for Odoo 20.0. This build includes:

- Embed live Metabase dashboards & questions inside Odoo 20 via short-lived signed JWTs (HS256, secret never leaves the server).
- Plus a searchable catalog of databases, questions & dashboards. Resilient queue, dry-run, auditable source.

Feature additions and fixes ship as new builds on the Odoo Apps store; this page and
the module's `version` reflect the current published release. Always keep the build
matched to your Odoo series (see *Upgrading & version compatibility*).

## How we make sure it works

Before every release, **85 automated checks** run against this app on Odoo 16, 17, 18, 19 and 20.
Among them:

- Entity bindings do not collide.
- Resolve action always ignore.
- Disabled by default no url no crash.
- Lock claims pending then refuses second claim.
- Action domain selects only conflicts.

Tested for real against a current Metabase release: 1 database, 42 questions and 3 dashboards came into Odoo with 0 errors.

The complete list is in the source you receive.
