Complete API Description
1 Basic Information
The system provides a public API for integration with external systems, scripts, and other tools. The public API allows automating the retrieval of key platform metadata.
The public API is divided into two basic surfaces:
| Surface | Base Path | Modules | Audience |
|---|---|---|---|
| Public RMD API | /rmd-api/v1 | rmd-api | External integrations, AI agents |
| Public DF API | /df-api/v1 | df-api (data marts, connections, dimension groups, fact tables, relationships, consolidated RMD export) | External integrations, AI agents |
Basic principles applied to all public API calls:
| Principle | Description |
|---|---|
| Versioning in URL | Public API is versioned via URL path (/v1/). Within a single major version, the contract is extended additively — new optional fields may appear, existing fields are not removed or renamed |
| Data versioning | Each endpoint working with RMD, fact tables, data marts, or connections is tied to a project version |
| Read-only | Public API does not modify data, does not execute queries in connected DBMS, and does not expose DBMS credentials |
| License control | The entire public API is available only under an active license of the calling company |
| Audit | Each authentication attempt in the public API — successful or unsuccessful — is recorded in the audit log |
The base URL depends on the deployment environment:
| Environment | Base URL |
|---|---|
| On-Premise | https://${YOUR_SERVER_HOST}/api |
| Cloud | https://api.[cloud system URL] |
Note:
${YOUR_SERVER_HOST}is your server address specified in theSERVER_HOSTenvironment variable.
All API requests require passing an API key header:
| Header | Type | Required | Description |
|---|---|---|---|
X-Api-Key |
string |
Yes | Your API key obtained during creation |
2 Authentication and Authorization
2.1 API Key
The public API uses long-lived API keys generated through user management. Each user has at most one active key at any time; generating a new key replaces the previous one. The plaintext key is shown to the operator once and is not stored — only the bcrypt hash is stored in the api_key table.
ApiKeyGuard (applied to all rmd-api/v1 and df-api/v1 endpoints) performs the following checks in the specified order on each request:
- API key presence. If the X-Api-Key header is missing → 401 API_KEY.KEY_MISSING.
- API key validity. The passed key is bcrypt-compared with each stored hash; if no match → 401 API_KEY.INVALID_KEY.
- User status. If the found user is not in ENABLED status → 403 API_KEY.ACCOUNT_LOCKED.
- IP filtering. The client IP (from X-Forwarded-For, otherwise from the connection) is checked against the company's whitelist and blacklist. If blocked → 403 API_KEY.IP_BLOCKED.
- Company presence. If the user has no company → 403 API.NOT_AVAILABLE_WITHOUT_VALID_LICENSE.
- Active license. LicenseService.checkLimitedAndThrow(companyId) is called. On failure → 403 API.NOT_AVAILABLE_WITHOUT_VALID_LICENSE. See section 5.
- Audit. A record of success or failure is written to the audit log (type API_ACCESS, action API_ACCESS_SUCCESSFUL / API_ACCESS_FAILED).
2.2 Roles
The public API is read-only for all roles — RolesGuard does not apply to it because every operation is a read operation. The user's role remains important for ApiKeyGuard authentication (the user must be in ENABLED status).
2.3 IP Filtering
IP filtering is implemented in CompanyService.checkIp(user, ip) and is called on each public API request as part of ApiKeyGuard.
A company can enable an IP whitelist, an IP blacklist, or both. Each list supports either exact IPv4 addresses or CIDR ranges. When a whitelist is enabled, requests from IPs not in the list are rejected; when a blacklist is enabled, requests from IPs in the list are rejected. Failed IP checks are written to the audit log as API_ACCESS / API_ACCESS_FAILED.
2.4 Throttling
The request limit per API key for the public API is under development. After implementation, successful responses will include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers, and exceeding the limit will return 429 Too Many Requests with code RATE_LIMIT_EXCEEDED.
2.5 License Control
ApiKeyGuard calls LicenseService.checkLimitedAndThrow after authentication and IP filtering. The entire public API surface is rejected when there is no active license. The returned error is the same regardless of the specific reason: 403 Forbidden with code API.NOT_AVAILABLE_WITHOUT_VALID_LICENSE. See section 5 for conditions and recovery procedure.
2.6 Transport Security
All public API endpoints must be served over HTTPS with TLS 1.2 or higher. The on-premises deployment template uses nginx with TLS termination in front of the backend; the backend itself listens on plain HTTP inside a trusted network.
API keys are stored as bcrypt hashes and are never returned in responses. See section 6 for the complete list of fields not returned in responses.
3 Common Request and Response Conventions
3.1 Request Format
| Property | Value |
|---|---|
| Content-Type | application/json for JSON bodies |
| Charset | UTF-8 |
| Authentication header | X-Api-Key: |
| Language | Optional Accept-Language; language query parameter takes precedence |
3.2 Pagination
List endpoints use a unified pagination form. Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number (1-indexed) |
| pageSize | integer | 20 | Maximum 100 on public API |
| Filter parameters | various | — | Endpoint-specific filters (described inline) |
Response envelope (DF API list endpoints):
{ "data_marts": [ /* items */ ], "pagination": { "page": 1, "pageSize": 20, "total": 45, "total_pages": 3 } }
Response envelope (RMD API list endpoints):
{ "measures": [ /* items */ ], "pagination": { "total": 42, "page": 1, "pageSize": 20, "totalPages": 3 } }
3.3 Localization
Public API responses respect the language query parameter (ru or en). Only descriptive text values are localized (status labels, type labels, dropdown values); JSON keys are always in English.
3.4 Timestamps
All timestamps in responses use ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ).
3.5 Output Formats
The Public RMD API and data model / RMD export endpoints of the Public DF API support ?format=json (default) and ?format=xlsx. For xlsx, the response body has the form { "downloadUrl": "
4 Error Handling
The public API uses a unified JSON envelope:
{ "error": { "code": "string", "message": "string", "details": {} } }
Standard HTTP status codes:
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad Request — validation, parameter, or business rule violation |
| 401 | Unauthorized — missing or invalid API key |
| 403 | Forbidden — IP blocked, account locked, license invalid |
| 404 | Not Found — resource does not exist or is unavailable to the caller |
| 429 | Too Many Requests — rate limit exceeded (planned) |
| 500 | Internal Server Error |
Full error code catalog — see section 10.
5 License Validation
License validation is the single control point that decides whether public API access is allowed for a company. It is called by ApiKeyGuard after authentication and IP filtering.
LicenseService.checkLimitedAndThrow(companyId) throws 403 Forbidden when any of the following conditions is true for the calling company:
| Condition | Description |
|---|---|
| Company has no license record | license is null |
| License type is NO_LICENSE | Placeholder assigned by default before activation |
| License status is INVALID | Expired, not validated, or revoked |
| License status is SUSPENDED | License limits exceeded (users, projects, versions) |
The public API maps all these conditions to a single error to avoid exposing license state details:
| HTTP | Code | Body |
|---|---|---|
| 403 | API.NOT_AVAILABLE_WITHOUT_VALID_LICENSE | { "error": { "code": "API.NOT_AVAILABLE_WITHOUT_VALID_LICENSE", "message": "API is not available without a valid license", "details": {} } } |
An unsuccessful access attempt due to an invalid license is recorded in the audit log (type API_ACCESS, action API_ACCESS_FAILED).
To restore API access, the company administrator (or Super Admin in cloud installations) must upload an active license via the license management screen and ensure the company does not exceed the user, project, and version limits of the corresponding plan.
6 Security Restrictions
The following fields are never included in public API responses:
| Field | Reason |
|---|---|
| password | DBMS connection credentials and user password |
| ssl_certificate, caCertificateFileName, clientCertificateFileName | Certificate material |
| ssl_key, clientPrivateKeyFileName | Private keys |
| token, apiKey, key | JWT, refresh tokens, API keys, session tokens |
| connection_string | May contain embedded credentials |
| signature (License entity) | License HMAC; never returned to client |
| Any custom credential fields | Sensitive values set by the user |
Identifiers intentionally exposed as non-secret context:
| Field | Reason for exposure |
|---|---|
| username (DBMS connection) | DBMS username — needed for SQL construction, not a secret |
| host, port, database, schema | Network coordinates, known to anyone with read access to the data model |
Data at rest encryption:
| Field | Storage | Cipher |
|---|---|---|
| API key | api_key.key | bcrypt |
| Remote DBMS password | remote_db.password | AES (key managed by application) |
| Remote DBMS SSL files | object storage (MinIO/S3) | Server-side encryption |
| License signature | license.signature | HMAC; verified, not returned |
7 Public RMD API (rmd-api/v1)
Base path: /rmd-api/v1. Authentication: X-Api-Key. All endpoints are read-only and controlled by company license (section 5).
7.1 List Projects
GET /projects
Query parameters: page, pageSize, format.
Response (200 OK):
{ "projects": [ { "id": 12, "name": "Sales Analytics", "description": "Production sales warehouse" } ], "pagination": { "total": 1, "page": 1, "pageSize": 20, "totalPages": 1 } }
7.2 List Versions
GET /projects/{id}/versions
Query parameters: page, pageSize, format.
{ "versions": [ { "id": 33, "name": "Q4 2025", "is_global": true } ], "pagination": { "total": 1, "page": 1, "pageSize": 20, "totalPages": 1 } }
is_global indicates a published project version.
7.3 Get Measures
GET /projects/{id}/versions/{versionId}/measures
Query parameters: language, format, page, pageSize.
{ "measures": [ { "row_number": 1, "group": "Revenue", "block": "Sales", "measure_name": "Total revenue", "measure_description": "Gross revenue across all channels", "original_source_type": "Database", "original_source": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_sales", "column": "amount" }, "display_data_type": "Numeric", "measure_type": "base", "formula": null, "status": "Active" } ], "pagination": { "total": 42, "page": 1, "pageSize": 20, "totalPages": 3 } }
The structure of returned keys matches the column layout of RMD (metricsIdToKeyMap in the service). Select fields are returned as localized labels; formula fields contain references replaced from numeric IDs with readable names; select_db fields (source) are structured objects with allowed connection name, database, schema, table, and column.
When requesting format=xlsx, the response body has the form { "downloadUrl": "
7.4 Get Dimensions
GET /projects/{id}/versions/{versionId}/dimensions
Returns dimension rows in the same envelope, with dimension column layout (measurementIdToKeyMap). Query parameters: language, format, page, pageSize.
{ "dimensions": [ { "row_number": 1, "group": "Customer", "block": "Profile", "dimension_name": "Customer name", "dimension_type": "primary", "dimension_group": "Customers", "display_data_type": "Text", "source_data_type": "VARCHAR(255)", "status": "Active" } ], "pagination": { "total": 18, "page": 1, "pageSize": 20, "totalPages": 1 } }
7.5 Get Facts
GET /projects/{id}/versions/{versionId}/facts
Returns fact rows (factsIdToKeyMap). Query parameters: language, format, page, pageSize.
{ "facts": [ { "row_number": 1, "group": "Sales", "block": "Orders", "fact_name": "Order line", "fact_description": "An individual line item on a sales order", "original_source_type": "Database", "original_source": { "connection": "Production PostgreSQL", "db": "analytics_db", "schema": "public", "table": "fact_order_line", "column": null }, "fact_type": "primary" } ], "pagination": { "total": 6, "page": 1, "pageSize": 20, "totalPages": 1 } }
7.6 Get Complete RMD
GET /projects/{id}/versions/{versionId}/rmd
Returns measures, dimensions, and facts in one payload, paginating each section independently.
{ "measures": [ /* see 7.3 */ ], "dimensions":[ /* see 7.4 */ ], "facts": [ /* see 7.5 */ ], "pagination": { "measures": { "total": 42, "page": 1, "pageSize": 20, "totalPages": 3 }, "dimensions": { "total": 18, "page": 1, "pageSize": 20, "totalPages": 1 }, "facts": { "total": 6, "page": 1, "pageSize": 20, "totalPages": 1 } } }
With format=xlsx, measures and dimensions are combined into one workbook with a type column distinguishing rows.
8 Public DF API (df-api/v1)
Base path: /df-api/v1. Authentication: X-Api-Key. All endpoints are read-only.
The DF API is grouped into six logical areas: data marts, connections, dimension groups, fact tables, relationships, and consolidated RMD export. Data mart and connection endpoints return only JSON; dimension group, fact table, relationship, and consolidated export endpoints also support ?format=xlsx (see section 3.5).
8.1 List Data Marts
GET /projects/{project_id}/versions/{version_id}/data-marts
Query parameters:
| Parameter | Type | Description |
|---|---|---|
| page, pageSize, language | — | See section 3 |
| type | string | Filter by data mart type: with_grouping, without_grouping, with_grouping_and_pivoting |
| merge_type | string | Filter by merge type: union, join |
| search | string | Case-insensitive substring search in name and description |
Response (200 OK):
{ "data_marts": [ { "id": "57", "name": "Monthly Sales Mart", "description": "Aggregated by month and region", "owner": "Pavel Shalavin", "created_at": "2026-04-15T08:30:00Z", "type": "with_grouping", "merge_type": "union", "source_fact_table_count": 2, "has_physical_view": true } ], "pagination": { "page": 1, "pageSize": 20, "total": 45, "total_pages": 3 } }
8.2 Get Data Mart Details
GET /projects/{project_id}/versions/{version_id}/data-marts/{data_mart_id}
Returns the complete data mart configuration.
{ "id": "57", "name": "Monthly Sales Mart", "description": "Aggregated by month and region", "owner": "Pavel Shalavin", "created_at": "2026-04-15T08:30:00Z", "type": "with_grouping", "merge_type": "union", "source_fact_tables": [ { "id": "11", "name": "fact_sales", "description": "Primary sales facts" } ], "selected_measures": [ { "instance_id": "204", "measure_id": "501", "measure_name": "Total revenue", "description": "Gross revenue", "formula": "SUM([Amount])", "data_type": "Numeric", "display_name": "Revenue", "aggregation_configuration": { "type": "default", "group_by_fields": [] }, "source_fact_table_id": "11" } ], "selected_facts": [ { "fact_id": "611", "fact_name": "Order line", "data_type": "Numeric", "display_name": "Order line", "include_in_result": true, "filter_condition": null, "source_fact_table_id": "11" } ], "selected_dimensions": [ { "dimension_id": "722", "dimension_name": "Region", "description": "Geographic region", "data_type": "Text", "display_name": "Region", "include_in_result": true, "filter_condition": null, "source_fact_table_id": "11", "source_dimension_group_id": "9", "source_dimension_group_name": "Geography" } ], "physical_view": { "exists": false, "type": null, "database": null, "schema": null, "name": null, "created_at": null } }
For transposed data marts (type = "with_grouping_and_pivoting"), the response additionally contains selected_measure_attributes, where the implicit transposition key Measure name is always added first:
"selected_measure_attributes": [ { "attribute_id": "3", "attribute_name": "Measure name" }, { "attribute_id": "27", "attribute_name": "Measure description" } ]
Field semantics:
| Field | Notes |
|---|---|
| merge_type | null for data marts that do not merge multiple fact tables |
| instance_id | Unique measure instance identifier |
| aggregation_configuration.type | default, none, custom, or global |
| aggregation_configuration.group_by_fields | Identifiers of column-value grouping fields of the measure |
| include_in_result | false — element used only for filtering, not included in projection |
| filter_condition | Filter expression with column-value references replaced by names; null if no filter |
| source_dimension_group_id / source_dimension_group_name | Resolved from data model even if dimension group is not explicitly specified in mart attributes |
8.3 Get Physical View Metadata
GET /projects/{project_id}/versions/{version_id}/data-marts/{data_mart_id}/view
Returns metadata of the physical object materialized by DataForge for the data mart. Does not connect to the target DBMS.
When physical view exists:
{ "exists": true, "type": "materialized_view", "database": "analytics_db", "schema": "marts", "name": "monthly_sales", "created_at": "2026-04-21T09:00:00Z", "connection": { "id": "3", "name": "Production PostgreSQL", "db_type": "postgres" } }
When absent:
{ "exists": false, "type": null, "database": null, "schema": null, "name": null, "created_at": null, "connection": null }
type takes one of: regular_view, materialized_view, table.
8.4 Generate SQL Script
POST /projects/{project_id}/versions/{version_id}/data-marts/{data_mart_id}/generate-sql
Generates a SQL SELECT statement. SQL is only constructed and returned — it is not executed.
Optional request body:
{ "limit": 100, "offset": 0 }
| Parameter | Type | Description |
|---|---|---|
| limit | integer (>0) | Adds row limit in dialect syntax (LIMIT n OFFSET m for PostgreSQL/ClickHouse, OFFSET m ROWS FETCH NEXT n ROWS ONLY for MS SQL). offset without limit is ignored |
| offset | integer (≥0) | Row offset; meaningful together with limit |
For MS SQL, if ORDER BY is absent in the SQL, the service adds ORDER BY (SELECT NULL) for syntactic validity of OFFSET … FETCH NEXT.
Response (200 OK):
{ "sql_script": "SELECT ...\nFROM ...\nGROUP BY ...\nLIMIT 100 OFFSET 0", "target_db_type": "postgres", "validation_errors": [] }
If SQL generation fails (corrupted formulas or missing bindings), the response remains HTTP 200, and the failure is described in validation_errors:
{ "sql_script": "", "target_db_type": null, "validation_errors": [ { "code": "SQL_GENERATION_FAILED", "message": "Measure 'Revenue' references an unresolved field" } ] }
This form allows the client to distinguish between "data mart cannot currently generate SQL" and "data mart does not exist" (which returns 404).
8.5 List Connections
GET /projects/{project_id}/versions/{version_id}/connections
Query parameters:
| Parameter | Type | Description |
|---|---|---|
| page, pageSize, language | — | See section 3 |
| db_type | string | Filter by DBMS type: postgresql, clickhouse, sqlserver |
| status | string | Filter by status: active, inactive, never_verified, failed |
Response (200 OK):
{ "connections": [ { "id": "1", "name": "Production PostgreSQL", "db_type": "postgresql", "host": "db.production.company.com", "port": 5432, "database": "analytics_db", "schema": "public", "username": "analytics_user", "status": "active", "last_updated_at": "2026-04-15T08:30:00Z" }, { "id": "2", "name": "Legacy SQL Server", "db_type": "sqlserver", "host": "sqlserver.legacy.company.com", "port": 1433, "database": "legacy_warehouse", "schema": null, "username": "etl_service", "status": "active", "last_updated_at": "2026-04-14T10:00:00Z" } ], "pagination": { "page": 1, "pageSize": 20, "total": 8, "total_pages": 1 } }
Notes:
- schema is returned only for PostgreSQL. For MS SQL Server, the schema is included in the table name elsewhere in the system; for ClickHouse, the database name is used instead of a schema.
- status is derived from internal state. never_verified takes precedence — without a successful update marker, the system cannot assert state. failed corresponds to internal UPDATE_ERROR. inactive corresponds to DISABLED. Otherwise, status is active.
8.6 Get Connection Details
GET /projects/{project_id}/versions/{version_id}/connections/{connection_id}
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| language | string | en | Localization |
| include_db_schema | boolean | false | When true, lightweight db_tables array is replaced with full db_schema object |
Response (200 OK) — include_db_schema=false:
{ "id": "1", "name": "Production PostgreSQL", "db_type": "postgresql", "host": "db.production.company.com", "port": 5432, "database": "analytics_db", "schema": "public", "username": "analytics_user", "status": "active", "last_updated_at": "2026-04-15T08:30:00Z", "db_tables": [ { "name": "fact_sales" }, { "name": "dim_customer" } ] }
Response (200 OK) — include_db_schema=true:
{ "id": "1", "name": "Production PostgreSQL", "db_type": "postgresql", "host": "db.production.company.com", "port": 5432, "database": "analytics_db", "schema": "public", "username": "analytics_user", "status": "active", "last_updated_at": "2026-04-15T08:30:00Z", "db_schema": { "tables": [ { "table_name": "fact_sales", "schema": "public", "columns": [ { "column_name": "sale_id", "data_type": "int" }, { "column_name": "sale_date", "data_type": "datetime" }, { "column_name": "amount", "data_type": "decimal(18,2)" } ] } ] } }
8.7 Get Connection Schema
GET /projects/{project_id}/versions/{version_id}/connections/{connection_id}/schema
Returns only the cached schema (tables and columns) of the connection.
{ "id": "1", "name": "Production PostgreSQL", "db_type": "postgresql", "last_updated_at": "2026-04-15T08:30:00Z", "schema": { "tables": [ { "table_name": "fact_sales", "schema": "public", "columns": [ { "column_name": "sale_id", "data_type": "int" }, { "column_name": "sale_date", "data_type": "datetime" } ] } ] } }
The returned schema is a cached snapshot taken when the connection was configured or manually updated. It does not reflect real-time changes in the target DBMS. The last_updated_at field records when the snapshot was taken.
8.8 List Dimension Groups
GET /projects/{project_id}/versions/{version_id}/dimension-groups
Returns dimension groups (reference data) of the current project version with their primary key, dimension composition, and identifiers of fact tables referencing the group.
Query parameters: page, pageSize, language, format.
Response (200 OK):
{ "dimension_groups": [ { "id": "9", "name": "Geography", "description": "Geographic regions and countries", "primary_key": { "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" }, "dimensions": [ { "id": "722", "name": "Region", "level": 1, "data_type": "Text", "physical_column": "region_name" }, { "id": "723", "name": "Country", "level": 2, "data_type": "Text", "physical_column": "country_code" } ], "related_fact_tables": ["11", "14"], "created_at": "2026-04-15T08:30:00Z" } ], "pagination": { "page": 1, "pageSize": 20, "total": 12, "total_pages": 1 } }
| Field | Notes |
|---|---|
| primary_key | null if the group does not have a resolved physical key. db and schema may be null for source types that don't have them (e.g., ClickHouse) |
| dimensions | Group members, ordered by hierarchy level. data_type is a localized display type; physical_column is the column name in the source table |
| related_fact_tables | String IDs of fact tables referencing the group; full configuration available via 8.10 / 8.11 |
With format=xlsx, the response is { "downloadUrl": "
8.9 Get Dimension Group Details
GET /projects/{project_id}/versions/{version_id}/dimension-groups/{dimension_group_id}
Returns the complete configuration of a single dimension group, including related fact tables with foreign key columns.
Query parameters: language, format.
Response (200 OK):
{ "id": "9", "name": "Geography", "description": "Geographic regions and countries", "primary_key": { "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" }, "related_fact_tables": [ { "fact_table_id": "11", "fact_table_name": "fact_sales", "foreign_key_column": "region_id" }, { "fact_table_id": "14", "fact_table_name": "fact_inventory", "foreign_key_column": "region_id" } ], "created_at": "2026-04-15T08:30:00Z", "updated_at": "2026-04-21T11:15:00Z" }
If the group does not exist in the specified version, the API returns 404 Not Found with code DF_API.DIMENSION_GROUP_NOT_FOUND.
8.10 List Fact Tables
GET /projects/{project_id}/versions/{version_id}/fact-tables
Returns fact tables of the current project version with item counts.
Query parameters: page, pageSize, language, format.
Response (200 OK):
{ "fact_tables": [ { "id": "11", "name": "fact_sales", "description": "Primary sales facts", "owner": "Pavel Shalavin", "created_at": "2026-04-15T08:30:00Z", "measures_count": 5, "dimensions_count": 3, "facts_count": 2, "verification_filters_count": 2, "related_dimension_groups_count": 1 } ], "pagination": { "page": 1, "pageSize": 20, "total": 6, "total_pages": 1 } }
dimensions_count counts only dimensions added directly to the fact table (i.e., not belonging to any dimension group). Dimensions inherited through groups are available via related_dimension_groups_count and via the details endpoint.
With format=xlsx, the workbook contains one sheet Fact Tables.
8.11 Get Fact Table Details
GET /projects/{project_id}/versions/{version_id}/fact-tables/{fact_table_id}
Returns the complete fact table configuration — its measures, dimensions, facts, dimension groups (with primary and foreign keys), and verification filters.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| language | string | en | Localization |
| include_dependencies | boolean | false | When true, each measure carries a recursive dependencies tree, resolving formula references to target measures |
| format | string | json | json or xlsx |
Response (200 OK):
{ "id": "11", "name": "fact_sales", "description": "Primary sales facts", "owner": "Pavel Shalavin", "created_at": "2026-04-15T08:30:00Z", "updated_at": "2026-04-21T09:00:00Z", "measures": [ { "id": "501", "name": "Total revenue", "description": "Gross revenue", "formula": "SUM([Amount])", "data_type": "Numeric", "measure_type": "base", "dependencies": null } ], "dimensions": [ { "id": "801", "name": "Sale date", "description": "Calendar date of the sale", "data_type": "Date", "physical_column": "sale_date", "is_from_dimension_group": false, "dimension_group_id": null } ], "facts": [ { "id": "611", "name": "Order line", "description": null, "data_type": "Numeric", "fact_type": "primary", "formula": null, "physical_column": "amount" } ], "dimension_groups": [ { "id": "9", "name": "Geography", "description": "Geographic regions and countries", "primary_key": { "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" }, "foreign_key": { "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 } ] }
| Field | Notes |
|---|---|
| measure_type | base (direct source) or calculated (derived by formula) |
| fact_type | primary, derived, or constant |
| is_from_dimension_group | true — dimension inherited from a linked group; false — set directly on the fact table |
| dependencies | Present and populated only when include_dependencies=true. Node contains id, name, type, formula, and recursive dependencies array |
| verification_filters[].is_valid | false if filter expression does not resolve (missing reference, syntax error). invalid_reason carries a localized explanation |
With format=xlsx, the workbook contains five sheets: Measures, Dimensions, Facts, Dimension Groups, Verification Filters.
If the fact table does not exist in the specified version, the API returns 404 Not Found with code DF_API.FACT_TABLE_NOT_FOUND.
8.12 List Relationships
GET /projects/{project_id}/versions/{version_id}/relationships
Returns foreign key relationships between fact tables and dimension groups in the current project version.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
| page, pageSize, language | — | See section 3 |
| fact_table_id | integer | Filter: relationships with the specified fact table as source |
| dimension_group_id | integer | Filter: relationships with the specified dimension group as target |
| format | string | json or xlsx |
Response (200 OK):
{ "relationships": [ { "id": "1101", "source_fact_table": { "id": "11", "name": "fact_sales" }, "target_dimension_group": { "id": "9", "name": "Geography" }, "foreign_key": { "db": "analytics_db", "schema": "public", "table": "fact_sales", "column": "region_id" }, "primary_key": { "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" }, "relationship_type": "many_to_one", "created_at": "2026-04-15T08:30:00Z" } ], "pagination": { "page": 1, "pageSize": 20, "total": 4, "total_pages": 1 } }
relationship_type describes the multiplicity from source (fact table) to target (dimension group). foreign_key and primary_key may be null if the corresponding mapping is incomplete; in this case the relationship is visible but not usable for constructing SQL joins.
8.13 Get Relationship Details
GET /projects/{project_id}/versions/{version_id}/relationships/{relationship_id}
Returns a single relationship with descriptive fields for both sides.
Query parameters: language, format.
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": { "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" } }, "foreign_key": { "db": "analytics_db", "schema": "public", "table": "fact_sales", "column": "region_id" }, "primary_key": { "db": "analytics_db", "schema": "public", "table": "dim_geography", "column": "region_id" }, "relationship_type": "many_to_one", "created_at": "2026-04-15T08:30:00Z", "updated_at": "2026-04-21T11:15:00Z" }
If the relationship does not exist in the specified version, the API returns 404 Not Found with code DF_API.RELATIONSHIP_NOT_FOUND.
8.14 Consolidated RMD Export
GET /projects/{project_id}/versions/{version_id}/rmd
Returns a complete snapshot of the project version's metadata — RMD content (measures, dimensions, facts) and data model (dimension groups, fact tables, relationships) — in one payload plus an exported_at timestamp. Intended for external catalogs and full state synchronization.
Query parameters: language, format.
Response (200 OK):
{ "project": { "id": "12", "name": "Sales Analytics", "description": "Production sales warehouse" }, "version": { "id": "33", "name": "Q4 2025", "is_global": true }, "measures": [ /* same row format as in 7.3 */ ], "dimensions": [ /* same row format as in 7.4 */ ], "facts": [ /* same row format as in 7.5 */ ], "dimension_groups": [ /* same row format as in 8.8 */ ], "fact_tables": [ /* same row format as in 8.10 */ ], "relationships": [ /* same row format as in 8.12 */ ], "exported_at": "2026-05-05T08:30:00Z" }
With format=xlsx, the workbook contains six sheets — Measures, Dimensions, Facts, Dimension Groups, Fact Tables, Relationships — and the response body has the form { "downloadUrl": "
This endpoint is the public counterpart of the legacy rmd-api/v1 /rmd (7.6), extended with data model entities. Either endpoint can be used for RMD content; the data model is only exposed by the DF API endpoint.
9 Audit Logging
Authentication events in the public API are recorded with type, action, actor, source IP, target, and a free-form what field:
| Event | Type | Action | Trigger |
|---|---|---|---|
| Successful API authentication | API_ACCESS | API_ACCESS_SUCCESSFUL | Public API request passes all ApiKeyGuard checks |
| Unsuccessful API authentication | API_ACCESS | API_ACCESS_FAILED | Account locked, IP blocked, or license invalid |
Audit log fields:
| Field | Value |
|---|---|
| type | Event category (API_ACCESS) |
| action | Specific action within category |
| who | Actor email (API key owner) |
| from_where | Client IP (respects X-Forwarded-For) |
| where | Company name |
| what | Endpoint group (RmdApi v1 / DfApi v1) |
| before, after | Diff for change events (not used in read-only API) |
The log retention period is set by company settings; records are available on the Audit Log screen to users with Company Admin or Super Admin role. Internal API audit events (UI login, license management, etc.) are described in chapter 8.
10 Error Code Reference
The table summarizes public API error codes.
| Code | HTTP | Surface | Description |
|---|---|---|---|
| API_KEY.KEY_MISSING | 401 | both | X-Api-Key header not provided |
| API_KEY.INVALID_KEY | 401 | both | API key does not match any stored key |
| API_KEY.INVALID_ENCRYPTED_API_KEY | 400 | both | Encrypted key payload could not be decoded |
| API_KEY.AUTH_FAILED | 401 | both | General authentication failure |
| API_KEY.ACCOUNT_LOCKED | 403 | both | User is not in ENABLED status |
| API_KEY.IP_BLOCKED | 403 | both | Client IP in blacklist or outside whitelist |
| API.NOT_AVAILABLE_WITHOUT_VALID_LICENSE | 403 | both | Company does not have an active license |
| PROJECT.NOT_FOUND | 404 | rmd-api | Project does not exist or is unavailable |
| VERSION.NOT_FOUND | 404 | rmd-api | Version does not exist or is unavailable |
| DF_API.INVALID_PARAMETER | 400 | df-api | Path or query parameter failed validation |
| DF_API.PAGE_SIZE_EXCEEDED | 400 | df-api | pageSize exceeds 100 |
| DF_API.INVALID_TYPE | 400 | df-api | Invalid type filter value for data marts |
| DF_API.INVALID_MERGE_TYPE | 400 | df-api | Invalid merge_type filter value for data marts |
| DF_API.INVALID_DB_TYPE | 400 | df-api | Invalid db_type filter value for connections |
| DF_API.INVALID_STATUS | 400 | df-api | Invalid status filter value for connections |
| DF_API.INVALID_FORMAT | 400 | df-api | format differs from json and xlsx |
| DF_API.PROJECT_NOT_FOUND | 404 | df-api | Project does not exist or is unavailable |
| DF_API.VERSION_NOT_FOUND | 404 | df-api | Version does not exist or is unavailable |
| DF_API.DATA_MART_NOT_FOUND | 404 | df-api | Data mart does not exist in the specified version |
| DF_API.CONNECTION_NOT_FOUND | 404 | df-api | Connection does not exist in the specified version |
| DF_API.DIMENSION_GROUP_NOT_FOUND | 404 | df-api | Dimension group does not exist in the specified version |
| DF_API.FACT_TABLE_NOT_FOUND | 404 | df-api | Fact table does not exist in the specified version |
| DF_API.RELATIONSHIP_NOT_FOUND | 404 | df-api | Relationship does not exist in the specified version |
| SQL_GENERATION_FAILED | 200 (in validation_errors) | df-api | Data mart cannot currently generate SQL |
| RATE_LIMIT_EXCEEDED | 429 | both | Request quota per key exceeded (planned) |
| INTERNAL_ERROR | 500 | both | Unexpected server error |