Uncertain Systems Proof-of-Work API
Workspace API Reference
Full request and response specifications for every Proof-of-Work API endpoint: workspaces, proof-of-work schema generation, integration skill generation, proof-of-work upload, performance analysis, TAP links, ILE practice, guest provisioning, and dashboard key management. Bearer endpoints use base path /api/v3/pow and require active api_metered.
Authentication
Authorization: Bearer <api_key>
Content-Type: application/jsonKey types
| Field | Type | Required | Description |
|---|---|---|---|
| sk_ | string prefix | — | Organization member key from dashboard or POST /api/v3/pow/keys (browser session). |
| gsk_ | string prefix | — | Guest key from POST /api/v3/pow/org/guests. |
Error response
{
"error": {
"code": "forbidden",
"message": "Human-readable explanation",
"details": {}
}
}Common codes: unauthorized, key_revoked, key_expired, forbidden, api_plan_required, validation_error, workspace_not_found, block_not_found, tap_link_not_found, guest_not_found, not_found, rate_limit_exceeded (429).
Evaluation modes
Workspaces use evaluation_mode to choose between full semantic verification and privacy-preserving opaque protocol verification.
Modes
| Field | Type | Required | Description |
|---|---|---|---|
| semantic | default | — | Create with initial_prompt. Schema uses definition. Performance returns semantic gap_analysis. |
| opaque | privacy mode | — | Create with protocol (protocol_id, goal_ref). Schema uses definition_ref + contract.event_verbs. Performance returns protocol_report; partner refs are stored but not semantically inferred. |
Canonical opaque protocol agent-trace-v3: enumerate → fingerprint → aggregate → emit → validate. Upload metadata is allowlisted; tool payloads are plaintext-linted in opaque mode.
Scopes
Each Bearer-authenticated endpoint requires one scope. The wildcard * grants all scopes.
Scope reference
| Field | Type | Required | Description |
|---|---|---|---|
| workspaces:read | scope | — | List blocks; generate proof-of-work schemas and integration skills; call lwm-snapshot (LWM Snapshot). |
| workspaces:write | scope | — | Upload proof of work (workspace create is UI-only). |
| tap:read | scope | — | List TAP links and poll completion status (score via POST .../lwm-snapshot). |
| tap:write | scope | — | Create Think Aloud Protocol (TAP) links for blocks. |
| org:read | scope | — | Reserved for org admin keys (future org read endpoints). |
| org:write | scope | — | Create guest users and issue gsk_ keys. |
| * | scope | — | All scopes. Org admins only when assigning to sk_ keys. |
Default sk_ key scopes: workspaces:read, workspaces:write, tap:read, tap:write. Guest gsk_ keys receive the same four scopes automatically.
Rate limits
API keys default to 120 requests per minute per key. Exceeding the limit returns 429 with code rate_limit_exceeded.
Endpoint index
- POST /api/v3/pow/workspaces
- POST /api/v3/snapshot/workspaces/{workspace_id}/lwm-snapshot
- POST /api/v3/pow/workspaces/{workspace_id}/tap-links
- GET /api/v3/pow/workspaces/{workspace_id}/tap-links
- POST /api/v3/pow/org/guests
- GET /api/v3/pow/keys
- POST /api/v3/pow/keys
- DELETE /api/v3/pow/keys/{key_id}
- PATCH /api/v3/pow/keys/{key_id}/scopes
/api/v3/pow/workspacesworkspaces:write403 ForbiddenNot available. Programmatic workspace creation is disabled; create workspaces manually in the product UI at /workspace/new.
Response body
| Field | Type | Required | Description |
|---|---|---|---|
| error.code | string | — | forbidden |
| error.message | string | — | Workspace creation is not available via API or MCP. Create workspaces manually in the product UI at /workspace/new. |
Response example
{
"error": {
"code": "forbidden",
"message": "Workspace creation is not available via API or MCP. Create workspaces manually in the product UI at /workspace/new."
}
}- Workspace creation is UI-only (blank, template, or files+goal at /workspace/new).
- MCP tool create_workspace is not offered and hard-fails with the same message if called.
- Use list_workspaces / get_workspace / get_learning_progress against UI-created workspace IDs.
/api/v3/snapshot/workspaces/{workspace_id}/lwm-snapshotworkspaces:read200 OKLWM Snapshot score (0–100) + GHC + spider markers, analysis, and next actions. Sole product snapshot strategy. Manual Knowledge UI or Snapshot API/MCP (not auto on TAP/ILE end).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Workspace ID. |
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| block_id | uuid | — | Optional: scope analysis to one block. |
| style_prompt | string | — | Optional voice/tone for narrative fields. |
| file_ids | string[] | — | Optional xAI file IDs from a prior score call. Empty → rebuild context bundle. |
Request example
{ "block_id": "e57844a6-1b69-465c-9120-d0812d6339ae" }Response body
| Field | Type | Required | Description |
|---|---|---|---|
| mode | "score" | — | Always score for this endpoint. |
| strategy | string | — | lwm_snapshot |
| label | string | — | LWM Snapshot |
| workspace_goal | string | — | Inferred or owner-set workspace goal. |
| report | object | — | LWM Snapshot score report payload. |
| report.score | integer | — | 0–100 primary LWM Snapshot score. |
| report.lwm_snapshot_score | integer | — | Named primary field (equals score). |
| report.ghc_score | integer | — | 0–100 secondary GHC signal. |
| proof_of_work_summary | object | null | — | Counts used in context. |
| file_ids | string[] | — | xAI file IDs for follow-up calls. |
Response example
{
"mode": "score",
"strategy": "lwm_snapshot",
"label": "LWM Snapshot",
"workspace_goal": "Trial-to-paid subscription activation",
"report": {
"score": 68,
"lwm_snapshot_score": 68,
"ghc_score": 40,
"workspace_goal": "Trial-to-paid subscription activation",
"marker_scores": [],
"summary": "...",
"confidence": "developing"
},
"proof_of_work_summary": {
"blocks": 1,
"proof_of_work_artifacts": 2,
"linked_sessions": 0,
"workspace_files": 0
},
"file_ids": ["file_814439bd-4894-4e11-852d-314e9f777a7f"]
}- Sole product score strategy — no verification/augmentation/optimization score routes.
- First call with empty file_ids uploads a workspace performance JSON summary + up to 19 artifact files to xAI.
- If no proof of work exists, returns 200 with an empty-data score template.
/api/v3/pow/workspaces/{workspace_id}/tap-linkstap:write201 CreatedCreate a private Think Aloud Protocol (TAP) link for the workspace (or a block via body/path).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Workspace ID. |
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| block_id | uuid | — | Optional. When set, scopes the TAP session to that block. Omit for full-workspace scope. |
| minutes | integer | — | 1–120; default 15. |
| guest_user_id | uuid | — | Org admin only: assign link to a guest by ID. |
| guest_email | string | — | Org admin only: assign link to a guest by email. |
| participant_type | string | — | anonymous | guest | user. |
| user_id | uuid | — | Member user id when participant_type=user. |
| post_session | string | — | redirect_workspace | show_results | redirect_url. |
| redirect_url | string | — | Required when post_session=redirect_url. |
Request example
{
"minutes": 15,
"participant_type": "anonymous"
}Response body
| Field | Type | Required | Description |
|---|---|---|---|
| tap_link.id | uuid | — | TAP link / session row ID. |
| tap_link.workspace_id | uuid | — | Workspace ID. |
| tap_link.block_id | uuid | null | — | Block ID when scoped; null for full workspace. |
| tap_link.status | string | — | pending | in_progress | completed |
| tap_link.requested_duration_seconds | integer | — | Requested duration in seconds. |
| tap_link.focus_block_ids | uuid[] | — | Focused block IDs (empty = full workspace). |
| tap_link.created_at | ISO-8601 | — | Link creation time. |
| tap_link.private_url | string | — | Bearer URL: /tap/session/{token}. No login required. |
| interruption | object | null | — | TIM predictive interruption (see Predictive interruptions). |
Response example
{
"tap_link": {
"id": "ae0cc774-1832-4bb5-bc7d-bf119ddf759f",
"workspace_id": "75b3b4ef-4e47-4f39-bb09-f61406603d75",
"block_id": null,
"status": "pending",
"requested_duration_seconds": 900,
"focus_block_ids": [],
"created_at": "2026-06-23T01:29:03.861663+00:00",
"private_url": "https://uncertain.systems/tap/session/E8-ouJ9lErgDEmteyKc4tJ39meJ91vzZFNUiuRauHvw"
}
}- Also available as POST .../blocks/{block_id}/tap-links for block-scoped links (same body fields).
- Guest keys auto-attach the link to their guest identity.
- Org admins may set guest_user_id or guest_email to assign the link (404 guest_not_found if missing).
- Learner completes session at private_url without an API key.
/api/v3/pow/workspaces/{workspace_id}/tap-linkstap:read200 OKList TAP links for a workspace (filtered by caller role).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Workspace ID. |
Response body
| Field | Type | Required | Description |
|---|---|---|---|
| tap_links | array | — | Sessions ordered by created_at descending. |
| tap_links[].id | uuid | — | Link ID. |
| tap_links[].workspace_id | uuid | — | Workspace ID. |
| tap_links[].block_id | uuid | null | — | Block ID when scoped; null for full workspace. |
| tap_links[].status | string | — | pending | in_progress | completed |
| tap_links[].requested_duration_seconds | integer | — | Requested duration. |
| tap_links[].duration_seconds | integer | — | Actual duration (0 until completed). |
| tap_links[].focus_block_ids | uuid[] | — | Focused blocks. |
| tap_links[].score | integer | null | — | Score when completed. |
| tap_links[].created_at | ISO-8601 | — | Created at. |
| tap_links[].started_at | ISO-8601 | null | — | Started at. |
| tap_links[].completed_at | ISO-8601 | null | — | Completed at. |
| interruption | object | null | — | TIM predictive interruption. |
Response example
{
"tap_links": [
{
"id": "ae0cc774-1832-4bb5-bc7d-bf119ddf759f",
"workspace_id": "75b3b4ef-4e47-4f39-bb09-f61406603d75",
"block_id": "88a43ad8-62f8-4252-a847-2cbc0b754a57",
"status": "completed",
"requested_duration_seconds": 900,
"duration_seconds": 120,
"focus_block_ids": ["88a43ad8-62f8-4252-a847-2cbc0b754a57"],
"lwm_snapshot_score": 72,
"created_at": "2026-06-23T01:29:03.861663+00:00",
"started_at": "2026-06-23T01:30:00+00:00",
"completed_at": "2026-06-23T01:32:21.492+00:00"
}
]
}- Guests see only their own links.
- Non-admin members see only links they created.
- Org admins see all links on org workspaces.
/api/v3/pow/org/guestsorg:write201 Created (new guest) or 200 OK (existing guest)Create or look up a guest by email and issue a new guest API key (gsk_).
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| string | yes | Guest email address (normalized to lowercase). |
Request example
{ "email": "learner@example.com" }Response body
| Field | Type | Required | Description |
|---|---|---|---|
| guest_user.id | uuid | — | organization_guest_users.id |
| guest_user.organization_id | uuid | — | Org the guest belongs to. |
| guest_user.email | string | — | Guest email. |
| guest_user.status | string | — | active | claimed | revoked |
| guest_user.claimed_by_user_id | uuid | null | — | Set when guest signs up with same email. |
| guest_user.claimed_at | ISO-8601 | null | — | Claim timestamp. |
| guest_user.created_at | ISO-8601 | — | Guest record created at. |
| api_key | string | — | Raw gsk_ key — shown once; store securely. |
| key.id | uuid | — | agent_api_keys.id |
| key.key_prefix | string | — | First 13 chars of key for identification. |
| key.scopes | string[] | — | workspaces:read, workspaces:write, tap:read, tap:write |
| key.rate_limit | integer | — | Requests per minute (default 120). |
| key.created_at | ISO-8601 | — | Key creation time. |
Response example
{
"guest_user": {
"id": "f8b2c1d0-1234-5678-9abc-def012345678",
"organization_id": "64cc093b-31c1-4a7e-aead-e2e9378ecaf4",
"email": "learner@example.com",
"status": "active",
"claimed_by_user_id": null,
"claimed_at": null,
"created_at": "2026-06-23T13:00:00+00:00"
},
"api_key": "gsk_a1b2c3d4e5f6789012345678abcdef",
"key": {
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"key_prefix": "gsk_a1b2c3d4",
"scopes": ["workspaces:read", "workspaces:write", "tap:read", "tap:write"],
"rate_limit": 120,
"created_at": "2026-06-23T13:00:01+00:00"
}
}- Caller must be organization admin with org:write on their sk_ key.
- Re-calling for the same email mints another key; prior keys may remain active.
- 409 if email belongs to a real user in another organization.
/api/v3/pow/keysbrowser session200 OKList API keys for the signed-in dashboard user. Uses Supabase session cookies — not Bearer API key auth.
Response body
| Field | Type | Required | Description |
|---|---|---|---|
| keys | array | — | All agent_api_keys for the authenticated user, newest first. |
| keys[].id | uuid | — | Key ID. |
| keys[].label | string | null | — | Optional label set at creation. |
| keys[].key_prefix | string | — | First 12 characters of sk_ key (identification only). |
| keys[].scopes | string[] | — | Assigned scopes. |
| keys[].rate_limit | integer | — | Requests per minute (default 120). |
| keys[].is_active | boolean | — | False after revocation. |
| keys[].created_at | ISO-8601 | — | Creation timestamp. |
| keys[].last_used_at | ISO-8601 | null | — | Last successful API call. |
| keys[].expires_at | ISO-8601 | null | — | Expiry if set at creation. |
Response example
{
"keys": [
{
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"label": "Production agent",
"key_prefix": "sk_a1b2c3d4e5",
"scopes": ["workspaces:read", "workspaces:write", "tap:read", "tap:write"],
"rate_limit": 120,
"is_active": true,
"created_at": "2026-06-23T12:00:00+00:00",
"last_used_at": "2026-06-23T13:05:00+00:00",
"expires_at": null
}
]
}- Also available from Dashboard → Usage & API. Max 10 active keys per user.
/api/v3/pow/keysbrowser session201 CreatedCreate a new sk_ API key for the signed-in user. Raw key returned once.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| label | string | — | Optional label, max 128 characters. |
| scopes | string[] | — | Optional. Default: workspaces:read, workspaces:write, tap:read, tap:write. org:read/org:write require org admin. |
| expires_in_days | integer | — | Optional expiry: 1–365 days. |
Request example
{
"label": "CI pipeline",
"scopes": ["workspaces:read", "workspaces:write", "tap:read", "tap:write", "org:write"],
"expires_in_days": 90
}Response body
| Field | Type | Required | Description |
|---|---|---|---|
| key.id | uuid | — | agent_api_keys.id |
| key.label | string | null | — | Label if provided. |
| key.key_prefix | string | — | First 12 chars of sk_ key. |
| key.scopes | string[] | — | Assigned scopes. |
| key.rate_limit | integer | — | Default 120. |
| key.created_at | ISO-8601 | — | Creation time. |
| key.expires_at | ISO-8601 | null | — | Expiry if set. |
| api_key | string | — | Full sk_ key — shown once; store securely. |
Response example
{
"key": {
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"label": "CI pipeline",
"key_prefix": "sk_a1b2c3d4e5",
"scopes": ["workspaces:read", "workspaces:write", "tap:read", "tap:write", "org:write"],
"rate_limit": 120,
"created_at": "2026-06-23T12:00:00+00:00",
"expires_at": "2026-09-21T12:00:00+00:00"
},
"api_key": "sk_7f3a9b2c1d4e5f6789012345678abcdef"
}- Requires Teams tier (403 api_plan_required).
- Valid scopes: *, workspaces:read, workspaces:write, tap:read, tap:write, org:read, org:write.
- 403 if more than 10 active keys or if non-admin requests org scopes.
/api/v3/pow/keys/{key_id}browser session200 OKRevoke (soft-delete) an API key owned by the signed-in user.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| key_id | uuid | yes | agent_api_keys.id from list or create. |
Response body
| Field | Type | Required | Description |
|---|---|---|---|
| deleted | boolean | — | True on success. |
| key_id | uuid | — | Revoked key ID. |
Response example
{
"deleted": true,
"key_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab"
}- 404 not_found if key does not belong to user. 400 if already revoked.
/api/v3/pow/keys/{key_id}/scopesbrowser session200 OKReplace scopes on an active API key.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| key_id | uuid | yes | agent_api_keys.id. |
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| scopes | string[] | yes | Non-empty array of valid scope strings. |
Request example
{
"scopes": ["workspaces:read", "tap:read"]
}Response body
| Field | Type | Required | Description |
|---|---|---|---|
| key.id | uuid | — | Updated key ID. |
| key.scopes | string[] | — | New scope list. |
| key.updated_at | ISO-8601 | — | Update timestamp. |
Response example
{
"key": {
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"scopes": ["workspaces:read", "tap:read"],
"updated_at": "2026-06-23T13:10:00+00:00"
}
}- Cannot update revoked keys. org:read/org:write require organization admin.
TAP session completion (learner-facing)
Learners open private_url without an API key. Completion uses web APIs (not Proof-of-Work API):
POST /api/workspace-tap-score/chat
| Field | Type | Required | Description |
|---|---|---|---|
| privateToken | string | yes | Token from private_url path. |
| thought | string | yes | Learner thought fragment. |
| messages | array | — | Optional prior chat messages. |
POST /api/workspace-tap-score/complete
| Field | Type | Required | Description |
|---|---|---|---|
| privateToken | string | yes | Token from private_url path. |
| transcript | array | yes | Session transcript entries with role and text. |
| durationSeconds | integer | — | Elapsed session seconds. |
For agents
Machine-readable spec
Agents should also load /skill.md for integration checklists, guest responsibilities, and MCP transport. Generate a custom skill per workspace via POST .../integration-skill, or add the PumaDoc policy snippets: Customer Agent policy, PumaClaw Mentor policy.