2026-07-26 19:33:29 +08:00
# Semantic Authoring API Reference
Complete REST API documentation for creating calculated fields, dimensions, and metrics on Semantic Data Models.
## Base URL
All endpoints use the base URL:
```
https://{instance}.salesforce.com/services/data/v66.0
```
Where `{instance}` is your Salesforce instance (e.g., `myorg` for `myorg.salesforce.com` )
**Important:** Semantic authoring endpoints do NOT use `minorVersion` query parameter (unlike visualization/dashboard endpoints).
## Authentication
All requests require Bearer token authentication:
```bash
-H "Authorization: Bearer {access_token}"
-H "Content-Type: application/json"
```
**Getting an Access Token (using SF CLI):**
```bash
export SF_ORG=myorg
export SF_TOKEN=$(sf org auth show-access-token --target-org $SF_ORG --json | jq -r '.result.accessToken')
export SF_INSTANCE=$(sf org display --target-org $SF_ORG --json | jq -r '.result.instanceUrl')
```
**Required Permissions:**
- View Semantic Models
- Create/Edit Semantic Models
---
## Discovery Endpoints
### List Semantic Models
Get all semantic models available to the authenticated user.
**Endpoint:** `GET /ssot/semantic/models`
**Request:**
```bash
curl -X GET \
"${SF_INSTANCE}/services/data/v66.0/ssot/semantic/models" \
-H "Authorization: Bearer ${SF_TOKEN}"
```
**Response:**
```json
{
"semantic_models": [
{
"id": "0FKxx0000000001",
"apiName": "Sales_Cloud12_backward",
"label": "Sales Analytics",
"description": "Sales performance metrics and trends",
"dataspace": "default",
"categories": ["Sales"],
"createdDate": "2024-01-15T10:30:00Z",
"lastModifiedDate": "2024-02-20T14:45:00Z"
}
],
"count": 1
}
```
### Get Semantic Model Definition
Retrieve complete structure of a semantic model including objects, dimensions, measurements, and metrics.
**Endpoint:** `GET /ssot/semantic/models/{sdmApiNameOrId}`
**Request:**
```bash
curl -X GET \
"${SF_INSTANCE}/services/data/v66.0/ssot/semantic/models/Sales_Cloud12_backward" \
-H "Authorization: Bearer ${SF_TOKEN}"
```
**Response Structure:**
```json
{
"id": "0FKxx0000000001",
"apiName": "Sales_Cloud12_backward",
"label": "Sales Analytics",
"dataspace": "default",
"semanticDataObjects": [
{
"apiName": "Opportunity_TAB_Sales_Cloud",
"label": "Opportunities",
"semanticDimensions": [
{
"apiName": "Region",
"label": "Region",
"fieldName": "Region__c",
"dataType": "Text",
"objectName": "Opportunity_TAB_Sales_Cloud"
}
],
"semanticMeasurements": [
{
"apiName": "Amount",
"label": "Amount",
"fieldName": "Amount",
"dataType": "Number",
"aggregationType": "Sum",
"decimalPlace": 2,
"objectName": "Opportunity_TAB_Sales_Cloud"
}
]
}
],
"calculatedMeasurements": [
{
"apiName": "Total_Revenue_clc",
"label": "Total Revenue",
"expression": "SUM([Amount])",
"aggregationType": "Sum",
"dataType": "Number"
}
],
"calculatedDimensions": [
{
"apiName": "Deal_Size_Category_clc",
"label": "Deal Size Category",
"expression": "IF [Amount] > 100000 THEN 'Large' ELSE 'Small' END",
"dataType": "Text"
}
],
"semanticMetrics": [
{
"apiName": "Total_Revenue_mtc",
"label": "Total Revenue",
"measurementReference": {
"calculatedFieldApiName": "Total_Revenue_clc"
},
"timeDimensionReference": {
"tableFieldReference": {
"fieldApiName": "Close_Date",
"tableApiName": "Opportunity_TAB_Sales_Cloud"
}
},
"timeGrains": ["Day", "Week", "Month", "Quarter", "Year"]
}
]
}
```
---
## Creation Endpoints
### Create Calculated Measurement
Create a calculated measurement field on an SDM.
**Endpoint:** `POST /ssot/semantic/models/{{modelName}}/calculated-measurements`
**Request body:**
```json
{
"apiName": "Total_Revenue_clc",
"label": "Total Revenue",
"expression": "SUM([Amount])",
"aggregationType": "Sum",
"dataType": "Number",
"decimalPlace": 2,
"description": "Total revenue from all opportunities"
}
```
**curl example:**
```bash
curl -X POST \
"${SF_INSTANCE}/services/data/v66.0/ssot/semantic/models/Sales_Cloud12_backward/calculated-measurements" \
-H "Authorization: Bearer ${SF_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"apiName": "Total_Revenue_clc",
"label": "Total Revenue",
"expression": "SUM([Amount])",
"aggregationType": "Sum",
"dataType": "Number",
"decimalPlace": 2
}'
```
**Required fields:**
- `apiName` (must end with `_clc` , no double underscores)
- `label`
- `expression` (Tableau formula)
- `aggregationType` (Sum, Avg, Min, Max, Count, UserAgg)
- `dataType` (Number, Text, Date, Boolean)
**Optional fields:**
- `description`
- `decimalPlace` (for Number type, default 2)
**Response:**
```json
{
"id": "0Fmxx0000000001",
"apiName": "Total_Revenue_clc",
"label": "Total Revenue",
"success": true
}
```
### Create Calculated Dimension
Create a calculated dimension field on an SDM.
**Endpoint:** `POST /ssot/semantic/models/{{modelName}}/calculated-dimensions`
**Request body:**
```json
{
"apiName": "Deal_Size_Category_clc",
"label": "Deal Size Category",
"expression": "IF [Amount] > 100000 THEN 'Large' ELSEIF [Amount] > 50000 THEN 'Medium' ELSE 'Small' END",
"dataType": "Text",
"description": "Categorizes deals by size"
}
```
**curl example:**
```bash
curl -X POST \
"${SF_INSTANCE}/services/data/v66.0/ssot/semantic/models/Sales_Cloud12_backward/calculated-dimensions" \
-H "Authorization: Bearer ${SF_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"apiName": "Deal_Size_Category_clc",
"label": "Deal Size Category",
"expression": "IF [Amount] > 100000 THEN '\''Large'\'' ELSEIF [Amount] > 50000 THEN '\''Medium'\'' ELSE '\''Small'\'' END",
"dataType": "Text"
}'
```
**Required fields:**
- `apiName` (must end with `_clc` , no double underscores)
- `label`
- `expression` (Tableau formula)
- `dataType` (Text, Date, Boolean)
**Optional fields:**
- `description`
**Note:** Dimensions don't require `aggregationType` (only measurements do).
### Create Semantic Metric
Create a semantic metric on an SDM. Metrics reference existing calculated fields.
**Endpoint:** `POST /ssot/semantic/models/{{modelName}}/metrics`
**Request body (basic):**
```json
{
"apiName": "Total_Revenue_mtc",
"label": "Total Revenue",
"aggregationType": "UserAgg",
"measurementReference": {
"calculatedFieldApiName": "Total_Revenue_clc"
},
"timeDimensionReference": {
"tableFieldReference": {
"fieldApiName": "Close_Date",
"tableApiName": "Opportunity_TAB_Sales_Cloud"
}
},
"timeGrains": ["Day", "Week", "Month", "Quarter", "Year"],
"filters": [],
"isCumulative": false,
"isGoalEditingBlocked": false
}
```
**curl example:**
```bash
curl -X POST \
"${SF_INSTANCE}/services/data/v66.0/ssot/semantic/models/Sales_Cloud12_backward/metrics" \
-H "Authorization: Bearer ${SF_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"apiName": "Total_Revenue_mtc",
"label": "Total Revenue",
"aggregationType": "UserAgg",
"measurementReference": {
"calculatedFieldApiName": "Total_Revenue_clc"
},
"timeDimensionReference": {
"tableFieldReference": {
"fieldApiName": "Close_Date",
"tableApiName": "Opportunity_TAB_Sales_Cloud"
}
},
"timeGrains": ["Day", "Week", "Month", "Quarter", "Year"],
"filters": [],
"isCumulative": false,
"isGoalEditingBlocked": false
}'
```
**Required fields:**
- `apiName` (must end with `_mtc` , no double underscores)
- `label`
- `aggregationType` (typically `UserAgg` )
- `measurementReference.calculatedFieldApiName` (must exist on SDM)
- `timeDimensionReference` (field + table for time-based analysis)
- `timeGrains` (array of: "Day", "Week", "Month", "Quarter", "Year")
**Optional fields:**
- `description`
- `additionalDimensions` (for breakdown analysis)
- `insightsSettings` (auto-generated if not provided)
- `filters`
- `isCumulative` (default false)
- `isGoalEditingBlocked` (default false)
- `sentiment` (SentimentTypeUpIsGood, SentimentTypeUpIsBad, SentimentTypeNone)
**Request body (with additional dimensions):**
```json
{
"apiName": "Revenue_by_Region_mtc",
"label": "Revenue by Region",
"aggregationType": "UserAgg",
"measurementReference": {
"calculatedFieldApiName": "Total_Revenue_clc"
},
"timeDimensionReference": {
"tableFieldReference": {
"fieldApiName": "Close_Date",
"tableApiName": "Opportunity_TAB_Sales_Cloud"
}
},
"timeGrains": ["Day", "Week", "Month", "Quarter", "Year"],
"additionalDimensions": [
{
"tableFieldReference": {
"fieldApiName": "Region",
"tableApiName": "Opportunity_TAB_Sales_Cloud"
}
}
],
"insightsSettings": {
"insightTypes": [
{"enabled": true, "type": "TopContributors"},
{"enabled": true, "type": "TrendChangeAlert"},
{"enabled": true, "type": "BottomContributors"}
],
"insightsDimensionsReferences": [
{
"tableFieldReference": {
"fieldApiName": "Region",
"tableApiName": "Opportunity_TAB_Sales_Cloud"
}
}
],
"pluralNoun": "regions",
"singularNoun": "region",
"sentiment": "SentimentTypeUpIsGood"
},
"filters": [],
"isCumulative": false,
"isGoalEditingBlocked": false
}
```
**Note:** `additionalDimensions` can reference either `tableFieldReference` (SDM fields) or `calculatedFieldApiName` (calculated dimensions).
**The `additionalDimensions` superset rule.** Every field referenced by `insightsSettings.identifyingDimension` , `insightsSettings.insightsDimensionsReferences[]` , or `filters[].fieldName` MUST also appear in top-level `additionalDimensions[]` . Two failure modes:
1. Insight / identifying dimension missing → **create fails** : `Validation Failed: ... Insight dimension (<Table>.<Field>) is missing from the metric additional dimensions.`
2. Filter field missing → **create succeeds but the metric is unqueryable** : `Metric Definition Filter Field <Field> is not found in Metric Definition.`
Downstream builders that consume this contract should enforce this pre-POST and auto-mirror `--identifying-dimension` / `--filter` fields into `additionalDimensions` .
**`identifyingDimension`.** The Tableau Next metric UI dereferences `insightsSettings.identifyingDimension` on load and crashes if it is absent. The builder emits it, defaulting to the first additional dimension:
```json
"insightsSettings": {
"identifyingDimension": {
"identifierDimensionReference": {
"tableFieldReference": {
"fieldApiName": "Region",
"tableApiName": "Opportunity_TAB_Sales_Cloud"
}
}
}
}
```
**Metric filters.** A non-empty `filters[]` requires a sibling `filterLogic` and fully-qualified `fieldName` values:
```json
"filters": [
{
"fieldName": "Opportunity_TAB_Sales_Cloud.Region",
"operator": "Equals",
"values": ["West"]
}
],
"filterLogic": "1"
```
- `fieldName` must be qualified `Table.Field` (a bare name triggers "is not found in Metric Definition").
- `filterLogic` is `"1"` for one filter, `"1 AND 2"` for two, etc.
- Each filter field is auto-mirrored into `additionalDimensions` (the superset rule).
- **Operator enum** (verified live against a v66.0 org, 2026-06-23): `Equals` , `In` , `NotIn` , `Contains` , `NotContains` , `GreaterThan` , `LessThan` , `Between` , `StartsWith` . The server uses these CamelCase names and **rejects** SQL-style `EQUAL` /`GREATER_THAN_OR_EQUAL`/`NOT_EQUAL`; there is **no** `>=` , `<=` , or `!=` operator.
---
## Error Responses
2026-07-31 00:11:14 +08:00
Salesforce API error responses come in two shapes depending on the endpoint.
**Array form (most common):** a top-level JSON array of one or more error objects.
2026-07-26 19:33:29 +08:00
```json
2026-07-31 00:11:14 +08:00
[
{
"errorCode": "INVALID_API_INPUT",
"message": "Field 'Amount' is not found in the semantic model.",
"fields": ["dataObjectFields"]
2026-07-26 19:33:29 +08:00
}
2026-07-31 00:11:14 +08:00
]
```
**Single-object form:** a bare JSON object (used by some endpoints and by the platform for auth/session errors).
```json
{
"errorCode": "INVALID_SESSION_ID",
"message": "Session expired or invalid.",
"fields": []
2026-07-26 19:33:29 +08:00
}
```
2026-07-31 00:11:14 +08:00
A `localizedMessage` key may appear in place of (or alongside) `message` in platform-level errors.
The `fields` array is present when the error is scoped to specific request fields; it is omitted or empty otherwise.
2026-07-26 19:33:29 +08:00
**Common Error Codes:**
- `INVALID_TOKEN` : Authentication token is invalid or expired
- `INSUFFICIENT_PERMISSIONS` : User lacks required permissions
- `RESOURCE_NOT_FOUND` : SDM not found
- `INVALID_FIELD` : Field reference doesn't exist in SDM
- `INVALID_JSON` : Malformed JSON structure
- `VALIDATION_ERROR` : JSON structure valid but business rules violated
- `DUPLICATE_API_NAME` : API name already exists on SDM
---
## Common Validation Rules
### API Name Rules
- Must end with `_clc` (calculated fields) or `_mtc` (metrics)
- Cannot contain double underscores (`__`)
- Must be unique within the SDM
- 1-80 characters, alphanumeric + underscore only
**Valid:**
- `Total_Revenue_clc`
- `Win_Rate_mtc`
**Invalid:**
- `Total__Revenue_clc` (double underscore)
- `Total_Revenue` (missing suffix)
- `Total Revenue_clc` (space not allowed)
### Expression Rules
- Must use valid Tableau functions
- Field references must exist in SDM: `[Field_Name]`
- String literals use single quotes: `'Large'`
- Case-insensitive function names: `SUM` , `Sum` , `sum` all work
### Aggregation Type Rules
- Required for measurements, not for dimensions
- Use `UserAgg` when expression includes aggregation functions
- Don't use `UserAgg` for simple field references
### Field-Reference Rules
- **Read back the auto-bind– suffixed apiName; never guess it.** Binding a field auto-suffixes its apiName *even with no collision* — `Region` → `Region5` , `Revenue` → `Revenue5` . The suffix is unpredictable. After creating/binding a field, read the real `apiName` from `discover_sdm.py --json` and use it verbatim; guessing yields "Field not found" or an opaque failure.
- **Calculated measures are model-level — omit `objectName` .** A calculated measure (`_clc`) is not bound to a single data object. When referencing it (e.g. in viz creation), omit `objectName` (leave it `null` ). Supplying any `objectName` for a calc measure returns `UNKNOWN_EXCEPTION` . Raw SDM fields, by contrast, carry `objectName` = the owning `semanticDataObjects[].apiName` .
- **The server does no field-role validation.** It accepts a field in the wrong role (Text dimension as a measure, numeric measure as a dimension) and fails opaquely later with `UNKNOWN_EXCEPTION` . Validate roles client-side from discovery before constructing the call.
---
## Complete Workflow Example
**Scenario:** Create a win rate metric with regional breakdown.
**Step 1: Discover SDM**
```bash
curl -X GET \
"${SF_INSTANCE}/services/data/v66.0/ssot/semantic/models/Sales_Cloud12_backward" \
-H "Authorization: Bearer ${SF_TOKEN}"
```
**Step 2: Create calculated field (win rate)**
```bash
curl -X POST \
"${SF_INSTANCE}/services/data/v66.0/ssot/semantic/models/Sales_Cloud12_backward/calculated-measurements" \
-H "Authorization: Bearer ${SF_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"apiName": "Win_Rate_clc",
"label": "Win Rate",
"expression": "SUM([Won_Count]) / SUM([Total_Count])",
"aggregationType": "UserAgg",
"dataType": "Number",
"decimalPlace": 4
}'
```
**Step 3: Create metric with regional breakdown**
```bash
curl -X POST \
"${SF_INSTANCE}/services/data/v66.0/ssot/semantic/models/Sales_Cloud12_backward/metrics" \
-H "Authorization: Bearer ${SF_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"apiName": "Win_Rate_by_Region_mtc",
"label": "Win Rate by Region",
"aggregationType": "UserAgg",
"measurementReference": {
"calculatedFieldApiName": "Win_Rate_clc"
},
"timeDimensionReference": {
"tableFieldReference": {
"fieldApiName": "Close_Date",
"tableApiName": "Opportunity_TAB_Sales_Cloud"
}
},
"timeGrains": ["Day", "Week", "Month", "Quarter", "Year"],
"additionalDimensions": [
{
"tableFieldReference": {
"fieldApiName": "Region",
"tableApiName": "Opportunity_TAB_Sales_Cloud"
}
}
],
"filters": [],
"isCumulative": false,
"isGoalEditingBlocked": false
}'
```
**Step 4: Verify creation**
```bash
curl -X GET \
"${SF_INSTANCE}/services/data/v66.0/ssot/semantic/models/Sales_Cloud12_backward" \
-H "Authorization: Bearer ${SF_TOKEN}"
```
Check response for `calculatedMeasurements` and `semanticMetrics` arrays to verify your new field and metric appear.
---
## Rate Limits
Salesforce API rate limits apply:
- **Standard:** 15,000 API requests per 24 hours per org
- **Unlimited:** 25,000 API requests per 24 hours per org
**Best Practices:**
- Batch field creation when possible
- Cache SDM definitions (they change infrequently)
- Use dry-run mode (`--dry-run` flag) to validate payloads before POSTing
---
## Additional Resources
- [Salesforce Semantic Layer API Docs ](https://developer.salesforce.com/docs/data/semantic-layer )
- [OAuth 2.0 Authentication Guide ](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_flows.htm )
- [SKILL.md ](../SKILL.md ) - Main workflow guide
- [tableau-functions.md ](tableau-functions.md ) - Complete function reference
## Endpoint Quick Reference (all in one place)
```
Discovery (no minor version):
GET /services/data/v66.0/ssot/semantic/models
GET /services/data/v66.0/ssot/semantic/models/{sdmName}
Build an SDM from scratch (no minor version):
POST /services/data/v66.0/ssot/semantic/models (create, anchor)
POST /services/data/v66.0/ssot/semantic/models/{sdmName}/data-objects (add object)
POST /services/data/v66.0/ssot/semantic/models/{sdmName}/data-objects/{obj}/dimensions
POST /services/data/v66.0/ssot/semantic/models/{sdmName}/data-objects/{obj}/measurements
POST /services/data/v66.0/ssot/semantic/models/{sdmName}/relationships (add join)
POST /services/data/v66.0/semantic-engine/gateway (cross-object query)
DELETE /services/data/v66.0/ssot/semantic/models/{sdmName} (cleanup)
Enrichment (no minor version):
POST /services/data/v66.0/ssot/semantic/models/{sdmName}/calculated-measurements
POST /services/data/v66.0/ssot/semantic/models/{sdmName}/calculated-dimensions
POST /services/data/v66.0/ssot/semantic/models/{sdmName}/metrics
AI-readiness / update (no minor version):
PATCH /services/data/v66.0/ssot/semantic/models/{sdmName} (model-level: agentEnabled, businessPreferences, description, categories)
GET /services/data/v66.0/ssot/semantic/models/{sdmName}/metrics/{metricName} (resolve full metric)
PUT /services/data/v66.0/ssot/semantic/models/{sdmName}/metrics/{metricName} (full-payload metric update)
PUT /services/data/v66.0/ssot/semantic/models/{sdmName}/data-objects/{obj}/dimensions|measurements/{field} (full-payload base-field update; PATCH=405)
PUT /services/data/v66.0/ssot/semantic/models/{sdmName}/data-objects/{obj} (full-payload data-object update; PATCH=405)
```
**Authentication:** All requests require `Authorization: Bearer {token}` header.