Build on Zinvee Work
Read and change tasks, projects, comments and time from your own code, hear the moment work changes, and let an AI assistant work with your tasks. Everything acts as the person who owns the token, with exactly their access.
Base URL https://work-api.zinvee.com/v1. Every endpoint is in the API reference.
Quick start
- Sign in to Zinvee Work, open Profile → API tokens and choose New token. Pick Read only or Read and write and how long it should last. Copy the token: it is shown once.
- Check it works:
export WORK_TOKEN="dgt_pat_…" curl https://work-api.zinvee.com/v1/me \ -H "Authorization: Bearer $WORK_TOKEN" - Create a task. A plain date is due at 18:00 in your workspace’s timezone:
curl -X POST https://work-api.zinvee.com/v1/tasks \ -H "Authorization: Bearer $WORK_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"title": "Send the GST return", "dueAt": "2026-11-20", "priority": "high"}' - List what is on your plate, soonest first:
curl "https://work-api.zinvee.com/v1/tasks?assignee=me&open=true&sort=due_asc" \ -H "Authorization: Bearer $WORK_TOKEN"
A task can be addressed by its id or by its key: /v1/tasks/DGT-42.
Authentication
Send a personal API token as a bearer token: Authorization: Bearer dgt_pat_…. A token acts as the person who created it (their role, the projects they can see, a guest’s limits, the workspace’s plan) and never more. It stops working the moment it is revoked or expires, its owner is removed, or the workspace is suspended.
- Read only tokens can call GET endpoints and the MCP server’s read tools. Read and write tokens can call everything.
- Tokens are stored as a keyed hash: nobody can see one again after it is created, us included.
- Each person manages their own in Profile → API tokens. Workspace admins see every token (who holds it, its access, when and from where it was last used) and can revoke any of them in Settings → Security.
- Tokens work only on the API (
/v1) and the MCP server (/mcp), not for signing in, account security, billing or exports.
Pagination
Every list returns the same envelope. Pass nextCursor back as cursor until hasMore is false; limit sets the page size (1 to 100, default 50). Cursors are opaque, so don’t build them yourself.
{
"data": [ { "id": "…", "key": "DGT-41", "title": "…" } ],
"hasMore": true,
"nextCursor": "eyJ2IjoxLCJvIjo1MH0"
}Errors
Errors use HTTP status codes and one body shape: { "error": { "code", "message", "details" } }. Branch on code; show message to people.
| Status | Codes | Meaning |
|---|---|---|
401 | unauthorized, api_token_required | No token, or it is invalid, expired or revoked. The API only takes personal API tokens. |
403 | insufficient_scope | A read-only token tried to change something. |
403 | forbidden, guest_forbidden | Your role, or the project, does not allow it: the same rules as the app. |
403 | plan_upgrade_required | The feature needs a higher plan (hook subscriptions need Pro or Business). |
404 | not_found | It does not exist, or you cannot see it. The two look the same on purpose. |
409 | conflict, idempotency_key_reused, idempotency_in_progress | It clashes with the current state, or an Idempotency-Key was reused. |
422 | validation_error | The request is invalid. details lists each field: [{ path, message }]. |
429 | rate_limited | Over the token’s budget for the minute. Wait Retry-After seconds. |
Rate limits
Each token may make 120 requests a minute by default, across the API and the MCP server. Limits are per token, not per plan. Every response says where you are:
RateLimit-Limit: 120
RateLimit-Remaining: 117
RateLimit-Reset: 42Over the limit you get 429 with Retry-After (seconds). Back off and retry.
Idempotency
Network calls fail half-way. Send an Idempotency-Key header (a UUID is ideal) with any create except subscribing a hook (its signing secret is never stored, so it can’t be replayed). A retry with the same key within 24 hours returns the first response, marked Idempotent-Replayed: true, instead of creating a second task. Reusing a key for a different request is refused with 409. Requests that fail are not remembered, so you can fix them and retry with the same key.
Triggers and webhooks
Two ways to hear about new and changed work.
Polling
Ask for tasks newer than the newest one you have seen, newest first. Use createdAt of the first item as the next since:
curl "https://work-api.zinvee.com/v1/tasks?sort=created_desc&since=2026-11-03T08:15:00.000Z" \
-H "Authorization: Bearer $WORK_TOKEN"For changed tasks use sort=updated_desc and the newest updatedAt. For new comments, poll GET /v1/comments?since=…: newest first, each with the task it is on (add task=DGT-42 to follow one task).
REST hooks
On Pro and Business, subscribe a URL to an event and Work POSTs each one to it as it happens: the same object the API returns, as the subscriber sees it at that moment. Events: task.created, task.updated, task.completed, comment.created, project.created.
curl -X POST https://work-api.zinvee.com/v1/hooks \
-H "Authorization: Bearer $WORK_TOKEN" \
-H "Content-Type: application/json" \
-d '{"event": "task.completed", "targetUrl": "https://example.com/hooks/work"}'The response carries the hook’s id and its signing secret (shown once). Unsubscribe with DELETE /v1/hooks/{id}, or answer a delivery with 410 Gone. Each delivery looks like this:
POST /hooks/work
Content-Type: application/json
X-Diginet-Event: task.completed
X-Diginet-Delivery: 6f1c… (same on retries: dedupe on it)
X-Diginet-Signature: sha256=9b2e…
{
"id": "6f1c…",
"event": "task.completed",
"hookId": "c2a8…",
"occurredAt": "2026-11-03T08:15:02.114Z",
"sentAt": "2026-11-03T08:15:02.380Z",
"payload": { "id": "…", "key": "DGT-42", "title": "…", "status": "done" }
}Check the signature before trusting a delivery. It is an HMAC-SHA256 of the raw body with the hook’s secret:
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody: the request body exactly as received (a Buffer or string).
export function isFromWork(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(signatureHeader ?? '');
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}Answer with any 2xx within 10 seconds. Anything else is retried with backoff; after 10 failed deliveries in a row the hook is switched off and its owner is told. Targets must be on the public internet. A delivery is only sent when the hook’s owner can see the task, so moving a task into a private project they are not on stops its events.
Zapier, Make and n8n
Each platform’s own HTTP and webhook building blocks work with the API:
| To do this | Zapier | Make | n8n |
|---|---|---|---|
| Poll for new work (every plan) | Webhooks by Zapier → Retrieve Poll | Scheduled scenario + HTTP → Make a request | Schedule Trigger + HTTP Request |
| Instant events (Pro and Business) | Webhooks by Zapier → Catch Hook | Webhooks → Custom webhook | Webhook node (production URL) |
| Create, update, comment | Webhooks by Zapier → Custom Request | HTTP → Make a request | HTTP Request |
- Send your token on every request as the header
Authorization: Bearer dgt_pat_…. In n8n, save it once as a Header Auth credential and pick that in each HTTP Request node. - Poll for new work (every plan). Ask for the newest tasks; the list is under
dataand each item’sidis unique:GET https://work-api.zinvee.com/v1/tasks?sort=created_desc&since=2026-11-03T08:15:00.000Z Authorization: Bearer dgt_pat_…Zapier’s Retrieve Poll remembers which ids it has seen: give it the URL without
sinceand set its key todata. In Make and n8n, run on a schedule and setsinceto a little before the previous run, skipping ids you have handled. In n8n, for a workflow that runs every five minutes with two minutes of overlap:{{ $now.minus({ minutes: 7 }).toUTC().toISO() }}. - Or get events instantly (Pro and Business). Copy the URL your catch-hook trigger shows and subscribe it once with
POST /v1/hooksas above. Each delivery’spayloadis the task (or comment, or project). Keep the hook’sidand delete it when you switch the automation off. The URL must be reachable from the internet, so a self-hosted n8n needs a public address. - Create and update. Send JSON, and use the triggering item’s id as the
Idempotency-Keyso a step that runs twice still creates one task:POST https://work-api.zinvee.com/v1/tasks Authorization: Bearer dgt_pat_… Content-Type: application/json Idempotency-Key: form-entry-8812 { "title": "Call back Anil", "description": "From the website form", "priority": "high", "dueAt": "2026-11-20" }Change a task with
PATCH /v1/tasks/DGT-42and only the fields you send; comment withPOST /v1/tasks/DGT-42/commentsand{"body": "…"}.
Connect an AI assistant
The MCP server lets an assistant that speaks the Model Context Protocol search, read and update your tasks, as you and with your access. It uses the Streamable HTTP transport at:
https://work-api.zinvee.com/mcpAuthenticate with a personal API token in the Authorization header. With a Read only token the assistant gets only the read tools. For clients that take an mcpServers configuration with remote servers:
{
"mcpServers": {
"zinvee-work": {
"type": "http",
"url": "https://work-api.zinvee.com/mcp",
"headers": {
"Authorization": "Bearer dgt_pat_…"
}
}
}
}Clients that can only start local (stdio) servers can reach it through a stdio-to-HTTP bridge, for example the open-source mcp-remote package:
{
"mcpServers": {
"zinvee-work": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://work-api.zinvee.com/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer dgt_pat_…"
}
}
}
}| Tool | What it does | Token |
|---|---|---|
search_tasks | Find tasks by words and filters | Read |
get_task | One task with its checklist and latest comments | Read |
list_my_tasks | Your open tasks, grouped by when they are due | Read |
list_projects | Projects with open and overdue counts | Read |
get_project_status | A project’s health and recent status updates | Read |
list_people | People you work with | Read |
create_task | Create a task | Read and write |
update_task | Change title, description, status, priority, due date, assignees or project | Read and write |
add_comment | Comment on a task | Read and write |
Every tool answers with task keys and links into the app, so an assistant can point you straight at the work. The server is stateless and needs no session.