Skip to content

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) with dd_api: "dd_core_api" and action: "<method>".
  • The request uses the Request Query Object (RQO) shape: dd_api, action, source, sqo, show, options, data.
  • Every action is registered in ACTION_REGISTRY in src/core/api/dispatch.ts and gated (auth, CSRF, per-action permissions) before it runs.

Notes

  • source and sqo are central: when one is missing, the server builds a sensible default from the source metadata.
  • 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 in data and 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 and result is a forbidden top-level key.
  • section_id is an int. A matrix record address is a safe integer, negatives included (-1 is the root/superuser record). A numeric string still coerces at the boundary but is deprecated and counted; a value that addresses no record refuses as section_id.not_an_address.

start

Purpose

Initialize the application, retrieve environment and session state.

Accepts

  • options: object (optional)
  • search_obj: object (optional) — URL parameters
  • menu: boolean (optional) — whether to load menu state
  • recovery: 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 start returns the login element context (this is what renders the login form) — or, on a fresh unconfigured machine, the installer element context, which keeps the wizard mounted across the restart that follows persisting the configuration. The environment block is built by src/core/resolve/environment.ts.

read

Purpose

Retrieve record/component data with optional filtering.

Accepts

  • source: object (required)
  • section_tipo: string (required) — section type identifier
  • section_id: int (optional) — specific record to read
  • action: string (optional) — "get_value" or default (build_json_rows)
  • tipo: string (optional) — component type
  • model: string (optional) — component model
  • mode: string (optional) — "edit" or "list"
  • lang: string (optional) — language code
  • sqo: object (optional)
  • limit: int (optional) — max records to return
  • offset: int (optional) — pagination offset
  • section_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.

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 type
  • tipo: string (required) — component/section type
  • model: string (optional) — component model
  • type: 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"
}

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 duplicate
  • section_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; tipo is accepted as a fallback)
  • section_id: int (required unless sqo is 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.tssaveComponentData):

  • source: object (required)
  • tipo: string (required) — component type
  • section_tipo: string (required) — parent section type
  • section_id: int (required) — parent section id
  • lang: string (optional, default "lg-nolan") — language code
  • caller_dataframe: object (optional) — dataframe pairing context (main_component_tipo, id_key)
  • data: object (required)
  • changed_data: array (required) — change objects, each { action, key, value } where actionupdate / 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 count
  • limit: int (optional) — max results
  • offset: 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.

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 sends model: "tool_x" with no tipo)
  • section_tipo: string (optional, defaults to tipo)
  • 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 to buildSectionElementsContext (src/core/resolve/section_elements_context.ts). Permissions are always enforced server-side (the client's skip_permissions flag 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) — a section_map scope; null uses 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 model
  • range_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 tipo is not exposed as an API action: it is resolved internally through the ontology resolver (src/core/ontology/resolver.ts).