Publication API v2 — Overview
Read-only REST API over the publication MariaDB databases produced by the Dédalo diffusion process.
The Publication API v2 is a standalone service that exposes published cultural heritage data as addressable HTTP resources. It is the public, read-side counterpart of Dédalo: the back-end runs the diffusion process that writes denormalized records into one or more publication databases, and this API serves those databases to websites, integrations and AI agents — without ever touching the editing back-end.
What it is, in one sentence
A resource-oriented, multi-database, read-only REST API (OpenAPI 3.1, spec version 2.1.0) over the diffusion-produced publication databases, built with Bun + TypeScript.
What it is
- Read-only. No write endpoint exists. Connect it with a read-only database user.
- Resource-oriented REST. Databases, tables and records are addressable URLs
(
/{db}/tables/{table}/records/{id}). - Multi-database. A single server exposes every database listed in the
DB_NAMESallowlist. Every data route is scoped by a{db}path segment that must appear in that allowlist. - Diffusion-produced data. Records originate from the Dédalo diffusion process, which flattens
work data into the publication databases. Records are identified by
section_id; multilingual values are stored one row per language in alangcolumn (lg-xxxformat, e.g.lg-eng). - OpenAPI 3.1. The full contract ships as a spec served by the API itself, with interactive documentation (Swagger UI + Scalar), all hosted offline (no CDN dependencies).
- Standard HTTP semantics. RFC 9457 Problem Details for errors, RFC 8288
Linkheaders for pagination, weakETag/304caching, gzip compression. - Secure by design. Parameterized queries, identifier validation, optional API key, per-IP rate limiting, bounded fragment extraction and request timeouts.
Architecture
Click the diagram to open it full size.
Dédalo back-end ──(diffusion process)──▶ publication MariaDB databases
│
┌───────┴────────┐
│ Bun runtime │
│ TypeScript │ ← this service (v2)
│ per-db pools │
└───────┬────────┘
│ REST / OpenAPI 3.1 / MCP
▼
websites · integrations · AI agents
Requests pass through a middleware chain (defined in src/index.ts) before reaching the router:
compression → timing → request-id → http-cache → CORS / timeout → router
- Runtime: Bun (
Bun.serve), TypeScript, MariaDB via Bun's nativeBun.sql(mariadbadapter) — no third-party database client. - Routing: a path-parameter router (
src/router.ts). Static routes are registered first, so a database name in the{db}segment can never shadow/databases,/health,/docs, etc. - Connection pooling: one connection pool per database (
DB_POOL_MAXeach). - Caching: schema introspection is cached (~30 s);
COUNTqueries only run whencount=true; HTTPETag/304lets clients and proxies skip transfers entirely. - Validation: environment config and every request are validated with Zod schemas.
meta.response_time_ms
Every JSON success response carries the total server processing time under
meta.response_time_ms (mirrored in the X-Response-Time header). It is injected by a
middleware layer and is excluded from the ETag, so it never disturbs 304 validation.
Quick Start
Prerequisites
- Bun v1.1+
- MariaDB 11+ with published data (created by the Dédalo diffusion process)
Install
cd publication/server_api/v2
bun install
cp .env.example .env
Configure
Edit .env. The most important variables (all read from the process environment and validated by
src/config.ts):
| Variable | Default | Purpose |
|---|---|---|
DB_HOST |
localhost |
MariaDB host |
DB_PORT |
3306 |
MariaDB port |
DB_USER |
readonly_user |
Read-only DB user |
DB_PASSWORD |
(empty) | DB password |
DB_NAMES |
dedalo_web |
Comma-separated allowlist of public databases (must list at least one) |
DB_POOL_MAX |
10 |
Connection pool size, per database |
DEPLOYMENT_MODE |
apache |
apache | nginx | standalone |
PORT |
3100 |
Listen port |
HOST |
127.0.0.1 |
Listen host |
BASE_PATH |
/publication/server_api/v2 |
URL prefix the API is mounted under |
TRUST_PROXY |
derived from DEPLOYMENT_MODE |
Honor X-Forwarded-For (client IP for rate limiting) — on behind apache/nginx, off standalone |
TRUSTED_PROXY_HOPS |
1 |
How many X-Forwarded-For entries your own proxies append; the caller is read that many entries from the right |
CACHE_MAX_AGE |
60 |
Cache-Control: max-age seconds (0 → no-cache) |
REQUEST_TIMEOUT_MS |
10000 |
Request-level timeout (0 disables) |
API_KEYS |
(empty) | Comma-separated keys; empty = open access |
RATE_LIMIT_RPM |
100 |
Token-bucket rate limit per IP, per minute |
CORS_ORIGIN |
* |
Allowed CORS origin |
MAX_BODY_SIZE |
65536 |
Max request body size in bytes |
MEDIA_BASE_URL |
/dedalo/media |
Base URL prefix for media in AV fragment responses |
MCP_ENABLED |
true |
Enable the Model Context Protocol endpoint |
MCP_PATH |
/mcp |
MCP endpoint path |
LOG_LEVEL |
info |
debug | info | warn | error |
DB_NAMES must not be empty
The service refuses to start (exits non-zero) if environment validation fails or if DB_NAMES
resolves to an empty list. Each {db} path segment is checked against this allowlist; an unknown
database returns 404.
Run
bun run dev # development (hot reload, --watch)
bun run start # production
On start the service logs its base URL, deployment mode and docs URL. With the defaults above the API is served at:
http://127.0.0.1:3100/publication/server_api/v2/
First requests
# API index — discover the entry-point links
curl http://localhost:3100/publication/server_api/v2/
# List the public databases
curl http://localhost:3100/publication/server_api/v2/databases
# List tables in a database, then inspect one table's schema
curl http://localhost:3100/publication/server_api/v2/dedalo_web/tables
curl http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview
The API index (GET /) returns the BASE_PATH-prefixed entry-point links:
{
"name": "Dédalo Publication API",
"version": "2.1.0",
"links": {
"databases": "/publication/server_api/v2/databases",
"docs": "/publication/server_api/v2/docs",
"openapi": "/publication/server_api/v2/openapi.yaml",
"health": "/publication/server_api/v2/health"
}
}
Base URL
Every path in this documentation is relative to BASE_PATH. With the default deployment that is
http://<host>:<port>/publication/server_api/v2. In standalone mode you can set
BASE_PATH= (empty) to serve from the root.
Interactive documentation
The OpenAPI 3.1 contract and two interactive explorers are served by the API itself — entirely offline, with no CDN dependencies:
| Endpoint | What it serves |
|---|---|
GET {BASE_PATH}/docs |
Interactive documentation landing |
GET {BASE_PATH}/docs/swagger |
Swagger UI (plus its asset subpaths) |
GET {BASE_PATH}/docs/scalar |
Scalar API reference (plus its asset subpaths) |
GET {BASE_PATH}/openapi.yaml |
The raw OpenAPI 3.1 spec (version 2.1.0) |
# Fetch the machine-readable contract
curl http://localhost:3100/publication/server_api/v2/openapi.yaml
# Open the interactive docs in a browser
open http://localhost:3100/publication/server_api/v2/docs
A liveness check is available separately and is never cached:
curl -i http://localhost:3100/publication/server_api/v2/health
# 200 when every configured database is connected; 503 if any database errors.
Project structure
publication/server_api/v2/
├── src/
│ ├── index.ts # Entry point: Bun.serve + middleware chain
│ ├── config.ts # Env config (Zod-validated) + derived lists (dbNames, apiKeys)
│ ├── constants.ts # API_VERSION = "2.1.0"
│ ├── router.ts # Path-parameter router; static routes first, then /:db/...
│ ├── routes/ # One handler per resource (discovery, tables, records,
│ │ # table-search, fragments, av-indexation-fragment, batch,
│ │ # docs, health)
│ ├── services/ # records, search, schema, relation resolve, batch, av-indexation
│ ├── db/ # Per-database pools, query builder, TTL schema cache
│ ├── security/ # API key auth, CORS, rate limiting
│ ├── middleware/ # error (problem+json), http-cache, timeout, compress, timing, logging
│ ├── utils/ # query-param parsing (filters/sort), fragments, response/Link helpers
│ ├── mcp/ # MCP server + tools
│ └── docs/ # OpenAPI 3.1 spec (openapi.yaml) + offline Swagger/Scalar assets
├── apache/ nginx/ # Deployment configs
└── tests/ # bun:test suite
Common commands (package.json):
bun run dev # development (hot reload)
bun run start # production
bun run typecheck # tsc --noEmit
bun test # test suite
bun run lint # eslint src/
Where to next
This overview is the entry point. The rest of the v2 reference is split into focused pages:
- Endpoints — every route, path parameters and response envelopes (databases, tables, records, search, text/AV fragments, indexation locator, batch, MCP).
- Querying — the record listing model: bracketed
filter[field][op]=valueoperators,sort,fields, pagination (limit/offset/count),lang, and relation resolution (resolve_relations/resolve_inverse_relations). - HTTP semantics — RFC 9457 errors, RFC 8288
Linkpagination,ETag/304caching, gzip, rate limiting and API-key auth.