Save domain template
Saves changes to a domain's template. You can send the whole template or only the sections you want to change.
The template field accepts a JSON object or a YAML string. Set
format to "yaml" when sending YAML; if omitted, the format is
detected (JSON is tried first, then YAML).
Sections. A 2.1 template has these top-level sections: id,
name, label, description, aiContext, datasets,
relationships, metrics, composedDimensions and
grainMappings. Each section you send replaces the stored one; a
section sent empty ([]) clears it.
Partial updates (default): When preserveOmittedSections is
true (the default), every section you leave out is kept. For
example, you can send only metrics without losing datasets or
relationships.
Full replacement: Set preserveOmittedSections to false to
replace the whole template. Any section you omit is cleared.
2.0 bodies. Bodies in the 2.0 shape (schemaVersion: "2.0",
calculatedMetrics, calculatedDimensions, datasets with type or
fields.metrics, relationships with sourceDataset) are still
accepted. The server upgrades them to 2.1 before saving. In a 2.0
body, datasets replaces the datasets and every dataset metric,
calculatedMetrics replaces every composed metric, and
calculatedDimensions replaces composedDimensions. Data that 2.0
can't express (context, uniqueKeys, exact database types) is kept
on the objects it belongs to. If a field now reads a different
column or source, its stored database type is cleared and the
response carries a native_type_needs_sync warning.
Mixed bodies are refused. A body that combines 2.0 keys with 2.1
keys (for example, calculatedMetrics together with metrics)
returns 400 with the code mixed_schema_versions. Send one
version.
The response always returns the saved template in the 2.1 shape.
Validation errors return 400 with an errors list; warnings,
such as relationship_keys_unmatched, come back with a successful
save.
Clearing the label or description: Send label: null or
description: null in the template.
Authorization
BearerAuth A project-scoped access token. Generate one via the Token API with your projectId and projectSecret.
In: header
Path Parameters
The unique identifier of the semantic domain.
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X put "https://semaphor.cloud/api/v1/domains/domain_abc123/template" \ -H "Content-Type: application/json" \ -d '{ "template": { "schemaVersion": "2.1", "metrics": [ { "name": "revenue", "dataset": "orders", "label": "Revenue", "expression": "SUM(amount)", "dataType": "number" }, { "name": "net_revenue", "label": "Net Revenue", "expression": "{sales} - COALESCE({returns}, 0)", "inputs": { "sales": { "metric": "revenue", "dataset": "orders" }, "returns": { "dataset": "returns", "field": "amount", "aggregate": "SUM" } } } ] }, "preserveOmittedSections": true }'{
"save": {
"success": true
},
"template": {
"schemaVersion": "2.1",
"id": "domain_abc123",
"name": "sales_metrics",
"label": "Sales Metrics",
"description": "Revenue and order tracking",
"aiContext": "Revenue is net of refunds. Use order_date for time questions.",
"datasets": [
{
"name": "orders",
"label": "Orders",
"description": "string",
"aiContext": "Revenue is net of refunds. Use order_date for time questions.",
"analyticsRole": "fact",
"source": {
"kind": "table",
"connectionId": "conn_1",
"connectionType": "PostgreSQL",
"database": "analytics",
"schema": "public",
"table": "orders"
},
"primaryKey": [
"order_id"
],
"uniqueKeys": [
[
"order_number"
]
],
"primaryDateField": "order_date",
"fields": {
"identifiers": [
{
"name": "amount",
"label": "Amount",
"description": "string",
"aiContext": "Revenue is net of refunds. Use order_date for time questions.",
"dataType": "number",
"nativeDataType": "numeric(12,2)",
"sourceField": "amount",
"hidden": true,
"tags": [
"string"
],
"type": "primary"
}
],
"facts": [
{
"name": "amount",
"label": "Amount",
"description": "string",
"aiContext": "Revenue is net of refunds. Use order_date for time questions.",
"dataType": "number",
"nativeDataType": "numeric(12,2)",
"sourceField": "amount",
"hidden": true,
"tags": [
"string"
],
"expression": "amount_cents / 100",
"defaultAggregate": "SUM",
"filters": [
{
"field": "string",
"operator": "string",
"value": null
}
],
"format": null,
"unit": "USD",
"comparisonPolarity": "higher_is_better",
"isDefault": true
}
],
"dimensions": [
{
"name": "amount",
"label": "Amount",
"description": "string",
"aiContext": "Revenue is net of refunds. Use order_date for time questions.",
"dataType": "number",
"nativeDataType": "numeric(12,2)",
"sourceField": "amount",
"hidden": true,
"tags": [
"string"
],
"expression": "string",
"isDisplayField": true,
"granularity": "second",
"format": {
"type": "temporal_bucket",
"presentation": {
"mode": "auto"
}
}
}
]
}
}
],
"relationships": [
{
"id": "string",
"name": "orders_to_customers",
"description": "string",
"aiContext": "Revenue is net of refunds. Use order_date for time questions.",
"from": {
"dataset": "orders",
"fields": [
"customer_id"
]
},
"to": {
"dataset": "orders",
"fields": [
"customer_id"
]
},
"cardinality": "many_to_one",
"defaultJoinType": "LEFT",
"isAutoJoin": true,
"joinPriority": 10,
"isActive": true,
"discoveredBy": "user",
"confidence": "high"
}
],
"metrics": [
{
"name": "revenue",
"dataset": "orders",
"expression": "SUM(amount)",
"label": "Revenue",
"description": "string",
"aiContext": "Revenue is net of refunds. Use order_date for time questions.",
"dataType": "number",
"nativeDataType": "string",
"format": null,
"unit": "string",
"comparisonPolarity": "higher_is_better",
"isDefault": true,
"hidden": true,
"tags": [
"string"
]
}
],
"composedDimensions": [
{
"name": "order_month",
"label": "Order Month",
"description": "string",
"aiContext": "Revenue is net of refunds. Use order_date for time questions.",
"expression": "DATE_TRUNC('month', {order_date})",
"inputs": {
"order_date": {
"dataset": "orders",
"field": "order_date"
}
},
"dataType": "string",
"granularity": "second",
"format": {
"type": "temporal_bucket",
"presentation": {
"mode": "auto"
}
},
"grainMapping": {
"property1": "string",
"property2": "string"
}
}
],
"grainMappings": [
{
"sourceDimension": "order_month",
"targetDataset": "returns",
"targetColumn": "return_date"
}
]
},
"warnings": [
{
"code": "row_expression_in_dataset_metric",
"message": "margin works row by row. Add it as a fact (to aggregate) or a dimension (to group by).",
"severity": "error",
"hint": "string",
"context": {}
}
]
}{
"error": "string",
"details": {},
"errors": [
{
"code": "row_expression_in_dataset_metric",
"message": "margin works row by row. Add it as a fact (to aggregate) or a dimension (to group by).",
"severity": "error",
"hint": "string",
"context": {}
}
],
"warnings": [
{
"code": "row_expression_in_dataset_metric",
"message": "margin works row by row. Add it as a fact (to aggregate) or a dimension (to group by).",
"severity": "error",
"hint": "string",
"context": {}
}
]
}{
"error": "string",
"details": {},
"errors": [
{
"code": "row_expression_in_dataset_metric",
"message": "margin works row by row. Add it as a fact (to aggregate) or a dimension (to group by).",
"severity": "error",
"hint": "string",
"context": {}
}
],
"warnings": [
{
"code": "row_expression_in_dataset_metric",
"message": "margin works row by row. Add it as a fact (to aggregate) or a dimension (to group by).",
"severity": "error",
"hint": "string",
"context": {}
}
]
}{
"error": "string",
"details": {},
"errors": [
{
"code": "row_expression_in_dataset_metric",
"message": "margin works row by row. Add it as a fact (to aggregate) or a dimension (to group by).",
"severity": "error",
"hint": "string",
"context": {}
}
],
"warnings": [
{
"code": "row_expression_in_dataset_metric",
"message": "margin works row by row. Add it as a fact (to aggregate) or a dimension (to group by).",
"severity": "error",
"hint": "string",
"context": {}
}
]
}{
"error": "string",
"details": {},
"errors": [
{
"code": "row_expression_in_dataset_metric",
"message": "margin works row by row. Add it as a fact (to aggregate) or a dimension (to group by).",
"severity": "error",
"hint": "string",
"context": {}
}
],
"warnings": [
{
"code": "row_expression_in_dataset_metric",
"message": "margin works row by row. Add it as a fact (to aggregate) or a dimension (to group by).",
"severity": "error",
"hint": "string",
"context": {}
}
]
}