Skip to main content

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:

MethodHeader / CookieUse case
Bearer token (JWT)Authorization: Bearer <token>Web app sessions
API tokenAuthorization: Bearer wl_<hex>CI/CD and scripts
Session cookieauthjs.session-tokenBrowser requests

API tokens are prefixed wl_ followed by 64 hex characters. Create them via the Settings UI.

Pagination​

List endpoints use cursor-based pagination:

ParameterDescription
cursorOpaque cursor from the previous response
limitItems 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": {\}
\}
StatusCodeMeaning
400BAD_REQUESTInvalid input
401UNAUTHORIZEDMissing or invalid credentials
403FORBIDDENInsufficient permissions
404NOT_FOUNDResource does not exist
409CONFLICTInvalid state transition
500INTERNAL_ERRORServer 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:

ParameterTypeRequiredDescription
actionstringNoFilter by action type
resourceTypestringNoFilter by resource type
cursorISO 8601NoPagination cursor
limitintNoResults 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:

ParameterTypeRequiredDescription
scopestringNoFilter by package scope
visibilitystringNoFilter by visibility
statusstringNodraft, active, deprecated, yanked
qstringNoSearch by name (case-insensitive)
cursorISO 8601NoPagination cursor
limitintNoResults 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 → active
  • active → deprecated
  • deprecated → active or yanked

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:

ParameterTypeRequiredDescription
org_idUUIDNoOrganization filter
cursorISO 8601NoPagination cursor
limitintNoResults 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:

ParameterTypeRequiredDescription
cursorintNoRevision number cursor
limitintNoResults 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:

ParameterTypeRequiredDescription
fromintYesSource revision number
tointYesTarget 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:

ParameterTypeRequiredDescription
workspace_idUUIDNoFilter by workspace
event_typestringNoFilter by event type
cursorISO 8601NoPagination cursor
limitintNoResults 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:

ParameterTypeRequiredDescription
workspace_idUUIDNoFilter by workspace
bucketstringNohour, 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"
\}