API Reference
The Workloom API is a REST API built with Hono and Zod OpenAPI. All endpoints are mounted under /api/v1 and require authentication.
Base URL: http://localhost:3001/api/v1
OpenAPI spec: GET /api/v1/openapi.json returns the full OpenAPI 3.1.0 schema.
Authentication
All requests require one of:
| Method | Header / Cookie | Use case |
|---|---|---|
| Bearer token (JWT) | Authorization: Bearer <token> | Web app sessions |
| API token | Authorization: Bearer wl_<hex> | CI/CD and scripts |
| Session cookie | authjs.session-token | Browser requests |
API tokens are prefixed wl_ followed by 64 hex characters. Create them via the Settings UI.
Pagination
List endpoints use cursor-based pagination:
| Parameter | Description |
|---|---|
cursor | Opaque cursor from the previous response |
limit | Items per page (1-100, varies by endpoint) |
Responses include nextCursor (null when no more pages) and hasMore (boolean).
Error Responses
{
"error": "Human-readable message",
"code": "ERROR_CODE",
"details": {\}
\}
| Status | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST | Invalid input |
| 401 | UNAUTHORIZED | Missing or invalid credentials |
| 403 | FORBIDDEN | Insufficient permissions |
| 404 | NOT_FOUND | Resource does not exist |
| 409 | CONFLICT | Invalid state transition |
| 500 | INTERNAL_ERROR | Server error |
Organizations
GET /orgs/{orgId}/sso
Get SSO configuration for an organization.
Parameters:
orgId(path, UUID) — Organization ID
Response 200:
{
"provider": "okta" | "azure-ad" | "google-workspace" | null,
"issuer": "https://idp.example.com",
"clientId": "abc123",
"enforced": false,
"configured": true
\}
PUT /orgs/{orgId}/sso
Update SSO configuration. Requires owner or admin role.
Parameters:
orgId(path, UUID) — Organization ID
Body:
{
"provider": "okta",
"issuer": "https://idp.example.com",
"clientId": "abc123",
"clientSecret": "secret (write-only)",
"enforced": false
\}
Response 200: Updated SSO config (same shape as GET, no secret).
GET /orgs/audit
Query organization audit logs with cursor pagination.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
action | string | No | Filter by action type |
resourceType | string | No | Filter by resource type |
cursor | ISO 8601 | No | Pagination cursor |
limit | int | No | Results per page (default: 50, max: 100) |
Response 200:
{
"entries": [
{
"id": "uuid",
"orgId": "uuid",
"actorType": "user",
"actorId": "uuid",
"action": "package.status_updated",
"resourceType": "package",
"resourceId": "uuid",
"details": {},
"createdAt": "2025-01-15T10:30:00Z"
\}
],
"nextCursor": "2025-01-15T10:29:00Z"
\}
GET /orgs/members
List all organization members.
Response 200:
{
"members": [
{
"id": "uuid",
"userId": "uuid",
"email": "user@example.com",
"name": "Jane Smith",
"role": "admin",
"permissions": ["packages:write", "workspaces:write"],
"createdAt": "2025-01-01T00:00:00Z"
\}
]
\}
POST /orgs/members
Invite a new member. Requires owner or admin role.
Body:
{
"email": "newuser@example.com",
"role": "developer"
\}
Response 200: Created membership object.
PATCH /orgs/members/{userId}
Update a member's role. Requires owner or admin role.
Parameters:
userId(path, UUID) — User to update
Body:
{
"role": "maintainer"
\}
Response 200: Updated membership object.
DELETE /orgs/members/{userId}
Remove a member. Requires owner or admin role. Cannot remove yourself.
Parameters:
userId(path, UUID) — User to remove
Response 200:
{ "deleted": true \}
Packages
GET /packages
List packages with filtering and cursor pagination.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
scope | string | No | Filter by package scope |
visibility | string | No | Filter by visibility |
status | string | No | draft, active, deprecated, yanked |
q | string | No | Search by name (case-insensitive) |
cursor | ISO 8601 | No | Pagination cursor |
limit | int | No | Results per page (default: 20, max: 100) |
Response 200:
{
"data": [
{
"id": "uuid",
"orgId": "uuid",
"scope": "@acme",
"name": "coding-standards",
"description": "Team coding standards package",
"visibility": "internal",
"status": "active",
"trustClass": "verified",
"createdBy": "uuid",
"createdAt": "2025-01-10T00:00:00Z"
\}
],
"nextCursor": "2025-01-09T23:59:00Z",
"hasMore": true
\}
PATCH /packages/{scope}/{name}
Update package status. Valid transitions:
draft→activeactive→deprecateddeprecated→activeoryanked
Parameters:
scope(path) — Package scope (URL-encoded)name(path) — Package name (URL-encoded)
Body:
{
"status": "deprecated"
\}
Response 200: Updated package with new status.
Response 409: Invalid status transition.
GET /packages/{scope}/{name}/versions/{version}/scan
Get the latest security scan result for a package version.
Parameters:
scope,name,version(path) — Package identifier
Response 200:
{
"scan": {
"id": "uuid",
"scannerVersion": "1.2.0",
"result": "pass",
"findingsJson": null,
"scannedAt": "2025-01-12T08:00:00Z"
\}
\}
Returns { "scan": null } if no scan has been run.
POST /packages/{scope}/{name}/versions/{version}/submit
Submit a draft version for review. Version must be in draft status.
Response 200:
{
"id": "uuid",
"version": "1.0.0",
"approvalStatus": "pending_review"
\}
POST /packages/{scope}/{name}/versions/{version}/approve
Approve a pending version. Requires owner, admin, or maintainer role.
Response 200:
{
"id": "uuid",
"version": "1.0.0",
"approvalStatus": "approved",
"approvedBy": "uuid",
"approvedAt": "2025-01-15T12:00:00Z"
\}
POST /packages/{scope}/{name}/versions/{version}/reject
Reject a pending version. Requires owner, admin, or maintainer role.
Body:
{
"reason": "Missing required security review section"
\}
Response 200:
{
"id": "uuid",
"version": "1.0.0",
"approvalStatus": "rejected",
"rejectionReason": "Missing required security review section"
\}
Workspaces
GET /workspaces
List workspaces with cursor pagination.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
org_id | UUID | No | Organization filter |
cursor | ISO 8601 | No | Pagination cursor |
limit | int | No | Results per page (default: 20, max: 100) |
Response 200:
{
"data": [
{
"id": "uuid",
"orgId": "uuid",
"name": "production",
"bindingType": "github",
"bindingRef": "acme/monorepo",
"revision": 12,
"settings": {},
"createdAt": "2025-01-01T00:00:00Z"
\}
],
"nextCursor": "2024-12-31T23:59:00Z",
"hasMore": false
\}
GET /workspaces/{id}
Get workspace detail including targets and their packages.
Response 200:
{
"id": "uuid",
"orgId": "uuid",
"name": "production",
"bindingType": "github",
"bindingRef": "acme/monorepo",
"revision": 12,
"settings": {},
"createdAt": "2025-01-01T00:00:00Z",
"targets": [
{
"id": "uuid",
"workspaceId": "uuid",
"targetId": "backend",
"path": "apps/backend",
"createdAt": "2025-01-02T00:00:00Z",
"packages": [
{
"id": "uuid",
"workspaceTargetId": "uuid",
"packageId": "uuid",
"versionConstraint": "^1.0.0",
"channel": "stable",
"overridesJson": null
\}
]
\}
]
\}
PATCH /workspaces/{id}
Update workspace metadata and policies. Increments revision and creates a snapshot.
Body (all fields optional):
{
"name": "production-v2",
"projectionPolicy": { "scope": "strict" },
"telemetryPolicy": { "enabled": true },
"ciPolicy": { "requiredChecks": ["drift"] },
"settings": { "autoSync": true \}
\}
Response 200: Updated workspace with new revision number.
GET /workspaces/{id}/targets
List all targets in a workspace.
Response 200: Array of targets with their packages.
POST /workspaces/{id}/targets
Create a new target. Increments workspace revision.
Body:
{
"targetId": "frontend",
"path": "apps/web"
\}
Response 201: Created target object.
DELETE /workspaces/{id}/targets/{targetId}
Remove a target. Increments workspace revision.
Response 204: No content.
GET /workspaces/{id}/revisions
List workspace revision history with pagination.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
cursor | int | No | Revision number cursor |
limit | int | No | Results per page (default: 20, max: 100) |
Response 200:
{
"data": [
{
"id": "uuid",
"workspaceId": "uuid",
"revisionNumber": 12,
"packagesSnapshot": {},
"createdBy": "uuid",
"createdAt": "2025-01-15T10:00:00Z"
\}
],
"nextCursor": 11,
"hasMore": true
\}
GET /workspaces/{id}/config
Get full workspace configuration including targets, providers, and packages.
Response 200:
{
"id": "uuid",
"orgId": "uuid",
"name": "production",
"bindingType": "github",
"bindingRef": "acme/monorepo",
"revision": 12,
"projectionPolicy": {},
"telemetryPolicy": {},
"ciPolicy": {},
"settings": {},
"createdAt": "2025-01-01T00:00:00Z",
"targets": [
{
"id": "uuid",
"targetId": "backend",
"path": "apps/backend",
"createdAt": "2025-01-02T00:00:00Z",
"providers": [
{
"id": "uuid",
"workspaceTargetId": "uuid",
"provider": "claude",
"rendererVersion": "1.0.0",
"adapterVersion": "1.0.0",
"providerFormatVersion": "2024-01"
\}
],
"packages": []
\}
]
\}
PUT /workspaces/{id}/config
Replace workspace configuration. Increments revision and creates a snapshot.
Body:
{
"name": "production",
"bindingType": "github",
"bindingRef": "acme/monorepo",
"projectionPolicy": {},
"telemetryPolicy": {},
"ciPolicy": {},
"settings": {\}
\}
Response 200: Updated configuration with new revision.
GET /workspaces/{id}/config/diff
Compute diff between two workspace revisions.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
from | int | Yes | Source revision number |
to | int | Yes | Target revision number |
Response 200:
{
"from": 10,
"to": 12,
"changes": [
{
"path": "/settings/autoSync",
"op": "add",
"newValue": true
},
{
"path": "/name",
"op": "replace",
"oldValue": "staging",
"newValue": "production"
},
{
"path": "/ciPolicy/requiredChecks/1",
"op": "remove",
"oldValue": "lint"
\}
]
\}
POST /workspaces/{id}/preview
Generate a render preview showing what the workspace would produce.
Response 200:
{
"workspace": {
"id": "uuid",
"name": "production",
"bindingType": "github",
"bindingRef": "acme/monorepo"
},
"targets": [
{
"targetId": "backend",
"path": "apps/backend",
"packages": [
{
"packageId": "uuid",
"scope": "@acme",
"name": "coding-standards",
"version": "1.2.0",
"versionConstraint": "^1.0.0",
"channel": "stable",
"overridesJson": null
\}
]
\}
]
\}
POST /workspaces/{id}/packages
Add a package to a workspace target. Increments workspace revision.
Body:
{
"packageId": "uuid",
"workspaceTargetId": "uuid",
"versionConstraint": "^1.0.0",
"channel": "stable"
\}
Response 201:
{
"id": "uuid",
"workspaceTargetId": "uuid",
"packageId": "uuid",
"versionConstraint": "^1.0.0",
"channel": "stable"
\}
Telemetry
POST /telemetry/events
Batch ingest telemetry events. Requires telemetry:write scope.
Body:
{
"events": [
{
"eventType": "wl.pull.completed",
"workspaceId": "uuid",
"sessionId": "session-abc",
"payload": { "packagesUpdated": 3, "durationMs": 450 \}
\}
]
\}
Maximum 100 events per request.
Response 201:
{
"inserted": 1
\}
GET /telemetry/events
Query telemetry events with cursor pagination. Requires telemetry:read scope.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
workspace_id | UUID | No | Filter by workspace |
event_type | string | No | Filter by event type |
cursor | ISO 8601 | No | Pagination cursor |
limit | int | No | Results per page (default: 50, max: 100) |
Response 200:
{
"data": [
{
"id": "uuid",
"orgId": "uuid",
"workspaceId": "uuid",
"userId": "uuid",
"eventType": "wl.pull.completed",
"payload": { "packagesUpdated": 3 },
"sessionId": "session-abc",
"createdAt": "2025-01-15T10:30:00Z"
\}
],
"nextCursor": "2025-01-15T10:29:00Z",
"hasMore": true
\}
GET /telemetry/summary
Aggregate event counts grouped by event type. Requires telemetry:read scope.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
workspace_id | UUID | No | Filter by workspace |
bucket | string | No | hour, day (default), or week |
Response 200:
{
"data": [
{ "eventType": "wl.pull.completed", "count": 142 },
{ "eventType": "wl.drift.detected", "count": 8 },
{ "eventType": "wl.push.completed", "count": 23 \}
],
"bucket": "day"
\}