Skip to content

REST API

Everything the interface does goes through the REST API: the interface has no private route. A script, a reporting tool or your own application calls it the same way, with the permissions of the token it presents.

AddressPurpose
https://bi.example.com/api/v1/…the API, served behind Caddy (in development: http://localhost:4100, or http://localhost:3100, which relays /api/*)
/api/v1/openapi.jsonthe OpenAPI 3.1 specification, generated from the route schemas
/api/healththe status of the API and Trino, without authentication

The application’s API and MCP page presents the same reference, filterable, with a curl and fetch example for each endpoint and a button to download the specification.

An integration token starts with eoi_ and is passed in the Authorization header:

Fenêtre de terminal
curl https://bi.example.com/api/v1/me \
-H "Authorization: Bearer $EODIA_TOKEN"

You create it from My profile or the API and MCP page, with:

  • a name, which says where it is used (“Reporting script”, “Claude Desktop”);
  • its surfaces: REST API, MCP, or both. A token is only accepted on the surfaces it declares: an MCP-only token is rejected by the REST API;
  • an expiration: 30 days, 90 days, 1 year, or never.

The token is displayed only once: only its hash is kept. The list of your tokens shows when each was last used, and Revoke cuts them off immediately. A token cannot create another token, and it stops working if its owner is deactivated. Creations and revocations are recorded in the audit log.

The interface authenticates with a session cookie. With this cookie, any request that writes (POST, PUT, PATCH, DELETE) must carry the X-Eodia-Csrf: 1 header, otherwise it is rejected (CSRF). A Bearer token does not need it.

POST /api/v1/query runs a query — Trino SQL, visual editor or native SQL — in Trino, under the caller’s identity:

Fenêtre de terminal
curl -X POST https://bi.example.com/api/v1/query \
-H "Authorization: Bearer $EODIA_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"query": {
"kind": "sql",
"sql": "SELECT region, count(*) AS clients FROM boutique.public.clients GROUP BY 1"
}
}'

The response gives the columns (name, label, type…), the rows, a truncated flag, the duration and the executed SQL. limit sets the number of rows read (100,000 at most), parameters gives the values of the variables, and fresh: true bypasses the cache.

EndpointPurpose
POST /api/v1/queryrun a query
POST /api/v1/query/cancelcancel an execution, by its execution_id
POST /api/v1/query/exportdownload a result as CSV, JSON or XLSX
POST /api/v1/questions/{id}/runrun a saved question, with its variables
POST /api/v1/questions/{id}/exportexport a question’s result
POST /api/v1/dashboards/{id}/cards/{card}/runrun a card with the filter values

The query level matters: without the SQL level on any source, POST /api/v1/query rejects a SQL query. See Permissions.

GroupExamples
ProfileGET /api/v1/me, your tokens (/api/v1/me/tokens)
Sourcesavailable engines, sources, connection test, synchronization, job status (also over SSE)
Structuretables, columns, metadata, values, relationships, configuration copy-paste
Executionqueries, cancellation, export, history, snippets
Questionsquestions, models (/api/v1/models), metrics (/api/v1/metrics), duplication
Dashboardsreading, creation, editing, duplication, running a card
Foldersfolders and their contents, search, favorites, home, item shares
Sharingshare links (/api/v1/share-links)
Copilotconversation over SSE (POST /api/v1/copilot), saved conversations
Administrationpeople, invitations, groups, permissions, audit log, cache, integration secrets

Administration routes require the corresponding permissions (Manage data sources, Manage metadata, Manage permissions) or being an administrator. A few public routes, without a token, handle sign-in (/api/auth/…) and shared content (/api/public/…).

An error returns an HTTP status and a body of the form:

{ "error": { "code": "DATA_ACCESS_DENIED", "message": "Data access denied: …" } }

The code is stable and machine-readable; the message is ready to display, in the caller’s language: the one chosen in the interface (eodia-locale cookie), otherwise the Accept-Language header, otherwise English.

CodeStatusMeaning
UNAUTHENTICATED401token missing, invalid, expired, or not allowed on this surface
FORBIDDEN, CSRF403missing permission; cookie-based write without X-Eodia-Csrf
DATA_ACCESS_DENIED403Trino denied access to a table or column
NOT_FOUND404not found — or not visible to you
INVALID_INPUT400invalid body or parameters
CONFLICT409conflict: address or catalog already taken
QUERY_FAILED400Trino rejected the query; details.location pinpoints the error
QUERY_TIMEOUT408timeout exceeded (QUERY_TIMEOUT_MS)
QUERY_CANCELLED499query cancelled
CONNECTION_FAILED400a source’s connection test failed
ENGINE_UNAVAILABLE503Trino or the identity provider unreachable
AI_DISABLED, AI_QUOTA400, 429copilot not configured; quota reached
INTERNAL500internal error

eodia insights is free software by Eodia.