Records and languages
How the Publication API v2 identifies records by section_id, returns multilingual values as one row per language, and introspects table schemas.
The data model
A record is identified by its section_id. Multilingual values are not nested inside a single row; instead they are stored as one row per language, all sharing the same section_id. The language of each row lives in a lang column whose values use the lg-xxx format (for example lg-eng, lg-spa).
section_id | lang | code | title
-----------+--------+--------+----------------------------
1 | lg-eng | OH-001 | Interview with María
1 | lg-spa | OH-001 | Entrevista con María
Not every published table is multilingual. Thesaurus tables (the ts_* tables) and other reference tables have no lang column. Endpoints detect this at runtime (tableHasColumn(db, table, 'lang')) and adapt their behaviour accordingly.
section_id vs. row id
section_id is the stable, public identifier you address records by. The path parameter on GET .../records/{id} is the section_id, validated as a positive integer (z.coerce.number().int().positive()). It is not the underlying auto-increment row id.
Getting a single record
GET /{db}/tables/{table}/records/{id}
By default this returns all language variants of the record as an array, ordered by lang ascending.
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview/records/1"
{
"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"]
}
}
data is always an array
Even for a single-language result, data is an array. Each element is one language variant. Do not assume data is an object.
Narrowing to one language
Pass ?lang=lg-xxx to return only that variant. The value must match the pattern ^lg-[a-z]{2,5}$.
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview/records/1?lang=lg-eng"
{
"data": [
{ "section_id": 1, "lang": "lg-eng", "code": "OH-001", "title": "Interview with María" }
],
"meta": {
"section_id": 1,
"languages": ["lg-eng"]
}
}
When lang narrows the response, the server also sets a Content-Language response header echoing the value:
Content-Language: lg-eng
Keep lang selectable when projecting fields
If you combine fields= with a multilingual table, the server automatically adds lang to the selection (when the table has a lang column) so variants stay identifiable, even if you did not list it. For example ?fields=section_id,title still returns lang on each row.
Tables without a lang column
Thesaurus and other non-multilingual tables reject the lang parameter with a 400 Validation Error:
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/ts_themes/records/1?lang=lg-eng"
{
"type": "https://dedalo.dev/api/problems/validation-error",
"title": "Validation Error",
"status": 400,
"detail": "Table \"ts_themes\" has no \"lang\" column; the lang parameter is not supported",
"instance": "/dedalo_web/tables/ts_themes/records/1?lang=lg-eng"
}
For these tables meta.languages is omitted entirely (it appears only when the table has a lang column):
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/ts_themes/records/1"
{
"data": [
{ "term_id": "ts1", "term": "Civil war", "indexation": "ts1" }
],
"meta": {
"section_id": 1
}
}
Record not found
A missing record (or a missing variant when lang is given) returns 404 with an application/problem+json body. The lang is included in the detail when it was part of the request:
{
"type": "https://dedalo.dev/api/problems/not-found",
"title": "Not Found",
"status": 404,
"detail": "Record not found: interview/9999 (lang: lg-eng)",
"instance": "/dedalo_web/tables/interview/records/9999?lang=lg-eng"
}
Single-record query parameters
GET .../records/{id} accepts only the following query parameters (no limit, offset, sort or count):
| Parameter | Format | Notes |
|---|---|---|
fields |
comma-separated columns | Trimmed; empty entries dropped. lang is re-added automatically on multilingual tables. |
lang |
^lg-[a-z]{2,5}$ |
Rejected on tables without a lang column. |
resolve_relations |
JSON string or "auto" |
Forward relation resolution (see Querying). |
resolve_inverse_relations |
true or JSON object |
Resolves the dd_relations column (see Querying). |
Listing records
GET /{db}/tables/{table}/records
The list endpoint returns the paginated list envelope. With a multilingual table and no lang filter, every language variant is a separate row in data (each row counts toward limit). Add ?lang=lg-xxx to restrict to one language.
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview/records?lang=lg-eng&limit=2&count=true"
{
"data": [
{ "section_id": 1, "lang": "lg-eng", "code": "OH-001", "title": "Interview with María" },
{ "section_id": 2, "lang": "lg-eng", "code": "OH-002", "title": "Interview with José" }
],
"pagination": { "limit": 2, "offset": 0, "total": 142 }
}
pagination vs. meta
The list envelope is { data, pagination, meta? }: the page window lives in pagination (limit, offset, and total only when count=true). The single-record envelope is { data, meta }: it has no pagination, and its meta carries section_id plus (on multilingual tables) languages.
Filtering, sorting, field selection, pagination and relation resolution for the list endpoint are documented in Querying. For the full route list see Endpoints.
Table schema introspection
Schemas are read live from INFORMATION_SCHEMA and cached in-process for ~30 seconds (a TTL cache per database / per table).
List tables
GET /{db}/tables
Returns the database's tables with row and column counts. Row counts come from INFORMATION_SCHEMA.TABLES.TABLE_ROWS (an estimate for some storage engines), and column_count is the length of the introspected column list.
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables"
{
"data": [
{ "name": "interview", "row_count": 142, "column_count": 12 },
{ "name": "audiovisual", "row_count": 142, "column_count": 8 }
]
}
Get one table's schema
GET /{db}/tables/{table}
Returns the table name, an exact row count (SELECT COUNT(*)), and the ordered list of columns with their SQL DATA_TYPE.
curl "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview"
{
"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" }
]
}
}
Two count sources
GET /{db}/tables reports row_count from the TABLE_ROWS estimate (fast, cached), while GET /{db}/tables/{table} reports an exact COUNT(*). The list view also reduces columns to a column_count; the single-table view returns the full columns array.
To detect whether a table is multilingual before querying, look for a column named lang in this schema — that is exactly the check the record endpoints perform internally.
# Multilingual? -> a "lang" column is present in the schema
curl -s "http://localhost:3100/publication/server_api/v2/dedalo_web/tables/interview" \
| grep -o '"name": *"lang"'
An unknown table returns 404 (NotFoundError); a syntactically invalid table name (it must match ^[A-Za-z_][A-Za-z0-9_]*$) is rejected before any query runs.
Field and envelope summary
| Field | Where | Meaning |
|---|---|---|
section_id |
row + meta.section_id |
Public record identifier; same across language variants. |
lang |
row | Language of the variant, lg-xxx; absent on non-multilingual tables. |
data |
list & single | Always an array of row objects. |
pagination.limit / .offset |
list only | Page window (defaults 100 / 0). |
pagination.total |
list only | Present only when count=true. |
meta.section_id |
single only | Echoes the requested id. |
meta.languages |
single only | Available variants; present only when the table has a lang column. |
meta.response_time_ms |
all JSON success | Total server processing time in ms (also in the X-Response-Time header); excluded from ETag. |
Related
- Endpoints — full route reference.
- Querying — filters, sorting, field selection, pagination, relation resolution.
- HTTP semantics — Problem Details errors, ETag/304 caching,
Linkpagination, headers. - Publication API v2 — version landing page.