Public API v2 write operations
The write endpoints of public API v2 create, replace, update and delete RMD content and data-model objects, and manage project access, version transfer and Git connections. Base path, authentication, pagination and the error format are the same as for the read endpoints (the Public API v2 page).
1 Write API — conventions
v2 write endpoints create, replace, update, and delete RMD and data-model entities. Every write is audited (section 7 of the API page) and returns the standard error shape (section 4 of the Public API v2 page); the complete code catalogue is in section 9.11. The conventions below apply to every endpoint of section 2 and are not repeated there.
HTTP verb semantics:
| Verb | Meaning |
|---|---|
POST (collection) |
Create a new resource; returns the created resource with 201 Created |
POST (…/bulk) |
Bulk create/update a set of RMD rows in one call. All rows applied — 201; some rows rejected — 207 Multi-Status with the succeeded and failed arrays; none applied — 422 DF_API.BULK_REJECTED |
POST (membership, e.g. …/fact-tables/{id}/measures) |
Assign existing elements to a parent; returns 201 with the summary { timestamp, succeeded: [{ id }], failed: [{ index, id, error: { code, message, details } }] } (207 when some elements were rejected; an already-assigned element lands in failed with constraint_violation, a non-existent one with resource_not_found; none succeeded — 422 DF_API.BULK_REJECTED). Adding dimensions to a group (…/dimension-groups/{id}/dimensions) answers 200 with the group object; a non-existent dimension — 404 DF_API.RESOURCE_NOT_FOUND |
PUT |
Full replacement — required fields must be present (400 DF_API.MISSING_REQUIRED_FIELD otherwise); any optional field not supplied is reset to its default/empty value |
PATCH |
Partial update — only the supplied fields change |
DELETE |
Remove the resource; returns 204 No Content |
Effective access. RMD and data-model writes require the API-key owner to resolve to an effective project role of developer or higher for the target project (the same per-project access model as section 2.2 of the API page). If the owner has no access to the project the API returns 404 DF_API.PROJECT_NOT_FOUND (existence is not disclosed); if the owner has access below developer (analyst or viewer) it returns 403 DF_API.WRITE_ACCESS_DENIED. Creating a project requires the key owner to belong to a company.
Idempotency. A POST request may carry an Idempotency-Key header (UUID v4). The key is scoped per (API-key hash, HTTP method, path, key) and kept for 24 hours. A completed response is replayed verbatim for a repeated key; a still-in-flight duplicate returns 409 DF_API.IDEMPOTENCY_IN_PROGRESS; a malformed key returns 400 DF_API.INVALID_IDEMPOTENCY_KEY. PUT, PATCH and DELETE do not use the header — repeating them brings the resource to the same state.
Body validation. Bodies are strict: an unknown field answers 400 DF_API.VALIDATION_FAILED with details[].code = unknown_field; a value of the wrong type answers the same code with invalid_value. The read-only fields id, timestamp, created_at, updated_at are tolerated in write bodies (so a read result can be sent back) and ignored — except id, which must match the path identifier when present (400 DF_API.ID_MISMATCH).
Validation and referential integrity. Names must match the allowed pattern and be unique within their scope (409 DF_API.DUPLICATE_NAME). Measure formulas are parsed and their references resolved. The parser accepts the same syntax as the formula editor — including the NULL literal, IS [NOT] NULL, [NOT] IN (…) and [NOT] BETWEEN (a, b) with value lists in parentheses, and unary minus — so a formula read through the API can be written back unchanged. A syntax error returns 422 DF_API.INVALID_FORMULA_SYNTAX, an unknown reference 422 DF_API.FORMULA_REFERENCE_NOT_FOUND, and a cycle 422 DF_API.CIRCULAR_DEPENDENCY. Deleting an element referenced by another element's formula, a dimension that belongs to a dimension group, or a dimension group assigned to a fact table returns 409 DF_API.CONSTRAINT_VIOLATION; a measure that is merely assigned to a fact table is deleted together with the assignment (204). Every create and update response carries the created/updated object plus a timestamp field (the moment of the operation).
Select-type RMD fields. measure_type, dimension_type, fact_type, display_data_type, status, relevance, required, visibility and the other select columns accept either the English or the Russian label of an option (Base / Базовый, Number / Число); the comparison ignores case and surrounding whitespace, and the response echoes the labels in the language the request used. A value matching no option of a required select (measure_type, dimension_type, fact_type) answers 400 DF_API.INVALID_ENUM_VALUE; for optional selects a non-matching value is dropped. Flag columns (relevance, required, visibility) take the strings "true" / "false".
Version fields. A version has only name, is_global, and (on create) an optional clone_from_version. The version write API does not accept a description field.
Source objects in write requests. connected_source (dimensions, facts), primary_key (dimension groups, relationships) and foreign_key (relationships) accept the object of section 3.6 of the API page, in single-item and bulk requests alike. db and table are required; schema and column accept the empty string, null, or absence. connection is optional, and its presence selects which of the two contracts applies:
connection |
Behaviour |
|---|---|
| supplied | The connection is resolved by name only, matched exactly (names are unique per version, and Prod is not prod). db, table, column, and — for relationships — the connection itself are validated; see the table below |
| omitted | The pre-existing behaviour is kept unchanged: db is resolved leniently as a connection id, a database name, or a connection name, and nothing is validated |
Validation applied when connection is supplied:
| Field | Rule | Error |
|---|---|---|
connection |
Must name a connection of the project version | DF_API.INVALID_SOURCE_CONNECTION |
db |
Required, as before; must be that connection's database name — a connection name is no longer accepted here | DF_API.INVALID_SOURCE_DB |
schema |
Optional, PostgreSQL only; when given and non-empty, must be that connection's own schema | DF_API.INVALID_SOURCE_SCHEMA |
table |
Must exist in the connection's cached schema | DF_API.INVALID_SOURCE_TABLE |
table (MS SQL) |
May be given either as schema + short name or as the single schema.table form the read endpoints return. A short name with no schema, where a table of that name exists under some schema, is reported as the schema being absent rather than the table being wrong |
DF_API.MISSING_REQUIRED_FIELD on …schema |
column |
Optional; when given, must exist in that table | DF_API.INVALID_SOURCE_COLUMN |
foreign_key.connection / primary_key.connection |
Must be the connection the source fact table / target dimension group already uses | DF_API.CONNECTION_MISMATCH |
foreign_key vs primary_key |
Both keys of one relationship must name the same connection — a join cannot span two databases. Checked against the stored value of whichever key a PATCH does not send |
DF_API.CONNECTION_MISMATCH |
Three deliberate exemptions:
schemais validated for PostgreSQL only. There the schema is a property of the connection, so a value that disagrees with it asks for something the API cannot store — selecting another schema means naming another connection. MS SQL Server keeps its schema insidetable(dbo.orders), where the table check already covers it, and ClickHouse has no schemas, so for both the field is ignored. An emptyschemaasserts nothing and is treated as not supplied.table/columnare checked against the cached schema snapshot, the same list the product's source picker is built from. When the snapshot has never been refreshed or cannot be read, the check is skipped rather than failed — an unreadable snapshot is not evidence that the table is wrong.CONNECTION_MISMATCHis only raised when the other side's connection is known. A fact table created through the API has no base table of its own, and a dimension group has no primary key until the first relationship sets one; in both cases there is nothing to compare against and the request is accepted.
Errors shared by every write endpoint. Beyond the common errors of section 4 of the API page: 403 DF_API.WRITE_ACCESS_DENIED (role below developer), 400 DF_API.VALIDATION_FAILED (body does not match the schema), 400 DF_API.INVALID_IDEMPOTENCY_KEY and 409 DF_API.IDEMPOTENCY_IN_PROGRESS (POST with Idempotency-Key). The per-endpoint "Errors" blocks of section 2 list only what is specific to the endpoint.
2 Write API — endpoints
Every endpoint of this section follows the conventions of section 1 (developer role, idempotency, strict bodies, timestamp in every create/update response, shared error codes). {v} abbreviates /df-api/v2/projects/{project_id}/versions/{version_id}.
Index:
| Group | Endpoints | Sections |
|---|---|---|
| Projects and versions | POST /projects; PATCH, DELETE /projects/{project_id}; POST /projects/{project_id}/versions; PATCH, DELETE …/versions/{version_id} |
2.1–2.6 |
| Measures | POST {v}/measures, POST {v}/measures/bulk, PUT, PATCH, DELETE {v}/measures/{measure_id} |
2.7–2.11 |
| Dimensions | POST {v}/dimensions, POST {v}/dimensions/bulk, PUT, PATCH, DELETE {v}/dimensions/{dimension_id} |
2.12–2.16 |
| Facts | POST {v}/facts, POST {v}/facts/bulk, PUT, PATCH, DELETE {v}/facts/{fact_id} |
2.17–2.21 |
| Dimension groups | POST {v}/dimension-groups; PUT, PATCH, DELETE …/{dimension_group_id}; POST …/dimensions; PATCH, DELETE …/dimensions/{dimension_id} |
2.22–2.28 |
| Fact tables | POST {v}/fact-tables; PUT, PATCH, DELETE …/{fact_table_id}; membership POST / DELETE for measures, dimensions, facts, dimension-groups; per-fact-table verification-filters POST, PUT, PATCH, DELETE |
2.29–2.44 |
| Verification filters (version scope) | POST {v}/verification-filters; PUT, PATCH, DELETE …/{filter_id} |
2.45–2.48 |
| Relationships | POST {v}/relationships; PUT, PATCH, DELETE …/{relationship_id} |
2.49–2.52 |
| Project access | POST /projects/{projectId}/access, DELETE …/access/{userId}, PUT /projects/{projectId}/owner |
3 |
| Version transfer | POST {v}/export/git, export/file, import/validate, import/preview, import/git, import/file |
4 |
| Git connections | POST /git-connections; PUT, DELETE /git-connections/{connection_id}; POST …/test |
5 |
2.1 Create project
Request
POST /df-api/v2/projects
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection (section 1) |
name |
body | string | yes | 1–128 characters; letters, digits, _, ., -, space; must start with a letter or digit |
description |
body | string | no | Up to 255 characters |
color |
body | string | no | HEX colour, e.g. #FF8000 |
{ "name": "Sales Analytics", "description": "Production sales warehouse", "color": "#2479BC" }
Details
The key owner must belong to a company (otherwise 403 DF_API.WRITE_ACCESS_DENIED); the project is created in that company with the key owner as its owner and an initial version named Version 1. The name must be unique among the company's projects.
Response
201 Created
{
"id": "12",
"name": "Sales Analytics",
"description": "Production sales warehouse",
"color": "#2479BC",
"created_at": "2026-05-05T08:30:00.000Z",
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Project identifier |
name, description |
string / string | null | As stored |
color |
string | null | HEX colour |
created_at |
string | null | Creation timestamp |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DUPLICATE_NAME |
409 | A project with this name already exists |
DF_API.WRITE_ACCESS_DENIED |
403 | The key owner has no company, or the license project limit is reached (the internal LICENSE.CREATE_PROJECT_ACCESS_DENIED has no dedicated public code and degrades to the generic 403) |
2.2 Update project
Request
PATCH /df-api/v2/projects/{project_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | integer | yes | Project identifier |
name |
body | string | no | Same rules as on create |
description |
body | string | no | Up to 255 characters |
color |
body | string | no | HEX colour |
Details
Partial update: only the supplied fields change. Requires developer or higher on the project.
Response
200 OK
{
"id": "12",
"name": "Sales Analytics",
"description": "Production sales warehouse",
"color": "#2479BC",
"created_at": "2026-05-05T08:30:00.000Z",
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Project identifier |
name, description |
string / string | null | As stored |
color |
string | null | HEX colour |
created_at |
string | null | Creation timestamp |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DUPLICATE_NAME |
409 | Another project already has the new name |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
2.3 Delete project
Request
DELETE /df-api/v2/projects/{project_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | integer | yes | Project identifier |
Details
Deletes the project with all its versions and contents. The before snapshot of the project is written to the audit log (section 7 of the API page).
Response
204 No Content
Errors. Shared write errors only (section 1).
2.4 Create version
Request
POST /df-api/v2/projects/{project_id}/versions
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
project_id |
path | integer | yes | Project identifier |
Idempotency-Key |
header | UUID v4 | no | Replay protection |
name |
body | string | yes | 1–64 characters; letters, digits, _, ., -, space |
is_global |
body | boolean | no | Make the new version the project's published (global) version |
clone_from_version |
body | string | no | Identifier of a version to copy the content from. When omitted, the content is copied from the current global version |
{ "name": "Q1 2027", "is_global": false, "clone_from_version": "33" }
Details
A version has no description field. Creating a version counts against the license version limit.
Response
201 Created
{ "id": "34", "name": "Q1 2027", "is_global": false, "timestamp": "2026-05-05T08:30:00.120Z" }
| Field | Type | Description |
|---|---|---|
id |
string | Version identifier |
name |
string | null | Version name |
is_global |
boolean | Whether this is the project's global version |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DUPLICATE_NAME |
409 | A live version with this name exists in the project |
DF_API.VERSION_NOT_FOUND |
404 | clone_from_version does not exist in the project |
DF_API.LICENSE_LIMIT_REACHED |
403 | The license version limit is reached |
DF_API.GLOBAL_VERSION_CONFLICT |
422 | is_global cannot be set |
2.5 Update version
Request
PATCH /df-api/v2/projects/{project_id}/versions/{version_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
project_id, version_id |
path | integer | yes | Identifiers |
name |
body | string | no | Same rules as on create |
is_global |
body | boolean | no | true makes this version the global one (the previous global version loses the flag) |
Response
200 OK
{ "id": "34", "name": "Q1 2027", "is_global": false, "timestamp": "2026-05-05T08:30:00.120Z" }
| Field | Type | Description |
|---|---|---|
id |
string | Version identifier |
name |
string | null | Version name |
is_global |
boolean | Whether this is the project's global version |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DUPLICATE_NAME |
409 | Another live version already has the new name |
DF_API.GLOBAL_VERSION_CONFLICT |
422 | is_global cannot be set — it conflicts with another global version |
2.6 Delete version
Request
DELETE /df-api/v2/projects/{project_id}/versions/{version_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
project_id, version_id |
path | integer | yes | Identifiers |
Details
Deletes the version and all its contents (soft delete, subject to the retention cleanup). The current global version cannot be deleted.
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.GLOBAL_VERSION_CONFLICT |
422 | The version is the project's global version |
2.7 Create measure
Request
POST {v}/measures
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection |
measure_name |
body | string | yes | Measure name; unique within the version's measures |
measure_type |
body | string | yes | Label of the "Measure type" select: Base / Calculated (or the Russian label) |
group, block |
body | string | no | RMD grouping columns |
measure_description |
body | string | no | Up to 255 characters |
original_source_type, original_source, original_object |
body | string | no | Provenance columns (free text) |
display_data_type |
body | string | no | Label of the "Display data type" select (Number, Text, Date, …) |
restrictions |
body | string | no | Restrictions column |
formula |
body | string | no | Formula of a calculated measure; references as [Measure name] |
report_for_verification, comment, responsible_for_data, variation |
body | string | no | Free-text columns |
status |
body | string | no | Label of the "Status" select |
relevance, required, visibility |
body | "true" | "false" |
no | Flag columns |
{ "measure_name": "Total revenue", "measure_type": "base", "group": "Revenue", "display_data_type": "Number" }
Details
The formula is parsed and its references resolved to existing measures of the version (section 1). The response mirrors the row of GET {v}/measures without row_number: id first, then every writable column (unset ones as null) and timestamp last; select labels are echoed in the language of the request.
Response
201 Created
{
"id": "1000",
"group": "Revenue",
"block": null,
"measure_name": "Total revenue",
"measure_description": null,
"original_source_type": null,
"original_source": null,
"original_object": null,
"display_data_type": "Number",
"measure_type": "Base",
"restrictions": null,
"formula": null,
"report_for_verification": null,
"comment": null,
"status": null,
"relevance": null,
"required": null,
"visibility": null,
"responsible_for_data": null,
"variation": null,
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Identifier of the new measure — the {measure_id} of the other endpoints |
group, block |
string | null | Grouping columns of the RMD |
measure_name |
string | Measure name |
measure_description |
string | null | Description |
original_source_type, original_source, original_object |
string | null | Provenance columns; free text |
display_data_type |
string | null | Localized data type label (Number, Text, Date, …) |
measure_type |
string | Localized label: Base or Calculated |
restrictions |
string | null | Restrictions column |
formula |
string | null | Formula of a calculated measure with references rendered as [Name] |
report_for_verification, comment, responsible_for_data, variation |
string | null | Free-text columns |
status |
string | null | Localized label of the "Status" select |
relevance, required, visibility |
boolean | null | Flag columns; booleans in write responses, null when unset |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DUPLICATE_NAME |
409 | A measure with this name exists in the version |
DF_API.INVALID_ENUM_VALUE |
400 | measure_type matches no option |
DF_API.INVALID_FORMULA_SYNTAX |
422 | The formula failed to parse |
DF_API.FORMULA_REFERENCE_NOT_FOUND |
422 | The formula references an unknown element |
DF_API.CIRCULAR_DEPENDENCY |
422 | The formula introduces a cycle |
DF_API.INVALID_PARAMETER_TYPE |
422 | A dimension passed to a period function is not bound to a date/time column |
2.8 Bulk create/update measures
Request
POST {v}/measures/bulk
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection |
measures |
body | array | yes | At least one item |
measures[].id |
body | string | no | Present — the existing measure is updated (partial, as PATCH); absent — a measure is created |
measures[].measure_name |
body | string | no | Measure name; unique within the version's measures |
measures[].measure_type |
body | string | no | Label of the "Measure type" select: Base / Calculated (or the Russian label) |
measures[].group, measures[].block |
body | string | no | RMD grouping columns |
measures[].measure_description |
body | string | no | Up to 255 characters |
measures[].original_source_type, measures[].original_source, measures[].original_object |
body | string | no | Provenance columns (free text) |
measures[].display_data_type |
body | string | no | Label of the "Display data type" select (Number, Text, Date, …) |
measures[].restrictions |
body | string | no | Restrictions column |
measures[].formula |
body | string | no | Formula of a calculated measure; references as [Measure name] |
measures[].report_for_verification, measures[].comment, measures[].responsible_for_data, measures[].variation |
body | string | no | Free-text columns |
measures[].status |
body | string | no | Label of the "Status" select |
measures[].relevance, measures[].required, measures[].visibility |
body | "true" | "false" |
no | Flag columns |
{
"measures": [
{ "measure_name": "Total revenue", "measure_type": "base", "group": "Revenue" },
{ "id": "1000", "measure_description": "Gross revenue across all channels" }
]
}
Details
Items are processed in order, each in its own transaction: a failing item leaves nothing behind and does not stop the others. failed[].index is the item's position in the request. One consolidated audit entry is written for the request.
Response
201 Created when every item was applied; 207 Multi-Status when some items failed:
{
"timestamp": "2026-05-05T08:30:00.120Z",
"succeeded": [
{ "id": "1000", "measure_name": "Total revenue", "measure_type": "Base", "…": "…" }
],
"failed": [
{
"index": 1,
"error": { "code": "duplicate_name", "message": "Measure name already exists", "details": [{ "field": "measure_name", "code": "invalid_value" }] }
}
]
}
| Field | Type | Description |
|---|---|---|
succeeded[] |
array | Measure objects (the object of a single create response, without timestamp) |
failed[].index |
integer | Position of the rejected item in measures |
failed[].error |
object | code, message, details of section 4 of the Public API v2 page |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.VALIDATION_FAILED |
400 | measures is empty or an item is malformed |
DF_API.BULK_REJECTED |
422 | No item was applied; details[] addresses every item (measures.<index>) |
| Per-item codes | in failed[] |
duplicate_name, missing_required_field, invalid_enum_value, resource_not_found (unknown id), invalid_formula_syntax, formula_reference_not_found, circular_dependency, invalid_parameter_type |
2.9 Replace measure
Request
PUT {v}/measures/{measure_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
measure_id |
path | integer | yes | Measure identifier |
measure_name |
body | string | yes | Measure name; unique within the version's measures |
measure_type |
body | string | yes | Label of the "Measure type" select: Base / Calculated (or the Russian label) |
group, block |
body | string | no | RMD grouping columns |
measure_description |
body | string | no | Up to 255 characters |
original_source_type, original_source, original_object |
body | string | no | Provenance columns (free text) |
display_data_type |
body | string | no | Label of the "Display data type" select (Number, Text, Date, …) |
restrictions |
body | string | no | Restrictions column |
formula |
body | string | no | Formula of a calculated measure; references as [Measure name] |
report_for_verification, comment, responsible_for_data, variation |
body | string | no | Free-text columns |
status |
body | string | no | Label of the "Status" select |
relevance, required, visibility |
body | "true" | "false" |
no | Flag columns |
Details
measure_name and measure_type are required; every omitted optional column is reset to empty.
Response
200 OK
{
"id": "1000",
"group": "Revenue",
"block": null,
"measure_name": "Total revenue",
"measure_description": null,
"original_source_type": null,
"original_source": null,
"original_object": null,
"display_data_type": "Number",
"measure_type": "Base",
"restrictions": null,
"formula": null,
"report_for_verification": null,
"comment": null,
"status": null,
"relevance": null,
"required": null,
"visibility": null,
"responsible_for_data": null,
"variation": null,
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Identifier of the new measure — the {measure_id} of the other endpoints |
group, block |
string | null | Grouping columns of the RMD |
measure_name |
string | Measure name |
measure_description |
string | null | Description |
original_source_type, original_source, original_object |
string | null | Provenance columns; free text |
display_data_type |
string | null | Localized data type label (Number, Text, Date, …) |
measure_type |
string | Localized label: Base or Calculated |
restrictions |
string | null | Restrictions column |
formula |
string | null | Formula of a calculated measure with references rendered as [Name] |
report_for_verification, comment, responsible_for_data, variation |
string | null | Free-text columns |
status |
string | null | Localized label of the "Status" select |
relevance, required, visibility |
boolean | null | Flag columns; booleans in write responses, null when unset |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The measure does not exist in the version |
DF_API.MISSING_REQUIRED_FIELD |
400 | measure_name or measure_type missing |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
DF_API.DUPLICATE_NAME |
409 | A measure with this name exists in the version |
DF_API.INVALID_ENUM_VALUE |
400 | measure_type matches no option |
DF_API.INVALID_FORMULA_SYNTAX |
422 | The formula failed to parse |
DF_API.FORMULA_REFERENCE_NOT_FOUND |
422 | The formula references an unknown element |
DF_API.CIRCULAR_DEPENDENCY |
422 | The formula introduces a cycle |
DF_API.INVALID_PARAMETER_TYPE |
422 | A dimension passed to a period function is not bound to a date/time column |
2.10 Update measure
Request
PATCH {v}/measures/{measure_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
measure_id |
path | integer | yes | Measure identifier |
measure_name |
body | string | no | Measure name; unique within the version's measures |
measure_type |
body | string | no | Label of the "Measure type" select: Base / Calculated (or the Russian label) |
group, block |
body | string | no | RMD grouping columns |
measure_description |
body | string | no | Up to 255 characters |
original_source_type, original_source, original_object |
body | string | no | Provenance columns (free text) |
display_data_type |
body | string | no | Label of the "Display data type" select (Number, Text, Date, …) |
restrictions |
body | string | no | Restrictions column |
formula |
body | string | no | Formula of a calculated measure; references as [Measure name] |
report_for_verification, comment, responsible_for_data, variation |
body | string | no | Free-text columns |
status |
body | string | no | Label of the "Status" select |
relevance, required, visibility |
body | "true" | "false" |
no | Flag columns |
Details
Only the supplied fields change.
Response
200 OK
{
"id": "1000",
"group": "Revenue",
"block": null,
"measure_name": "Total revenue",
"measure_description": null,
"original_source_type": null,
"original_source": null,
"original_object": null,
"display_data_type": "Number",
"measure_type": "Base",
"restrictions": null,
"formula": null,
"report_for_verification": null,
"comment": null,
"status": null,
"relevance": null,
"required": null,
"visibility": null,
"responsible_for_data": null,
"variation": null,
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Identifier of the new measure — the {measure_id} of the other endpoints |
group, block |
string | null | Grouping columns of the RMD |
measure_name |
string | Measure name |
measure_description |
string | null | Description |
original_source_type, original_source, original_object |
string | null | Provenance columns; free text |
display_data_type |
string | null | Localized data type label (Number, Text, Date, …) |
measure_type |
string | Localized label: Base or Calculated |
restrictions |
string | null | Restrictions column |
formula |
string | null | Formula of a calculated measure with references rendered as [Name] |
report_for_verification, comment, responsible_for_data, variation |
string | null | Free-text columns |
status |
string | null | Localized label of the "Status" select |
relevance, required, visibility |
boolean | null | Flag columns; booleans in write responses, null when unset |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The measure does not exist in the version |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
DF_API.DUPLICATE_NAME |
409 | A measure with this name exists in the version |
DF_API.INVALID_ENUM_VALUE |
400 | measure_type matches no option |
DF_API.INVALID_FORMULA_SYNTAX |
422 | The formula failed to parse |
DF_API.FORMULA_REFERENCE_NOT_FOUND |
422 | The formula references an unknown element |
DF_API.CIRCULAR_DEPENDENCY |
422 | The formula introduces a cycle |
DF_API.INVALID_PARAMETER_TYPE |
422 | A dimension passed to a period function is not bound to a date/time column |
2.11 Delete measure
Request
DELETE {v}/measures/{measure_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
measure_id |
path | integer | yes | Measure identifier |
Details
A measure referenced by another element's formula cannot be deleted. A measure merely assigned to a fact table is deleted together with the assignment.
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The measure does not exist in the version |
DF_API.CONSTRAINT_VIOLATION |
409 | The measure is referenced by a formula |
2.12 Create dimension
Request
POST {v}/dimensions
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection |
dimension_name |
body | string | yes | Dimension name; unique within the version's dimensions |
dimension_type |
body | string | yes | Label of the "Dimension type" select: Primary / Derived |
group, block |
body | string | no | RMD grouping columns |
dimension_description |
body | string | no | Up to 255 characters |
original_source_type, original_source, original_object |
body | string | no | Provenance columns |
dimension_group |
body | string | no | Name of an existing dimension group; the dimension becomes its member |
display_data_type |
body | string | no | Label of the "Display data type" select |
source_data_type |
body | string | no | Accepted and ignored — derived from connected_source |
formula |
body | string | no | Formula of a derived dimension |
connected_source |
body | object | no | Source object (section 3.6 of the API page): connection (optional), db (required), schema, table (required), column |
comment, value_options, responsible_for_data |
body | string | no | Free-text columns |
status |
body | string | no | Label of the "Status" select |
relevance, required, visibility |
body | "true" | "false" |
no | Flag columns |
{
"dimension_name": "Region",
"dimension_type": "primary",
"dimension_group": "Geography",
"display_data_type": "Text",
"connected_source": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_name" }
}
Details
connected_source is validated according to section 1 when connection is supplied. Naming a dimension_group adds the dimension to that group.
Response
201 Created
{
"id": "2000",
"group": "Customer",
"block": "Profile",
"dimension_name": "Customer name",
"dimension_description": "Full customer name",
"original_source_type": null,
"original_source": null,
"original_object": null,
"dimension_group": "Customers",
"display_data_type": "Text",
"source_data_type": "VARCHAR(255)",
"dimension_type": "Primary",
"formula": null,
"connected_source": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_customer", "column": "customer_name" },
"comment": null,
"value_options": null,
"status": "Active",
"relevance": null,
"required": null,
"visibility": null,
"responsible_for_data": null,
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Identifier of the new dimension — the {dimension_id} of the other endpoints |
group, block |
string | null | Grouping columns of the RMD |
dimension_name, dimension_description |
string / string | null | Name and description |
original_source_type, original_source, original_object |
string | null | Provenance columns; free text |
dimension_group |
string | null | Name of the dimension group the dimension belongs to |
display_data_type |
string | null | Localized data type label |
source_data_type |
string | null | Physical column type resolved from the connection's cached schema |
dimension_type |
string | Localized label: Primary or Derived |
formula |
string | null | Formula of a derived dimension |
connected_source |
object | null | Source object { db, schema, table, column } (section 3.6 of the API page) |
comment, responsible_for_data |
string | null | Free-text columns |
value_options |
string | null | Allowed values column |
status |
string | null | Localized label of the "Status" select |
relevance, required, visibility |
boolean | null | Flag columns; booleans in write responses, null when unset |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DUPLICATE_NAME |
409 | A dimension with this name exists in the version |
DF_API.INVALID_ENUM_VALUE |
400 | dimension_type matches no option |
DF_API.INVALID_SOURCE_*, DF_API.MISSING_REQUIRED_FIELD |
400 | connected_source validation (section 1) |
DF_API.INVALID_FORMULA_SYNTAX |
422 | The formula failed to parse |
DF_API.FORMULA_REFERENCE_NOT_FOUND |
422 | The formula references an unknown element |
DF_API.CIRCULAR_DEPENDENCY |
422 | The formula introduces a cycle |
DF_API.INVALID_PARAMETER_TYPE |
422 | A dimension passed to a period function is not bound to a date/time column |
2.13 Bulk create/update dimensions
Request
POST {v}/dimensions/bulk
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection |
dimensions |
body | array | yes | At least one item |
dimensions[].dimension_name |
body | string | no | Dimension name; unique within the version's dimensions |
dimensions[].dimension_type |
body | string | no | Label of the "Dimension type" select: Primary / Derived |
dimensions[].group, dimensions[].block |
body | string | no | RMD grouping columns |
dimensions[].dimension_description |
body | string | no | Up to 255 characters |
dimensions[].original_source_type, dimensions[].original_source, dimensions[].original_object |
body | string | no | Provenance columns |
dimensions[].dimension_group |
body | string | no | Name of an existing dimension group; the dimension becomes its member |
dimensions[].display_data_type |
body | string | no | Label of the "Display data type" select |
dimensions[].source_data_type |
body | string | no | Accepted and ignored — derived from connected_source |
dimensions[].formula |
body | string | no | Formula of a derived dimension |
dimensions[].connected_source |
body | object | no | Source object (section 3.6 of the API page): connection (optional), db (required), schema, table (required), column |
dimensions[].comment, dimensions[].value_options, dimensions[].responsible_for_data |
body | string | no | Free-text columns |
dimensions[].status |
body | string | no | Label of the "Status" select |
dimensions[].relevance, dimensions[].required, dimensions[].visibility |
body | "true" | "false" |
no | Flag columns |
{
"dimensions": [
{ "dimension_name": "Region", "dimension_type": "primary", "dimension_group": "Geography" },
{ "id": "2000", "dimension_description": "Sales region" }
]
}
Details
Items are processed in order, each in its own transaction: a failing item leaves nothing behind and does not stop the others. failed[].index is the item's position in the request. One consolidated audit entry is written for the request.
Response
201 Created when every item was applied; 207 Multi-Status when some items failed:
{
"timestamp": "2026-05-05T08:30:00.120Z",
"succeeded": [
{ "id": "2000", "dimension_name": "Region", "dimension_type": "Primary", "…": "…" }
],
"failed": [
{
"index": 1,
"error": { "code": "duplicate_name", "message": "Dimension name already exists", "details": [{ "field": "dimension_name", "code": "invalid_value" }] }
}
]
}
| Field | Type | Description |
|---|---|---|
succeeded[] |
array | Dimension objects (the object of a single create response, without timestamp) |
failed[].index |
integer | Position of the rejected item in dimensions |
failed[].error |
object | code, message, details of section 4 of the Public API v2 page |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.VALIDATION_FAILED |
400 | dimensions is empty or an item is malformed |
DF_API.BULK_REJECTED |
422 | No item was applied; details[] addresses every item (dimensions.<index>) |
| Per-item codes | in failed[] |
duplicate_name, missing_required_field, invalid_enum_value, resource_not_found (unknown id), invalid_formula_syntax, formula_reference_not_found, circular_dependency, invalid_parameter_type |
DF_API.INVALID_SOURCE_*, DF_API.MISSING_REQUIRED_FIELD |
in failed[] |
connected_source validation of an item (section 1) |
2.14 Replace dimension
Request
PUT {v}/dimensions/{dimension_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
dimension_id |
path | integer | yes | Dimension identifier |
dimension_name |
body | string | yes | Dimension name; unique within the version's dimensions |
dimension_type |
body | string | yes | Label of the "Dimension type" select: Primary / Derived |
group, block |
body | string | no | RMD grouping columns |
dimension_description |
body | string | no | Up to 255 characters |
original_source_type, original_source, original_object |
body | string | no | Provenance columns |
dimension_group |
body | string | no | Name of an existing dimension group; the dimension becomes its member |
display_data_type |
body | string | no | Label of the "Display data type" select |
source_data_type |
body | string | no | Accepted and ignored — derived from connected_source |
formula |
body | string | no | Formula of a derived dimension |
connected_source |
body | object | no | Source object (section 3.6 of the API page): connection (optional), db (required), schema, table (required), column |
comment, value_options, responsible_for_data |
body | string | no | Free-text columns |
status |
body | string | no | Label of the "Status" select |
relevance, required, visibility |
body | "true" | "false" |
no | Flag columns |
Details
dimension_name and dimension_type are required; every omitted optional column is reset to empty. An omitted dimension_group removes the dimension from its group.
Response
200 OK
{
"id": "2000",
"group": "Customer",
"block": "Profile",
"dimension_name": "Customer name",
"dimension_description": "Full customer name",
"original_source_type": null,
"original_source": null,
"original_object": null,
"dimension_group": "Customers",
"display_data_type": "Text",
"source_data_type": "VARCHAR(255)",
"dimension_type": "Primary",
"formula": null,
"connected_source": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_customer", "column": "customer_name" },
"comment": null,
"value_options": null,
"status": "Active",
"relevance": null,
"required": null,
"visibility": null,
"responsible_for_data": null,
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Identifier of the new dimension — the {dimension_id} of the other endpoints |
group, block |
string | null | Grouping columns of the RMD |
dimension_name, dimension_description |
string / string | null | Name and description |
original_source_type, original_source, original_object |
string | null | Provenance columns; free text |
dimension_group |
string | null | Name of the dimension group the dimension belongs to |
display_data_type |
string | null | Localized data type label |
source_data_type |
string | null | Physical column type resolved from the connection's cached schema |
dimension_type |
string | Localized label: Primary or Derived |
formula |
string | null | Formula of a derived dimension |
connected_source |
object | null | Source object { db, schema, table, column } (section 3.6 of the API page) |
comment, responsible_for_data |
string | null | Free-text columns |
value_options |
string | null | Allowed values column |
status |
string | null | Localized label of the "Status" select |
relevance, required, visibility |
boolean | null | Flag columns; booleans in write responses, null when unset |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The dimension does not exist in the version |
DF_API.MISSING_REQUIRED_FIELD |
400 | dimension_name or dimension_type missing |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
DF_API.DUPLICATE_NAME |
409 | A dimension with this name exists in the version |
DF_API.INVALID_ENUM_VALUE |
400 | dimension_type matches no option |
DF_API.INVALID_SOURCE_*, DF_API.MISSING_REQUIRED_FIELD |
400 | connected_source validation (section 1) |
DF_API.INVALID_FORMULA_SYNTAX |
422 | The formula failed to parse |
DF_API.FORMULA_REFERENCE_NOT_FOUND |
422 | The formula references an unknown element |
DF_API.CIRCULAR_DEPENDENCY |
422 | The formula introduces a cycle |
DF_API.INVALID_PARAMETER_TYPE |
422 | A dimension passed to a period function is not bound to a date/time column |
2.15 Update dimension
Request
PATCH {v}/dimensions/{dimension_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
dimension_id |
path | integer | yes | Dimension identifier |
dimension_name |
body | string | no | Dimension name; unique within the version's dimensions |
dimension_type |
body | string | no | Label of the "Dimension type" select: Primary / Derived |
group, block |
body | string | no | RMD grouping columns |
dimension_description |
body | string | no | Up to 255 characters |
original_source_type, original_source, original_object |
body | string | no | Provenance columns |
dimension_group |
body | string | no | Name of an existing dimension group; the dimension becomes its member |
display_data_type |
body | string | no | Label of the "Display data type" select |
source_data_type |
body | string | no | Accepted and ignored — derived from connected_source |
formula |
body | string | no | Formula of a derived dimension |
connected_source |
body | object | no | Source object (section 3.6 of the API page): connection (optional), db (required), schema, table (required), column |
comment, value_options, responsible_for_data |
body | string | no | Free-text columns |
status |
body | string | no | Label of the "Status" select |
relevance, required, visibility |
body | "true" | "false" |
no | Flag columns |
Details
Only the supplied fields change.
Response
200 OK
{
"id": "2000",
"group": "Customer",
"block": "Profile",
"dimension_name": "Customer name",
"dimension_description": "Full customer name",
"original_source_type": null,
"original_source": null,
"original_object": null,
"dimension_group": "Customers",
"display_data_type": "Text",
"source_data_type": "VARCHAR(255)",
"dimension_type": "Primary",
"formula": null,
"connected_source": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_customer", "column": "customer_name" },
"comment": null,
"value_options": null,
"status": "Active",
"relevance": null,
"required": null,
"visibility": null,
"responsible_for_data": null,
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Identifier of the new dimension — the {dimension_id} of the other endpoints |
group, block |
string | null | Grouping columns of the RMD |
dimension_name, dimension_description |
string / string | null | Name and description |
original_source_type, original_source, original_object |
string | null | Provenance columns; free text |
dimension_group |
string | null | Name of the dimension group the dimension belongs to |
display_data_type |
string | null | Localized data type label |
source_data_type |
string | null | Physical column type resolved from the connection's cached schema |
dimension_type |
string | Localized label: Primary or Derived |
formula |
string | null | Formula of a derived dimension |
connected_source |
object | null | Source object { db, schema, table, column } (section 3.6 of the API page) |
comment, responsible_for_data |
string | null | Free-text columns |
value_options |
string | null | Allowed values column |
status |
string | null | Localized label of the "Status" select |
relevance, required, visibility |
boolean | null | Flag columns; booleans in write responses, null when unset |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The dimension does not exist in the version |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
DF_API.DUPLICATE_NAME |
409 | A dimension with this name exists in the version |
DF_API.INVALID_ENUM_VALUE |
400 | dimension_type matches no option |
DF_API.INVALID_SOURCE_*, DF_API.MISSING_REQUIRED_FIELD |
400 | connected_source validation (section 1) |
DF_API.INVALID_FORMULA_SYNTAX |
422 | The formula failed to parse |
DF_API.FORMULA_REFERENCE_NOT_FOUND |
422 | The formula references an unknown element |
DF_API.CIRCULAR_DEPENDENCY |
422 | The formula introduces a cycle |
DF_API.INVALID_PARAMETER_TYPE |
422 | A dimension passed to a period function is not bound to a date/time column |
2.16 Delete dimension
Request
DELETE {v}/dimensions/{dimension_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
dimension_id |
path | integer | yes | Dimension identifier |
Details
A dimension that belongs to a dimension group, or is referenced by a formula, cannot be deleted — remove it from the group first (DELETE {v}/dimension-groups/{dimension_group_id}/dimensions/{dimension_id}).
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The dimension does not exist in the version |
DF_API.CONSTRAINT_VIOLATION |
409 | The dimension is a member of a dimension group or is referenced by a formula |
2.17 Create fact
Request
POST {v}/facts
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection |
fact_name |
body | string | yes | Fact name; unique within the version's facts |
fact_type |
body | string | yes | Label of the "Fact type" select: Primary / Derived |
group, block |
body | string | no | RMD grouping columns |
fact_description |
body | string | no | Up to 255 characters |
original_source_type, original_source, original_object |
body | string | no | Provenance columns |
source_data_type |
body | string | no | Accepted and ignored — derived from connected_source |
formula |
body | string | no | Formula of a derived fact |
connected_source |
body | object | no | Source object (section 3.6 of the API page) |
report_for_verification, comment, responsible_for_data |
body | string | no | Free-text columns |
status |
body | string | no | Label of the "Status" select |
relevance, required, visibility |
body | "true" | "false" |
no | Flag columns |
{
"fact_name": "Order line",
"fact_type": "primary",
"connected_source": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_order_line", "column": "amount" }
}
Response
201 Created
{
"id": "3000",
"group": "Sales",
"block": "Orders",
"fact_name": "Order line",
"fact_description": "An individual line item on a sales order",
"original_source_type": null,
"original_source": null,
"original_object": null,
"source_data_type": "DECIMAL(18,2)",
"fact_type": "Primary",
"formula": null,
"connected_source": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_order_line", "column": "amount" },
"report_for_verification": null,
"comment": null,
"status": null,
"relevance": null,
"required": null,
"visibility": null,
"responsible_for_data": null,
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Identifier of the new fact — the {fact_id} of the other endpoints |
group, block |
string | null | Grouping columns of the RMD |
fact_name, fact_description |
string / string | null | Name and description |
original_source_type, original_source, original_object |
string | null | Provenance columns; free text |
source_data_type |
string | null | Physical column type resolved from the connection's cached schema |
fact_type |
string | Localized label: Primary or Derived |
formula |
string | null | Formula of a derived fact |
connected_source |
object | null | Source object { db, schema, table, column } (section 3.6 of the API page) |
report_for_verification, comment, responsible_for_data |
string | null | Free-text columns |
status |
string | null | Localized label of the "Status" select |
relevance, required, visibility |
boolean | null | Flag columns; booleans in write responses, null when unset |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DUPLICATE_NAME |
409 | A fact with this name exists in the version |
DF_API.INVALID_ENUM_VALUE |
400 | fact_type matches no option |
DF_API.INVALID_SOURCE_*, DF_API.MISSING_REQUIRED_FIELD |
400 | connected_source validation (section 1) |
DF_API.INVALID_FORMULA_SYNTAX |
422 | The formula failed to parse |
DF_API.FORMULA_REFERENCE_NOT_FOUND |
422 | The formula references an unknown element |
DF_API.CIRCULAR_DEPENDENCY |
422 | The formula introduces a cycle |
DF_API.INVALID_PARAMETER_TYPE |
422 | A dimension passed to a period function is not bound to a date/time column |
2.18 Bulk create/update facts
Request
POST {v}/facts/bulk
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection |
facts |
body | array | yes | At least one item |
facts[].fact_name |
body | string | no | Fact name; unique within the version's facts |
facts[].fact_type |
body | string | no | Label of the "Fact type" select: Primary / Derived |
facts[].group, facts[].block |
body | string | no | RMD grouping columns |
facts[].fact_description |
body | string | no | Up to 255 characters |
facts[].original_source_type, facts[].original_source, facts[].original_object |
body | string | no | Provenance columns |
facts[].source_data_type |
body | string | no | Accepted and ignored — derived from connected_source |
facts[].formula |
body | string | no | Formula of a derived fact |
facts[].connected_source |
body | object | no | Source object (section 3.6 of the API page) |
facts[].report_for_verification, facts[].comment, facts[].responsible_for_data |
body | string | no | Free-text columns |
facts[].status |
body | string | no | Label of the "Status" select |
facts[].relevance, facts[].required, facts[].visibility |
body | "true" | "false" |
no | Flag columns |
{
"facts": [
{ "fact_name": "Order line", "fact_type": "primary" },
{ "id": "3000", "fact_description": "An individual line item on a sales order" }
]
}
Details
Items are processed in order, each in its own transaction: a failing item leaves nothing behind and does not stop the others. failed[].index is the item's position in the request. One consolidated audit entry is written for the request.
Response
201 Created when every item was applied; 207 Multi-Status when some items failed:
{
"timestamp": "2026-05-05T08:30:00.120Z",
"succeeded": [
{ "id": "3000", "fact_name": "Order line", "fact_type": "Primary", "…": "…" }
],
"failed": [
{
"index": 1,
"error": { "code": "duplicate_name", "message": "Fact name already exists", "details": [{ "field": "fact_name", "code": "invalid_value" }] }
}
]
}
| Field | Type | Description |
|---|---|---|
succeeded[] |
array | Fact objects (the object of a single create response, without timestamp) |
failed[].index |
integer | Position of the rejected item in facts |
failed[].error |
object | code, message, details of section 4 of the Public API v2 page |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.VALIDATION_FAILED |
400 | facts is empty or an item is malformed |
DF_API.BULK_REJECTED |
422 | No item was applied; details[] addresses every item (facts.<index>) |
| Per-item codes | in failed[] |
duplicate_name, missing_required_field, invalid_enum_value, resource_not_found (unknown id), invalid_formula_syntax, formula_reference_not_found, circular_dependency, invalid_parameter_type |
DF_API.INVALID_SOURCE_*, DF_API.MISSING_REQUIRED_FIELD |
in failed[] |
connected_source validation of an item (section 1) |
2.19 Replace fact
Request
PUT {v}/facts/{fact_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_id |
path | integer | yes | Fact identifier |
fact_name |
body | string | yes | Fact name; unique within the version's facts |
fact_type |
body | string | yes | Label of the "Fact type" select: Primary / Derived |
group, block |
body | string | no | RMD grouping columns |
fact_description |
body | string | no | Up to 255 characters |
original_source_type, original_source, original_object |
body | string | no | Provenance columns |
source_data_type |
body | string | no | Accepted and ignored — derived from connected_source |
formula |
body | string | no | Formula of a derived fact |
connected_source |
body | object | no | Source object (section 3.6 of the API page) |
report_for_verification, comment, responsible_for_data |
body | string | no | Free-text columns |
status |
body | string | no | Label of the "Status" select |
relevance, required, visibility |
body | "true" | "false" |
no | Flag columns |
Details
fact_name and fact_type are required; every omitted optional column is reset to empty.
Response
200 OK
{
"id": "3000",
"group": "Sales",
"block": "Orders",
"fact_name": "Order line",
"fact_description": "An individual line item on a sales order",
"original_source_type": null,
"original_source": null,
"original_object": null,
"source_data_type": "DECIMAL(18,2)",
"fact_type": "Primary",
"formula": null,
"connected_source": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_order_line", "column": "amount" },
"report_for_verification": null,
"comment": null,
"status": null,
"relevance": null,
"required": null,
"visibility": null,
"responsible_for_data": null,
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Identifier of the new fact — the {fact_id} of the other endpoints |
group, block |
string | null | Grouping columns of the RMD |
fact_name, fact_description |
string / string | null | Name and description |
original_source_type, original_source, original_object |
string | null | Provenance columns; free text |
source_data_type |
string | null | Physical column type resolved from the connection's cached schema |
fact_type |
string | Localized label: Primary or Derived |
formula |
string | null | Formula of a derived fact |
connected_source |
object | null | Source object { db, schema, table, column } (section 3.6 of the API page) |
report_for_verification, comment, responsible_for_data |
string | null | Free-text columns |
status |
string | null | Localized label of the "Status" select |
relevance, required, visibility |
boolean | null | Flag columns; booleans in write responses, null when unset |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The fact does not exist in the version |
DF_API.MISSING_REQUIRED_FIELD |
400 | fact_name or fact_type missing |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
DF_API.DUPLICATE_NAME |
409 | A fact with this name exists in the version |
DF_API.INVALID_ENUM_VALUE |
400 | fact_type matches no option |
DF_API.INVALID_SOURCE_*, DF_API.MISSING_REQUIRED_FIELD |
400 | connected_source validation (section 1) |
DF_API.INVALID_FORMULA_SYNTAX |
422 | The formula failed to parse |
DF_API.FORMULA_REFERENCE_NOT_FOUND |
422 | The formula references an unknown element |
DF_API.CIRCULAR_DEPENDENCY |
422 | The formula introduces a cycle |
DF_API.INVALID_PARAMETER_TYPE |
422 | A dimension passed to a period function is not bound to a date/time column |
2.20 Update fact
Request
PATCH {v}/facts/{fact_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_id |
path | integer | yes | Fact identifier |
fact_name |
body | string | no | Fact name; unique within the version's facts |
fact_type |
body | string | no | Label of the "Fact type" select: Primary / Derived |
group, block |
body | string | no | RMD grouping columns |
fact_description |
body | string | no | Up to 255 characters |
original_source_type, original_source, original_object |
body | string | no | Provenance columns |
source_data_type |
body | string | no | Accepted and ignored — derived from connected_source |
formula |
body | string | no | Formula of a derived fact |
connected_source |
body | object | no | Source object (section 3.6 of the API page) |
report_for_verification, comment, responsible_for_data |
body | string | no | Free-text columns |
status |
body | string | no | Label of the "Status" select |
relevance, required, visibility |
body | "true" | "false" |
no | Flag columns |
Details
Only the supplied fields change.
Response
200 OK
{
"id": "3000",
"group": "Sales",
"block": "Orders",
"fact_name": "Order line",
"fact_description": "An individual line item on a sales order",
"original_source_type": null,
"original_source": null,
"original_object": null,
"source_data_type": "DECIMAL(18,2)",
"fact_type": "Primary",
"formula": null,
"connected_source": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_order_line", "column": "amount" },
"report_for_verification": null,
"comment": null,
"status": null,
"relevance": null,
"required": null,
"visibility": null,
"responsible_for_data": null,
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Identifier of the new fact — the {fact_id} of the other endpoints |
group, block |
string | null | Grouping columns of the RMD |
fact_name, fact_description |
string / string | null | Name and description |
original_source_type, original_source, original_object |
string | null | Provenance columns; free text |
source_data_type |
string | null | Physical column type resolved from the connection's cached schema |
fact_type |
string | Localized label: Primary or Derived |
formula |
string | null | Formula of a derived fact |
connected_source |
object | null | Source object { db, schema, table, column } (section 3.6 of the API page) |
report_for_verification, comment, responsible_for_data |
string | null | Free-text columns |
status |
string | null | Localized label of the "Status" select |
relevance, required, visibility |
boolean | null | Flag columns; booleans in write responses, null when unset |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The fact does not exist in the version |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
DF_API.DUPLICATE_NAME |
409 | A fact with this name exists in the version |
DF_API.INVALID_ENUM_VALUE |
400 | fact_type matches no option |
DF_API.INVALID_SOURCE_*, DF_API.MISSING_REQUIRED_FIELD |
400 | connected_source validation (section 1) |
DF_API.INVALID_FORMULA_SYNTAX |
422 | The formula failed to parse |
DF_API.FORMULA_REFERENCE_NOT_FOUND |
422 | The formula references an unknown element |
DF_API.CIRCULAR_DEPENDENCY |
422 | The formula introduces a cycle |
DF_API.INVALID_PARAMETER_TYPE |
422 | A dimension passed to a period function is not bound to a date/time column |
2.21 Delete fact
Request
DELETE {v}/facts/{fact_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_id |
path | integer | yes | Fact identifier |
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The fact does not exist in the version |
DF_API.CONSTRAINT_VIOLATION |
409 | The fact is referenced by a formula |
2.22 Create dimension group
Request
POST {v}/dimension-groups
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection |
name |
body | string | yes | 1–64 characters; letters, digits, space, ., -, _; must start with a letter or digit; unique among the version's groups |
description |
body | string | no | Up to 255 characters |
primary_key |
body | object | yes | Source object of the group's key (section 3.6 of the API page): connection (optional), db, table required, schema, column optional |
dimensions |
body | array | no | Initial members: { "id": "<dimension id>", "level": <integer ≥ 1> }; levels must be unique |
{
"name": "Geography",
"description": "Geographic regions and countries",
"primary_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" },
"dimensions": [ { "id": "722", "level": 1 }, { "id": "723", "level": 2 } ]
}
Details
level is the hierarchy level of the member inside the group and is stored verbatim (not renumbered).
Response
201 Created
{
"id": "9",
"name": "Geography",
"description": "Geographic regions and countries",
"primary_key": {
"connection": "Production PostgreSQL",
"db": "analytics_db",
"schema": "public",
"table": "dim_geography",
"column": "region_id"
},
"dimensions": [
{ "id": "722", "name": "Region", "description": "Sales region", "level": 1, "display_data_type": "Text", "physical_column": "region_name" }
],
"related_fact_tables": [
{ "fact_table_id": "11", "fact_table_name": "fact_sales", "foreign_key_column": "region_id" }
],
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
dimensions[] |
array | Members with id, name, description, level, display_data_type, physical_column |
related_fact_tables[] |
array | fact_table_id, fact_table_name, foreign_key_column |
id, name, description |
string / string / string | null | Identity |
primary_key |
object | null | Source object with connection (section 3.6 of the API page); null when the group has no key yet |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DUPLICATE_NAME |
409 | A group with this name exists in the version |
DF_API.DUPLICATE_LEVEL |
409 | Two members share a level |
DF_API.RESOURCE_NOT_FOUND |
404 | A member id is not a dimension of the version |
DF_API.INVALID_SOURCE_* |
400 | primary_key validation (section 1) |
2.23 Replace dimension group
Request
PUT {v}/dimension-groups/{dimension_group_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
dimension_group_id |
path | integer | yes | Group identifier |
name |
body | string | yes | As on create |
primary_key |
body | object | yes | As on create |
description |
body | string | no | Omitted — reset to empty |
Details
Replaces the group's metadata only; membership is managed through …/dimension-groups/{dimension_group_id}/dimensions and is not touched.
Response
200 OK
{
"id": "9",
"name": "Geography",
"description": "Geographic regions and countries",
"primary_key": {
"connection": "Production PostgreSQL",
"db": "analytics_db",
"schema": "public",
"table": "dim_geography",
"column": "region_id"
},
"dimensions": [
{ "id": "722", "name": "Region", "description": "Sales region", "level": 1, "display_data_type": "Text", "physical_column": "region_name" }
],
"related_fact_tables": [
{ "fact_table_id": "11", "fact_table_name": "fact_sales", "foreign_key_column": "region_id" }
],
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
dimensions[] |
array | Members with id, name, description, level, display_data_type, physical_column |
related_fact_tables[] |
array | fact_table_id, fact_table_name, foreign_key_column |
id, name, description |
string / string / string | null | Identity |
primary_key |
object | null | Source object with connection (section 3.6 of the API page); null when the group has no key yet |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DIMENSION_GROUP_NOT_FOUND |
404 | The group does not exist in the version |
DF_API.MISSING_REQUIRED_FIELD |
400 | name or primary_key missing |
DF_API.DUPLICATE_NAME |
409 | Another group has the new name |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
2.24 Update dimension group
Request
PATCH {v}/dimension-groups/{dimension_group_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
dimension_group_id |
path | integer | yes | Dimension group identifier |
name |
body | string | no | As on create |
primary_key |
body | object | no | As on create |
description |
body | string | no | Omitted — reset to empty |
Details
Only the supplied fields change; membership is not touched.
Response
200 OK
{
"id": "9",
"name": "Geography",
"description": "Geographic regions and countries",
"primary_key": {
"connection": "Production PostgreSQL",
"db": "analytics_db",
"schema": "public",
"table": "dim_geography",
"column": "region_id"
},
"dimensions": [
{ "id": "722", "name": "Region", "description": "Sales region", "level": 1, "display_data_type": "Text", "physical_column": "region_name" }
],
"related_fact_tables": [
{ "fact_table_id": "11", "fact_table_name": "fact_sales", "foreign_key_column": "region_id" }
],
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
dimensions[] |
array | Members with id, name, description, level, display_data_type, physical_column |
related_fact_tables[] |
array | fact_table_id, fact_table_name, foreign_key_column |
id, name, description |
string / string / string | null | Identity |
primary_key |
object | null | Source object with connection (section 3.6 of the API page); null when the group has no key yet |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DIMENSION_GROUP_NOT_FOUND |
404 | The group does not exist in the version |
DF_API.DUPLICATE_NAME |
409 | Another group has the new name |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
2.25 Delete dimension group
Request
DELETE {v}/dimension-groups/{dimension_group_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
dimension_group_id |
path | integer | yes | Dimension group identifier |
Details
A group assigned to any fact table — whether merely assigned or joined through a relationship — cannot be deleted; unassign it first (DELETE {v}/fact-tables/{fact_table_id}/dimension-groups/{dimension_group_id}).
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DIMENSION_GROUP_NOT_FOUND |
404 | The group does not exist in the version |
DF_API.CONSTRAINT_VIOLATION |
409 | The group is assigned to a fact table |
2.26 Add dimensions to a group
Request
POST {v}/dimension-groups/{dimension_group_id}/dimensions
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
dimension_group_id |
path | integer | yes | Group identifier |
Idempotency-Key |
header | UUID v4 | no | Replay protection |
dimensions |
body | array | yes | At least one { "id": "<dimension id>", "level": <integer ≥ 1> } |
{ "dimensions": [ { "id": "724", "level": 3 } ] }
Details
Members already in the group keep their place; a listed member that is already present gets the new level. A level the request assigns — to a new member or to one it moves — must not be held by another member and must not repeat within the request; members that already share a level (a group created in the UI keeps all its dimensions on level 1) are not checked against each other and do not block the request. Unlike the fact-table membership endpoints, this call is all-or-nothing and answers 200 with the group object (no succeeded / failed summary).
Response
200 OK
{
"id": "9",
"name": "Geography",
"description": "Geographic regions and countries",
"primary_key": {
"connection": "Production PostgreSQL",
"db": "analytics_db",
"schema": "public",
"table": "dim_geography",
"column": "region_id"
},
"dimensions": [
{ "id": "722", "name": "Region", "description": "Sales region", "level": 1, "display_data_type": "Text", "physical_column": "region_name" }
],
"related_fact_tables": [
{ "fact_table_id": "11", "fact_table_name": "fact_sales", "foreign_key_column": "region_id" }
],
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
dimensions[] |
array | Members with id, name, description, level, display_data_type, physical_column |
related_fact_tables[] |
array | fact_table_id, fact_table_name, foreign_key_column |
id, name, description |
string / string / string | null | Identity |
primary_key |
object | null | Source object with connection (section 3.6 of the API page); null when the group has no key yet |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DIMENSION_GROUP_NOT_FOUND |
404 | The group does not exist in the version |
DF_API.RESOURCE_NOT_FOUND |
404 | A dimension id does not exist in the version |
DF_API.DUPLICATE_LEVEL |
409 | A requested level is already held by another member or repeats in the request; the message names the level |
2.27 Change a member's hierarchy level
Request
PATCH {v}/dimension-groups/{dimension_group_id}/dimensions/{dimension_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
dimension_group_id, dimension_id |
path | integer | yes | Identifiers |
level |
body | integer ≥ 1 | yes | New hierarchy level |
Response
200 OK
{
"id": "9",
"name": "Geography",
"description": "Geographic regions and countries",
"primary_key": {
"connection": "Production PostgreSQL",
"db": "analytics_db",
"schema": "public",
"table": "dim_geography",
"column": "region_id"
},
"dimensions": [
{ "id": "722", "name": "Region", "description": "Sales region", "level": 1, "display_data_type": "Text", "physical_column": "region_name" }
],
"related_fact_tables": [
{ "fact_table_id": "11", "fact_table_name": "fact_sales", "foreign_key_column": "region_id" }
],
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
dimensions[] |
array | Members with id, name, description, level, display_data_type, physical_column |
related_fact_tables[] |
array | fact_table_id, fact_table_name, foreign_key_column |
id, name, description |
string / string / string | null | Identity |
primary_key |
object | null | Source object with connection (section 3.6 of the API page); null when the group has no key yet |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The dimension is not a member of the group |
DF_API.DUPLICATE_LEVEL |
409 | Another member already has that level |
2.28 Remove a dimension from a group
Request
DELETE {v}/dimension-groups/{dimension_group_id}/dimensions/{dimension_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
dimension_group_id |
path | integer | yes | Dimension group identifier |
dimension_id |
path | integer | yes | Dimension identifier |
Details
The remaining members keep their levels; they are not renumbered.
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The dimension is not a member of the group (a repeated delete is not a silent no-op) |
2.29 Create fact table
Request
POST {v}/fact-tables
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection |
name |
body | string | yes | 1–64 characters; letters, digits, _, ., -, space; must start with a letter or digit; unique among the version's fact tables |
description |
body | string | no | Up to 255 characters |
owner |
body | string | no | Accepted and ignored — the key owner becomes the owner |
{ "name": "fact_sales", "description": "Primary sales facts" }
Details
A fact table created through the API has no base physical table of its own; elements are attached through the membership endpoints (POST {v}/fact-tables/{fact_table_id}/measures, …/dimensions, …/facts, …/dimension-groups).
Response
201 Created
{
"id": "11",
"name": "fact_sales",
"description": "Primary sales facts",
"owner": "Pavel Shalavin",
"created_at": "2026-05-05T08:30:00.000Z",
"measures": [],
"dimensions": [],
"facts": [],
"dimension_groups": [],
"verification_filters": [],
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
measures[].display_data_type |
string | null | Localized data type label |
measures[].measure_type |
string | null | base or calculated |
dimensions[].dimension_type |
string | null | primary or derived |
dimensions[].formula |
string | null | Formula of a derived dimension |
facts[].fact_type |
string | null | primary, derived, or constant |
measures[].dependencies |
array | null | Present and populated only when include_dependencies=true; each node has id, name, type, formula and a recursive dependencies array |
dimensions[].is_from_dimension_group |
boolean | true for dimensions inherited from a linked dimension group; false for dimensions defined directly on the fact table |
dimensions[].dimension_group_id |
string | null | The group an inherited dimension comes from |
…physical_column |
string | null | Column bound to the element in the source table |
dimension_groups[].primary_key, foreign_key |
object | null | Source objects of the join (section 3.6 of the API page) |
verification_filters[].is_valid |
boolean | false when the filter expression cannot be resolved (missing reference, syntax error); invalid_reason carries the localized explanation |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.DUPLICATE_NAME |
409 | A fact table with this name exists in the version |
2.30 Replace fact table
Request
PUT {v}/fact-tables/{fact_table_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
name |
body | string | yes | 1–64 characters; letters, digits, _, ., -, space; must start with a letter or digit; unique among the version's fact tables |
description |
body | string | no | Up to 255 characters |
owner |
body | string | no | Accepted and ignored — the key owner becomes the owner |
Details
name is required; an omitted description is reset to empty. Element assignments are not touched.
Response
200 OK
{
"id": "11",
"name": "fact_sales",
"description": "Primary sales facts",
"owner": "Pavel Shalavin",
"created_at": "2026-04-15T08:30:00Z",
"measures": [
{ "id": "501", "name": "Total revenue", "description": "Gross revenue", "formula": "SUM([Amount])", "display_data_type": "Number", "measure_type": "base", "dependencies": null }
],
"dimensions": [
{ "id": "801", "name": "Sale date", "description": "Calendar date of the sale", "display_data_type": "Date", "dimension_type": "primary", "formula": null, "physical_column": "sale_date", "is_from_dimension_group": false, "dimension_group_id": null }
],
"facts": [
{ "id": "611", "name": "Order line", "description": null, "fact_type": "primary", "formula": null, "physical_column": "amount" }
],
"dimension_groups": [
{
"id": "9", "name": "Geography", "description": "Geographic regions and countries",
"primary_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" },
"foreign_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_sales", "column": "region_id" }
}
],
"verification_filters": [
{ "id": "301", "name": "Valid sales only", "description": "Excludes test and cancelled orders", "conditions": "[Status] != 'cancelled'", "is_valid": true, "invalid_reason": null }
],
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
measures[].display_data_type |
string | null | Localized data type label |
measures[].measure_type |
string | null | base or calculated |
dimensions[].dimension_type |
string | null | primary or derived |
dimensions[].formula |
string | null | Formula of a derived dimension |
facts[].fact_type |
string | null | primary, derived, or constant |
measures[].dependencies |
array | null | Present and populated only when include_dependencies=true; each node has id, name, type, formula and a recursive dependencies array |
dimensions[].is_from_dimension_group |
boolean | true for dimensions inherited from a linked dimension group; false for dimensions defined directly on the fact table |
dimensions[].dimension_group_id |
string | null | The group an inherited dimension comes from |
…physical_column |
string | null | Column bound to the element in the source table |
dimension_groups[].primary_key, foreign_key |
object | null | Source objects of the join (section 3.6 of the API page) |
verification_filters[].is_valid |
boolean | false when the filter expression cannot be resolved (missing reference, syntax error); invalid_reason carries the localized explanation |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.MISSING_REQUIRED_FIELD |
400 | name missing |
DF_API.DUPLICATE_NAME |
409 | Another fact table has the new name |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
2.31 Update fact table
Request
PATCH {v}/fact-tables/{fact_table_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
name |
body | string | no | 1–64 characters; letters, digits, _, ., -, space; must start with a letter or digit; unique among the version's fact tables |
description |
body | string | no | Up to 255 characters |
owner |
body | string | no | Accepted and ignored — the key owner becomes the owner |
Details
Only the supplied fields change.
Response
200 OK
{
"id": "11",
"name": "fact_sales",
"description": "Primary sales facts",
"owner": "Pavel Shalavin",
"created_at": "2026-04-15T08:30:00Z",
"measures": [
{ "id": "501", "name": "Total revenue", "description": "Gross revenue", "formula": "SUM([Amount])", "display_data_type": "Number", "measure_type": "base", "dependencies": null }
],
"dimensions": [
{ "id": "801", "name": "Sale date", "description": "Calendar date of the sale", "display_data_type": "Date", "dimension_type": "primary", "formula": null, "physical_column": "sale_date", "is_from_dimension_group": false, "dimension_group_id": null }
],
"facts": [
{ "id": "611", "name": "Order line", "description": null, "fact_type": "primary", "formula": null, "physical_column": "amount" }
],
"dimension_groups": [
{
"id": "9", "name": "Geography", "description": "Geographic regions and countries",
"primary_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" },
"foreign_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_sales", "column": "region_id" }
}
],
"verification_filters": [
{ "id": "301", "name": "Valid sales only", "description": "Excludes test and cancelled orders", "conditions": "[Status] != 'cancelled'", "is_valid": true, "invalid_reason": null }
],
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
measures[].display_data_type |
string | null | Localized data type label |
measures[].measure_type |
string | null | base or calculated |
dimensions[].dimension_type |
string | null | primary or derived |
dimensions[].formula |
string | null | Formula of a derived dimension |
facts[].fact_type |
string | null | primary, derived, or constant |
measures[].dependencies |
array | null | Present and populated only when include_dependencies=true; each node has id, name, type, formula and a recursive dependencies array |
dimensions[].is_from_dimension_group |
boolean | true for dimensions inherited from a linked dimension group; false for dimensions defined directly on the fact table |
dimensions[].dimension_group_id |
string | null | The group an inherited dimension comes from |
…physical_column |
string | null | Column bound to the element in the source table |
dimension_groups[].primary_key, foreign_key |
object | null | Source objects of the join (section 3.6 of the API page) |
verification_filters[].is_valid |
boolean | false when the filter expression cannot be resolved (missing reference, syntax error); invalid_reason carries the localized explanation |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.DUPLICATE_NAME |
409 | Another fact table has the new name |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
2.32 Delete fact table
Request
DELETE {v}/fact-tables/{fact_table_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
Details
A fact table with an active relationship (a dimension group joined through a foreign key) cannot be deleted — delete the relationships first (DELETE {v}/relationships/{relationship_id}). Plain assignments (measures, dimensions, facts, groups without a key, verification filters) are removed together with the table, atomically.
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.CONSTRAINT_VIOLATION |
409 | The fact table has active relationships |
2.33 Assign measures to a fact table
Request
POST {v}/fact-tables/{fact_table_id}/measures
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
Idempotency-Key |
header | UUID v4 | no | Replay protection |
measure_ids |
body | array of string | yes | At least one measure identifier |
{ "measure_ids": ["501", "502"] }
Details
Identifiers are partitioned into assignable and rejected ones, in request order; the assignable ones are attached and the rest reported in failed[] — the request never fails as a whole while at least one identifier was applied. An identifier already assigned lands in failed[] with constraint_violation, an identifier that is not a measure of the version with resource_not_found. A measure whose overall health status is error (see the Measure health cards page) cannot be assigned and lands in failed[] with measure_health_error ("A measure with Error health status can't be added"); a measure already assigned stays and is reported as already assigned.
Response
201 Created when every identifier was applied; 207 Multi-Status when some were rejected:
{
"timestamp": "2026-05-05T08:30:00.120Z",
"succeeded": [ { "id": "501" } ],
"failed": [
{ "index": 1, "id": "502", "error": { "code": "constraint_violation", "message": "Element is already assigned", "details": [] } }
]
}
| Field | Type | Description |
|---|---|---|
succeeded[].id |
string | Applied identifiers |
failed[].index, id |
integer / string | Position and value of the rejected identifier |
failed[].error |
object | code, message, details (empty — the item is addressed by index / id) |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.VALIDATION_FAILED |
400 | measure_ids empty or malformed |
DF_API.BULK_REJECTED |
422 | No identifier was applied; details[] addresses measure_ids.<index> |
2.34 Unassign a measure from a fact table
Request
DELETE {v}/fact-tables/{fact_table_id}/measures/{measure_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
measure_id |
path | integer | yes | Measure identifier |
Details
Removes the assignment only; the measure stays in the RMD.
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.RESOURCE_NOT_FOUND |
404 | The measure is not assigned to the fact table |
2.35 Assign dimensions to a fact table
Request
POST {v}/fact-tables/{fact_table_id}/dimensions
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
Idempotency-Key |
header | UUID v4 | no | Replay protection |
dimension_ids |
body | array of string | yes | At least one dimension identifier |
{ "dimension_ids": ["801", "802"] }
Details
Identifiers are partitioned into assignable and rejected ones, in request order; the assignable ones are attached and the rest reported in failed[] — the request never fails as a whole while at least one identifier was applied. An identifier already assigned lands in failed[] with constraint_violation, an identifier that is not a dimension of the version with resource_not_found. A dimension that belongs to a dimension group is rejected with constraint_violation — it reaches the fact table through its group.
Response
201 Created when every identifier was applied; 207 Multi-Status when some were rejected:
{
"timestamp": "2026-05-05T08:30:00.120Z",
"succeeded": [ { "id": "501" } ],
"failed": [
{ "index": 1, "id": "502", "error": { "code": "constraint_violation", "message": "Element is already assigned", "details": [] } }
]
}
| Field | Type | Description |
|---|---|---|
succeeded[].id |
string | Applied identifiers |
failed[].index, id |
integer / string | Position and value of the rejected identifier |
failed[].error |
object | code, message, details (empty — the item is addressed by index / id) |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.VALIDATION_FAILED |
400 | dimension_ids empty or malformed |
DF_API.BULK_REJECTED |
422 | No identifier was applied; details[] addresses dimension_ids.<index> |
2.36 Unassign a dimension from a fact table
Request
DELETE {v}/fact-tables/{fact_table_id}/dimensions/{dimension_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
dimension_id |
path | integer | yes | Dimension identifier |
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.RESOURCE_NOT_FOUND |
404 | The dimension is not assigned to the fact table |
2.37 Assign facts to a fact table
Request
POST {v}/fact-tables/{fact_table_id}/facts
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
Idempotency-Key |
header | UUID v4 | no | Replay protection |
fact_ids |
body | array of string | yes | At least one fact identifier |
{ "fact_ids": ["611", "612"] }
Details
Identifiers are partitioned into assignable and rejected ones, in request order; the assignable ones are attached and the rest reported in failed[] — the request never fails as a whole while at least one identifier was applied. An identifier already assigned lands in failed[] with constraint_violation, an identifier that is not a fact of the version with resource_not_found.
Response
201 Created when every identifier was applied; 207 Multi-Status when some were rejected:
{
"timestamp": "2026-05-05T08:30:00.120Z",
"succeeded": [ { "id": "501" } ],
"failed": [
{ "index": 1, "id": "502", "error": { "code": "constraint_violation", "message": "Element is already assigned", "details": [] } }
]
}
| Field | Type | Description |
|---|---|---|
succeeded[].id |
string | Applied identifiers |
failed[].index, id |
integer / string | Position and value of the rejected identifier |
failed[].error |
object | code, message, details (empty — the item is addressed by index / id) |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.VALIDATION_FAILED |
400 | fact_ids empty or malformed |
DF_API.BULK_REJECTED |
422 | No identifier was applied; details[] addresses fact_ids.<index> |
2.38 Unassign a fact from a fact table
Request
DELETE {v}/fact-tables/{fact_table_id}/facts/{fact_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
fact_id |
path | integer | yes | Fact identifier |
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.RESOURCE_NOT_FOUND |
404 | The fact is not assigned to the fact table |
2.39 Assign dimension groups to a fact table
Request
POST {v}/fact-tables/{fact_table_id}/dimension-groups
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
Idempotency-Key |
header | UUID v4 | no | Replay protection |
dimension_group_ids |
body | array of string | yes | At least one dimension group identifier |
{ "dimension_group_ids": ["9", "10"] }
Details
Identifiers are partitioned into assignable and rejected ones, in request order; the assignable ones are attached and the rest reported in failed[] — the request never fails as a whole while at least one identifier was applied. An identifier already assigned lands in failed[] with constraint_violation, an identifier that is not a dimension group of the version with resource_not_found. Assigning a group makes its dimensions available to the fact table (is_from_dimension_group: true in the fact table details). The assignment carries no foreign key until a relationship is created for it (POST {v}/relationships).
Response
201 Created when every identifier was applied; 207 Multi-Status when some were rejected:
{
"timestamp": "2026-05-05T08:30:00.120Z",
"succeeded": [ { "id": "501" } ],
"failed": [
{ "index": 1, "id": "502", "error": { "code": "constraint_violation", "message": "Element is already assigned", "details": [] } }
]
}
| Field | Type | Description |
|---|---|---|
succeeded[].id |
string | Applied identifiers |
failed[].index, id |
integer / string | Position and value of the rejected identifier |
failed[].error |
object | code, message, details (empty — the item is addressed by index / id) |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.VALIDATION_FAILED |
400 | dimension_group_ids empty or malformed |
DF_API.BULK_REJECTED |
422 | No identifier was applied; details[] addresses dimension_group_ids.<index> |
2.40 Unassign a dimension group from a fact table
Request
DELETE {v}/fact-tables/{fact_table_id}/dimension-groups/{dimension_group_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
dimension_group_id |
path | integer | yes | Dimension group identifier |
Details
A group joined through an active relationship cannot be unassigned — delete the relationship first (DELETE {v}/relationships/{relationship_id}).
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.RESOURCE_NOT_FOUND |
404 | The group is not assigned to the fact table |
DF_API.CONSTRAINT_VIOLATION |
409 | The group is joined through a relationship |
2.41 Create a per-fact-table verification filter
Request
POST {v}/fact-tables/{fact_table_id}/verification-filters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
Idempotency-Key |
header | UUID v4 | no | Replay protection |
name |
body | string | yes | 1–64 characters; unique among the fact table's filters |
description |
body | string | no | Up to 255 characters |
conditions |
body | string | yes | Filter expression; element references as [Element name] |
{ "name": "Valid sales only", "description": "Excludes test and cancelled orders", "conditions": "[Status] != 'cancelled'" }
Details
Element references in conditions — written as [Element name] or as bare element names outside string literals — are resolved to the version's elements on write and rendered back as [Name] in responses; a name that matches no element is left as written and is reported by the read endpoints as is_valid: false in the fact table details.
Response
201 Created
{
"id": "301",
"name": "Valid sales only",
"description": "Excludes test and cancelled orders",
"conditions": "[Status] != 'cancelled'",
"created_at": "2026-05-05T08:30:00.000Z",
"updated_at": "2026-05-05T08:30:00.000Z",
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Filter identifier |
name, description, conditions |
string / string | null / string | null | As stored |
created_at, updated_at |
string | null | Filter timestamps |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.DUPLICATE_NAME |
409 | A filter with this name exists on the fact table |
2.42 Replace a per-fact-table verification filter
Request
PUT {v}/fact-tables/{fact_table_id}/verification-filters/{filter_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
filter_id |
path | integer | yes | Filter identifier |
name |
body | string | yes | 1–64 characters; unique among the fact table's filters |
description |
body | string | no | Up to 255 characters |
conditions |
body | string | yes | Filter expression; element references as [Element name] |
Details
name and conditions are required; an omitted description is reset to null. Element references in conditions are resolved and rendered back as [Name].
Response
200 OK
{
"id": "301",
"name": "Valid sales only",
"description": "Excludes test and cancelled orders",
"conditions": "[Status] != 'cancelled'",
"created_at": "2026-05-05T08:30:00.000Z",
"updated_at": "2026-05-05T08:30:00.000Z",
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Filter identifier |
name, description, conditions |
string / string | null / string | null | As stored |
created_at, updated_at |
string | null | Filter timestamps |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.RESOURCE_NOT_FOUND |
404 | The filter does not belong to the fact table |
DF_API.MISSING_REQUIRED_FIELD |
400 | name or conditions missing |
DF_API.DUPLICATE_NAME |
409 | Another filter of the fact table has the new name |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
2.43 Update a per-fact-table verification filter
Request
PATCH {v}/fact-tables/{fact_table_id}/verification-filters/{filter_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
filter_id |
path | integer | yes | Filter identifier |
name |
body | string | no | 1–64 characters; unique among the fact table's filters |
description |
body | string | no | Up to 255 characters |
conditions |
body | string | no | Filter expression; element references as [Element name] |
Details
Only the supplied fields change.
Response
200 OK
{
"id": "301",
"name": "Valid sales only",
"description": "Excludes test and cancelled orders",
"conditions": "[Status] != 'cancelled'",
"created_at": "2026-05-05T08:30:00.000Z",
"updated_at": "2026-05-05T08:30:00.000Z",
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Filter identifier |
name, description, conditions |
string / string | null / string | null | As stored |
created_at, updated_at |
string | null | Filter timestamps |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | The fact table does not exist in the version |
DF_API.RESOURCE_NOT_FOUND |
404 | The filter does not belong to the fact table |
DF_API.DUPLICATE_NAME |
409 | Another filter of the fact table has the new name |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
2.44 Delete a per-fact-table verification filter
Request
DELETE {v}/fact-tables/{fact_table_id}/verification-filters/{filter_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
fact_table_id |
path | integer | yes | Fact table identifier |
filter_id |
path | integer | yes | Filter identifier |
Response
204 No Content
Errors. 404 DF_API.FACT_TABLE_NOT_FOUND, 404 DF_API.RESOURCE_NOT_FOUND.
2.45 Create a version-scoped verification filter
Request
POST {v}/verification-filters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection |
name |
body | string | yes | 1–64 characters; unique among the version's global filters |
description |
body | string | no | Up to 255 characters |
conditions |
body | string | yes | Filter expression |
Details
A version-scoped ("global") filter is bound to no fact table and applies to global measures. Element references in conditions — written as [Element name] or as bare element names outside string literals — are resolved to the version's elements on write and rendered back as [Name] in responses; a name that matches no element is left as written and is reported by the read endpoints as is_valid: false in the fact table details.
Response
201 Created
{
"id": "301",
"name": "Valid sales only",
"description": "Excludes test and cancelled orders",
"conditions": "[Status] != 'cancelled'",
"created_at": "2026-05-05T08:30:00.000Z",
"updated_at": "2026-05-05T08:30:00.000Z",
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Filter identifier |
name, description, conditions |
string / string | null / string | null | As stored |
created_at, updated_at |
string | null | Filter timestamps |
timestamp |
string | Moment of the operation |
Errors. 409 DF_API.DUPLICATE_NAME.
2.46 Replace a version-scoped verification filter
Request
PUT {v}/verification-filters/{filter_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
filter_id |
path | integer | yes | Filter identifier |
name |
body | string | yes | 1–64 characters; unique among the version's global filters |
description |
body | string | no | Up to 255 characters |
conditions |
body | string | yes | Filter expression |
Details
name and conditions are required; an omitted description is reset to null.
Response
200 OK
{
"id": "301",
"name": "Valid sales only",
"description": "Excludes test and cancelled orders",
"conditions": "[Status] != 'cancelled'",
"created_at": "2026-05-05T08:30:00.000Z",
"updated_at": "2026-05-05T08:30:00.000Z",
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Filter identifier |
name, description, conditions |
string / string | null / string | null | As stored |
created_at, updated_at |
string | null | Filter timestamps |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The filter does not exist in the version |
DF_API.MISSING_REQUIRED_FIELD |
400 | name or conditions missing |
DF_API.DUPLICATE_NAME |
409 | Another global filter of the version has the new name |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
2.47 Update a version-scoped verification filter
Request
PATCH {v}/verification-filters/{filter_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
filter_id |
path | integer | yes | Filter identifier |
name |
body | string | no | 1–64 characters; unique among the version's global filters |
description |
body | string | no | Up to 255 characters |
conditions |
body | string | no | Filter expression |
Details
Only the supplied fields change.
Response
200 OK
{
"id": "301",
"name": "Valid sales only",
"description": "Excludes test and cancelled orders",
"conditions": "[Status] != 'cancelled'",
"created_at": "2026-05-05T08:30:00.000Z",
"updated_at": "2026-05-05T08:30:00.000Z",
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Filter identifier |
name, description, conditions |
string / string | null / string | null | As stored |
created_at, updated_at |
string | null | Filter timestamps |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The filter does not exist in the version |
DF_API.DUPLICATE_NAME |
409 | Another global filter of the version has the new name |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
2.48 Delete a version-scoped verification filter
Request
DELETE {v}/verification-filters/{filter_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
filter_id |
path | integer | yes | Filter identifier |
Response
204 No Content
Errors. 404 DF_API.RESOURCE_NOT_FOUND.
2.49 Create relationship
Request
POST {v}/relationships
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection |
source_fact_table_id |
body | string | yes | Fact table holding the foreign key |
target_dimension_group_id |
body | string | yes | Dimension group holding the primary key |
foreign_key |
body | object | yes | Source object on the fact table side (section 3.6 of the API page) |
primary_key |
body | object | yes | Source object on the dimension group side; becomes the group's primary key |
relationship_type |
body | string | yes | Must be many_to_one |
{
"source_fact_table_id": "11",
"target_dimension_group_id": "9",
"foreign_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_sales", "column": "region_id" },
"primary_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" },
"relationship_type": "many_to_one"
}
Details
If the group is not yet assigned to the fact table, the assignment is created together with the relationship. Both keys are validated (section 1): with connection supplied, each key must name the connection its side already uses, and both keys must name the same connection.
Response
201 Created
{
"id": "1101",
"source_fact_table": { "id": "11", "name": "fact_sales", "description": "Primary sales facts" },
"target_dimension_group": {
"id": "9", "name": "Geography", "description": "Geographic regions and countries",
"primary_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" }
},
"foreign_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_sales", "column": "region_id" },
"primary_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" },
"relationship_type": "many_to_one",
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Relationship identifier |
source_fact_table, target_dimension_group |
object | Both sides with description; the group side repeats its primary_key |
foreign_key, primary_key |
object | null | Source objects with connection; null when the mapping is incomplete |
relationship_type |
string | Slug many_to_one (the only cardinality the model supports) |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.FACT_TABLE_NOT_FOUND |
404 | source_fact_table_id does not exist in the version |
DF_API.RESOURCE_NOT_FOUND |
404 | target_dimension_group_id does not exist in the version |
DF_API.INVALID_RELATIONSHIP_TYPE |
400 | relationship_type is not many_to_one |
DF_API.DUPLICATE_RELATIONSHIP |
409 | A relationship between this fact table and group already exists |
DF_API.INVALID_SOURCE_*, DF_API.CONNECTION_MISMATCH |
400 | Key validation (section 1) |
2.50 Replace relationship
Request
PUT {v}/relationships/{relationship_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
relationship_id |
path | integer | yes | Relationship identifier |
foreign_key |
body | object | yes | New foreign key |
primary_key |
body | object | yes | New primary key of the group |
Details
Only the key coordinates can be changed; the fact table and the group are fixed. Both keys are validated before either is written.
Response
200 OK
{
"id": "1101",
"source_fact_table": { "id": "11", "name": "fact_sales", "description": "Primary sales facts" },
"target_dimension_group": {
"id": "9", "name": "Geography", "description": "Geographic regions and countries",
"primary_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" }
},
"foreign_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_sales", "column": "region_id" },
"primary_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" },
"relationship_type": "many_to_one",
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Relationship identifier |
source_fact_table, target_dimension_group |
object | Both sides with description; the group side repeats its primary_key |
foreign_key, primary_key |
object | null | Source objects with connection; null when the mapping is incomplete |
relationship_type |
string | Slug many_to_one (the only cardinality the model supports) |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RELATIONSHIP_NOT_FOUND |
404 | The relationship does not exist in the version |
DF_API.MISSING_REQUIRED_FIELD |
400 | foreign_key or primary_key missing |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
DF_API.INVALID_SOURCE_*, DF_API.CONNECTION_MISMATCH |
400 | Key validation |
2.51 Update relationship
Request
PATCH {v}/relationships/{relationship_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
relationship_id |
path | integer | yes | Relationship identifier |
foreign_key |
body | object | no | New foreign key |
primary_key |
body | object | no | New primary key of the group |
Details
A key that is not sent keeps its stored value and is used for the same-connection check against the one that is sent.
Response
200 OK
{
"id": "1101",
"source_fact_table": { "id": "11", "name": "fact_sales", "description": "Primary sales facts" },
"target_dimension_group": {
"id": "9", "name": "Geography", "description": "Geographic regions and countries",
"primary_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" }
},
"foreign_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_sales", "column": "region_id" },
"primary_key": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" },
"relationship_type": "many_to_one",
"timestamp": "2026-05-05T08:30:00.120Z"
}
| Field | Type | Description |
|---|---|---|
id |
string | Relationship identifier |
source_fact_table, target_dimension_group |
object | Both sides with description; the group side repeats its primary_key |
foreign_key, primary_key |
object | null | Source objects with connection; null when the mapping is incomplete |
relationship_type |
string | Slug many_to_one (the only cardinality the model supports) |
timestamp |
string | Moment of the operation |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RELATIONSHIP_NOT_FOUND |
404 | The relationship does not exist in the version |
DF_API.ID_MISMATCH |
400 | Body id differs from the path |
DF_API.INVALID_SOURCE_*, DF_API.CONNECTION_MISMATCH |
400 | Key validation |
2.52 Delete relationship
Request
DELETE {v}/relationships/{relationship_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
relationship_id |
path | integer | yes | Relationship identifier |
Details
Deleting a relationship clears the foreign key only; the dimension group stays assigned to the fact table (unassign it with DELETE {v}/fact-tables/{fact_table_id}/dimension-groups/{dimension_group_id}).
Response
204 No Content
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RELATIONSHIP_NOT_FOUND |
404 | The relationship does not exist in the version |
3 Project access management
These endpoints manage the interactive project access that API keys subsequently inherit (section 2.2 of the API page). Unlike the RMD and data-model write endpoints (sections 1–2, which require an effective project role of developer or higher), the access-management endpoints require the caller (API-key owner) to be the project owner or a user with the Administrator or Super administrator role; otherwise the request is rejected with 403 DF_API.WRITE_ACCESS_DENIED. Path identifiers are projectId and userId (camelCase, as in the internal API).
| Method | Path | Section |
|---|---|---|
GET |
/projects/{projectId}/access |
3.1 |
POST |
/projects/{projectId}/access |
3.2 |
DELETE |
/projects/{projectId}/access/{userId} |
3.3 |
PUT |
/projects/{projectId}/owner |
3.4 |
The internal keys of these checks (PROJECT.INVALID_ACCESS_LEVEL, PROJECT.ACCESS_LEVEL_NOT_ASSIGNABLE, PROJECT.CANNOT_ELEVATE_ACCESS, PROJECT.INVALID_OWNER, PROJECT.ACCESS_DENIED, USER.NOT_FOUND) never leave the API: like any key outside the DF_API.* catalog they degrade to the generic code of their HTTP status (section 4 of the Public API v2 page); the localized cause stays in message.
3.1 Get access configuration
Request
GET /df-api/v2/projects/{projectId}/access
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
projectId |
path | integer | yes | Project identifier |
language |
query | ru | en |
no | Language of the role labels |
Details
Returns the project owner together with every user that has explicit access. Role labels are localized exactly as in the application's "Project members" table. The internal isAdmin and ownerStatus fields are not exposed. The response is a bare array (no pagination).
Response
200 OK
[
{
"id": 7,
"first_name": "Pavel",
"last_name": "Shalavin",
"email": "pavel@example.com",
"isOwner": true,
"globalRole": "Administrator",
"projectRole": "Developer"
}
]
| Field | Type | Description |
|---|---|---|
id |
integer | User identifier |
first_name, last_name, email |
string | User identity |
isOwner |
boolean | true for the project owner |
globalRole |
string | null | Localized global-role label, or null if not resolvable |
projectRole |
string | null | Localized project-role label, or null if the user has no explicit project role |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.WRITE_ACCESS_DENIED |
403 | Caller is neither the owner nor an administrator |
DF_API.PROJECT_NOT_FOUND |
404 | Project does not exist or is not visible |
3.2 Add or update access
Request
POST /df-api/v2/projects/{projectId}/access
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
projectId |
path | integer | yes | Project identifier |
Idempotency-Key |
header | UUID v4 | no | Replay protection |
userId |
body | integer | yes | Target user; must exist and belong to the project's company |
accessLevel |
body | string | yes | developer, analyst or viewer (project_manager is inherited by the owner and not assignable) |
{ "userId": 12, "accessLevel": "developer" }
Details
If the user already has access, the level is updated; otherwise access is granted. The grant is subject to the downgrade-only rule — an access level higher than the user's global role is rejected.
Response
201 Created
{ "success": true }
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true on success |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.INVALID_PARAMETER |
400 | accessLevel missing or not one of developer / analyst / viewer; requested level exceeds the target user's global role |
DF_API.RESOURCE_NOT_FOUND |
404 | Target user does not exist or is outside the project's company |
DF_API.WRITE_ACCESS_DENIED |
403 | Caller is neither the owner nor an administrator |
3.3 Remove access
Request
DELETE /df-api/v2/projects/{projectId}/access/{userId}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
projectId, userId |
path | integer | yes | Identifiers |
Details
Removes the target user's explicit access. The user must exist.
Response
200 OK
{ "success": true }
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true on success |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | Target user does not exist |
DF_API.WRITE_ACCESS_DENIED |
403 | Caller is neither the owner nor an administrator |
3.4 Transfer ownership
Request
PUT /df-api/v2/projects/{projectId}/owner
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
projectId |
path | integer | yes | Project identifier |
newOwnerId |
body | integer | yes | The new owner; must exist, belong to the project's company and hold a role eligible for ownership (PROJECT_MANAGER, COMPANY_ADMIN or SUPER_ADMIN) |
{ "newOwnerId": 12 }
Response
200 OK
{ "success": true }
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true on success |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.RESOURCE_NOT_FOUND |
404 | The new owner does not exist or is outside the project's company |
DF_API.INVALID_PARAMETER |
400 | The new owner's role is ineligible for ownership |
DF_API.WRITE_ACCESS_DENIED |
403 | Caller is neither the owner nor an administrator |
4 Version transfer: export and import (df-api/v2)
The project-version export/import engine is available through the public API, so a CI/CD pipeline can push configuration to Git and roll it out across environments without a browser.
| Method | Path | Role | Section |
|---|---|---|---|
POST |
{v}/export/git |
Analyst or higher | 4.1 |
POST |
{v}/export/file |
Analyst or higher | 4.2 |
POST |
{v}/import/validate |
Analyst or higher | 4.3 |
POST |
{v}/import/preview |
Analyst or higher | 4.4 |
POST |
{v}/import/git |
Developer or higher | 4.5 |
POST |
{v}/import/file |
Developer or higher | 4.6 |
The role is the key owner's effective role in the project (section 2.2 of the API page); an insufficient role answers 403 DF_API.WRITE_ACCESS_DENIED. Export and the dry runs require Analyst or higher (stricter than the internal API, where a viewer may export too); import requires Developer or higher. All six endpoints answer 200 OK on success.
Git credentials travel either in the authentication object as plaintext over TLS (a CI client cannot assemble the browser's encrypted envelope) or by connection_id, referencing a saved Git connection (section 5):
| Field | Method | Description |
|---|---|---|
authentication.method |
— | pat, ssh or password |
authentication.token |
pat |
Personal Access Token |
authentication.private_key, authentication.passphrase |
ssh |
SSH private key and its passphrase (optional) |
authentication.username, authentication.password |
password |
Login and password |
The target repository is always the request's repository_url (an internal host is refused); a saved connection contributes only its credentials, and they are accepted only for the connection's own host. A connection with allow_branch_override / allow_path_override switched off refuses a request whose branch or path differs from the stored ones. An omitted path in a request through a connection means the stored one. Credentials embedded into repository_url are stripped from the response, the audit record and the saved connection.
Cloud-deployment constraint. The multipart endpoints (import/file and the file flavor of import/validate / import/preview) work only in on-premises installations; in the cloud the AWS Lambda wrapper does not pass binary bodies, and synchronous clone/push of large repositories may exceed the gateway's 29-second limit.
Shared errors of the group, beyond the common ones: 422 DF_API.GIT_CONNECTION_FAILED (repository unreachable, credentials rejected, insecure transport), 400 DF_API.INVALID_PARAMETER (unknown branch or commit, malformed archive, no or several project roots in the tree), 422 DF_API.UNPROCESSABLE_ENTITY (size or file-count limits exceeded, invalid import source), 404 DF_API.CONNECTION_NOT_FOUND (connection_id names no visible Git connection).
4.1 Export to Git
Request
POST {v}/export/git
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | A replayed key returns the original response |
repository_url |
body | string | yes | Target repository URL (up to 2000 characters, no whitespace) |
branch |
body | string | yes | Target branch (created if absent) |
path |
body | string | no | Path within the repository; / or omitted — the root |
commit_message |
body | string | yes | 1–500 characters |
connection_id |
body | string | one of | Saved Git connection supplying the credentials |
authentication |
body | object | one of | Credentials (table above) |
options.include_rmd, include_fact_tables, include_data_marts, include_connections |
body | boolean | no | Which sections the export carries. Default true |
options.include_history |
body | boolean | no | Include the RMD cell change history. Default false |
options.encrypt_sensitive |
body | boolean | no | Default true; the one refused value is false — connection passwords are never exported in cleartext (400) |
options.add_gitattributes |
body | boolean | no | Lay a .gitattributes into the tree. Default true |
options.save_connection, options.connection_name |
body | boolean, string | no | Store the credentials passed in authentication as a new private connection with the given name (visible to its creator and administrators; sharing is an administrator's decision) |
{
"repository_url": "https://gitlab.example.com/analytics/dataforge-config.git",
"branch": "main",
"path": "/sales",
"commit_message": "Export from DataForge, pipeline 4711",
"authentication": { "method": "pat", "token": "<personal-access-token>" },
"options": { "include_history": false }
}
Details
Export is deterministic: re-exporting an unchanged version yields the byte-identical tree, so a repeated export creates no commit and answers commit_hash: null (the branch is never overwritten — changes land as ordinary commits on top of its history). The chain "export → import as a new version → re-export" reproduces the tree without a single difference.
Response
200 OK
{
"success": true,
"commit_hash": "3f2a9c1e7b0d4a6f8c2e1b9d7a5c3e1f0b8d6a4c",
"repository_url": "https://gitlab.example.com/analytics/dataforge-config.git",
"branch": "main",
"path": "/sales",
"files_created": 128,
"timestamp": "2026-05-05T08:30:00.120Z",
"connection_id": null
}
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true on a 200 |
commit_hash |
string | null | Hash of the created commit; null when the tree was unchanged |
repository_url, branch, path |
string | Where the tree landed; credentials stripped from the URL |
files_created |
integer | Files written into the tree |
timestamp |
string | Moment of the operation |
connection_id |
string | null | The saved connection that was used or created (options.save_connection) |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.MISSING_REQUIRED_FIELD |
400 | Neither connection_id nor authentication given; connection_name missing with save_connection |
DF_API.INVALID_PARAMETER |
400 | encrypt_sensitive: false; branch or path override refused by the connection; unknown branch |
DF_API.GIT_PUSH_FAILED |
422 | The push was rejected: the branch changed during the export or is protected |
| Shared errors of the group | — | Above |
4.2 Export to a file
Request
POST {v}/export/file
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
options.include_rmd, include_fact_tables, include_data_marts, include_connections |
body | boolean | no | Which sections the export carries. Default true |
options.include_history |
body | boolean | no | Include the RMD cell change history. Default false |
options.encrypt_sensitive |
body | boolean | no | Default true; the one refused value is false — connection passwords are never exported in cleartext (400) |
options.encryption_password |
body | string | no | Wrap the archive in an AES-256-GCM envelope with this password |
options.use_system_key |
body | boolean | no | Wrap the archive with the system key instead (the password wins when both are given). Default false |
{ "options": { "include_connections": true, "encryption_password": "<password>" } }
Details
Produces a .dfexport.zip archive in object storage and returns a signed download URL; its lifetime is set by the installation and reported in expires_at. Idempotency-Key is deliberately not honoured — a cached response would carry an expired link.
Response
200 OK
{
"download_url": "https://storage.example.com/exports/…?signature=…",
"file_name": "Sales Analytics_Q4 2025.dfexport.zip",
"file_size": 48213,
"expires_at": "2026-05-05T09:30:00.000Z"
}
| Field | Type | Description |
|---|---|---|
download_url |
string | Signed URL of the archive |
file_name |
string | Suggested file name |
file_size |
integer | Size in bytes |
expires_at |
string | When the URL stops working, ISO 8601 |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.INVALID_PARAMETER |
400 | encrypt_sensitive: false |
DF_API.UNPROCESSABLE_ENTITY |
422 | The export exceeds the size limit |
4.3 Validate an import source
Request
POST {v}/import/validate — JSON for a Git source, multipart/form-data for a file source.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
source_type |
body | git | file |
yes | What the request carries |
repository_url, branch |
body | string | git |
Source repository and branch |
path, commit_hash |
body | string | no | Path within the repository; a commit (7–40 hex characters) to pin instead of the branch tip |
connection_id / authentication |
body | — | git |
Credentials (one of the two) |
file |
multipart part | binary | file |
The .dfexport.zip archive, plain or sealed |
encryption_password |
body | string | no | Password of a sealed archive |
conflict_strategy, options |
body | — | no | Accepted with the same meaning as in an import request (smart_merge / overwrite / skip / manual; the import_*, detect_merges, decrypt_sensitive, encryption_password options), so the documented import body can be sent as is; a dry run does not act on them. In multipart, options is a JSON string |
Details
Validates the source without writing anything. The version in the path is only the access anchor. Idempotency-Key is not honoured (a cached verdict could be stale).
Response
200 OK
{
"valid": false,
"errors": [
{ "level": "error", "code": "DUPLICATE_ID", "message": "Duplicate element id", "element": "measures/total_revenue.json", "element_type": "measure" }
],
"warnings": [
{ "level": "warning", "code": "REFERENCE_MISSING", "message": "Referenced fact table is not in the export", "element": "data_marts/monthly.json", "element_type": "data_mart" }
],
"summary": { "measures": 42, "dimensions": 18, "facts": 6, "fact_tables": 3, "data_marts": 2 },
"schema_version": "1.0"
}
| Field | Type | Description |
|---|---|---|
valid |
boolean | false when errors[] is not empty |
errors[], warnings[] |
array | Issues with level, machine code (REQUIRED_FILE_MISSING, DUPLICATE_ID, CIRCULAR_DEPENDENCY, REFERENCE_MISSING, …), message, element, element_type |
summary |
object | Element counts in the source: measures, dimensions, facts, fact_tables, data_marts |
schema_version |
string | Export format version found in the source |
Errors. 400 DF_API.MISSING_REQUIRED_FIELD (a field the source_type requires is missing); shared errors of the group.
4.4 Preview an import
Request
POST {v}/import/preview — JSON for a Git source, multipart/form-data for a file source.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
source_type |
body | git | file |
no | What the request carries. May be omitted — then read from the request itself: a multipart upload is a file, anything else a Git branch |
repository_url, branch |
body | string | git |
Source repository and branch |
path, commit_hash |
body | string | no | Path within the repository; a commit (7–40 hex characters) to pin instead of the branch tip |
connection_id / authentication |
body | — | git |
Credentials (one of the two) |
file |
multipart part | binary | file |
The .dfexport.zip archive, plain or sealed |
encryption_password |
body | string | no | Password of a sealed archive |
conflict_strategy, options |
body | — | no | Accepted with the same meaning as in an import request (smart_merge / overwrite / skip / manual; the import_*, detect_merges, decrypt_sensitive, encryption_password options), so the documented import body can be sent as is; a dry run does not act on them. In multipart, options is a JSON string |
Details
Compares the source against the version in the path and reports what an import would change, without writing anything. Idempotency-Key is not honoured.
Response
200 OK
{
"preview_id": "0b1d2c3e-4f5a-6789-abcd-ef0123456789",
"summary": {
"measures": { "added": 3, "modified": 5, "deleted": 0, "total": 45 },
"dimensions": { "added": 0, "modified": 1, "deleted": 0, "total": 18 },
"facts": { "added": 0, "modified": 0, "deleted": 0, "total": 6 },
"fact_tables": { "added": 0, "modified": 1, "deleted": 0, "total": 3 },
"data_marts": { "added": 1, "modified": 0, "deleted": 0, "total": 3 }
},
"conflicts": [
{ "element_type": "measure", "element_id": "1000", "element_name": "Total revenue", "field": "formula", "current_value": "SUM([Amount])", "imported_value": "SUM([Net amount])" }
],
"conflict_resolution_required": true
}
| Field | Type | Description |
|---|---|---|
preview_id |
string | Identifier of this preview |
summary.<section> |
object | added, modified, deleted, total per section |
conflicts[] |
array | Field-level conflicts: element_type, element_id, element_name, field, current_value, imported_value |
conflict_resolution_required |
boolean | true when conflicts[] is not empty |
Errors. 400 DF_API.MISSING_REQUIRED_FIELD (a field the source_type requires is missing); shared errors of the group.
4.5 Import from Git
Request
POST {v}/import/git
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | A CI runner's retry lands on the same version with the same import_id |
repository_url, branch |
body | string | yes | Source repository and branch |
path |
body | string | no | Path within the repository |
commit_hash |
body | string | no | Pin one commit instead of the branch tip |
connection_id / authentication |
body | — | one of | Credentials |
target.method |
body | create | replace |
no | create (default) — a new version (the license version limit applies); replace — fully replace the version addressed in the path |
target.version_name |
body | string | yes | The resulting version's name in both modes (the name of the new version, or the new name of the replaced one); up to 64 characters, letters/digits/underscore/space/dot/dash |
conflict_strategy |
body | string | no | smart_merge (default), overwrite, skip, manual |
options.import_rmd, import_fact_tables, import_data_marts, import_connections |
body | boolean | no | Default true; a switched-off section stays as the current version has it |
options.import_history |
body | boolean | no | Import the RMD cell history. Default false |
options.detect_merges |
body | boolean | no | Carry the merge provenance from the manifest. Default true |
options.decrypt_sensitive |
body | boolean | no | Default true; with false connections are imported without passwords, in the "needs refresh" status |
options.encryption_password |
body | string | no | Password of a sealed source |
{
"repository_url": "https://gitlab.example.com/analytics/dataforge-config.git",
"branch": "main",
"path": "/sales",
"target": { "method": "create", "version_name": "staging_4711" },
"conflict_strategy": "smart_merge",
"authentication": { "method": "pat", "token": "<personal-access-token>" }
}
Details
In both modes the version in the path is the baseline the conflict strategy compares against. conflict_strategy acts element by element:
| Strategy | Behavior |
|---|---|
smart_merge (default) |
Additions and modifications from the source land; elements only the current version has survive |
overwrite |
The source lands verbatim, deletions included |
skip |
Conflicting elements keep their current values; additions land, deletions do not |
manual |
With conflicts present nothing is written: the response is success: false with the conflict list |
The import is one transaction and creates the resulting version with element uuids preserved, so re-exports stay comparable in Git. Every import is audited with a before/after snapshot (section 7 of the API page).
Response
200 OK
{
"success": true,
"import_id": "7c0e4b2a-1d3f-4e5a-9b8c-6d7e8f9a0b1c",
"target_project_id": "12",
"target_version_id": "35",
"target_version_name": "staging_4711",
"summary": {
"measures": { "added": 3, "modified": 5, "deleted": 0, "total": 45 },
"dimensions": { "added": 0, "modified": 1, "deleted": 0, "total": 18 },
"facts": { "added": 0, "modified": 0, "deleted": 0, "total": 6 },
"fact_tables": { "added": 0, "modified": 1, "deleted": 0, "total": 3 },
"data_marts": { "added": 1, "modified": 0, "deleted": 0, "total": 3 }
},
"conflicts": [],
"conflict_resolution_required": false
}
| Field | Type | Description |
|---|---|---|
success |
boolean | false only with the manual strategy and conflicts present — nothing was written |
import_id |
string | Identifier of the import operation; stable across an idempotent retry |
target_project_id, target_version_id, target_version_name |
string / string / string | null | Where the import landed (identifiers as strings) |
summary.<section> |
object | added, modified, deleted, total per section |
conflicts[] |
array | Field-level conflicts: element_type, element_id, element_name, field, current_value, imported_value |
conflict_resolution_required |
boolean | true when conflicts[] is not empty |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.MISSING_REQUIRED_FIELD |
400 | target.version_name missing; neither connection_id nor authentication given |
DF_API.DUPLICATE_NAME |
409 | target.version_name is already taken by another live version of the project |
DF_API.LICENSE_LIMIT_REACHED |
403 | target.method: create while the license version limit is reached |
DF_API.UNPROCESSABLE_ENTITY |
422 | The source fails validation (the errors[] the validation endpoint reports) or exceeds the limits |
| Shared errors of the group | — | Above |
4.6 Import from a file
Request
POST {v}/import/file — multipart/form-data.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | A CI runner's retry lands on the same version with the same import_id |
file |
multipart part | binary | yes | A .dfexport.zip archive, plain or sealed |
target.method |
form field | create | replace |
no | create (default) — a new version (the license version limit applies); replace — fully replace the version addressed in the path |
target.version_name |
form field | string | yes | The resulting version's name in both modes (the name of the new version, or the new name of the replaced one); up to 64 characters, letters/digits/underscore/space/dot/dash |
conflict_strategy |
form field | string | no | smart_merge (default), overwrite, skip, manual |
options |
form field | JSON string | no | JSON string with the import options: import_rmd, import_fact_tables, import_data_marts, import_connections (default true; a switched-off section stays as the current version has it), import_history (false), detect_merges (true), decrypt_sensitive (true; with false connections are imported without passwords), encryption_password |
encryption_password |
form field | string | no | Password of a sealed archive |
Details
On-premises installations only (cloud constraint above).
In both modes the version in the path is the baseline the conflict strategy compares against. conflict_strategy acts element by element:
| Strategy | Behavior |
|---|---|
smart_merge (default) |
Additions and modifications from the source land; elements only the current version has survive |
overwrite |
The source lands verbatim, deletions included |
skip |
Conflicting elements keep their current values; additions land, deletions do not |
manual |
With conflicts present nothing is written: the response is success: false with the conflict list |
The import is one transaction and creates the resulting version with element uuids preserved, so re-exports stay comparable in Git. Every import is audited with a before/after snapshot (section 7 of the API page).
Response
200 OK
{
"success": true,
"import_id": "7c0e4b2a-1d3f-4e5a-9b8c-6d7e8f9a0b1c",
"target_project_id": "12",
"target_version_id": "35",
"target_version_name": "staging_4711",
"summary": {
"measures": { "added": 3, "modified": 5, "deleted": 0, "total": 45 },
"dimensions": { "added": 0, "modified": 1, "deleted": 0, "total": 18 },
"facts": { "added": 0, "modified": 0, "deleted": 0, "total": 6 },
"fact_tables": { "added": 0, "modified": 1, "deleted": 0, "total": 3 },
"data_marts": { "added": 1, "modified": 0, "deleted": 0, "total": 3 }
},
"conflicts": [],
"conflict_resolution_required": false
}
| Field | Type | Description |
|---|---|---|
success |
boolean | false only with the manual strategy and conflicts present — nothing was written |
import_id |
string | Identifier of the import operation; stable across an idempotent retry |
target_project_id, target_version_id, target_version_name |
string / string / string | null | Where the import landed (identifiers as strings) |
summary.<section> |
object | added, modified, deleted, total per section |
conflicts[] |
array | Field-level conflicts: element_type, element_id, element_name, field, current_value, imported_value |
conflict_resolution_required |
boolean | true when conflicts[] is not empty |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.MISSING_REQUIRED_FIELD |
400 | target.version_name missing; neither connection_id nor authentication given |
DF_API.DUPLICATE_NAME |
409 | target.version_name is already taken by another live version of the project |
DF_API.LICENSE_LIMIT_REACHED |
403 | target.method: create while the license version limit is reached |
DF_API.UNPROCESSABLE_ENTITY |
422 | The source fails validation (the errors[] the validation endpoint reports) or exceeds the limits |
| Shared errors of the group | — | Above |
DF_API.INVALID_PARAMETER |
400 | A corrupted archive or a wrong file type |
5 Git connection management (df-api/v2)
The company's registry of saved Git connections (see also section 4). Every endpoint requires the COMPANY_ADMIN role (or SUPER_ADMIN); an insufficient role answers 403 DF_API.WRITE_ACCESS_DENIED, a connection the key cannot see 404 DF_API.CONNECTION_NOT_FOUND. Paths are company-scoped and carry no project or version.
| Method | Path | Section |
|---|---|---|
GET |
/git-connections |
5.1 |
POST |
/git-connections |
5.2 |
GET |
/git-connections/{connection_id} |
5.3 |
PUT |
/git-connections/{connection_id} |
5.4 |
DELETE |
/git-connections/{connection_id} |
5.5 |
POST |
/git-connections/{connection_id}/test |
5.6 |
The connection object returned by 5.1–5.4:
{
"id": "17",
"name": "Config repository",
"platform": "gitlab",
"repository_url": "https://gitlab.example.com/analytics/dataforge-config.git",
"branch": "main",
"path": "/sales",
"status": "active",
"last_used": "2026-05-05T08:30:00.000Z",
"created_at": "2026-04-01T10:00:00.000Z",
"created_by": "admin@example.com",
"shared": false,
"default": true
}
| Field | Type | Description |
|---|---|---|
id |
string | Connection identifier |
name |
string | Display name |
platform |
string | github, gitlab, bitbucket, azure-devops, generic; additionally github-enterprise on read (created only in the browser dialog) |
repository_url |
string | Repository URL without credentials |
branch |
string | Default branch |
path |
string | Path within the repository, always with a leading /; / for the root |
status |
string | active (the last check passed) or failed |
last_used |
string | null | Stamped by exports/imports running through the connection |
created_at |
string | Creation timestamp |
created_by |
string | null | The creator's e-mail |
shared |
boolean | Usable by every user of the company; a non-shared connection is visible only to its creator and administrators |
default |
boolean | The company's default connection |
Credentials are never returned.
5.1 List Git connections
Request
GET /df-api/v2/git-connections
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
page |
query | integer | no | Page number, 1-indexed. Default 1 |
pageSize |
query | integer | no | Items per page. Default 20, maximum 100 (a larger value is refused) |
Response
200 OK
{
"git-connections": [ { "id": "17", "name": "Config repository", "…": "…" } ],
"pagination": { "total": 1, "page": 1, "pageSize": 20, "totalPages": 1 }
}
| Field | Type | Description |
|---|---|---|
git-connections[] |
array | Connection objects (above). The key is spelled with a hyphen |
pagination |
object | total, page, pageSize, totalPages |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.PAGE_SIZE_EXCEEDED |
400 | pageSize above 100 |
DF_API.WRITE_ACCESS_DENIED |
403 | The key owner has neither the Administrator nor the Super administrator role |
5.2 Create a Git connection
Request
POST /df-api/v2/git-connections
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | UUID v4 | no | Replay protection |
name |
body | string | yes | 1–255 characters |
platform |
body | string | yes | github, gitlab, bitbucket, azure-devops, generic |
repository_url |
body | string | yes | Repository URL (an internal host is refused) |
branch |
body | string | yes | Default branch |
path |
body | string | no | Path within the repository |
authentication |
body | object | yes | Credentials (section 4): method pat / ssh / password and its fields |
settings.allow_branch_override |
body | boolean | no | Allow an export/import through this connection to name a different branch. Default true |
settings.allow_path_override |
body | boolean | no | The same for the path. Default true |
settings.set_as_default |
body | boolean | no | Make it the company's default connection (displaces the previous holder). Default false |
settings.share_with_all |
body | boolean | no | Make it usable by every user of the company. Default false (connections created in the internal UI are shared) |
{
"name": "Config repository",
"platform": "gitlab",
"repository_url": "https://gitlab.example.com/analytics/dataforge-config.git",
"branch": "main",
"path": "/sales",
"authentication": { "method": "pat", "token": "<personal-access-token>" },
"settings": { "set_as_default": true }
}
Details
The connection is tested before it is stored — an unreachable repository or rejected credentials refuse the creation. Credentials are encrypted at rest.
Response
201 Created
{
"id": "17",
"name": "Config repository",
"platform": "gitlab",
"repository_url": "https://gitlab.example.com/analytics/dataforge-config.git",
"branch": "main",
"path": "/sales",
"status": "active",
"last_used": "2026-05-05T08:30:00.000Z",
"created_at": "2026-04-01T10:00:00.000Z",
"created_by": "admin@example.com",
"shared": false,
"default": true
}
| Field | Type | Description |
|---|---|---|
id |
string | Connection identifier |
name |
string | Display name |
platform |
string | github, gitlab, bitbucket, azure-devops, generic; additionally github-enterprise on read (created only in the browser dialog) |
repository_url |
string | Repository URL without credentials |
branch |
string | Default branch |
path |
string | Path within the repository, always with a leading /; / for the root |
status |
string | active (the last check passed) or failed |
last_used |
string | null | Stamped by exports/imports running through the connection |
created_at |
string | Creation timestamp |
created_by |
string | null | The creator's e-mail |
shared |
boolean | Usable by every user of the company; a non-shared connection is visible only to its creator and administrators |
default |
boolean | The company's default connection |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.GIT_CONNECTION_FAILED |
422 | The repository is unreachable or rejected the credentials |
DF_API.INVALID_PARAMETER |
400 | The branch does not exist |
DF_API.WRITE_ACCESS_DENIED |
403 | The key owner is not an administrator |
5.3 Get a Git connection
Request
GET /df-api/v2/git-connections/{connection_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
connection_id |
path | integer | yes | Connection identifier |
Response
200 OK
{
"id": "17",
"name": "Config repository",
"platform": "gitlab",
"repository_url": "https://gitlab.example.com/analytics/dataforge-config.git",
"branch": "main",
"path": "/sales",
"status": "active",
"last_used": "2026-05-05T08:30:00.000Z",
"created_at": "2026-04-01T10:00:00.000Z",
"created_by": "admin@example.com",
"shared": false,
"default": true
}
| Field | Type | Description |
|---|---|---|
id |
string | Connection identifier |
name |
string | Display name |
platform |
string | github, gitlab, bitbucket, azure-devops, generic; additionally github-enterprise on read (created only in the browser dialog) |
repository_url |
string | Repository URL without credentials |
branch |
string | Default branch |
path |
string | Path within the repository, always with a leading /; / for the root |
status |
string | active (the last check passed) or failed |
last_used |
string | null | Stamped by exports/imports running through the connection |
created_at |
string | Creation timestamp |
created_by |
string | null | The creator's e-mail |
shared |
boolean | Usable by every user of the company; a non-shared connection is visible only to its creator and administrators |
default |
boolean | The company's default connection |
Errors. 404 DF_API.CONNECTION_NOT_FOUND, 403 DF_API.WRITE_ACCESS_DENIED.
5.4 Update a Git connection
Request
PUT /df-api/v2/git-connections/{connection_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
connection_id |
path | integer | yes | Connection identifier |
name, platform, repository_url, branch, path |
body | — | no | As on create; only the supplied fields change |
authentication |
body | object | no | New credentials; when absent, the stored credentials are kept |
settings.* |
body | boolean | no | Any subset of the create settings |
Details
Repointing the connection to another host requires re-entering the credentials.
Response
200 OK
{
"id": "17",
"name": "Config repository",
"platform": "gitlab",
"repository_url": "https://gitlab.example.com/analytics/dataforge-config.git",
"branch": "main",
"path": "/sales",
"status": "active",
"last_used": "2026-05-05T08:30:00.000Z",
"created_at": "2026-04-01T10:00:00.000Z",
"created_by": "admin@example.com",
"shared": false,
"default": true
}
| Field | Type | Description |
|---|---|---|
id |
string | Connection identifier |
name |
string | Display name |
platform |
string | github, gitlab, bitbucket, azure-devops, generic; additionally github-enterprise on read (created only in the browser dialog) |
repository_url |
string | Repository URL without credentials |
branch |
string | Default branch |
path |
string | Path within the repository, always with a leading /; / for the root |
status |
string | active (the last check passed) or failed |
last_used |
string | null | Stamped by exports/imports running through the connection |
created_at |
string | Creation timestamp |
created_by |
string | null | The creator's e-mail |
shared |
boolean | Usable by every user of the company; a non-shared connection is visible only to its creator and administrators |
default |
boolean | The company's default connection |
Errors
| Code | HTTP | Condition |
|---|---|---|
DF_API.CONNECTION_NOT_FOUND |
404 | The connection does not exist or is not visible |
DF_API.INVALID_PARAMETER |
400 | repository_url moves to another host without new authentication |
DF_API.WRITE_ACCESS_DENIED |
403 | The key owner is not an administrator |
5.5 Delete a Git connection
Request
DELETE /df-api/v2/git-connections/{connection_id}
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
connection_id |
path | integer | yes | Connection identifier |
Details
Versions imported through the connection survive and lose the link.
Response
204 No Content
Errors. 404 DF_API.CONNECTION_NOT_FOUND, 403 DF_API.WRITE_ACCESS_DENIED.
5.6 Test a Git connection
Request
POST /df-api/v2/git-connections/{connection_id}/test
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
connection_id |
path | integer | yes | Connection identifier |
Details
No body. Runs the five checks against the repository and refreshes the connection's stored status, as in the internal UI. Idempotency-Key is deliberately not honoured — a cached verdict would be stale. An empty repository is a valid export target: the checks pass and last_commit is null.
Response
200 OK
{
"git-connection_id": "17",
"status": "success",
"tests": {
"repository_reachable": true,
"authentication_valid": true,
"branch_exists": true,
"write_permission": true,
"path_accessible": true
},
"details": {
"last_commit": "3f2a9c1",
"last_commit_date": "2026-05-05T08:30:00.000Z",
"available_branches": ["main", "develop"]
},
"tested_at": "2026-05-05T09:00:00.000Z"
}
| Field | Type | Description |
|---|---|---|
git-connection_id |
string | The tested connection (the key is spelled exactly so, hyphenated) |
status |
string | success or failed |
tests |
object | Five checks forming a ladder — each implies the ones before it: repository_reachable, authentication_valid, branch_exists, write_permission, path_accessible |
details.last_commit |
string | null | The branch tip's short hash |
details.last_commit_date |
string | null | Date of that commit |
details.available_branches |
array of string | Branches of the repository |
tested_at |
string | When the test ran |
Errors. 404 DF_API.CONNECTION_NOT_FOUND, 403 DF_API.WRITE_ACCESS_DENIED. A failed check is not an error: the response is 200 with status: "failed".