Assurance Projects & Scoping Guide#

This guide walks through reading assurance projects and scoping their controls 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 for reads, and the grc:write scope for setting scope.

Important

Projects are read-only through this API — you cannot create, rename, or delete a project. Setting the scoping rationale and changing scope are supported only for Independent Controls Testing projects (type = independentControlsTesting). A scope write against any other project type returns 409 with code unsupportedProjectType.

Note

Scope reasoning is captured in two independent places: the project’s overall scopingRationale (one narrative per project, set with PATCH /assuranceProjects/{id}) and a control’s scopeRationale (the reason a control was removed or re-added, required on every removal and re-addition). See Set the scoping rationale and attached files and Remove controls from scope for each in full.

Resources#

Resource

Path

Description

Assurance projects (read + PATCH)

/assuranceProjects, /assuranceProjects/{id}

List and read projects; PATCH a project’s scoping rationale and attached files.

Scoped controls (read)

/assuranceProjects/{id}/scopedControls

The controls in a project’s scope; removed controls carry a removal rationale.

Add to scope (write)

/assuranceProjects/{id}/scopedControls/addition

Put controls in scope by control id.

Remove from scope (write)

/assuranceProjects/{id}/scopedControls/removal

Take scope entries out of scope, with a rationale.

Re-add to scope (write)

/assuranceProjects/{id}/scopedControls/reAddition

Restore removed scope entries, with a rationale.

List projects#

GET /assuranceProjects returns a page of projects of every type. Filter by status, year, or name.

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

A listed project returns the fields common to every project type — id, type, name, status, year, startDate, endDate, budgetedHours, managers, and contributors — so the shape is stable as more project types are added. The free-text objective and scopingRationale are returned when you fetch a single project by id (below), not in the list.

Note

type and status are open strings. New project types and statuses may be introduced over time, so tolerate values beyond those documented here.

Get a single project#

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

Working with an existing project#

This API does not create assurance projects — a project is created in Workiva by a user with the appropriate role. Use GET /assuranceProjects to find the project you want to scope and note its id; every operation below works against that id.

Read a project’s scope#

GET /assuranceProjects/{id}/scopedControls lists the controls in a project’s scope. Each entry has its own id (the scope-entry id you pass to the removal and reAddition operations), the control it is for (id, controlId, name), a scopeStatus, and an optional scopeRationale. That scopeRationale is the reason the control was removed from scope; it is null while the control is in scope, and is independent of the project-level scopingRationale. By default only in-scope controls are returned; include controls that have been removed from scope with a filter:

$filter=scopeStatus eq 'removedFromScope'
curl -X GET "https://api.app.wdesk.com/assuranceProjects/{id}/scopedControls" \
    -H "Accept: application/json" \
    -H "Authorization: Bearer {access-token}" \
    -H "X-Version: 2026-09-01.grc.preview" \
    -H "Wk-Workspace: {workspace-id}"

Set the scoping rationale and attached files#

PATCH /assuranceProjects/{id} updates a project’s scope documentation: its project-level scopingRationale (the whole-engagement narrative of how the overall scope was determined) and the set of Workiva files attached as supporting evidence. The body is a JSON Patch document (RFC 6902, media type application/json-patch+json): an array of operations. The supported operations are replace on /scopingRationale and replace on /scopingFiles; include an operation only for the field you want to change, and omit it to leave that field unchanged.

/scopingFiles is replace-set: the array you supply becomes the complete attached set, referenced by each file’s Workiva id. A currently-attached file you leave out is detached, and [] detaches all. Read the current set back from the project’s scopingFiles (whose id is the attachment id, not the file id). Files must be a document, spreadsheet, or presentation.

curl -X PATCH "https://api.app.wdesk.com/assuranceProjects/{id}" \
    -H "Content-Type: application/json-patch+json" \
    -H "Accept: application/json" \
    -H "Authorization: Bearer {access-token}" \
    -H "X-Version: 2026-09-01.grc.preview" \
    -H "Wk-Workspace: {workspace-id}" \
    -d '[
      { "op": "replace", "path": "/scopingRationale", "value": "FY26 scope limited to key controls over financial reporting per materiality analysis." },
      { "op": "replace", "path": "/scopingFiles", "value": [ { "id": "019f2a3b-4c5d-7e6f-8a9b-0c1d2e3f4a5b" } ] }
    ]'

Add controls to scope#

POST /assuranceProjects/{id}/scopedControls/addition adds up to 250 controls to scope, identified by their control id (as returned by the Controls API). It takes no rationale; the overall reason for the scope lives in the project’s scopingRationale (set with PATCH above). A control already in scope is rejected; use the reAddition operation to restore one that was removed. The response returns the newly scoped controls, each with its assigned ScopedControl.id.

curl -X POST "https://api.app.wdesk.com/assuranceProjects/{id}/scopedControls/addition" \
    -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": ["019cafc7-4901-7181-a53b-b2263115961d", "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"] }'

Remove controls from scope#

POST /assuranceProjects/{id}/scopedControls/removal removes up to 250 controls from scope, identified by scope-entry id (the id on each entry from the scoped-controls list, not the control’s own id). Removal is a soft change: removed controls stay visible in the scoped-controls list with a removedFromScope status. A rationale is required and is recorded against every entry in the request as its scopeRationale, separate from the project’s overall scopingRationale.

curl -X POST "https://api.app.wdesk.com/assuranceProjects/{id}/scopedControls/removal" \
    -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": ["019cb0a1-2f3e-7c22-9a10-4d5e6f7a8b90"],
      "rationale": "Below materiality threshold for FY26."
    }'

Re-add controls to scope#

POST /assuranceProjects/{id}/scopedControls/reAddition restores up to 250 previously removed controls to scope, identified by scope-entry id. A rationale is required and recorded as each entry’s scopeRationale, explaining the re-addition. An entry that is not currently removed is rejected; add a never-scoped control with the addition operation instead.

curl -X POST "https://api.app.wdesk.com/assuranceProjects/{id}/scopedControls/reAddition" \
    -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": ["019cb0a1-2f3e-7c22-9a10-4d5e6f7a8b90"],
      "rationale": "Business retained; control back in scope for FY26."
    }'

Scoping a project end to end#

  1. Find the projectGET /assuranceProjects and note the id of the project to scope.

  2. Set the scope documentationPATCH /assuranceProjects/{id} with the scopingRationale and any supporting scopingFiles.

  3. Set the initial scope — pull control ids from the Controls API, then POST …/scopedControls/addition with those ids.

  4. Adjust as decisions change — take an entry’s id from the scoped-controls list and POST …/scopedControls/removal it with a rationale for why it came out; POST …/scopedControls/reAddition the scope-entry id later with a rationale if the decision reverses.

Note

The GRC API stores the scope; your integration decides what is in scope. The project’s scopingRationale is the overall narrative (set with PATCH); a control’s scopeRationale is the reason it was removed or re-added (required on every removal and re-addition).