Reading Data
Reading from the Workvivo API follows two common patterns:
- A single-resource endpoint returns one object in
data. - A paginated list endpoint returns an array in
dataand pagination details inmeta.pagination.
The same techniques apply across resources. Use the API reference to find the exact endpoint, permissions, filters, and fields available for the resource you need.
Before you make a request
Create a static API key under API Keys & JWT Settings in Workvivo administration and send it as a Bearer token. This is an app API key, not OAuth2 or a personal user-login token. Every request also needs the Workvivo-Id header containing the organisation identifier shown alongside your Base URL.
The examples use these environment variables:
WORKVIVO_TOKENfor the API keyWORKVIVO_ORG_IDfor the organisation identifier
Workvivo does not provide a sandbox or staging environment, so integrations connect to production. Start with read-only permissions and narrowly filtered requests while developing.
Fetch one resource by ID
When you already have a resource's Workvivo ID, insert it into that resource's detail endpoint. For example, GET /v1/teams/{id} returns one team:
curl -X GET https://api.workvivo.com/v1/teams/1 \
-H "Authorization: Bearer $WORKVIVO_TOKEN" \
-H "Workvivo-Id: $WORKVIVO_ORG_ID" \
-H "Accept: application/json"
{
"data": {
"id": 1,
"name": "Accounting",
"external_id": "accounting-0001",
"avatar_url": "https://workvivo.com/img/teams/accounting.jpg",
"permalink": "https://workvivo.com/teams/1",
"team_type": {
"id": 1,
"name": "Departments"
}
},
"status": "success",
"meta": {}
}
For a successful detail response, data is an object. Read fields from that object, such as body.data.name.
Some resources also support lookup by an external ID, but that support is not universal. Check the API reference for the resource before using an external-ID route, and see External IDs & Syncing for the full pattern.
List a resource
A list endpoint returns the same top-level envelope, but data is an array. This request asks for the first two goals:
curl -X GET "https://api.workvivo.com/v1/goals?skip=0&take=2" \
-H "Authorization: Bearer $WORKVIVO_TOKEN" \
-H "Workvivo-Id: $WORKVIVO_ORG_ID" \
-H "Accept: application/json"
{
"data": [
{
"id": 1,
"name": "Growth - Going for Gold!",
"description": "Dream big and set ambitious goals",
"start_date": "2024-04-01T11:00:00Z",
"end_date": "2024-04-30T11:00:00Z",
"image": {
"mime_type": "image/png",
"url": "https://workvivo.com/img/diversity.jpeg",
"width": 128,
"height": 128
},
"permalink": "https://workvivo.com/goals/1"
},
{
"id": 2,
"name": "Purpose - Giving back to the Community",
"description": "We authentically and passionately believe in CSR",
"start_date": "2024-04-01T11:00:00Z",
"end_date": "2024-04-30T11:00:00Z",
"image": {
"mime_type": "image/png",
"url": "https://workvivo.com/img/diversity.jpeg",
"width": 128,
"height": 128
},
"permalink": "https://workvivo.com/goals/2"
}
],
"status": "success",
"meta": {
"pagination": {
"skip": 0,
"take": 2,
"total_records": 4,
"next_page": "https://api.workvivo.com/v1/goals?skip=2&take=2"
}
}
}
The response has three top-level fields:
| Field | Meaning |
|---|---|
data |
The resources in the current page. It is an array for a list response. |
status |
success when the request succeeds. |
meta.pagination |
The current offset, page size, total matching records, and URL for the next page. |
An empty result is still a successful list response: data is an empty array and, for a paginated endpoint, next_page is null.
Paginate through a complete collection
Paginated Customer API collections use offsets:
skipis the number of matching records to skip.takeis the number of records to return, from 1 to 100.next_pageis the complete URL for the next page, ornullafter the final page.
The first request can set take=100 to reduce the number of requests for a large collection. After that, follow next_page exactly instead of calculating the next skip value yourself. The returned URL carries the current filters forward.
The first example uses a regular while loop to read and process every user page. The second shows the same pagination flow as an async generator if you prefer to consume users with for await...of. Both approaches keep only the current page in memory:
const headers = {
Authorization: `Bearer ${process.env.WORKVIVO_TOKEN}`,
"Workvivo-Id": process.env.WORKVIVO_ORG_ID,
Accept: "application/json",
};
let nextPage = "https://api.workvivo.com/v1/users?take=100";
while (nextPage !== null) {
const response = await fetch(nextPage, { headers });
if (!response.ok) {
throw new Error(`Workvivo request failed with status ${response.status}`);
}
const body = await response.json();
for (const user of body.data) {
console.log(user.id, user.display_name);
}
nextPage = body.meta.pagination.next_page;
}
const headers = {
Authorization: `Bearer ${process.env.WORKVIVO_TOKEN}`,
"Workvivo-Id": process.env.WORKVIVO_ORG_ID,
Accept: "application/json",
};
async function* listAllUsers() {
let nextPage = "https://api.workvivo.com/v1/users?take=100";
while (nextPage !== null) {
const response = await fetch(nextPage, { headers });
if (!response.ok) {
throw new Error(`Workvivo request failed with status ${response.status}`);
}
const body = await response.json();
for (const user of body.data) {
yield user;
}
nextPage = body.meta.pagination.next_page;
}
}
for await (const user of listAllUsers()) {
console.log(user.id, user.display_name);
}
The loop ends only when next_page is null. Do not stop merely because a page contains fewer items than requested; the response metadata is the authoritative signal.
Offset-based collections can change while you traverse them. If records are added or removed during a long run, a later offset can shift. For a consistent full sync, record what you processed and make the operation safe to repeat rather than assuming a changing production collection is a fixed snapshot.
Filter a collection
Filters are query parameters and vary by endpoint. Consult the API reference rather than carrying a parameter from one resource to another.
For example, the spaces endpoint supports name, visibility, and in_categories. Parameters that accept multiple values use a pipe-delimited list before URL encoding. This request finds public engineering spaces in either category 3 or 8:
curl -X GET "https://api.workvivo.com/v1/spaces?name=Engineering&visibility=public&in_categories=3%7C8&take=50" \
-H "Authorization: Bearer $WORKVIVO_TOKEN" \
-H "Workvivo-Id: $WORKVIVO_ORG_ID" \
-H "Accept: application/json"
const url = new URL("https://api.workvivo.com/v1/spaces");
url.search = new URLSearchParams({
name: "Engineering",
visibility: "public",
in_categories: "3|8",
take: "50",
});
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.WORKVIVO_TOKEN}`,
"Workvivo-Id": process.env.WORKVIVO_ORG_ID,
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(`Workvivo request failed with status ${response.status}`);
}
const { data: spaces } = await response.json();
Apply filters on the first request, then follow the returned next_page URL. Rebuilding later page URLs by hand risks dropping a filter and mixing unrelated records into the result.
Read efficiently
- Use a detail endpoint when you know the ID. Listing an entire collection and searching locally transfers more data and requires more requests.
- Filter at the API. Narrow a collection by the supported query parameters before processing it.
- Choose an appropriate
take. A larger page reduces round trips for a full traversal; a smaller page is useful when you need only a few records. - Expand related data only when needed. For example,
GET /v1/users?expand=teamsadds team data to each user. Omit expansions when the base representation is enough. - Reuse results within one operation. If several steps need the same unchanged resource, pass the fetched object through your code instead of requesting it repeatedly.
- Do not treat production data as a sandbox. Keep development reads narrow and give the app only the permissions it needs.
For response failures and retry decisions, continue with Handling Errors.