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.previewandWk-Workspaceheaders on every request (see Request headers).The
grc:readscope for reads, and thegrc:writescope 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) |
|
List and read issues; |
Issue batch retrieval (read) |
|
Read up to 50 issues by id in one request. |
Issue export (read) |
|
Export issues to a CSV or JSONL file asynchronously, for example for an initial backfill. |
Action tasks (read + PATCH) |
|
List and read an issue’s action tasks; |
Submit externally (write) |
|
Mark a task sent and in progress because the work is tracked in an external system. |
Implementation (write) |
|
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 toinProgress(or staysoverdueif its due date has passed) and gets asentAttime, and itsassigneeStatusbecomesinProgress. 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 toimplementedand gets animplementedAttime.
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
jsonlfor sync. Each line is the same issue object thatGET /issues/{id}returns, includingcreated,modified, and custom fields.csvhas 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 ascreatedAt,updatedAt, andcreatedById, are CSV-only names and are not$orderBykeys.) Action tasks are not included; list them per issue as in Step 2.Window. An
updatedAtwindow 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 theupdatedAt.tothe response echoes, overlap windows by about a minute, and de-duplicate byidandmodified.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’sstatus 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, socontrolIds,processIds, andriskIds(controls,processes, andrisksinjsonl) 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, andupdatedAtbounds) returns202for the export already running, so retrying after a lost response is safe. A different request returns429with aRetry-Afterheader 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
filteror window is invalid, it returns400; otherwise the operation fails with the reason indetails. 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, anddueDateto the item’s title, description, and due date.Use the issue’s
name,severity, owners, and linkedcontrols,processes, andrisks(from Step 1) as context. Relationships are returned as digests.Map the task’s
assignee, a user digest holding only anid, to a user in the external system yourself.Store the Workiva task
id(and theissue.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#
Find the issues in scope —
GET /issueswith filters,POST /issues/batchRetrievalfor known ids, orPOST /issues/exportfor an initial backfill.Spot new and changed tasks —
GET /issues/{issueId}/actionTasksfor each issue, comparing each task’smodified.dateTimewith the one you stored.Create the external item — from the task and its issue, and store the task
idwith it.Keep fields in step —
PATCH /issues/{issueId}/actionTasks/{id}for edits made in the external system; usemodified.dateTimeto find edits made in Workiva.Report progress —
POST …/externalSubmissionwhen the external item is created, andPOST …/implementationwhen it is done.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.