dd_core_api
See also: JSON API v1 · RQO field mapping · dispatch
Core Dédalo API for the section/record lifecycle and page context. It is the API that front-end page builders and components use most.
How to call
- POST JSON to
/api/v1/json(or the client-relative/dedalo/core/api/v1/json) withdd_api: "dd_core_api"andaction: "<method>". - The request uses the Request Query Object (RQO) shape:
dd_api,action,source,sqo,show,options,data. - Every action is registered in
ACTION_REGISTRYinsrc/core/api/dispatch.tsand gated (auth, CSRF, per-action permissions) before it runs.
Notes
sourceandsqoare central: when one is missing, the server builds a sensible default from thesourcemetadata.- For file uploads, use
multipart/form-data; the media ingest branch of the API path (src/server.ts) handles them, not the JSON dispatcher. - Envelope v2. Success is
{ ok: true, request_id, data, … }— the payload lives indataand nowhere else; keys a client reads by their own name (environment,table, …) ride at the top level as extension keys. A refusal is{ ok: false, request_id, error: { code, category, message, label_key, retryable } }and carries the registry's HTTP status. The v1{ result, msg, errors }shape was removed on 2026-08-16 andresultis a forbidden top-level key. section_idis an int. A matrix record address is a safe integer, negatives included (-1is the root/superuser record). A numeric string still coerces at the boundary but is deprecated and counted; a value that addresses no record refuses assection_id.not_an_address.
start
Purpose
Initialize the application, retrieve environment and session state.
Accepts
options: object (optional)search_obj: object (optional) — URL parametersmenu: boolean (optional) — whether to load menu staterecovery: string (optional) — recovery key for maintenance mode
Returns
{ ok: true, request_id, data: { context: [ … ], data: [] }, environment: { page_globals, plain_vars, get_label } }. environment is a top-level extension key, not part of data.
Usage
Typically called on page load. data.context describes the element the client is to mount; data.data is always empty here.
Example Request
{
"dd_api": "dd_core_api",
"action": "start",
"options": {
"search_obj": {},
"menu": false
}
}
Example Response
{
"ok": true,
"request_id": "c0ffea00",
"data": { "context": [ /* section/menu element contexts */ ], "data": [] },
"environment": { "page_globals": {}, "plain_vars": {}, "get_label": {} }
}
Unauthenticated
startreturns the login element context (this is what renders the login form). There is no installer context: a machine that is not installed does not serve at all — the server refuses to start (installer reference). The environment block is built bysrc/core/resolve/environment.ts.
read
Purpose
Retrieve record/component data with optional filtering.
Accepts
source: object (required)section_tipo: string (required) — section type identifiersection_id: int (optional) — specific record to readaction: string (optional) — "get_value" or default (build_json_rows)tipo: string (optional) — component typemodel: string (optional) — component modelmode: string (optional) — "edit" or "list"lang: string (optional) — language codesqo: object (optional)limit: int (optional) — max records to returnoffset: int (optional) — pagination offsetsection_tipo: array (optional) — filter by section types
Returns
{ ok: true, request_id, data: { context: [ … ], data: [ … ] } } — the section (or component) structure contexts plus the resolved records. Note the nesting: the read's own {context, data} pair is the envelope's data.
Usage
Core method for reading record data. Can fetch full section data or a single component (source.action: "get_data"), search filter chips (resolve_data), or the References panel (get_relation_list). Read requires permission level ≥ 1 on the section.
A related search (sqo.mode: "related" — the References panel, its graph, the tools' related_search) is gated on the host records named in sqo.filter_by_locators: the caller needs level ≥ 1 on each host's section, and the literal sqo.section_tipo: ["all"] (every owning section) is not checked as a section of its own. The referencing records are then restricted to the ones the caller may see — read grant on their section and inside the caller's projects — before the page is cut, so a page is full while more of the caller's records exist, and count returns exactly the number of records the pages hold.
Example Request
{
"dd_api": "dd_core_api",
"action": "read",
"source": {
"section_tipo": "rsc167",
"section_id": 528,
"action": null
},
"sqo": {
"limit": 10,
"offset": 0
}
}
Example Response
{
"ok": true,
"request_id": "c0ffea01",
"data": {
"context": [ /* structure contexts */ ],
"data": [
{ "section_id": 528, "section_tipo": "rsc167", "tipo": "rsc36", "value": ["Sample transcription"] }
]
}
}
read_raw
Purpose
Retrieve full raw data from database for section or component.
Accepts
Note: read_raw reads its identifiers from options (not source), matching src/core/api/dispatch.ts.
options: object (required)section_tipo: string (required) — section typetipo: string (required) — component/section typemodel: string (optional) — component modeltype: string (optional)sqo: object (optional) — target sections default to[options.section_tipo]when absent.
Returns
{ ok: true, request_id, data: [ … ], table: "<matrix table>" } — data is the raw stored value(s) for the SQO's matched records; table is a top-level extension key the raw view reads by name.
Usage
Read requires level ≥ 1 on every SQO target section. A missing options.section_tipo or options.tipo refuses with request.invalid_source; an insufficient grant with perm.denied.
Example Request
{
"dd_api": "dd_core_api",
"action": "read_raw",
"options": {
"section_tipo": "rsc167",
"tipo": "rsc167"
},
"sqo": {
"section_tipo": ["rsc167"],
"limit": 1
}
}
Example Response
{
"ok": true,
"request_id": "c0ffea02",
"data": [ /* raw stored values per matched record */ ],
"table": "matrix"
}
relation_targets
Purpose
The census behind "Open relationships": which sections the records of a source set point at, and how many records of each.
Accepts
Like read_raw, the input rides in options; there is no sqo, so no page limit applies.
options: object (required), holding exactly:source: object (required) — areferenced_bysource set:section_tipo: string[] (required) — the source sectionsfilter: object (optional) — the source search filter (may nest areferenced_byleaf, depth ≤ 4)filter_by_locators: object[] (optional) — explicit source recordsfilter_by_locators_op:"OR"(optional) — the only accepted valuefrom_component_tipo: string (optional) — count only the locators of this component
Returns
{ ok: true, request_id, data: [{ tipo, label, total }, …] } — one entry per target section, ordered by tipo, label in the request language. Each total is exact (the count the opened window would show, with the target's access rules applied). Unreadable targets and targets with total 0 are omitted; [] means nothing to open. A perm.out_of_scope notice is added when the user's component permissions hide relation components of a source section.
Usage
Read requires level ≥ 1 on every source section (perm.denied). A malformed source, an unknown options key or a sqo key refuses with request.invalid_options; an invalid tipo or a source section with no record table with request.invalid_tipo. The locators are read from the source records' stored relation column (the read_raw target_section reading, not the relation index), so log sections are valid sources. The census (a grouped pass, then one statement for every exact total) has one 30-second deadline across its statements, capped by DB_STATEMENT_TIMEOUT_MS; past it the request fails with db.statement_timeout (503). The client's own timeout for this call (35 s) is longer, so that refusal arrives first.
Example Request
{
"dd_api": "dd_core_api",
"action": "relation_targets",
"options": {
"source": {
"section_tipo": ["oh1"],
"filter_by_locators": [{ "section_tipo": "oh1", "section_id": 3 }],
"from_component_tipo": "oh24"
}
}
}
Example Response
{
"ok": true,
"request_id": "c0ffea03",
"data": [ { "tipo": "rsc197", "label": "People", "total": 2 } ]
}
create
Purpose
Create a new record in a section.
Accepts
source: object (required)section_tipo: string (required) — target section type
Returns
{ ok: true, request_id, data: <new section_id> } — an int.
Usage
Creates a new blank record. Requires permission level ≥ 2 on the section (perm.denied otherwise); a missing source.section_tipo refuses with request.invalid_source.
Example Request
{
"dd_api": "dd_core_api",
"action": "create",
"source": {
"section_tipo": "rsc167"
}
}
Example Response
{
"ok": true,
"request_id": "c0ffea03",
"data": 125
}
duplicate
Purpose
Duplicate an existing record with all its data.
Accepts
source: object (required)section_id: int (required) — source record to duplicatesection_tipo: string (required) — section type
Returns
{ ok: true, request_id, data: <new section_id> } — an int.
Usage
Creates a complete copy of the source record, including all component data. Requires level ≥ 2 (perm.denied); a non-admin must also have the source record inside their projects scope (perm.out_of_scope). A section_id that is not a record address refuses with section_id.not_an_address.
Example Request
{
"dd_api": "dd_core_api",
"action": "duplicate",
"source": {
"section_id": 1,
"section_tipo": "rsc167"
}
}
Example Response
{
"ok": true,
"request_id": "c0ffea04",
"data": 126
}
delete
Purpose
Delete a record or section data.
Accepts
source: object (required)section_tipo: string (required;tipois accepted as a fallback)section_id: int (required unlesssqois present)delete_mode: string (optional, default"delete_data") —"delete_data"empties every component and keeps the row;"delete_record"removes the row (a Time Machine snapshot is taken first).sqo: object (optional) — bulk delete by search. Global-admin only, and constrained to the gated section.
Returns
{ ok: true, request_id, data: [ <deleted section_id>, … ] } — an array of ints, the records actually deleted.
Parents whose children refused to go are not a failure: what was deleted really was deleted and stays the payload, and the refusal travels as one coded notice in notices[]:
"notices": [
{
"code": "record.delete_children_refused",
"label_key": "error_record_delete_children_refused",
"retryable": false,
"details": { "not_deleted": "12,17" }
}
]
Usage
Requires level ≥ 2 on the section (perm.denied), and a non-admin must have the record in their projects scope (perm.out_of_scope). Ontology-main sections cascade (uninstall the TLD) under delete_record, global-admin only. Deleting a user or profile record also drops the affected security caches, so access reflects the deletion on the next request.
Example Request
{
"dd_api": "dd_core_api",
"action": "delete",
"source": {
"section_tipo": "rsc167",
"section_id": 528,
"delete_mode": "delete_record"
}
}
Example Response
{
"ok": true,
"request_id": "c0ffea05",
"data": [1]
}
save
Purpose
Save component data to a record.
Accepts
The TS handler reads these from source (src/core/api/dispatch.ts → saveComponentData):
source: object (required)tipo: string (required) — component typesection_tipo: string (required) — parent section typesection_id: int (required) — parent section idlang: string (optional, default"lg-nolan") — language codecaller_dataframe: object (optional) — dataframe pairing context (main_component_tipo,id_key)data: object (required)changed_data: array (required) — change objects, each{ action, key, value }whereaction∈update/insert/remove/set_data/sort_data/sort_by_column/add_new_element.
Returns
{ ok: true, request_id, data: { context: [ … ], data: [ <saved DataItem> ] } }.
Usage
Saves component changes. Requires level ≥ 2 on (section_tipo, tipo). On success the server echoes the saved component in the canonical DataItem envelope (relation and select-family saves also carry datalist / pagination / context), triggers server-side observers, and writes an activity-log entry.
The echo is the twin of the edit read: for a translatable literal (the component_input_text / component_text_area family, component_iri) the record stores every language in one flat array, and the echoed entries carry only the source.lang slice — exactly what a get_data read of the same component returns. Non-translatable and relation components echo their full item array, since they are not language-sliced.
Warning
A client that assigns the echoed item to its in-memory component data (the standard post-save refresh) therefore keeps one language. Echoing every language instead is what made an ontology-tree inline term edit repaint the node with all its translations concatenated. Gate: test/unit/save_echo_lang_slice_native.test.ts.
Example Request
{
"dd_api": "dd_core_api",
"action": "save",
"source": {
"tipo": "oh16",
"section_tipo": "oh1",
"section_id": 124,
"lang": "lg-eng"
},
"data": {
"changed_data": [
{ "action": "update", "id": 1, "value": { "id": 1, "lang": "lg-eng", "value": "Updated Title" } }
]
}
}
oh16 is a translatable component_input_text, so its items carry id + lang; the id pairs the language versions of the same value.
Example Response
{
"ok": true,
"request_id": "c0ffea06",
"data": {
"context": [],
"data": [
{
"tipo": "oh16",
"section_tipo": "oh1",
"section_id": 124,
"mode": "edit",
"lang": "lg-eng",
"from_component_tipo": "oh16",
"entries": [ { "id": 1, "lang": "lg-eng", "value": "Updated Title" } ]
}
]
}
}
The record's other languages are untouched on disk and simply absent from this lg-eng echo.
count
Purpose
Count records matching SQO criteria.
Accepts
sqo: object (required)section_tipo: array (required) — section types to countlimit: int (optional) — max resultsoffset: int (optional) — pagination offset- Other filter criteria (filters, search terms, etc.)
Returns
{ ok: true, request_id, data: { total: <int> } }.
Usage
Returns the total count of records matching the SQO. The read strategy owns counting (the default matrix source runs the SQO full-count; sqo.mode: "related" counts inverse references; sqo.mode: "tm" counts matrix_time_machine). Requires level ≥ 1 on every SQO target section — for sqo.mode: "related", on the section of every host in sqo.filter_by_locators instead (["all"] is not a section). A related count counts only the referencing records the caller may see, so it equals the number of records the caller's References pages hold.
Example Request
{
"dd_api": "dd_core_api",
"action": "count",
"sqo": {
"section_tipo": ["rsc167"]
}
}
Example Response
{
"ok": true,
"request_id": "c0ffea07",
"data": { "total": 42 }
}
get_element_context
Purpose
Retrieve context information for a specific component/element.
Accepts
source: object (required)tipo: string (required for section/component; a tool sendsmodel: "tool_x"with notipo)section_tipo: string (optional, defaults totipo)mode: string (optional, default"list")lang: string (optional)model: string (optional)
Covered models: section, component_*, and the area models. Tool contexts require the tool to be authorized for the caller.
Returns
{ ok: true, request_id, data: [ <one context entry> ] } — an array of one (empty when nothing resolves), because the client build looks its own element up inside it.
Usage
Returns the structure context (no data) for one element. Requires level ≥ 1 on (section_tipo, tipo) (perm.denied). A tipo that resolves to no node refuses with request.invalid_source; a model this action does not cover with request.invalid_model; an unauthorized tool with tool.not_authorized.
Example Request
{
"dd_api": "dd_core_api",
"action": "get_element_context",
"source": {
"section_tipo": "oh1",
"tipo": "oh16",
"mode": "edit"
}
}
Note
oh16 is the component_input_text "Title" of the Oral History section oh1 on the monedaiberica install — section_tipo must be the section the component actually lives in.
Example Response
{
"ok": true,
"request_id": "c0ffea08",
"data": [
{ "tipo": "oh16", "model": "component_input_text", "label": "Title", "permissions": 3 }
]
}
get_section_elements_context
Purpose
Retrieve context for all components in a section.
Accepts
options: object — forwarded tobuildSectionElementsContext(src/core/resolve/section_elements_context.ts). Permissions are always enforced server-side (the client'sskip_permissionsflag is ignored).
Returns
{ ok: true, request_id, data: [ { context_for_each_element }, … ] }.
Usage
Returns the "simple" structure-context set for a section — the edit-mode search-filter panel's element list.
Example Request
{
"dd_api": "dd_core_api",
"action": "get_section_elements_context",
"options": {
"section_tipo": "oh1"
}
}
Example Response
{
"ok": true,
"request_id": "c0ffea09",
"data": [
{
"tipo": "oh16",
"label": "Title",
"model": "component_input_text"
},
{
"tipo": "oh25",
"label": "Audiovisual",
"model": "component_portal"
}
]
}
get_section_terms
Purpose
Batch-resolve the section_map display term for a set of records in one call — the graph view labels all of its nodes with a single request instead of one datum read per node.
Accepts
The locators, scope and lang ride at the top level of the RQO (not under source/options), matching the client's build_graph_data.js:
locators: array (required) —{ section_tipo, section_id }entries. Hard-capped at 1000; a longer batch is truncated.scope: string (optional) — asection_mapscope;nulluses the main → thesaurus → relation_list chain.lang: string (optional) — defaults to the request data language.
Returns
{ ok: true, request_id, data: { "<section_tipo>_<section_id>": "<term>|null", … } } — keyed per resolved record, deduped by composite key. An empty or invalid locators array refuses with section.bad_locators (HTTP 400).
Usage
Read permission (level ≥ 1) is required per section; unreadable, invalid, or non-section_map sections are skipped silently (never leaked, never clobbering the client's provisional node label).
Example Request
{
"dd_api": "dd_core_api",
"action": "get_section_terms",
"locators": [
{ "section_tipo": "oh1", "section_id": 368 },
{ "section_tipo": "es1", "section_id": 13919 }
]
}
Example Response
{
"ok": true,
"request_id": "c0ffea10",
"data": { "oh1_368": "Interview with…", "es1_13919": "Valle" }
}
get_indexation_grid
Purpose
Build the thesaurus "show indexations" grid for a term component in a record.
Accepts
source: object (required)section_tipo: string (required) — the term's section.section_id: int (required) — the term record.tipo: string (required) — the element the grid was opened from. The door requires it; the grid content itself is driven by the SQO below.
sqo: object (required in practice)section_tipo: string or array (required) — the sections to search for records indexing the term."all"searches every section. Absent or empty answers an empty grid.filter_by_locators: array (required) — the term locators to find inverse references to. An empty list answers an empty grid.limit(default 500) /offset.
Returns
{ ok: true, request_id, data: [ <grid cell>, … ] } — the nested grid-cell tree the thesaurus indexation view renders, grouped by the top record each indexation belongs to. Records outside the caller's scope are dropped from the groups; a section with no indexation-list configuration is skipped and logged, never guessed at.
Usage
Read permission (level ≥ 1) on the term's section is required (perm.denied, HTTP 403). A missing section_tipo / section_id / tipo — or a section_id that is not a record address — refuses with request.invalid_source (HTTP 400).
Example Request
{
"dd_api": "dd_core_api",
"action": "get_indexation_grid",
"source": {
"section_tipo": "es1",
"section_id": 13919,
"tipo": "hierarchy25"
},
"sqo": {
"section_tipo": ["oh1"],
"filter_by_locators": [ { "section_tipo": "es1", "section_id": 13919 } ],
"limit": 100
}
}
Note
es1 is a thesaurus section (a virtual section of the core Thesaurus section hierarchy20) on the monedaiberica install, and hierarchy25 is its component_input_text term component — the one es1's section_map names as thesaurus.term.
Example Response
{
"ok": true,
"request_id": "c0ffea11",
"data": [ { "type": "column", "label": "Oral History", "value": [ /* … */ ] } ]
}
get_activity_metric
Purpose
Fetch an on-demand activity-metric dataset for the area dashboard's timeline range switch (3m / 6m / 1y). The dashboard read serves only the recent range inline; wider ranges are fetched here so the initial payload stays small.
Accepts
options: object (required)area_tipo: string (required) — must resolve to an area modelrange_days: int (required) — one of the supported ranges (ACTIVITY_RANGE_DAYS,src/core/area/dashboard.ts)
Returns
{ ok: true, request_id, data: <activity dataset> }.
Usage
Gated identically to the dashboard read — read permission (level ≥ 1) on the area (perm.denied). An unresolvable area_tipo refuses with request.invalid_tipo, a tipo that is not an area model with request.invalid_model, and an unsupported range_days with request.invalid_options — all HTTP 400.
Example Request
{
"dd_api": "dd_core_api",
"action": "get_activity_metric",
"options": {
"area_tipo": "dd542",
"range_days": 90
}
}
Example Response
{
"ok": true,
"request_id": "c0ffea12",
"data": { /* timeline buckets for the requested range */ }
}
get_ip_country
Purpose
Resolve an IP address to a country for the Activity (dd542) IP-list view. Resolution is local and offline against the openly-licensed DB-IP Country Lite database (src/core/geoip) — no third-party request.
Accepts
options: object (required)ip: string (required) — the address to resolve (1–64 chars)
Returns
{ ok: true, request_id, data: { country_code: <ISO code|null> } }. country_code is null for private/reserved/unresolved addresses and when the database is not loaded, so the client simply shows no flag.
Usage
Authenticated (the dispatch session + CSRF gates already ran). A missing or malformed ip refuses with request.invalid_options (HTTP 400).
Example Request
{
"dd_api": "dd_core_api",
"action": "get_ip_country",
"options": {
"ip": "8.8.8.8"
}
}
Example Response
{
"ok": true,
"request_id": "c0ffea13",
"data": { "country_code": "US" }
}
get_environment
Purpose
Return the full client environment payload — page_globals + plain_vars + get_label (the same block start embeds). The copied client injects it via set_environment() at boot.
Accepts
No arguments. The block is built for the current session (or the anonymous environment when unauthenticated).
Returns
{ ok: true, request_id, data: { page_globals, plain_vars, get_label } }, built by src/core/resolve/environment.ts. It is the same block start ships as its environment extension key.
Usage
get_environment is a NO_LOGIN action — it serves the anonymous environment before a session exists and the authenticated one after login. The page-globals / plain-vars / label payloads are not separate actions; they are all served through this one block.
Example Request
{
"dd_api": "dd_core_api",
"action": "get_environment"
}
Example Response
{
"ok": true,
"request_id": "c0ffea14",
"data": {
"page_globals": { /* … */ },
"plain_vars": { /* … */ },
"get_label": { /* … */ }
}
}
The ontology locator of a
tipois not exposed as an API action: it is resolved internally through the ontology resolver (src/core/ontology/resolver.ts).