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.
| Variable | Purpose |
|---|---|
FIDARA_DATABASE_URL | Fidara's own PostgreSQL |
FIDARA_LLM_API_KEY, FIDARA_LLM_MODEL | The model. The key is not needed for a local endpoint |
FIDARA_LLM_BASE_URL | Any OpenAI-compatible endpoint, such as DeepSeek, OpenAI, vLLM, llama.cpp or Ollama. This selects the provider |
FIDARA_APP_BASE_URL | Your app's base URL for the webhooks |
FIDARA_WEBHOOK_SECRET | Shared secret; both sides must match |
FIDARA_VERIFY_PATH, _CATALOGUE_PATH, _QUERY_PATH, _WRITE_PATH | Webhook paths if yours differ from /fidara/verify and so on |
FIDARA_DB_TYPE | SQL 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
| Field | Meaning |
|---|---|
name, columns | Required. The table and its columns, each with a name and type |
row_filter or record_scope | Row-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_columns | Columns this user may not see. A query naming one is refused |
semantic_filters | Business defaults that are the same for everyone. Visible to the model and overridable |
primary_date, display_column, notes, description | Help for relative dates, for naming a row, and for domain knowledge |
column fk, note, label, visibility, values, unique | Links, meaning of codes, header text, whether a column is technical or denied, the legal values, and key uniqueness |
app_prompt, metrics, actions | Free-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.
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_asis 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 inapp_rules_verifiedwhether you ran all of your rules. - Each field an action may set needs a declared
valuesdomain, 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.
| Adapter | App | Form | Notes |
|---|---|---|---|
adapters/dolibarr | Dolibarr | PHP module | The catalogue is built per request from Dolibarr's own rights |
adapters/erpnext | ERPNext | Frappe app | Reads each field's links and options from the app's own metadata. The webhook paths differ, so set the *_PATH variables |
adapters/odoo | Odoo | Odoo module | The catalogue comes from Odoo's own access rights and record rules |
adapters/sandbox | None: a plain database | FastAPI service | Built from a spec and rebuilt at will. Its write endpoint really writes |
adapters/erpnext-openapi | ERPNext through its REST API | An OpenAPI document | No 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.
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.
| Path | What | Licence |
|---|---|---|
fidara/, frontend/, tests/, scripts/, docs/ | The service, chat screen, tests and docs | AGPL-3.0-only |
adapters/ | Reference adapters and the sandbox | Apache-2.0 |
eval/ | Evaluation tooling meant to be reused | Apache-2.0 |
examples/ | Plug-in examples, copy freely | Apache-2.0 |
data/ | The synthetic Arcwell demo dataset | CC-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.