afv-library/skills/tableau-next-semantic-model-generate/references/api-reference.md
Antoine Laviron b26d254871 feat: add tableau-next-semantic-model-generate skill
Adds a skill for authoring Tableau Next semantic models (SDMs) on Data 360: build from scratch, add data objects, define joins, enrich with calculated fields and metrics, and make models AI-ready.

Smoke-tested against a live Data 360 org: SDM discovery, AI-readiness flip, dimension creation, metric creation, and description backfill all exercised end-to-end.
2026-07-26 13:33:29 +02:00

608 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
All endpoints return structured error responses:
```json
{
"error": {
"code": "INVALID_FIELD",
"message": "Field 'Amount' not found in semantic model 'Sales_Cloud12_backward'",
"details": {
"fieldName": "Amount",
"availableFields": ["Total_Amount", "Close_Date", "Stage"]
}
}
}
```
**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-bindsuffixed 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.