Platform Datamask Run
Data Mask end-to-end operation on a sandbox: configure a masking policy over PII, run the masking job, poll it to completion, report masked-record results, and abort an in-progress run. Use when the user needs to run, monitor, or cancel a Salesforce Data Mask job, mask PII/sandbox data, or work with DataMaskPolicy / DataMaskPolicyJobRun. TRIGGER when: user runs a data mask job, masks sandbox PII, polls masking status, reports masked records, or aborts a running mask. DO NOT TRIGGER when: writing anonymization Apex by hand (use platform-apex-generate), generating test data (use platform-data-manage), or deploying unrelated metadata (use platform-metadata-deploy).
- Skill ID
- forcedotcom/sf-skills/platform-datamask-run
- Publisher
- forcedotcom
- Repository
- sf-skills
- Installs
- 0
- Files
- 5
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
forcedotcom/sf-skills/platform-datamask-runInstalls these files- SKILL.md
- references/api-surface.md
- references/policy-authoring.md
- references/run-and-abort.md
- scripts/poll-job.sh
What this skill tells the agent
platform-datamask-run: Salesforce Data Mask End-to-End Operation
Use this skill to operate the Salesforce Data Mask feature on a sandbox: configure a masking policy over PII fields, start a masking job, poll it to a terminal state, report which records were masked, and abort a run that is still in progress.
Data Mask is sandbox-only — the run/abort REST endpoints return 403 on production (a runtime sandbox guard). Confirm the target org is a sandbox before starting.
When This Skill Owns the Task
- Running a Data Mask job against a configured policy
- Polling masking-job status to completion
- Reporting masked-record counts / per-object results
- Aborting (canceling) an in-progress masking run
- Creating or identifying the policy the job runs against
Delegate elsewhere when the user is:
- hand-writing anonymization Apex →
platform-apex-generate - seeding or generating test data →
platform-data-manage - deploying unrelated metadata →
platform-metadata-deploy
The One Thing to Get Right First: the API surface map
The single biggest failure mode is assuming Data Mask entities are ordinary data-API objects. They are not, and the surface differs per entity. Memorize this table before running anything — guessing here is what turns a 3-second job into a 30-minute dead end.
| Entity | What it is | How you reach it |
|---|---|---|
DataMaskPolicy | The masking policy shell (config) | Tooling API or Metadata API (thin shell: <label>/<description>/<runOnRefresh> only) — NOT standard SOQL/sobject describe |
DataMaskPolicyObject | An object targeted by a policy (holds the optional row filter) | Tooling API only — query AND insert; row-subset "sample" runs set FilterEnabled+WhereCriteria here (no sampleSize on the policy) |
DataMaskPolicyField | A field + its masking treatment | Tooling API only — query AND insert; treatment cols are MaskingCategory + MaskValue |
DataMaskPolicyJobRun | The job (one masking run) | Standard SOQL — sf data query works |
DataMaskPolicyJobRunDtl | Per-object job detail (child, FK DataMaskPolicyJobRunId) | Standard SOQL |
| Start a run | — | REST run API POST /services/data/v67.0/platform/data-resilience/data-mask/policies/{policyId}/run |
| Abort a run | — | REST run API POST /services/data/v67.0/platform/data-resilience/data-mask/jobs/{jobRunId}/abort |
Concretely:
sf sobject describe --sobject DataMaskPolicy→ `NOT_FOUND` (don't retry it against standard API)SELECT ... FROM DataMaskPolicyviasf data query→ `INVALID_TYPE`- Query the policy via Tooling:
sf data query --use-tooling-api --query "SELECT Id, MasterLabel FROM DataMaskPolicy" - Query the job / job-detail via standard API:
sf data query --query "SELECT Id, Status FROM DataMaskPolicyJobRun"
Full command reference: references/api-surface.md.
Pick the workflow that matches the request
This skill has two distinct workflows. Select ONE up front from what the user asked for, then run every step of that workflow — neither has optional steps:
| The user wants to… | Run | Ends when |
|---|---|---|
| Configure/edit a policy and mask records; report how many were masked | Workflow A — Mask & report (below) | The masked count is reported from the detail rows |
| Cancel / abort a masking run | Workflow B — Cancel a run (further below) | The job's status is confirmed canceled |
Choose by the verb in the request. "Create/edit a policy and run it", "mask the PII", "how many records were masked" → Workflow A only. "Abort", "cancel", "stop the run" → Workflow B. A mask-and-report request does not include an abort: do not start a second job to "demonstrate" cancelling — an unrequested run wastes a full ~5–10 min job (see the pool floor in A4) and is the top cause of this task running out of turn before it finishes the masked count it was asked for.
Workflow A — Mask & report
A1. Confirm sandbox + capture org context
Verify the org is a sandbox and grab the instance URL + a session token for the run-API calls:
sf org display --target-org <alias> --jsonA2. Identify or create the policy
Prefer reusing an existing policy (fastest, no deploy):
sf data query --use-tooling-api --target-org <alias> \
--query "SELECT Id, DeveloperName, MasterLabel FROM DataMaskPolicy"If none targets the Contact PII you need, author one with the two-step recipe (the DataMaskPolicy Metadata shape is a thin shell; membership is Tooling-inserted):
- Metadata-deploy the thin shell in mdapi format (
--metadata-dir+package.xml; a source-format--source-dirdeploy fails "Could not infer a metadata type"). The shell carries only<label>,<description>,<runOnRefresh>. This creates the policy with an active revision, which A2 requires. - Tooling-insert the
DataMaskPolicyObject(one per object) then itsDataMaskPolicyFieldrows. Each field row's treatment isMaskingCategory(library) +MaskValue(a snake_case token likefirst_name,email,phone). There is no `MaskingRuleType` column.
Insert order matters: a Tooling-created parent (no active revision) makes the child insert fail INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY. Metadata-deploy the shell first.See references/policy-authoring.md for the full recipe and the MaskValue token table. Choose a MaskValue appropriate to each field; do not blanket-replace.
A3. Start the masking run (REST run API)
printf '{}' > ./empty-body.json
sf api request rest \
"/services/data/v67.0/platform/data-resilience/data-mask/policies/{policyId}/run" \
--method POST --body @./empty-body.json --target-org <alias>The endpoint needs an empty JSON body ({}) — sf api request rest requires --body on a POST even when the API takes no payload. Pass the file with an `@` prefix (--body @./empty-body.json); without it the literal path is sent as the body and the API returns JSON_PARSER_ERROR. A 200 returns jobRunId, policyId, status (the run-API status is UPPERCASE, e.g. RUNNING) and message: "Job started successfully". A 409/CONFLICT means a run is already in progress for that policy.
Write `report.md` NOW, before you poll — do not wait until the end. The masking job takes several minutes (see below), and the single most common way this task scores zero is the turn ending during the poll with no output file written at all. The instant you have thejobRunId, writereport.mdwith everything known so far (policy Id/label, the run command, thejobRunId, statusRUNNING, and a "polling for completion…" placeholder for the masked count). Then update that same file once the job finishes. A report that exists and says "still running" beats no file; a fabricated count is worse than either — only fill the count from the detail rows (A5).
A4. Poll to a terminal state (standard SOQL)
Poll DataMaskPolicyJobRun.Status until it reaches a terminal value. Do not report a mid-run status as final.
- Mid-run (pre-work):
pending,scheduled— the job is queued but not yet abortable - Mid-run (working):
running— this is the only state in which abort succeeds - Terminal:
completed,completed_with_errors,failed - Abort target:
canceled(single "l")
`pending` is not `running`. Abort on a pending/scheduled job returns 409 CONFLICT ("Job is not in a running state ... status=PENDING"). You must poll until the status is literally running before you can abort — see Workflow B.
Jobs are slow — expect several minutes, and poll with the bundled script. Data Mask runs on a backend pool/scheduler with a ~5–10 minute floor: even a tiny (20-row) job usually does not
More from forcedotcom
Use this skill in a project
Open any RiverX project, search the Skills panel for forcedotcom/sf-skills/platform-datamask-run, and install it in one click.
