Integrate your app

Expose three webhooks, point Fidara at them, and embed an iframe. This page is the short form of the contract.

In short

  • Expose three webhooks: verify, catalogue and query; write is optional.
  • Set a few environment variables, then embed the chat with an iframe.
  • Reference adapters exist for Dolibarr, ERPNext and Odoo.

The contract: three webhooks, and the app keeps what is its own

The contract is verify, catalogue and query, plus an optional write. The database connection, the credentials, the login, the row-scope rules, the business vocabulary and the list of sensitive columns stay in your app. Fidara never receives database credentials: your app runs every query itself, as the user who asked.

An integration should not need Python packages or React components in the app. Fidara serves the whole chat experience, and the adapter is the small piece that must live next to the data. That rules out handing Fidara database access and installing Fidara code inside the app. For authentication, Fidara takes a bearer token from the iframe address and calls your verify webhook on every request.

Why these choices: Safe by design.

Configuration

Fidara reads environment variables with the FIDARA_ prefix. Copy .env.example to .env. Every call Fidara makes carries the header X-Fidara-Secret, and your app must return 401 if it does not match.

VariablePurpose
FIDARA_DATABASE_URLFidara's own PostgreSQL
FIDARA_LLM_API_KEY, FIDARA_LLM_MODELThe model. The key is not needed for a local endpoint
FIDARA_LLM_BASE_URLAny OpenAI-compatible endpoint, such as DeepSeek, OpenAI, vLLM, llama.cpp or Ollama. This selects the provider
FIDARA_APP_BASE_URLYour app's base URL for the webhooks
FIDARA_WEBHOOK_SECRETShared secret; both sides must match
FIDARA_VERIFY_PATH, _CATALOGUE_PATH, _QUERY_PATH, _WRITE_PATHWebhook paths if yours differ from /fidara/verify and so on
FIDARA_DB_TYPESQL dialect: postgresql, mysql, mariadb, mssql, oracle or sqlite

An unknown variable name is ignored, not rejected, so a misspelling does nothing. A settings file (FIDARA_CONFIG_FILE, YAML) can hold the same settings.

1. verify

POST /fidara/verify. Fidara calls it on every request to check the bearer token. A non-null error, or a missing user_id, makes Fidara answer 401 to the browser.

Fidara sends

{"token": "eyJ...", "token_type": "bearer"}

Your app responds

{"user_id": "42", "role": "manager", "display_name": "Alice Smith", "error": null}

2. catalogue

POST /fidara/catalogue. Fidara calls it for each request. You return the tables, columns and rules for this user. It is the document the whole system reasons from.

{
  "tables": [{
    "name": "orders",
    "description": "Customer purchase orders",
    "record_scope": [{"column": "company", "op": "eq", "value": "acme"}],
    "semantic_filters": [
      {"column": "status", "kind": "optional", "op": "neq", "value": "CANCELLED",
       "note": "cancelled orders are excluded by default"}
    ],
    "denied_columns": ["internal_margin"],
    "primary_date": "created_at",
    "columns": [
      {"name": "id", "type": "number"},
      {"name": "customer_id", "type": "number", "fk": "customers.id"},
      {"name": "status", "type": "string", "note": "PENDING, CONFIRMED, SHIPPED, CANCELLED"},
      {"name": "total", "type": "number"}
    ]
  }],
  "app_prompt": "Active = PENDING or CONFIRMED.",
  "db_type": "postgresql"
}

What the catalogue declares

FieldMeaning
name, columnsRequired. The table and its columns, each with a name and type
row_filter or record_scopeRow-level security. Declare one, never both. row_filter is a SQL fragment; record_scope is the same rule as data and works for any query language
denied_columnsColumns this user may not see. A query naming one is refused
semantic_filtersBusiness defaults that are the same for everyone. Visible to the model and overridable
primary_date, display_column, notes, descriptionHelp for relative dates, for naming a row, and for domain knowledge
column fk, note, label, visibility, values, uniqueLinks, meaning of codes, header text, whether a column is technical or denied, the legal values, and key uniqueness
app_prompt, metrics, actionsFree-text domain knowledge; named measures; the writes the app allows (below)

Security and defaults are never mixed. The test is whether two requesters would get different values: company = 'acme' differs, so it is the row scope; status <> 'CANCELLED' is the same for everyone, so it is a semantic filter. See Safe by design.

LimitsAn omission in the catalogue is not an error. It gives a wrong number that looks right, and nothing downstream can detect it. The repository includes scripts/tools/adapter_conformance.py, which checks a declaration against the app's own data. Run it before you declare a group of columns that record the same fact.

3. query

POST /fidara/query. Fidara sends parameterized SQL with positional placeholders. Your app runs it on its own database connection and returns rows. Fidara never receives credentials.

// Fidara sends
{"user_id": "42", "language": "sql",
 "query": "SELECT status, COUNT(*) AS cnt FROM orders WHERE company = $1 AND status = $2 GROUP BY status",
 "params": ["acme", "PENDING"]}

// Your app responds
{"rows": [{"status": "PENDING", "cnt": 15}], "error": null}

Check language. For one you do not run, answer with an error and do not execute. Today Fidara sends only sql. A non-null error sends the pipeline back through its retry loop.

4. write (optional)

POST /fidara/write. Only for an app that wants writes. Fidara calls it only for actions the catalogue lists for that user, and only after a person approved the change.

// In the catalogue
"actions": [{"name": "update_customer_phone", "verb": "update", "table": "customers",
             "target_key": "id", "fields": ["phone"], "required": []}]

// Fidara sends, one row per call
{"user_id": "42", "action": "update_customer_phone", "op": "update",
 "target": "1007", "set": {"phone": "+1 312 555 0100"}, "dry_run": false}

// Your app responds
{"ok": true, "record_id": "1007", "acted_as": "42", "error": null, "field": null}
  • Perform the write through the app's own API as user_id, never as a service account.
  • acted_as is mandatory on every response, failures included. Fidara refuses a result where it differs from the requesting user.
  • With dry_run: true, validate and save nothing: no commit, no audit row, no mail, no queued job. Say in app_rules_verified whether you ran all of your rules.
  • Each field an action may set needs a declared values domain, or the write is refused.

An app that does not implement write stays read-only. More: how writes are held and read back.

Iframe and token

Your page renders the iframe with the signed-in user's session token. The chat reads the token from the address, removes it from the address bar, and sends it as a bearer token on every call. Fidara verifies it with your app each time.

<iframe src="https://fidara.example.com/?token={{ user.session_token }}"
        style="width:100%; height:600px; border:none;"
        allow="clipboard-write"></iframe>

The chat has three shells: full, docked and launcher. Pick one with ?shell=full|docked|launcher. The launcher is a floating bubble that opens a panel, and it also accepts presentation=centered.

Guide: one script tag (optional)

Guide answers "where do I do X?" by reading the user's own screen. Add one tag to your page template and set FIDARA_PAGE_PROBES_ENABLED=true. There is nothing to declare. The script answers only messages from the Fidara iframe's own origin, reads rather than fills, and at most presses the app's own menu item to take the user to a screen.

<script src="https://fidara.example.com/agent.js"></script>

Reference adapters

You do not have to start from nothing. An adapter is the code on your side of the webhooks. Each one implements the same contract in its app's own framework and login, and treats the app as a black box: it reaches the app only through its API or database views. You install an adapter into your app. You do not modify Fidara.

AdapterAppFormNotes
adapters/dolibarrDolibarrPHP moduleThe catalogue is built per request from Dolibarr's own rights
adapters/erpnextERPNextFrappe appReads each field's links and options from the app's own metadata. The webhook paths differ, so set the *_PATH variables
adapters/odooOdooOdoo moduleThe catalogue comes from Odoo's own access rights and record rules
adapters/sandboxNone: a plain databaseFastAPI serviceBuilt from a spec and rebuilt at will. Its write endpoint really writes
adapters/erpnext-openapiERPNext through its REST APIAn OpenAPI documentNo code runs inside the app. See below

An adapter owes Fidara a catalogue for each user. Where the app knows something, the adapter should derive it from the app rather than type it out. The ERPNext adapter, for example, reads each field's links and options from the app's own metadata.

In the three ERP adapters, real writes are off by default and are switched on for a run.

An OpenAPI source

An app that has a REST API described by an OpenAPI document can be connected without declaring tables. Set FIDARA_SOURCE=openapi. Each list operation becomes a virtual table. Filters the API supports are pushed into its parameters, and everything else (joins, aggregates, windows) runs in the local engine over the fetched records. The calling user's own token is forwarded, so the app's permissions decide what each person can ask about.

LimitsOnly read operations are supported as tables. Other operations as tools, MCP servers and OAuth are not built.

Quickstart without an ERP

You need Python 3.12 or later, Node 20 or later, Docker (for Fidara's own PostgreSQL) and a key for any OpenAI-protocol model endpoint.

cp .env.example .env     # set FIDARA_LLM_API_KEY / _LLM_MODEL (and _LLM_BASE_URL if not OpenAI),
                         # and a FIDARA_WEBHOOK_SECRET of your choosing
docker compose up -d db  # Fidara's own PostgreSQL
python3 -m venv .venv && .venv/bin/pip install -e .

# the Arcwell demo company as a plain database, behind the webhooks, on :8036
(cd adapters/sandbox && SANDBOX_BACKEND=sqlite ./sandbox.sh build && SANDBOX_BACKEND=sqlite ./sandbox.sh up)

(cd frontend && npm ci && npm run build)          # the chat screen, served by the backend
FIDARA_TESTING_TARGET=demo-sandbox .venv/bin/python -m fidara    # port 8000

Open port 8000 with ?token=<token>, taking a token from adapters/sandbox/tokens.json. The file is created on build, is git-ignored and has mode 600. Each token is one person's key, so treat it as a secret. The sandbox has an Admin, three Regional Managers and twelve Sales Reps. Ask the same question as each and compare the answers.

Licences

The repository is licensed by folder. The permissive licence on adapters and evaluation tools is deliberate: an adapter is code that runs inside your own application and should not carry copyleft obligations into it.

PathWhatLicence
fidara/, frontend/, tests/, scripts/, docs/The service, chat screen, tests and docsAGPL-3.0-only
adapters/Reference adapters and the sandboxApache-2.0
eval/Evaluation tooling meant to be reusedApache-2.0
examples/Plug-in examples, copy freelyApache-2.0
data/The synthetic Arcwell demo datasetCC-BY-4.0

The Arcwell data is entirely synthetic: the company, people, customers, suppliers, e-mail addresses (on reserved .example domains) and figures are generated. Contributions require the contributor licence agreement described in the repository.