description: "Use this skill to configure Salesforce Data Cloud DataSpace access for permission sets. Grants dataspace-level access via MDAPI PermissionSet XML with dataspaceScopes elements, and optionally grants object-level access to specific DMO, DLO, or CIO objects via the Object Access Grants Connect API. TRIGGER when: user needs to create or update a permission set that includes DataSpace access, grant a permission set access to a specific dataspace, configure dataAccessLevel or objectAccessLevel for a dataspace scope, add RBAC object access grants for Data Cloud objects, or list or remove object access grants for a permission set and DataSpace pair. DO NOT TRIGGER when: the task is a generic permission set without any dataspace access (use platform-permission-set-generate), the request is about data ingestion or streams (use data360-prepare), or the work involves creating dataspaces themselves rather than granting access to them."
Configure DataSpace access in Salesforce Data Cloud using a two-layer model:
1.**DataSpace-level access** — grant a `PermissionSet` access to a DataSpace by embedding a `<dataspaceScopes>` element in the permission set XML and deploying via MDAPI.
2.**Object-level access (optional)** — grant that permission set access to specific DMO / DLO / CIO objects within the DataSpace using the Object Access Grants Connect API.
The MDAPI layer is required to establish the PermissionSet → DataSpace linkage. The Connect API layer is optional and only needed when access should be scoped to specific objects rather than governed entirely by data governance policies.
---
## Decide the Case First
Pick exactly one case from the table below before writing any files. Each case has a different output shape.
| Case | User intent | Permission set state | Files to emit |
|---|---|---|---|
| **A. Create new permset with DS access** | "create a permission set called X with dataspace scope Y" | does NOT exist yet | `permissionsets/<Name>.permissionset-meta.xml`**and**`package.xml` |
| **B. Add DS access to existing permset** | "grant existing permission set X access to dataspace Y" | already deployed (may contain other permissions) | patched `permissionsets/<Name>.permissionset-meta.xml`**and**`package.xml` — see Case B workflow below |
| **C. Object-level grant only** | "grant permset X access to object Z (in dataspace Y)" — permset + scope already configured | already deployed with `dataspaceScopes` | `api-request.json` (Connect API body). NO permission set XML, NO `package.xml` |
Only emit the files listed for the case you picked. Emitting Case A/B files for a Case C prompt (or vice versa) is a correctness failure — extra files change the deployment shape.
> **Case B — critical:** PermissionSet MDAPI deploy is a **full metadata replace**. Every `<objectPermissions>`, `<fieldPermissions>`, `<userPermissions>`, `<tabSettings>`, `<applicationVisibilities>`, `<recordTypeVisibilities>`, `<customPermissions>`, `<pageAccesses>`, `<classAccesses>`, `<customMetadataTypeAccesses>`, `<customSettingAccesses>`, `<externalDataSourceAccesses>` element you omit from the redeploy is **deleted from the org**. Before adding `<dataspaceScopes>` to an existing permset, retrieve the current XML and patch it — do not hand-author from scratch.
### Case B workflow
1. Retrieve the existing permission set:
```bash
sf project retrieve start --metadata PermissionSet:<Name> --target-org <alias>
```
2. Open the retrieved `permissionsets/<Name>.permissionset-meta.xml`. Keep every element already there.
3. Insert the `<dataspaceScopes>` block for the target DataSpace (element order in the file does not matter for MDAPI). If the file already has a `<dataspaceScopes>` block **for this same DataSpace**, replace only that block. Leave every `<dataspaceScopes>` block for other DataSpaces untouched — one block per DataSpace, and removing a block revokes that DataSpace grant.
4. Write `package.xml` listing the permset in `<members>`.
5. Redeploy with `sf project deploy start`.
---
## When This Skill Owns the Task
Trigger this skill when the user wants to:
- Create a permission set that grants access to a Data Cloud DataSpace
- Add or modify `dataspaceScopes` on an existing permission set
- Grant a permission set access to specific DMO / DLO / CIO objects in a DataSpace
- Configure `dataAccessLevel` and `objectAccessLevel` for a DataSpace scope
- List or remove object access grants for a permission set + DataSpace pair
Delegate elsewhere when:
- The permission set has no DataSpace access at all → `platform-permission-set-generate`
- The task is creating the DataSpace itself → `data360-orchestrate`
- The task is ingesting data or configuring streams → `data360-prepare`
---
## Layer 1 — DataSpace-Level Access (MDAPI)
Embed a `<dataspaceScopes>` element inside the `PermissionSet` XML. Deploy with MDAPI.
| `<dataspaceScopes>` | yes | parent element (plural) | Container for a single dataspace scope grant |
| `<dataspaceScope>` | yes | DataSpace API name (e.g. `default`) | Which DataSpace this grant is for |
| `<dataAccessLevel>` | yes | `NONE`, `CONTROLLED_BY_PARENT`, `ALL` | Row-level data access within the DataSpace |
| `<objectAccessLevel>` | yes | `BY_POLICY`, `ALL_IN_DATASPACE` | Object-level access. `BY_POLICY` defers to data governance policies. `ALL_IN_DATASPACE` is only allowed when `dataAccessLevel` is `CONTROLLED_BY_PARENT` |
### Common Mistakes
- **Wrong parent name** — using `<dataspaceScopeAccess>` instead of `<dataspaceScopes>`. Deployment fails silently or with cryptic errors.
- **Wrong child name** — using `<dataspaceScopeName>` instead of `<dataspaceScope>`.
- **Wrong enum values** — `ViewAllRows` / `Read` / `OWNER` / `EDIT` are not valid. Use `NONE`, `CONTROLLED_BY_PARENT`, or `ALL` for `dataAccessLevel`; use `BY_POLICY` or `ALL_IN_DATASPACE` for `objectAccessLevel`. See Element Rules table for allowed combinations. Deployment error `-379999659` means invalid enum.
- **Multiple scopes in one element** — `<dataspaceScopes>` grants access to exactly one DataSpace. To grant access to multiple, add multiple `<dataspaceScopes>` blocks.
### Package Layout (Case A and Case B)
A deployable bundle for Layer 1 always contains **both** files:
```text
<output-root>/
package.xml
permissionsets/<Name>.permissionset-meta.xml
```
`package.xml` (required — list every permission set being deployed in `<members>`):
sf project deploy start --source-dir force-app/main/default/permissionsets/ --target-org <alias>
```
---
## Layer 2 — Object-Level Access (Connect API) — Case C
Only needed when `objectAccessLevel` is not `BY_POLICY`, or when governance policies do not cover the target objects. Grants are runtime — **no MDAPI deploy, no `package.xml`, no permission set XML**. The only artifact for a Case C task is a single `api-request.json` describing the Connect API call.
### Resolve the API version first
Every Connect API `endpoint` in this layer contains an `/services/data/v<apiVersion>/…` segment. **Do not hardcode `v67.0`.** Resolve the target org's actual API version before writing the envelope so the request matches the org's supported surface:
```bash
sf org display --target-org <alias> --json | jq -r '.result.apiVersion'
```
- Substitute the returned value (e.g. `67.0`, `68.0`) into the `endpoint` as `v<apiVersion>`.
- If the org can't be queried (offline authoring, no alias yet), fall back to the `minApiVersion` from this skill's frontmatter (`67.0`) — the endpoint was introduced there and any newer version accepts the same body.
- If the user explicitly specifies a version in the prompt, use that verbatim.
In the templates below, `{apiVersion}` is a placeholder. Replace it with the resolved API version (e.g., `67.0`, `68.0`) before emitting `api-request.json`.
### `api-request.json` — canonical shape
Emit the request as a self-describing envelope with `method`, `endpoint`, `headers`, `body`, and `expectedResponse`. Do NOT emit only the body — reviewers and downstream tooling read the envelope.
Same `api-request.json` envelope shape. `endpoint` gains the `/actions/bulk-create` suffix, `body.objectApiName` is replaced by the list-valued `body.objectApiNames`, and `expectedResponse` omits the `body` field because bulk responses return per-object status entries rather than the flat request payload (see Gotchas below).
## Combined Setup — Case A + Case C from a Cold Start
Use this section ONLY when the user is starting from nothing and asks for both the permset+scope AND per-object grants in a single request. If the user's prompt is only about the Connect API grant (Case C) — for example "grant Account__dlm access; the permset and dataspace scope already exist" — SKIP this section entirely and emit only `api-request.json` from Layer 2.
The commands below are operator-facing `sf` CLI invocations (a runnable cold-start walkthrough), NOT the artifact you emit. For a normal Case C task the artifact is a single `api-request.json` envelope as documented in Layer 2 above.
**Goal:** Grant `Data_Cloud_Analyst` permission set access to `Account__dlm` and `Contact__dlm` in the `default` DataSpace.
**Step 1 — Deploy PermissionSet with DataSpace scope (MDAPI):**
sf project deploy start --source-dir permissionsets/ --target-org <alias>
```
**Step 2 — Grant object access (Connect API):**
*(Required here because the `default` DataSpace has no governance policies covering `Account__dlm` and `Contact__dlm`. Skip Step 2 when `BY_POLICY` policies already govern the target objects — Layer 1 alone is sufficient.)*
Resolve the org's API version first (see [Resolve the API version first](#resolve-the-api-version-first) above), then substitute it into the `--path` value:
| Use `<dataspaceScopes>` (plural) as parent, `<dataspaceScope>` (singular) as child | XML schema requirement; other names deploy-fail |
| `dataAccessLevel` values: `NONE`, `CONTROLLED_BY_PARENT`, `ALL` only | Other values (e.g. `OWNER`, `ViewAllRows`) are rejected |
| `objectAccessLevel` values: `BY_POLICY`, `ALL_IN_DATASPACE` only | Other values (e.g. `READ`, `EDIT`, `Read`) are rejected. `ALL_IN_DATASPACE` requires `dataAccessLevel=CONTROLLED_BY_PARENT` |
| Prefer `BY_POLICY` when data governance policies exist | Delegates row/column filtering to central policy — no per-object grants needed |
| One `<dataspaceScopes>` block per DataSpace | Repeat the block for multiple DataSpaces on the same permission set |
| Org must have Data Cloud provisioned to deploy `<dataspaceScopes>` | On non-Data-Cloud orgs, the element is ignored or rejected |
| Do not query `DataspaceScope` / `DataspaceScopeAccess` via SOQL | Not queryable; use MDAPI retrieve to inspect existing grants |
---
## Gotchas
| Issue | Resolution |
|---|---|
| Deployment fails with error `-379999659` | Check enum values — `dataAccessLevel` must be `NONE`/`CONTROLLED_BY_PARENT`/`ALL`; `objectAccessLevel` must be `BY_POLICY`/`ALL_IN_DATASPACE` |
| Permission set deploys but users still can't query DataSpace data | Layer 2 not applied — objects need explicit grants if `objectAccessLevel != BY_POLICY` |
| Bulk-create returns `AlreadyExists` for some objects | Idempotent — safe to retry; response shows per-object status |
| Connect API returns 404 for object grants endpoint | Org lacks Data Cloud provisioning, or the resolved API version is below the minimum. The endpoint was introduced in `v67.0` — re-run `sf org display --json` to confirm the org's `apiVersion` field is `67.0` or later, and substitute that value into the `endpoint` path |
| Retrieved PermissionSet XML shows different element names than deployed | Metadata API sometimes echoes legacy names on retrieve — always author with current names |