Remediation Issues & Action Tasks Guide#

This guide walks through keeping remediation work in sync with an external system, such as Jira, using the GRC API. Workiva stays the source of truth for issues and action tasks; your integration reads what it needs to represent an action task in the external system, and pushes back the changes made there. 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 updating issues and action tasks.

Important

Issues and action tasks are created, assigned, and reviewed in Workiva. You cannot create or delete them through this API, and an issue’s status and resolution are read-only. Review and closure of the issue stay in Workiva.

Note

There are no webhooks. Your integration polls and compares each action task’s modified.dateTime with the value it saw last time to find new or changed work.

Resources#

Resource

Path

Description

Issues (read + PATCH)

/issues, /issues/{id}

List and read issues; PATCH an issue’s fields.

Issue batch retrieval (read)

/issues/batchRetrieval

Read up to 50 issues by id in one request.

Issue export (read)

/issues/export, /operations/{operationId}

Export issues to a CSV or JSONL file asynchronously, for example for an initial backfill.

Action tasks (read + PATCH)

/issues/{issueId}/actionTasks, /issues/{issueId}/actionTasks/{id}

List and read an issue’s action tasks; PATCH a task’s name, description, and due date.

Submit externally (write)

/issues/{issueId}/actionTasks/{id}/externalSubmission

Mark a task sent and in progress because the work is tracked in an external system.

Implementation (write)

/issues/{issueId}/actionTasks/{id}/implementation

Mark a task implemented.

Sent externally and implemented#

An action task has its own lifecycle status: notSent, inProgress, implemented, overdue (computed from the due date), or cancelled. Treat the value as an open string, since new statuses may be added. Two of those states can be set through the API:

  • Sent externally (externalSubmission). The work is now tracked in an external system. The task moves to inProgress (or stays overdue if its due date has passed) and gets a sentAt time, and its assigneeStatus becomes inProgress. Workiva does not create a Task Portal task for the assignee, send a notification or email, or allow reminders for the task, so the assignee has no duplicate work in Workiva. This is not the same as sending a task from the Workiva app, which does all of those.

  • Implemented (implementation). The work is done. The task moves to implemented and gets an implementedAt time.

assigneeStatus is a separate, read-only status for the assignee’s own response to the task.

Step 1: Find the issues in scope#

GET /issues returns a page of issues. Filter by status, type, severity, auditTeamOwner.id, businessOwner.id, or isOverdue, and order by severity, targetRemediationDate, name, created.dateTime, or reportedDate. See Filtering & Sorting. Follow @nextLink to read every page (see Pagination).

curl -X GET "https://api.app.wdesk.com/issues" \
    -G --data-urlencode "\$filter=status eq 'open' and severity eq 'high'" \
    -H "Accept: application/json" \
    -H "Authorization: Bearer {access-token}" \
    -H "X-Version: 2026-09-01.grc.preview" \
    -H "Wk-Workspace: {workspace-id}"
{
  "data": [
    {
      "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "name": "Segregation of duties violation in AP processing",
      "type": "auditFinding",
      "status": "open",
      "severity": "high",
      "isOverdue": false,
      "created": {
        "dateTime": "2026-03-15T14:30:00Z",
        "user": { "id": "u_4cc9f9128a684f45a7a37b08b70b54c1" }
      },
      "modified": {
        "dateTime": "2026-03-20T09:00:00Z",
        "user": { "id": "u_4cc9f9128a684f45a7a37b08b70b54c1" }
      }
    }
  ],
  "@nextLink": "https://api.app.wdesk.com/issues?$next=b3BhcXVlLW5leHQtcGFnZS10b2tlbg=="
}

The list omits the detail fields (description, rootCause, supplementalInformation, and resolutionNote). Fetch a single issue with GET /issues/{id} for the full representation, or many at once with POST /issues/batchRetrieval, which takes up to 50 ids and returns the issues in request order:

curl -X POST "https://api.app.wdesk.com/issues/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": ["1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d"] }'

Batch retrieval is all-or-nothing. A malformed id returns 400 with the position as target (for example /data/0), and an id that does not exist or that you cannot read returns 403 without saying which one. See Errors.

Bulk export for an initial backfill#

To load every issue the first time, instead of paging through GET /issues, export them with POST /issues/export. With no updatedAt, the last 365 days apply; to load older issues, export earlier windows of up to a year each. The export runs asynchronously as an operation. The body takes a format (jsonl or csv), an optional filter (the same grammar as GET /issues), and an optional updatedAt window with from and to (a half-open [from, to) range in UTC).

curl -X POST "https://api.app.wdesk.com/issues/export" \
    -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 '{ "format": "jsonl", "updatedAt": { "from": "2025-10-01T00:00:00Z", "to": "2026-10-01T00:00:00Z" } }'

The response is 202 Accepted with a Location header pointing at the operation, and the effective window in the body:

{
  "updatedAt": {
    "from": "2025-10-01T00:00:00Z",
    "to": "2026-10-01T00:00:00Z"
  }
}

Poll GET /operations/{operationId} from the Location header, waiting the number of seconds in any Retry-After header between polls, until the status is completed. Then download the file from the operation’s resourceUrl. The URL is pre-signed, so treat it as a credential and do not log it. The operation stays available for 7 days. A failed operation reports the reason in details.

  • Use jsonl for sync. Each line is the same issue object that GET /issues/{id} returns, including created, modified, and custom fields. csv has fixed columns followed by one column per custom field, whose names can vary by workspace, so it suits spreadsheets better than integrations. (The CSV column headers, such as createdAt, updatedAt, and createdById, are CSV-only names and are not $orderBy keys.) Action tasks are not included; list them per issue as in Step 2.

  • Window. An updatedAt window covers issues created or changed in it and can span up to 366 days, so backfill older history in chunks of up to a year. The effective end is capped at 30 seconds before the request, so a window that ends now does not miss recent writes. Start the next window at the updatedAt.to the response echoes, overlap windows by about a minute, and de-duplicate by id and modified.dateTime. For incremental sync:

    • Export without a filter. An issue that stops matching the filter never appears in a later window, so you would never learn it changed. Do not carry Step 1’s status eq 'open' example into the export.

    • Deleted issues do not appear in any window.

    • Deleting a linked control, process, or risk does not change an issue’s updatedAt, so controlIds, processIds, and riskIds (controls, processes, and risks in jsonl) can be out of date until the issue itself changes.

  • One export at a time. You can have one export in flight per caller per workspace. Repeating the same request (same format, filter, and updatedAt bounds) returns 202 for the export already running, so retrying after a lost response is safe. A different request returns 429 with a Retry-After header until the running export finishes.

  • Size. An export can contain up to 50,000 issues. If the count is already above that when the request arrives, or the filter or window is invalid, it returns 400; otherwise the operation fails with the reason in details. Either way, narrow the filter or window. The file is never truncated.

Step 2: List each issue’s action tasks and spot changes#

GET /issues/{issueId}/actionTasks lists an issue’s action tasks. An issue has at most 10 action tasks, so they are all returned in one page. There is no endpoint that lists action tasks across issues, so go issue by issue.

curl -X GET "https://api.app.wdesk.com/issues/{issueId}/actionTasks" \
    -H "Accept: application/json" \
    -H "Authorization: Bearer {access-token}" \
    -H "X-Version: 2026-09-01.grc.preview" \
    -H "Wk-Workspace: {workspace-id}"
{
  "data": [
    {
      "id": "3f2b1a4c-6d5e-4f7a-8b9c-1d2e3f4a5b6c",
      "name": "Implement dual-approval workflow",
      "description": "Configure the AP system so purchase order creation and approval require two distinct users.",
      "status": "notSent",
      "assigneeStatus": "notSent",
      "dueDate": "2026-06-30T23:59:00Z",
      "assignee": { "id": "u_4cc9f9128a684f45a7a37b08b70b54c1" },
      "created": {
        "dateTime": "2026-03-15T14:30:00Z",
        "user": { "id": "u_4cc9f9128a684f45a7a37b08b70b54c1" }
      },
      "modified": {
        "dateTime": "2026-03-15T14:30:00Z",
        "user": { "id": "u_4cc9f9128a684f45a7a37b08b70b54c1" }
      },
      "issue": { "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" }
    }
  ]
}

Store each task’s id and the modified.dateTime you last synced. On the next poll, a task id you have not seen is new work, and a later modified.dateTime means the task changed in Workiva, including changes on the assignee side. GET /issues/{issueId}/actionTasks/{id} returns a single task.

Your own PATCH, externalSubmission, and implementation calls also set a new modified.dateTime. After each write, store the modified.dateTime from the response, or skip changes where modified.user is the integration’s own user. Otherwise your next poll reads your own write as a Workiva edit and pushes it back to the external system.

Step 3: Create the external work item#

For each new task, build the work item in the external system from the task and its issue:

  • Map the task’s name, description, and dueDate to the item’s title, description, and due date.

  • Use the issue’s name, severity, owners, and linked controls, processes, and risks (from Step 1) as context. Relationships are returned as digests.

  • Map the task’s assignee, a user digest holding only an id, to a user in the external system yourself.

  • Store the Workiva task id (and the issue.id) on the external item, and the external key with it, so later events can be routed back to the right task. Workiva has nowhere to store the external key.

Comments and attachments stay in the external system; action tasks have neither in Workiva.

Step 4: Push edits back with PATCH#

When the name, description, or due date changes in the external system, update the action task with PATCH /issues/{issueId}/actionTasks/{id}. The body is a JSON Patch document (RFC 6902, media type application/json-patch+json) with replace operations on /name, /description, and /dueDate. Include an operation only for a field you want to change. /dueDate takes an RFC 3339 date-time and the task is due at the end of that date (UTC). A due date in the past is allowed, so an overdue date can be synced; the task then has the overdue status.

curl -X PATCH "https://api.app.wdesk.com/issues/{issueId}/actionTasks/{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": "/name", "value": "Implement dual-approval workflow" },
      { "op": "replace", "path": "/dueDate", "value": "2026-07-31T23:59:00Z" }
    ]'

The assignee and status cannot be patched and return 400. A task that is already implemented or cancelled, or whose assigneeStatus is completed or cancelled, can no longer be changed and returns 409.

Edits made in Workiva reach the external system the other way: they show up as a newer modified.dateTime on your next poll (Step 2), and you then update the external item.

Issues can be updated too, with PATCH /issues/{id}, for example when your integration owns a custom field on the issue. See Updating Resources for the patchable fields and how to patch a custom field by its schema id.

Step 5: Mark sent externally, then implemented#

When the external item is created, call externalSubmission so Workiva records that the work is in progress elsewhere. It takes no request body.

curl -X POST "https://api.app.wdesk.com/issues/{issueId}/actionTasks/{id}/externalSubmission" \
    -H "Accept: application/json" \
    -H "Authorization: Bearer {access-token}" \
    -H "X-Version: 2026-09-01.grc.preview" \
    -H "Wk-Workspace: {workspace-id}"
{
  "id": "3f2b1a4c-6d5e-4f7a-8b9c-1d2e3f4a5b6c",
  "name": "Implement dual-approval workflow",
  "status": "inProgress",
  "assigneeStatus": "inProgress",
  "dueDate": "2026-06-30T23:59:00Z",
  "sentAt": "2026-03-20T09:00:00Z",
  "created": {
    "dateTime": "2026-03-15T14:30:00Z",
    "user": { "id": "u_4cc9f9128a684f45a7a37b08b70b54c1" }
  },
  "modified": {
    "dateTime": "2026-03-20T09:00:00Z",
    "user": { "id": "u_4cc9f9128a684f45a7a37b08b70b54c1" }
  },
  "issue": { "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" }
}

It is allowed while the task has not been sent (notSent, or overdue for a task that was never sent). Retrying is safe: a task already sent this way is returned unchanged with 200. A task that was sent from the Workiva app, or that is implemented or cancelled, returns 409.

When the external item is done, call implementation. It takes no request body.

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

It is allowed from notSent, inProgress, or overdue. A task that is already implemented is returned unchanged with 200, so retrying is safe, and a cancelled task returns 409. If the task was sent from the Workiva app, its Task Portal task is cancelled. There is no endpoint to reopen an implemented task, so a reopened external item cannot be propagated; flag it for the issue owner on your side.

Step 6: Review stays in Workiva#

Once tasks are implemented, the issue owner reviews the remediation and continues the issue’s lifecycle, including verification and resolution, in Workiva. The API does not change an issue’s status. Read it with GET /issues/{id} if you want to reflect it, and retire the link to the external item when you no longer need it.

Syncing remediation work end to end#

  1. Find the issues in scope — GET /issues with filters, POST /issues/batchRetrieval for known ids, or POST /issues/export for an initial backfill.

  2. Spot new and changed tasks — GET /issues/{issueId}/actionTasks for each issue, comparing each task’s modified.dateTime with the one you stored.

  3. Create the external item — from the task and its issue, and store the task id with it.

  4. Keep fields in step — PATCH /issues/{issueId}/actionTasks/{id} for edits made in the external system; use modified.dateTime to find edits made in Workiva.

  5. Report progress — POST …/externalSubmission when the external item is created, and POST …/implementation when it is done.

  6. Review in Workiva — the issue owner reviews and closes the issue there.

Note

The GRC API stores the task; your integration decides which issues go to the external system and owns the link between the two. Plan for a polling cost of one list call per in-scope issue per cycle, and respect the limits.