diff --git a/skills/segmenting-datacloud-api/CREDITS.md b/skills/segmenting-datacloud-api/CREDITS.md new file mode 100644 index 0000000..f85eccf --- /dev/null +++ b/skills/segmenting-datacloud-api/CREDITS.md @@ -0,0 +1,5 @@ +# Credits & Acknowledgments + +Author: Matthew Lieber + +This skill provides a REST API-first approach to Data Cloud segment management, complementing the `segmenting-datacloud` skill which uses the `sf data360` CLI plugin. diff --git a/skills/segmenting-datacloud-api/README.md b/skills/segmenting-datacloud-api/README.md new file mode 100644 index 0000000..bbfb327 --- /dev/null +++ b/skills/segmenting-datacloud-api/README.md @@ -0,0 +1,35 @@ +# segmenting-datacloud-api + +Manage Salesforce Data Cloud segments via the Connect REST API (`sf api request rest`), without requiring the `sf data360` community plugin. + +## Use this skill for + +- creating, updating, and deleting segments via REST API +- publishing and deactivating segments +- getting segment details and member lists +- counting segment populations +- full endpoint schema reference (request/response bodies, query params, error codes) + +## Example requests + +```text +"Create a Dbt segment targeting individuals who purchased in the last 30 days" +"List all active waterfall segments in my org" +"Publish segment 1sgxx00000000PpAAI" +"Get the members of my High_Value_Customers segment" +"Delete the old test segment and recreate it on accounts" +``` + +## Common commands + +```bash +sf api request rest "/services/data/v65.0/ssot/segments" -o myorg +sf api request rest "/services/data/v65.0/ssot/segments" -X POST -H "Content-Type:application/json" -b @/tmp/segment.json -o myorg +sf api request rest "/services/data/v65.0/ssot/segments/My_Segment/actions/publish" -X POST -H "Content-Type:application/json" -b '{}' -o myorg +sf api request rest "/services/data/v65.0/ssot/segments/My_Segment/members?limit=100" -o myorg +``` + +## References + +- [SKILL.md](SKILL.md) +- [CREDITS.md](CREDITS.md) diff --git a/skills/segmenting-datacloud-api/SKILL.md b/skills/segmenting-datacloud-api/SKILL.md new file mode 100644 index 0000000..3a98f5f --- /dev/null +++ b/skills/segmenting-datacloud-api/SKILL.md @@ -0,0 +1,341 @@ +--- +name: segmenting-datacloud-api +description: "Manage Salesforce Data Cloud segments via the Connect REST API using `sf api request rest`. Use this skill when the user wants to create, list, get, update, delete, publish, deactivate, or count segments in Data Cloud — even if they say \"Data 360\", \"CDP segments\", or just \"segments\". Also use when the user asks about segment members or segment status. TRIGGER when: user manages segments via REST API, needs full endpoint schemas, or wants to bypass the sf data360 plugin. DO NOT TRIGGER when: user explicitly uses sf data360 CLI commands (use segmenting-datacloud), or the task is DMO/mapping/identity-resolution/activation work." +compatibility: "Requires an org with Data Cloud enabled and the sf CLI installed" +metadata: + version: "1.0" +--- + +# Data Cloud Segments — REST API via SF CLI + +This skill manages Data Cloud segments through the Salesforce Connect API Segments endpoints, executed via `sf api request rest`. + +## Authentication + +All Segments endpoints require the **Core Salesforce token** (not the CDP token). The `sf api request rest` command handles authentication automatically using the target org. + +Always include `-o ` or rely on the default target org. The user's current default org is shown in the `sf api request rest --help` output. + +## API Base Path + +All endpoints live under `/services/data/v65.0/ssot/segments`. When calling `sf api request rest`, pass the full path starting with `/services/data/v65.0/`. + +## Command Pattern + +```bash +# GET request (no body) +sf api request rest "/services/data/v65.0/ssot/segments" -o + +# POST/PATCH with inline body +sf api request rest "/services/data/v65.0/ssot/segments" -X POST \ + -H "Content-Type:application/json" \ + -b '{"field":"value"}' \ + -o + +# POST/PATCH with body from file +sf api request rest "/services/data/v65.0/ssot/segments" -X POST \ + -H "Content-Type:application/json" \ + -b @/tmp/segment-body.json \ + -o + +# DELETE (requires empty JSON body due to sf CLI quirk) +sf api request rest "/services/data/v65.0/ssot/segments/MySegment" -X DELETE \ + -H "Content-Type:application/json" \ + -b '{}' \ + -o +``` + +**Important sf CLI quirks:** +- DELETE requests require `-H "Content-Type:application/json" -b '{}'` — the CLI errors without a body. +- POST endpoints that accept no body (publish, deactivate) also need `-H "Content-Type:application/json" -b '{}'`. +- For complex JSON bodies, prefer writing to a temp file first and using `-b @/tmp/file.json` to avoid shell quoting issues. + +## Operations + +### 1. List Segments + +```bash +sf api request rest "/services/data/v65.0/ssot/segments?batchSize=20&offset=0" -o +``` + +**Query parameters** (all optional): +| Parameter | Type | Description | +|-----------|------|-------------| +| `batchSize` | integer | 1-200, default 20 | +| `offset` | integer | Number of segments to skip | +| `orderby` | string | Sort field + direction, e.g. `Name ASC`, `MarketSegmentType DESC` | +| `dataspace` | string | Data space name (default data space if omitted) | +| `filters` | string | Up to 10 filters joined by AND. See Filters section below | + +**Response:** +```json +{ + "batchSize": 20, + "offset": 0, + "orderByExpression": "[Name asc]", + "segments": [ ... ], + "totalSize": 42 +} +``` + +Each segment object in the array contains: `apiName`, `dataSpace`, `description`, `displayName`, `lastModifiedDate`, `marketSegmentDefinitionId`, `marketSegmentId`, `publishInterval`, `segmentOnApiName`, `segmentOnId`, `segmentStatus`, `segmentType`. + +#### List Filters + +Filterable fields: `Name` (displayName), `SegmentStatus`, `MarketSegmentType` (segmentType), `SegmentOn` (segmentOnApiName), `LastPublishedEndDateTime`. + +Operators: `eq`, `contains`, `in` (comma-separated values), `!=`. + +``` +filters=Name contains MySegment AND SegmentStatus eq Active +filters=MarketSegmentType in Dbt,Waterfall AND SegmentOn eq individual +``` + +### 2. Create Segment + +```bash +sf api request rest "/services/data/v65.0/ssot/segments" -X POST \ + -H "Content-Type:application/json" \ + -b @/tmp/create-segment.json \ + -o +``` + +**Required fields:** +| Field | Type | Description | +|-------|------|-------------| +| `description` | string | Segment description | +| `displayName` | string | Segment display name | +| `segmentOnApiName` | string | API name of the entity to segment on (e.g. `ssot__Individual__dlm`, `ssot__Account__dlm`) | +| `segmentType` | string | One of: `Dbt`, `Dynamic`, `Lookalike`, `Realtimez`, `Waterfall`. **Default to `Dbt` when the user does not specify a type.** | + +**Optional fields:** +| Field | Type | Description | +|-------|------|-------------| +| `developerName` | string | API name for the segment (required for POST) | +| `additionalMetadata` | object | Map of additional metadata | +| `dataSpace` | string | Data space (use `dataspace` query param in v59+) | +| `publishSchedule` | string | Enum: `NoRefresh`, `One`, `Two`, `Four`, `Six`, `Twelve`, `TwentyFour` (hours) | +| `publishScheduleStartDateTime` | string | ISO datetime for schedule start — **must be in the future** | +| `publishScheduleEndDate` | string | ISO datetime for schedule end — despite the name, use full `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` format | +| `includeDbt` | object | SQL-based segment definition for Dbt segments. Structure: `{"models": {"models": [{"name": "m1", "sql": "..."}]}}`. Note the nested `models.models` — the outer `models` is an object, the inner `models` is an array of model objects each with `name` and `sql`. The SQL SELECT must include both the primary key (`ssot__Id__c`) and the key qualifier (`KQ_Id__c`) of the segmentOn entity. See [Data Cloud SQL Reference](https://developer.salesforce.com/docs/data/data-cloud-query-guide/guide/dc-query-section.html) for query syntax | +| `lookalikeCriteria` | object | Lookalike criteria | +| `lookbackPeriod` | string | Lookback period | + +**Example — create a Dbt segment (default type):** +```json +{ + "description": "Customers with more than 5 purchases in the last 30 days", + "displayName": "Frequent Recent Buyers", + "developerName": "Frequent_Recent_Buyers", + "segmentOnApiName": "ssot__Individual__dlm", + "segmentType": "Dbt", + "additionalMetadata": {}, + "includeDbt": { + "models": { + "models": [ + { + "name": "m1", + "sql": "SELECT ssot__Individual__dlm.ssot__Id__c, ssot__Individual__dlm.KQ_Id__c FROM ssot__Individual__dlm WHERE ssot__Individual__dlm.ssot__Id__c IN (SELECT IndividualId__c FROM ssot__SalesOrder__dlm WHERE CreatedDate__c >= DATEADD(day, -30, CURRENT_DATE) GROUP BY IndividualId__c HAVING COUNT(*) > 5)" + } + ] + } + }, + "publishSchedule": "TwentyFour", + "publishScheduleStartDateTime": "2026-04-17T00:00:00.000Z", + "publishScheduleEndDate": "2026-05-17T23:59:59.000Z" +} +``` + +**Important notes on Dbt SQL:** +- The SQL SELECT must include both the primary key (`ssot__Id__c`) and the key qualifier (`KQ_Id__c`) of the segmentOn entity, or the API will return an `ENTITY_SAVE_ERROR`. +- The `includeDbt` structure uses nested `models.models` — the outer `models` is an object, the inner `models` is an array of model objects each with `name` (e.g. `"m1"`) and `sql`. +- The GET response flattens this to `includeDbt.models: [...]` (single-level array), but the POST body requires the nested `models.models` structure. +- The SQL must follow Data Cloud SQL syntax. See the [Data Cloud SQL Reference](https://developer.salesforce.com/docs/data/data-cloud-query-guide/guide/dc-query-section.html) for supported functions, operators, and DMO querying patterns. + +**Response (200):** Returns the full segment object with `apiName`, `marketSegmentId`, `marketSegmentDefinitionId`, `segmentStatus` (typically `PROCESSING`), and all provided fields. + +**Error responses:** +- 400: Invalid segment type, empty developerName/displayName, parse error +- 409: Segment already exists + +### 3. Get Segment + +```bash +sf api request rest "/services/data/v65.0/ssot/segments/" -o +``` + +Pass either the segment API name (developerName) or the segment ID. + +**Response (200):** +```json +{ + "segments": [ { ...segment object... } ] +} +``` + +### 4. Update Segment + +```bash +sf api request rest "/services/data/v65.0/ssot/segments/" -X PATCH \ + -H "Content-Type:application/json" \ + -b @/tmp/update-segment.json \ + -o +``` + +Uses the same body schema as Create, with these differences: +- `developerName` is **not supported** for PATCH (can't rename the API name) +- `segmentType` is **not supported** for PATCH (can't change type after creation) +- `additionalMetadata` is **not supported** for PATCH + +Include only the fields you want to change. The required fields (`description`, `displayName`, `segmentOnApiName`) must still be present. + +**Example — update description and publish schedule:** +```json +{ + "description": "Updated description", + "displayName": "Recent High Value Customers", + "segmentOnApiName": "ssot__Individual__dlm", + "publishSchedule": "TwentyFour" +} +``` + +### 5. Delete Segment + +```bash +sf api request rest "/services/data/v65.0/ssot/segments/" -X DELETE \ + -H "Content-Type:application/json" \ + -b '{}' \ + -o +``` + +Returns **204 No Content** on success (empty output). + +### 6. Publish Segment + +```bash +sf api request rest "/services/data/v65.0/ssot/segments//actions/publish" -X POST \ + -H "Content-Type:application/json" -b '{}' \ + -o +``` + +Uses the segment **ID** (not API name). No request body. + +**Response (200):** +```json +{ + "errors": [], + "jobId": "1f8f521f-0616-4bdf-a515-d95192475c95", + "partitionId": "1sgxx00000000Pp_6157146b-...", + "publishStatus": "PUBLISHING", + "segmentId": "1sgxx00000000PpAAI", + "success": true +} +``` + +### 7. Count Segment + +```bash +sf api request rest "/services/data/v65.0/ssot/segments//actions/count" -X POST \ + -H "Content-Type:application/json" \ + -b '{"preferApproxCount": true}' \ + -o +``` + +**Request body:** +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `preferApproxCount` | boolean | yes | `true` for approximate count (faster), `false` for exact | + +**Response (200):** +```json +{ + "errors": [], + "segmentId": "1sgxx00000000MbAAI", + "success": true +} +``` + +### 8. Deactivate Segment + +By API name: +```bash +sf api request rest "/services/data/v65.0/ssot/segments//actions/deactivate" -X POST \ + -H "Content-Type:application/json" -b '{}' \ + -o +``` + +By segment ID: +```bash +sf api request rest "/services/data/v65.0/ssot/segments//actions/deactivate" -X POST \ + -H "Content-Type:application/json" -b '{}' \ + -o +``` + +**Response (200):** +```json +{ + "errors": [], + "segmentApiName": "TestFilter", + "segmentId": "1sgxx00000000UfAAI", + "success": true +} +``` + +### 9. Get Segment Members + +**The segment must be published before you can retrieve members.** If the segment has never been published, the API will return a 404 `ITEM_NOT_FOUND` error. + +```bash +sf api request rest "/services/data/v65.0/ssot/segments//members?limit=100&offset=0" -o +``` + +**Query parameters** (all optional): +| Parameter | Type | Description | +|-----------|------|-------------| +| `fields` | string | Comma-separated: `Id__c`, `KQ_Id__c`, `Delta_Type__c`, `Segment_Id__c`, `Parent_Segment_Id__c`, `Snapshot_Type__c`, `Timestamp__c`, `Version_Stamp__c` | +| `filters` | string | e.g. `Delta_Type__c IN ('New','Existing') AND Timestamp__c IN 2026-04-09T14:12:41.111Z` | +| `limit` | integer | 1-1000, default 100 | +| `offset` | integer | Rows to skip | +| `orderBy` | string | e.g. `Timestamp__c DESC` | + +**Response (200):** +```json +{ + "data": [ ... ], + "endTime": "2025-04-11T15:16:10.000Z", + "filter": "Delta_Type__c in ( new , existing )", + "limit": 100, + "offSet": 0, + "orderBy": "Id__c asc", + "rowCount": 5, + "startTime": "2025-04-11T15:16:10.000Z", + "totalCount": 5 +} +``` + +## Common Segment Entity API Names + +| Entity | API Name | +|--------|----------| +| Individual | `ssot__Individual__dlm` | +| Account | `ssot__Account__dlm` | + +When the user says "segment on individuals" or "segment on accounts", use these API names. + +## Workflow: Create and Publish a Segment + +A typical end-to-end flow: + +1. **Create** the segment (POST) — returns `marketSegmentId` +2. **Get** the segment to confirm it was created and check status +3. **Publish** the segment using the `marketSegmentId` from step 1 +4. **Check status** by getting the segment again — look at `segmentStatus` +5. **Count** the segment to trigger population count +6. **Get members** to see who's in the segment + +## Troubleshooting + +- **401 Unauthorized**: Run `sf org login web -a ` to re-authenticate +- **404 Not Found**: Check the segment API name is correct (case-sensitive) +- **400 Bad Request**: Validate required fields are present; check `segmentType` enum values +- **409 Conflict**: Segment with that `developerName` already exists