Writing Data
Writing data is where an integration begins to affect employees, content, and access. Workvivo does not provide a sandbox or staging environment, so every write request reaches your production organisation. Start with a narrowly scoped API key, use test records that are safe to expose, and keep the first batch small.
If you have not authenticated a request yet, complete Quick Start first. Every example in this guide uses the static API key created under API Keys & JWT Settings as a Bearer token and sends the Workvivo-Id shown alongside the Base URL.
Choose the operation from the endpoint contract
HTTP verbs communicate intent, but the API reference for the endpoint is the source of truth for its fields and behaviour.
| Verb | Typical intent | What to check |
|---|---|---|
POST |
Create a resource or perform an action | Required fields, defaults, and whether the operation can notify users |
PUT |
Replace the writable state of a resource | Which fields must be resent and which omitted fields reset |
PATCH |
Apply named changes to part of a resource or relationship | The supported operation fields; do not send the whole resource |
DELETE |
Remove or archive the addressed resource | The resource-specific effect and successful response status |
Do not infer that every endpoint accepts the same body format. Most write endpoints in the API reference use multipart/form-data, including endpoints that do not upload a file. Other endpoints use JSON. Match the documented content type:
- With cURL, use
-For--form-stringfor multipart fields. Use--form-stringwhen an HTML value begins with<, so cURL does not interpret it as a file path. - With JavaScript
FormData, letfetchadd the multipartContent-Typeheader and boundary. Do not set that header yourself. - For a JSON endpoint, serialize the body with
JSON.stringify()and sendContent-Type: application/json.
Create in the safest available state
POST /v1/pages creates a page. The required content fields are title and html_content; this example also supplies an integer external_id and creates the page as a draft. The example assumes page 7000 already exists in the same space with that external ID.
Use identifiers and content reserved for your integration rather than copying these example values into production.
curl -X POST https://api.workvivo.com/v1/pages \
-H "Authorization: Bearer $WORKVIVO_TOKEN" \
-H "Workvivo-Id: $WORKVIVO_ORG_ID" \
-H "Accept: application/json" \
-F "external_id=7001" \
-F "external_parent_id=7000" \
-F "space_id=15" \
-F "title=Integration write test" \
--form-string "html_content=<p>Created as a draft while the integration is being verified.</p>" \
-F "is_draft=1"
const token = process.env.WORKVIVO_TOKEN;
const organisationId = process.env.WORKVIVO_ORG_ID;
if (!token || !organisationId) {
throw new Error(
'Set WORKVIVO_TOKEN and WORKVIVO_ORG_ID before running this example.',
);
}
const form = new FormData();
form.set('external_id', '7001');
form.set('external_parent_id', '7000');
form.set('space_id', '15');
form.set('title', 'Integration write test');
form.set(
'html_content',
'<p>Created as a draft while the integration is being verified.</p>',
);
form.set('is_draft', '1');
const response = await fetch('https://api.workvivo.com/v1/pages', {
method: 'POST',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${token}`,
'Workvivo-Id': organisationId,
},
body: form,
});
const body = await response.json();
if (!response.ok) {
throw new Error(
`Page creation failed (${response.status}): ${JSON.stringify(body)}`,
);
}
console.log(body);
A successful create returns 201 Created. Save the Workvivo ID from the response with the source record; you will need a stable identifier for later reads, updates, and deletes.
Creating a draft limits visibility, but it does not make the request a dry run. The draft is real production data. Not every resource has a draft state, so use the least disruptive valid configuration documented for that resource.
Full replacement and partial updates
Resend the desired state with PUT
PUT /v1/pages/{id} is a full replacement of the page's writable state, not a one-field patch. title and html_content are required. Optional writable fields can be cleared or reset when omitted, so read the current resource and merge your intended changes before sending the replacement.
The following request deliberately resends the page state it wants to preserve while changing the title:
curl -X PUT https://api.workvivo.com/v1/pages/{id} \
-H "Authorization: Bearer $WORKVIVO_TOKEN" \
-H "Workvivo-Id: $WORKVIVO_ORG_ID" \
-H "Accept: application/json" \
-F "title=Integration write test — reviewed" \
-F "subtitle=" \
-F "space_id=15" \
--form-string "html_content=<p>Created as a draft while the integration is being verified.</p>" \
-F "is_draft=1" \
-F "is_standalone=0" \
-F "is_sidebar_enabled=0"
A successful page replacement returns 200 OK. Build the body from an authoritative desired state, not from only the fields that happened to change.
Send only the named operation with PATCH
Some endpoints expose targeted mutations. PATCH /v1/spaces/{id}/users, for example, changes membership without replacing the space. At least one supported operation field is required; this request adds one user and leaves every other membership and role unchanged.
curl -X PATCH https://api.workvivo.com/v1/spaces/{id}/users \
-H "Authorization: Bearer $WORKVIVO_TOKEN" \
-H "Workvivo-Id: $WORKVIVO_ORG_ID" \
-H "Accept: application/json" \
-F "ids_to_add[0]=12345"
A successful membership change returns 200 OK. Keep additions, removals, grants, and revocations explicit, and reject contradictory operations in your own integration before sending them.
The presence of PUT or PATCH is resource-specific. If an endpoint only documents PUT, do not invent a PATCH route or assume a partial PUT is safe.
Delete deliberately
Delete effects vary by resource, so check the endpoint before relying on assumptions about archival, restoration, or cascading data. The Updates API accepts an update ID in the path, no request body, and returns the deleted record's identifiers with 200 OK.
curl -X DELETE https://api.workvivo.com/v1/updates/{id} \
-H "Authorization: Bearer $WORKVIVO_TOKEN" \
-H "Workvivo-Id: $WORKVIVO_ORG_ID" \
-H "Accept: application/json"
{
"data": {
"id": 24,
"external_id": "ext-24"
},
"status": "success",
"meta": {}
}
Before deleting, read the record using the same identifier and verify that it still matches the item your integration intends to remove. Persist the successful response so a later job does not repeat the operation unnecessarily.
Validation failures
Invalid write bodies return 400 Bad Request. The response uses the standard error envelope, with field-level messages in meta.errors. Treat this as a request problem: record the status and response body, correct the input, and only then try again.
See Handling Errors for the complete error shape and retry guidance.
External IDs and repeatable writes
External-ID support is not universal, and even its data type varies by resource. Confirm that the resource documents both an external_id on create and a corresponding by-external-id operation before building around it.
Where that pair exists, an integration can approximate an upsert by updating the known external ID and creating the resource when it does not exist. The PUT route itself does not create a missing resource, so this is an integration-side workflow rather than an atomic API operation. Serialize writes for each external ID and reconcile after an ambiguous response to avoid two workers creating the same record.
Workvivo does not provide a generic idempotency-key header. Never send Idempotency-Key or assume a repeated POST will be deduplicated. External IDs are the closest repeatable-write mechanism for the resources that support them.
See External IDs & Syncing for the full workflow and the resource-specific support you must verify.
A production-safe write workflow
Use the same guardrails no matter which resource you write:
- Grant minimum permissions. Give the API key only the scopes needed for the selected endpoints.
- Validate before sending. Check required fields, identifier types, mutually exclusive fields, and the documented body format locally.
- Plan the exact change. For replacements, merge the current record with your authoritative desired state. For targeted mutations, send only the named operations.
- Start with one reversible record. Prefer a draft or otherwise low-impact state when the resource supports one, then read it back and inspect the result.
- Bound each batch. Process small batches, serialize work for the same identifier, and stop automatically when failures cross a threshold you define.
- Record outcomes. Store the source identifier, Workvivo identifier, request type, response status, and timestamp. Do not log API keys or sensitive body fields.
- Reconcile before retrying. A timeout does not prove the write failed. Read the resource to determine whether the intended state already exists before repeating a create, replacement, or delete.
There is no API rate limit today, but that is not a reason to issue unbounded concurrent writes. Control concurrency to limit production impact, simplify reconciliation, and make failures easier to contain.