Semaphor

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.

PUT
/api/v1/domains/{domainId}/template

Authorization

BearerAuth
AuthorizationBearer <token>

A project-scoped access token. Generate one via the Token API with your projectId and projectSecret.

In: header

Path Parameters

domainId*string

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": {}
    }
  ]
}