Risk & Control Matrix Guide#

This guide walks through reading a Risk & Control Matrix (RCM) with the GRC API. Before you start you need:

  • A valid OAuth 2.0 Bearer token (see Authentication).

  • The X-Version: 2026-09-01.grc.preview and Wk-Workspace headers on every request (see Request headers).

  • The grc:read scope.

Resources#

This preview exposes two RCM resources, each with list, get by id, and batchRetrieval operations:

Resource

Path

Description

Controls

/controls

Safeguards that mitigate risks.

Risks

/risks

Potential events controls mitigate.

Full resources and digests#

An endpoint returns the full representation of its own resource. Related resources are embedded as digests — an id plus a few display fields (for example, a control’s risks are returned as risk digests). Use the control or risk endpoint with a digest id to retrieve that resource’s full representation. Processes, subprocesses, systems, and locations are included as context digests but do not have their own endpoints in this preview.

This keeps payloads bounded and avoids cycles (a control embeds its risks; each risk embeds its mitigating controls). See Digests for the general pattern.

List controls#

A workspace is in either standalone mode or multi-instance mode. Unfiltered GET /controls results contain standalone controls in standalone mode, or definition and instance controls in multi-instance mode. Use $filter=controlType to request a subset of the types available in that workspace mode.

curl -X GET "https://api.app.wdesk.com/controls?\$maxpagesize=100" \
    -H "Accept: application/json" \
    -H "Authorization: Bearer {access-token}" \
    -H "X-Version: 2026-09-01.grc.preview" \
    -H "Wk-Workspace: {workspace-id}"

See Control types and response shapes below for the three control shapes and for paging instances of a definition.

Get a single control#

curl -X GET "https://api.app.wdesk.com/controls/{id}" \
    -H "Accept: application/json" \
    -H "Authorization: Bearer {access-token}" \
    -H "X-Version: 2026-09-01.grc.preview" \
    -H "Wk-Workspace: {workspace-id}"

A control includes its controlType (standalone, definition, or instance), significance, frequency, automatedManual, preventiveDetective, its process and subProcess digests, its risks, systems, and locations digests, and any customFields configured for controls in the workspace.

See Control types and response shapes below for what each controlType returns.

Control types and response shapes#

Every control has a controlType of standalone, definition, or instance, and that type determines which type-specific sub-object the response carries. All three share the same top-level fields (id, controlId, name, significance, frequency, owner, process, risks, systems, locations, customFields, and the rest); they differ only in whether they carry a definition or an instance sub-object.

Standalone#

A classic, independent control that is not part of the definition/instance model. It carries neither sub-object, and its systems and locations may each list several dimensions.

{
  "id": "d9c09501-214c-432e-85c9-8cd1009b9660",
  "controlId": "BEN.001",
  "controlType": "standalone",
  "name": "General Ledger Reconciliation",
  "owner": { "id": "u_4cc9f9128a684f45a7a37b08b70b54c1" },
  "systems": [{ "id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a", "name": "Workday" }],
  "locations": [{ "id": "7f6e5d4c-3b2a-4c1d-9e8f-0a1b2c3d4e5f", "name": "North America" }]
}

Definition#

A template that spawns instances across location and system dimensions. It carries a definition object with instanceCount (how many instances exist). A template has no dimension of its own, so its systems and locations are empty. Definitions appear in unfiltered lists only in multi-instance-mode workspaces.

{
  "id": "4a3b2c1d-0e9f-4a8b-7c6d-5e4f3a2b1c0d",
  "controlId": "BEN.002",
  "controlType": "definition",
  "name": "AP Invoice Approval",
  "systems": [],
  "locations": [],
  "definition": { "instanceCount": 3 }
}

Instance#

A concrete control derived from a definition for a specific location or system. It carries an instance object with definition (a digest of the parent it came from) and overriddenFields (the fields whose values differ from that parent, listed as a public standard field name or a stable custom-field schema id; empty when the instance is fully aligned with its definition). Its systems and locations hold the single dimension it applies to.

{
  "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "controlId": "BEN.002",
  "controlType": "instance",
  "name": "AP Invoice Approval",
  "frequency": "quarterly",
  "systems": [{ "id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a", "name": "Workday" }],
  "locations": [{ "id": "7f6e5d4c-3b2a-4c1d-9e8f-0a1b2c3d4e5f", "name": "North America" }],
  "instance": {
    "definition": {
      "id": "4a3b2c1d-0e9f-4a8b-7c6d-5e4f3a2b1c0d",
      "controlId": "BEN.002",
      "name": "AP Invoice Approval"
    },
    "overriddenFields": ["frequency", "8f3c1a2b-4d5e-4f6a-8b7c-9d0e1f2a3b4c"]
  }
}

Here the instance runs quarterly while its definition runs monthly, so frequency appears in overriddenFields, and the custom field with schema id 8f3c1a2b-4d5e-4f6a-8b7c-9d0e1f2a3b4c has likewise been overridden on this instance.

Working with definitions in lists#

controlType filters request a subset of the types available in the workspace mode. In a multi-instance-mode workspace, unfiltered results already include definitions and instances:

$filter=controlType eq 'definition'
$filter=controlType in ('definition', 'instance')

To page through the instances derived from one definition, filter by that definition’s controlId (its human-readable id, not its UUID):

$filter=definitionId eq 'BEN.002'

Custom fields#

Controls and risks carry workspace-defined customFields. Each entry has a stable schema id (names can change), a name, a type, and a single value field determined by the type:

type

Value field

Example

string

stringValue

"Reviewed by internal audit."

singleSelectEnum

enumValue

"High"

multiSelectEnum

enumValues

["SOX", "COSO"]

date

dateValue

"2026-01-15"

invalid

invalidValue

"not-a-date"

"customFields": [
  { "id": "8f3c1a2b-4d5e-4f6a-8b7c-9d0e1f2a3b4c", "name": "Risk Rating", "type": "singleSelectEnum", "enumValue": "High" },
  { "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "name": "Frameworks", "type": "multiSelectEnum", "enumValues": ["SOX", "COSO"] },
  { "id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "name": "Last Reviewed", "type": "date", "dateValue": "2026-01-15" },
  { "id": "4d5e6f7a-8b9c-4d0e-1f2a-3b4c5d6e7f8a", "name": "Legacy Date", "type": "invalid", "invalidValue": "not-a-date" }
]

Note

type is an open string — new custom-field types may be introduced over time, so tolerate values beyond those listed here.

Note

A 404 means the requested control was not found.

Fetch a known set of ids (batch)#

When you already hold a set of ids, use batchRetrieval to retrieve up to 250 in one request. The response data array preserves the order of the requested ids. Batch get is all-or-nothing: if any id is missing or not accessible, the whole request fails with 400 and a per-id error list (in the error details), rather than returning a partial page.

curl -X POST "https://api.app.wdesk.com/controls/batchRetrieval" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -H "Authorization: Bearer {access-token}" \
    -H "X-Version: 2026-09-01.grc.preview" \
    -H "Wk-Workspace: {workspace-id}" \
    -d '{ "data": ["d9c09501-214c-432e-85c9-8cd1009b9660", "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"] }'

Reconstructing the matrix#

The heart of the RCM is the control↔risk linkage. Each control exposes risks (risk digests) and each risk exposes mitigatingControls (control digests). Walk either side and follow the digest ids to build the full matrix, grouping by process / subProcess and tagging by systems / locations as needed. To find controls for a specific system or location, filter GET /controls with systems.id or locations.id.