---
name: workiva-public-api-oauth-token
description: Generate code to obtain an OAuth2 access token for the Workiva Public API using client credentials flow.
---

# Workiva Public API OAuth Token

Generate code to obtain an OAuth2 access token for authenticating with the Workiva Public API.

> **Important:** Do not execute the example code below unless the user explicitly asks you to run it. Present the code for the user to review and run themselves.

> **Note for agents:** The examples below use `2026-01-01` as the `X-Version` header value, but this is only a default. The user may specify any valid API version date (e.g., `2022-01-01`, `2026-01-01`). If the user specifies a different version, substitute it in the generated code.

## When to Use

Use this skill when you need to:

- Authenticate with the Workiva Public API
- Generate an OAuth2 access token using client credentials
- Set up API authentication in a new project or script

## Prerequisites

You need a **Client ID** and **Client Secret** from an OAuth2 grant. Follow the [Setup](https://developers.workiva.com/setup.html) guide on the Workiva Developer Hub for step-by-step instructions on creating an integration user and OAuth2 grant.

## Steps

1. Follow the [Setup](https://developers.workiva.com/setup.html) guide to create an OAuth2 grant and obtain your **Client ID** and **Client Secret**.
2. Make a POST request to the Workiva OAuth2 token endpoint using the client credentials grant type.
3. Use the returned access token in the `Authorization: Bearer <token>` header for subsequent API requests.
4. Include the `X-Version` header with your API version date (e.g., `2026-01-01`) on all API requests.

## Token Endpoint

The token endpoint depends on your API version and Workiva region.

**Base URLs by region:**

| Region  | Base URL                        |
|---------|---------------------------------|
| US      | `https://api.app.wdesk.com`     |
| EU      | `https://api.eu.wdesk.com`      |
| APAC    | `https://api.apac.wdesk.com`    |

**Token path by API version:**

| API Version           | Token Path          |
|-----------------------|---------------------|
| 2026-01-01 and later  | `/oauth2/token`     |
| 2022-01-01            | `/iam/v1/oauth2/token` |

Combine the base URL for your region with the token path for your API version. For example, a US account using 2026-01-01 would use:

```
POST https://api.app.wdesk.com/oauth2/token
```

The examples below use the US region with the 2026-01-01 path.

### Request

Send a `POST` request with `Content-Type: application/x-www-form-urlencoded` and the following body parameters:

| Parameter      | Value                |
|----------------|----------------------|
| grant_type     | client_credentials   |
| client_id      | YOUR_CLIENT_ID       |
| client_secret  | YOUR_CLIENT_SECRET   |

The request must also include the `X-Version` header with your API version date. See the [Token Request](https://developers.workiva.com/{version}/tokenrequest.html) documentation for details.

### Response

A successful response returns a JSON object:

```json
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 600
}
```

## Code Examples

These examples have been tested against the live API. For the most up-to-date reference, see the [Token Request](https://developers.workiva.com/2026-01-01/tokenrequest.html) documentation.

### Python

```python
import os
import requests

# Base URL for your Workiva region
BASE_URL = os.environ.get("API_BASE_URL", "https://api.app.wdesk.com")


def get_workiva_token(client_id: str, client_secret: str) -> str:
    """Obtain an OAuth2 access token for the Workiva Public API."""
    response = requests.post(
        f"{BASE_URL}/oauth2/token",
        headers={
            "X-Version": "2026-01-01",
        },
        data={
            "grant_type": "client_credentials",
            "client_id": client_id,
            "client_secret": client_secret,
        },
    )
    response.raise_for_status()
    return response.json()["access_token"]


token = get_workiva_token(
    os.environ["CLIENT_ID"],
    os.environ["CLIENT_SECRET"],
)
print(f"Success: token={token[:8]}...")

# Use the token to make an authenticated API request:
#
# response = requests.get(
#     f"{BASE_URL}/path/to/endpoint",
#     headers={
#         "Authorization": f"Bearer {token}",
#         "X-Version": "2026-01-01",
#     },
# )
# response.raise_for_status()
```

### JavaScript / Node.js

```javascript
const clientId = process.env.CLIENT_ID;
const clientSecret = process.env.CLIENT_SECRET;

// Base URL for your Workiva region
const baseUrl = process.env.API_BASE_URL || "https://api.app.wdesk.com";

async function getWorkivaToken(clientId, clientSecret) {
  const response = await fetch(
    `${baseUrl}/oauth2/token`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        "X-Version": "2026-01-01",
      },
      body: new URLSearchParams({
        grant_type: "client_credentials",
        client_id: clientId,
        client_secret: clientSecret,
      }),
    }
  );

  if (!response.ok) {
    throw new Error(`Token request failed: ${response.status}`);
  }

  const data = await response.json();
  return data.access_token;
}

const token = await getWorkivaToken(clientId, clientSecret);
console.log(`Success: token=${token.slice(0, 8)}...`);

// Use the token to make an authenticated API request:
//
// const apiResponse = await fetch(
//   `${baseUrl}/path/to/endpoint`,
//   {
//     headers: {
//       Authorization: `Bearer ${token}`,
//       "X-Version": "2026-01-01",
//     },
//   }
// );
//
// if (!apiResponse.ok) {
//   throw new Error(`API request failed: ${apiResponse.status}`);
// }
```

### cURL

```bash
#!/bin/bash
set -e

# Base URL for your Workiva region
BASE_URL="${API_BASE_URL:-https://api.app.wdesk.com}"

# Get a token
TOKEN_RESPONSE=$(curl -s -X POST "${BASE_URL}/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "X-Version: 2026-01-01" \
  -d "grant_type=client_credentials&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}")

ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

echo "Success: token=${ACCESS_TOKEN:0:8}..."

# Use the token to make an authenticated API request:
#
# curl -s "${BASE_URL}/path/to/endpoint" \
#   -H "Authorization: Bearer ${ACCESS_TOKEN}" \
#   -H "X-Version: 2026-01-01"
```

## Common Errors

| Cause | Fix |
|-------|-----|
| Invalid or expired credentials | Verify your `client_id` and `client_secret` match an active OAuth2 grant. Regenerate the secret if unsure. |
| Missing or incorrect grant type | Ensure the body includes `grant_type=client_credentials`. |
| Wrong content type | Set the `Content-Type` header to `application/x-www-form-urlencoded`. |
| Insufficient scopes on the OAuth2 grant | Update the grant's scopes. See the [Setup](https://developers.workiva.com/setup.html) guide. |

## Important Notes

- **Never hardcode credentials** in source code. Use environment variables or a secrets manager.
- Tokens expire after 10 minutes (`expires_in: 600`). Reuse the same token for multiple API calls and only refresh it when it is about to expire—do not request a new token on every API call.
- Store tokens securely and do not log them.
- Use the base URL matching your Workiva region (see Token Endpoint table above).

## Storing Credentials Securely

Your `client_id` and `client_secret` should never appear in source code, commit history, or logs. Use one of the following approaches:

**Environment variables** (simplest for local development and CI):

```bash
export WORKIVA_CLIENT_ID="your-client-id"
export WORKIVA_CLIENT_SECRET="your-client-secret"
```

Then read them in code:

```python
import os

client_id = os.environ["WORKIVA_CLIENT_ID"]
client_secret = os.environ["WORKIVA_CLIENT_SECRET"]
```

**Secrets manager** (recommended for production):

Use your platform's secrets management service.

**`.env` files** (for local development only):

Store credentials in a `.env` file and load them with a library like `python-dotenv` or `dotenv` for Node.js. Always add `.env` to your `.gitignore` to prevent accidental commits.
