Endpoint reference
The authoritative, one-section-per-endpoint reference for the Dédalo Publication API v2 (OpenAPI 3.1, spec version 2.1.0): method, path, parameters, response shape and a working curl example for every route.
The API is read-only and multi-database. Every data route is scoped by a {db} path segment that must appear in the server's DB_NAMES allowlist (otherwise the request is rejected with 404). Records are identified by section_id; multilingual values are stored one row per language in a lang column (lg-xxx format).
Base path
All paths below are relative to the configurable BASE_PATH (default /publication/server_api/v2). The examples use http://localhost:3100/publication/server_api/v2. Static routes are registered before /:db/..., so a database name can never shadow them.
Cross-cutting concerns are documented in sibling pages and only summarised here:
- Filter DSL, sorting, field selection, pagination,
langand relation resolution → querying.md. - Error format (RFC 9457), caching/
ETag,Linkpagination and response timing → http_semantics.md. - Rate limiting, API-key authentication, CORS and DoS bounds → security.md.
Path parameters
These appear across the data routes:
| Parameter | Type | Validation |
|---|---|---|
db |
string | Must be a configured public database (assertKnownDb). |
table |
string | ^[A-Za-z_][A-Za-z0-9_]*$ |
id |
integer | Positive integer, coerced via recordIdSchema (z.coerce.number().int().positive()). |
GET /
API index. Entry point with BASE_PATH-prefixed links to the main resources.
Response
{
"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"
}
}
Example
curl http://localhost:3100/publication/server_api/v2/
GET /databases
Lists the public databases exposed by this server. Use the returned name as the {db} path segment.
Response
{
"data": [
{
"name": "dedalo_web",
"links": { "tables": "/publication/server_api/v2/dedalo_web/tables" }
}
]
}
Example
curl http://localhost:3100/publication/server_api/v2/databases
GET /{db}/tables
Lists the tables of a database with row and column counts.
Path params: db.
Caching
Table schema introspection is cached for about 30 seconds, so repeated calls are cheap.
Response
{
"data": [
{ "name": "interview", "row_count": 142, "column_count": 12 },
{ "name": "audiovisual", "row_count": 142, "column_count": 8 }
]
}
row_count here is an estimate
In the table list, row_count is InnoDB's own estimate (read from INFORMATION_SCHEMA), not
an exact count — counting every row of every table on each listing would be a needless full scan
per request. The single-table endpoint below returns an exact COUNT(*).
Example
curl http://localhost:3100/publication/server_api/v2/dedalo_web/tables
GET /{db}/tables/{table}
Returns the schema of one table: its columns (name and SQL type) and row count.
Path params: db, table.
Response
{
"data": {
"name": "interview",
"row_count": 142,
"columns": [
{ "name": "section_id", "type": "int" },
{ "name": "lang", "type": "varchar" },
{ "name": "code", "type": "varchar" },
{ "name": "title", "type": "varchar" },
{ "name": "transcription", "type": "longtext" }
]
}
}
Example
curl http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview
GET /{db}/tables/{table}/records
Lists records with filtering, sorting, pagination, field selection and relation resolution. This is the main query model.
Path params: db, table.
Query params (validated by listRecordsQuerySchema + parseFilterParams/parseSort):
| Parameter | Type | Default | Notes |
|---|---|---|---|
filter[field][op] |
string | — | Bracketed filters; operator defaults to eq. Repeat for AND. See querying.md. |
sort |
string | — | Comma-separated; leading - = descending (sort=title,-section_id). |
fields |
string | all columns | Comma-separated column allowlist. |
limit |
integer | 100 |
min(0), max(1000). 0 is allowed for count-only requests. |
offset |
integer | 0 |
min(0). |
lang |
string | — | ^lg-[a-z]{2,5}$. Rejected on tables without a lang column. |
count |
boolean | false |
When true, adds pagination.total (extra COUNT query). Accepts true/1. |
resolve_relations |
string (JSON) | — | Forward relation resolution, e.g. {"image":"image"} or "auto". See querying.md. |
resolve_inverse_relations |
string | — | Resolves the dd_relations column; true or a JSON map. See querying.md. |
Response (RecordList envelope). pagination.total is present only when count=true. An RFC 8288 Link header carries rel="next"/rel="prev" when more pages exist.
{
"data": [
{ "section_id": 1, "lang": "lg-eng", "code": "OH-001", "title": "Interview with María" }
],
"pagination": { "limit": 100, "offset": 0, "total": 142 }
}
Examples
# Filter + sort + field selection + page size
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview/records?filter%5Bcode%5D%5Blike%5D=OH-%25&sort=-section_id&fields=section_id,code,title&limit=10"
# Range query with total count
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview/records?filter%5Bdate%5D%5Bgte%5D=1936&filter%5Bdate%5D%5Blte%5D=1939&count=true"
# Count only (no rows)
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview/records?limit=0&count=true"
GET /{db}/tables/{table}/records/{id}
Returns a single record by section_id. The response data is always an array: one row per language variant (ordered by lang), or a single row when lang is given.
Path params: db, table, id.
Query params (validated by getRecordQuerySchema — note: no limit/offset/sort/count):
| Parameter | Type | Notes |
|---|---|---|
fields |
string | Comma-separated column allowlist. |
lang |
string | ^lg-[a-z]{2,5}$. Narrows to one variant; echoes a Content-Language response header. |
resolve_relations |
string (JSON) | See querying.md. |
resolve_inverse_relations |
string | See querying.md. |
Response. meta.languages (the available variants) appears only when the table has a lang column.
{
"data": [
{ "section_id": 1, "lang": "lg-eng", "code": "OH-001", "title": "Interview with María" },
{ "section_id": 1, "lang": "lg-spa", "code": "OH-001", "title": "Entrevista con María" }
],
"meta": { "section_id": 1, "languages": ["lg-eng", "lg-spa"] }
}
A missing record returns 404 with an application/problem+json body.
Examples
# All language variants
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview/records/1"
# Single variant (sets Content-Language: lg-eng)
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview/records/1?lang=lg-eng"
GET /{db}/tables/{table}/search
MariaDB FULLTEXT search (MATCH … AGAINST in boolean mode) over an indexed text column. Each result row is augmented with a numeric relevance score and a fragments array of highlighted excerpts.
Path params: db, table.
Query params (validated by fulltextQuerySchema):
| Parameter | Type | Default | Notes |
|---|---|---|---|
q |
string | — | Required. 1–512 chars. Supports +word, -word, "phrase". |
column |
string | transcription |
FULLTEXT-indexed column to search. |
limit |
integer | 100 |
min(0), max(1000). |
offset |
integer | 0 |
min(0). |
count |
boolean | false |
Adds pagination.total. |
resolve_relations |
string (JSON) | — | See querying.md. |
resolve_inverse_relations |
string | — | See querying.md. |
Response (RecordList envelope; each row carries relevance and fragments).
{
"data": [
{
"section_id": 7,
"lang": "lg-spa",
"title": "Entrevista con María",
"relevance": 11.2,
"fragments": [
{ "text": "...durante la <mark>guerra</mark> civil...", "position": 1204 }
]
}
],
"pagination": { "limit": 100, "offset": 0 }
}
Example
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview/search?q=guerra+civil&limit=20"
GET /{db}/tables/{table}/records/{id}/fragments
Extracts highlighted text excerpts around each occurrence of the search terms in a large text column (books, theses, transcriptions). Each fragment includes a page number derived from [page-n-X] markers when present.
Path params: db, table, id.
Query params (validated by fragmentsQuerySchema):
| Parameter | Type | Default | Notes |
|---|---|---|---|
terms |
string | — | Required. 1–512 chars. Whitespace-separated, matched literally and case-insensitively. |
column |
string | transcription |
Column containing the text. |
lang |
string | — | ^lg-[a-z]{2,5}$. |
max_characters |
integer | 320 |
Context size per fragment (min(10), max(5000)). |
max_occurrences |
integer | 1 |
Maximum fragments per term (min(1), max(10)). |
Response
{
"data": [
{ "text": "...the <mark>war</mark> started when...", "page": 27, "position": 5340 }
],
"meta": { "section_id": 4, "terms": "war" }
}
Example
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/publications/records/123/fragments?terms=economia&max_occurrences=3"
GET /{db}/tables/{table}/records/{id}/av-fragments
Extracts transcription excerpts with their [tc-in-out] timecode ranges and media URLs, joining the audiovisual assets for video and image. Like text fragments, but with media and speakers and no column parameter (the transcription source is fixed).
Path params: db, table, id.
Query params (validated by avFragmentsQuerySchema):
| Parameter | Type | Default | Notes |
|---|---|---|---|
terms |
string | — | Required. 1–512 chars. Whitespace-separated, literal, case-insensitive. |
lang |
string | — | ^lg-[a-z]{2,5}$. |
max_characters |
integer | 320 |
Context size per fragment (min(10), max(5000)). |
max_occurrences |
integer | 1 |
Maximum fragments per term (min(1), max(10)). |
Response. Media URLs encode the timecode window (e.g. …/video.mp4?vbegin=120&vend=180).
{
"data": [
{
"transcription": "...we crossed the <mark>border</mark> at night...",
"media": {
"video_url": "/dedalo/media/video.mp4?vbegin=120&vend=180",
"image_url": "/dedalo/media/poster.jpg",
"tc_in": 120,
"tc_out": 180
},
"speakers": []
}
],
"meta": { "section_id": 4, "terms": "border" }
}
Example
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview/records/46/av-fragments?terms=guerra&max_characters=500"
GET /{db}/av-indexation-fragment
Resolves a thesaurus indexation locator to an audiovisual fragment: the transcription slice between timecodes, media URLs, speakers and associated thesaurus terms.
Path params: db.
Query params (validated by avIndexationParamsSchema):
| Parameter | Type | Notes |
|---|---|---|
section_id |
integer | Required. Positive integer. |
section_tipo |
string | Optional. |
component_tipo |
string | Optional. |
tag_id |
integer | Optional. |
tc_in |
number | Optional, ≥ 0. |
tc_out |
number | Optional, ≥ 0. |
Response
{
"data": {
"locator": { "section_id": 1, "tag_id": 3, "tc_in": 120, "tc_out": 180 },
"transcription": "We crossed the border at night...",
"media": {
"video_url": "/dedalo/media/video.mp4?vbegin=120&vend=180",
"image_url": "/dedalo/media/posterframe/poster.jpg",
"tc_in": 120,
"tc_out": 180
},
"speakers": [ { "name": "María García", "role": "informant" } ],
"terms": [ { "term_id": "ts1_23", "term": "Exile" } ]
}
}
Example
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/av-indexation-fragment?section_id=1&tag_id=1&tc_in=120&tc_out=180"
POST /batch
Runs up to 20 GET data queries in one request, in parallel. Each query carries its own HTTP status; one failure never breaks the batch.
Request. Requires Content-Type: application/json (else 400) and a body validated by batchRequestSchema:
| Field | Type | Notes |
|---|---|---|
queries |
array | 1–20 items (MAX_BATCH_QUERIES). |
queries[].id |
string | Non-empty, unique within the batch; echoed in the result. |
queries[].path |
string | Must start with / and name a GET data route without query string. |
queries[].params |
object | Optional. Values are string/number/boolean or arrays thereof; arrays become repeated keys, enabling bracketed filters. |
Meta routes are not allowed in a batch
path cannot target /docs, /health, /mcp, /openapi.yaml or /batch itself — only GET data routes.
Response. Each result carries either data (the route's envelope) or a problem (RFC 9457 body), plus the per-query status.
{
"results": [
{
"id": "interviews",
"status": 200,
"data": {
"data": [ { "section_id": 142, "code": "OH-142" } ],
"pagination": { "limit": 5, "offset": 0 }
}
},
{
"id": "themes",
"status": 404,
"problem": {
"type": "https://dedalo.dev/api/problems/not-found",
"title": "Not Found",
"status": 404,
"detail": "Unknown table: ts_themes"
}
}
]
}
Example
curl -X POST http://localhost:3100/publication/server_api/v2/batch \
-H "Content-Type: application/json" \
-d '{
"queries": [
{ "id": "interviews", "path": "/dedalo_web/tables/interview/records",
"params": { "filter[code][like]": "OH-%", "sort": "-section_id", "limit": 5 } },
{ "id": "tables", "path": "/dedalo_web/tables" }
]
}'
GET /health
Pings every configured database. Returns 200 when all are connected, 503 if any errors. Never cached.
Response (200)
{
"status": "ok",
"databases": { "dedalo_web": "connected" },
"uptime": 3600.5,
"timestamp": "2026-06-10T12:00:00.000Z",
"version": "2.1.0"
}
When a database is unreachable, its entry reads "error", status becomes "error", and the HTTP status is 503.
Example
curl -i http://localhost:3100/publication/server_api/v2/health
GET /docs
Interactive API documentation, served offline (no CDN dependencies). The landing page links to both renderers:
GET /docs/swagger— Swagger UI.GET /docs/scalar— Scalar.
Their static assets are served under /docs/swagger/… and /docs/scalar/….
Example
# Open in a browser
open http://localhost:3100/publication/server_api/v2/docs
GET /openapi.yaml
Returns the OpenAPI 3.1 specification (spec version 2.1.0) that powers /docs.
Example
curl http://localhost:3100/publication/server_api/v2/openapi.yaml
POST /mcp
Model Context Protocol endpoint for AI agents, served at MCP_PATH (default /mcp) when MCP_ENABLED is set (enabled by default). It exposes the same data through structured MCP tools (e.g. search_records, get_record, fulltext_search).
Streamable HTTP transport
The endpoint speaks the MCP Streamable HTTP transport; use an MCP client rather than plain curl. See mcp.md for the full tool catalogue and connection details.
GET /favicon.ico
Returns 204 No Content. Present so browsers hitting the API host directly do not produce spurious 404s.
Related
- Publication API v2 — version landing — what the v2 API is and how it differs from v1.
- Querying records — the filter DSL, sorting, field selection, pagination,
langand relation resolution in depth. - HTTP semantics — envelopes, RFC 9457 errors, caching/
ETag,Linkpagination headers and response timing. - Search & fragments —
search, text/AV fragments and the indexation locator endpoint, in detail. - Batch queries — the
/batchrequest/response contract and limits. - MCP server — the
/mcpendpoint and its tool catalogue. - Security — read-only model, SQL-injection defense, rate limiting, API key, CORS and DoS bounds.