REST API Overview
Windshift ships a versioned REST API for automation, integrations, and embedding. Every endpoint lives under a single version prefix:
https://windshift.example.com/rest/api/v1The route table changes as features land, so the OpenAPI specification served by your own instance is the authoritative, always-current reference. Two formats are published without authentication so a client can fetch them before it has a token:
GET /rest/api/v1/openapi.json
GET /rest/api/v1/openapi.yamlPoint a generator or an API explorer at one of those URLs to get the complete, exact surface for the version you are running. This page covers the cross-cutting rules: authentication, scopes, common parameters, and a few endpoint groups worth calling out.
Authentication
The v1 surface accepts API bearer tokens only. Send the token in the Authorization header:
curl https://windshift.example.com/rest/api/v1/users/me \
-H "Authorization: Bearer crw_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"Tokens are prefixed with crw_. The cookie-authenticated surface (/api/*) used by the web UI explicitly rejects bearer tokens, and the bearer surface here rejects cookies, so the two never cross over.
The bearer surface is rate limited (1000 requests per minute) and every request carries a request ID for tracing.
Getting a token
The quickest path for CLI and agent workflows is the ws CLI, which mints a token through a browser flow on first use:
ws initSee CLI Configuration for the full flow and for passing a token explicitly with --token or WS_TOKEN. You can also create tokens in the Windshift UI, choosing the scopes the token should carry at creation time. Scope selection is the security boundary: a token only reaches what its scopes allow, and the system-admin bypass that applies to the web UI intentionally does not apply to token-scope checks, so a token issued for one bot never inherits owner privileges by accident.
Token scopes
Scopes are resource:action strings. A request is rejected unless the token carries the scope the route requires. Destructive access is opt-in: :delete is a separate scope from :write, so a token can be allowed to create and edit without ever being able to hard-delete.
| Scope | Grants |
|---|---|
items:read / items:write / items:delete |
Items, including comments, attachments, history, transitions, links, labels, and diagrams. |
workspaces:read / workspaces:write / workspaces:delete |
Workspaces and their read-only config (statuses, item types, priorities). |
collections:read |
Read-only access to collections and their items. Ideal for report and embed clients that should never mutate anything. |
milestones:read / milestones:write / milestones:delete |
Global milestone endpoints. |
iterations:read / iterations:write / iterations:delete |
Global iteration endpoints. |
projects:read / projects:write / projects:delete |
Projects. |
pages:read / pages:write / pages:delete |
Workspace pages (wiki), labels, history, permissions. :delete is required to archive a page. |
tests:read / tests:write |
Test management: folders, cases, labels, sets, plans, run templates, runs, results, and report summaries. |
assets:read / assets:write / assets:delete |
Asset sets, types, categories, statuses, and asset entities. :write covers mutations and CSV import; :delete is required to hard-delete an asset. |
time:read / time:write / time:delete |
Worklogs, timers, and time projects. :delete is required to delete a worklog. |
actions:read / actions:write |
Automation graphs: node catalog discovery plus action CRUD (:write for create/update). |
statuses:read, workflows:read, item-types:read, priorities:read, custom-fields:read |
Global, read-only configuration resources. |
users:read |
User directory and GET /users/me. |
agent-skills:read |
Read-only access to a workspace's agent skill library. |
mcp:access |
Access to the MCP server. A single binary scope (the MCP server exposes both read and write tools). |
admin:users:read / admin:users:write |
Admin user management. Requires the system-admin role in addition to the scope. |
admin:groups:read / admin:groups:write |
Admin group management. |
admin:audit-logs:read |
Read the audit log. |
admin:api-tokens:read / admin:api-tokens:write |
List and revoke API tokens. |
Admin scopes require both the scope on the token and the system-admin role on the user; one without the other is rejected. Workspace-content routes apply a second check in the handler (the user's per-workspace view/edit permission), so a token scope alone never grants access to a workspace the user cannot see.
Common parameters
exclude_personal
Several item-facing endpoints accept an exclude_personal query parameter. Set it to true (or 1) so the response never surfaces items from the caller's personal workspaces. Integrations that republish items into shared contexts (document embeds, dashboards, reports) should set it so a private personal item never leaks into a page other people read.
curl "https://windshift.example.com/rest/api/v1/search/items?q=login&exclude_personal=true" \
-H "Authorization: Bearer crw_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"It is supported on item lookup (GET /items/{id}), item search (GET /search/items), and collection items (GET /collections/{key}/items). When set on a single-item lookup, a personal item is treated as not found (404) rather than returned.
Assets
Asset entities live under their owning set. Read endpoints need assets:read, mutations need assets:write, and hard-delete needs assets:delete.
| Method and path | Scope |
|---|---|
GET /asset-sets/{setId}/assets |
assets:read |
POST /asset-sets/{setId}/assets |
assets:write |
POST /asset-sets/{setId}/assets/import |
assets:write (CSV import) |
GET /assets/{id} |
assets:read |
PUT /assets/{id} |
assets:write |
DELETE /assets/{id} |
assets:delete |
Sets, types, categories, and statuses are read-only on v1 (GET /asset-sets, GET /asset-sets/{setId}/types, and similar), all gated by assets:read. The route scope is only the first gate: the handler still asks the per-set asset role (Viewer, Editor, Administrator) before serving, and returns 404 (not 403) on any permission failure so set and asset existence is never leaked.
Attachments
Item attachments are uploaded against the item and deleted by attachment ID:
| Method and path | Scope |
|---|---|
GET /items/{id}/attachments |
items:read |
POST /items/{id}/attachments |
items:write |
GET /attachments/{id}/download |
items:read |
GET /attachments/{id}/thumbnail |
items:read |
DELETE /attachments/{id} |
items:write |
Page attachments use a parallel route, POST /workspaces/{id}/pages/{pageId}/attachments, gated by pages:write.
Input sanitization
Request input is sanitized server-side before it is stored. When sanitization changes what you sent (stripping disallowed markup from a comment or description, for example), the response reports the cleanup in a warnings array so a client can surface it rather than silently diverging from the stored value. The stored row is always the clean version; an empty or absent warnings array means nothing was changed.