OpenLesson 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/v2/agent and require active pro_teams.
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/v2/agent/keys (browser session). |
| gsk_ | string prefix | — | Guest key from POST /api/v2/agent/org/guests. |
Error response
{
"error": {
"code": "forbidden",
"message": "Human-readable explanation",
"details": {}
}
}Common codes: unauthorized, key_revoked, key_expired, forbidden, teams_required, validation_error, workspace_not_found, block_not_found, tap_link_not_found, guest_not_found, not_found, rate_limit_exceeded (429).
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; run performance analysis (report or chat). |
| workspaces:write | scope | — | Create workspaces; upload proof of work. |
| tap:read | scope | — | List TAP links and poll completion status (score via POST .../performance). |
| 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/v2/agent/workspaces
- GET /api/v2/agent/workspaces/{workspace_id}/blocks
- POST /api/v2/agent/workspaces/{workspace_id}/proof-of-work-schema
- POST /api/v2/agent/workspaces/{workspace_id}/integration-skill
- POST /api/v2/agent/workspaces/{workspace_id}/proof-of-work
- POST /api/v2/agent/workspaces/{workspace_id}/performance
- POST /api/v2/agent/workspaces/{workspace_id}/blocks/{block_id}/tap-links
- GET /api/v2/agent/workspaces/{workspace_id}/tap-links
- POST /api/v2/agent/org/guests
- GET /api/v2/agent/keys
- POST /api/v2/agent/keys
- DELETE /api/v2/agent/keys/{key_id}
- PATCH /api/v2/agent/keys/{key_id}/scopes
/api/v2/agent/workspacesworkspaces:write201 CreatedCreate a Workspace from an initial prompt and optional seed files.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| initial_prompt | string | yes | Task or learning goal used to generate workspace title and blocks. |
| files | array<object> | — | Optional seed files (max 5). Each item: name (string), mime_type (string), data (base64 string). |
| files[].name | string | yes | Filename including extension. |
| files[].mime_type | string | yes | application/pdf, text/plain, text/markdown, image/jpeg, image/png, image/webp. |
| files[].data | string (base64) | yes | File bytes, max 10 MB per file. |
Request example
{
"initial_prompt": "Prepare a CSM to handle enterprise renewal negotiations.",
"files": [
{
"name": "brief.md",
"mime_type": "text/markdown",
"data": "<base64>"
}
]
}Response body
| Field | Type | Required | Description |
|---|---|---|---|
| workspace.id | uuid | — | Workspace ID (workspaces.id). |
| workspace.title | string | — | Generated workspace title. |
| workspace.root_topic | string | — | Truncated prompt summary. |
| workspace.status | string | — | active |
| workspace.notes | string | — | Full initial prompt stored as notes. |
| workspace.created_at | ISO-8601 | — | Creation timestamp. |
| workspace.updated_at | ISO-8601 | — | Last update timestamp. |
| blocks | array | — | Generated assessable blocks (3–8). |
| blocks[].id | uuid | — | Block ID (blocks.id). |
| blocks[].title | string | — | Block title. |
| blocks[].description | string | — | What the learner should demonstrate. |
| blocks[].is_start | boolean | — | True for the entry block. |
| blocks[].next_block_ids | uuid[] | — | Linked next blocks in the graph. |
| blocks[].status | string | — | available |
| blocks[].created_at | ISO-8601 | — | Block creation timestamp. |
| files | array | — | Uploaded workspace_files records (empty if none). |
| files[].id | uuid | — | workspace_files.id |
| files[].file_name | string | — | Original filename. |
| files[].file_size | integer | — | Bytes. |
| files[].mime_type | string | — | MIME type. |
| files[].created_at | ISO-8601 | — | Upload timestamp. |
Response example
{
"workspace": {
"id": "a3090aa0-3498-4be8-aa87-32a1a6591641",
"title": "Enterprise Renewal Negotiation Prep",
"root_topic": "Prepare a CSM to handle enterprise renewal negotiations.",
"status": "active",
"notes": "Prepare a CSM to handle enterprise renewal negotiations.",
"created_at": "2026-06-23T13:01:32.64659+00:00",
"updated_at": "2026-06-23T13:01:32.64659+00:00"
},
"blocks": [
{
"id": "e57844a6-1b69-465c-9120-d0812d6339ae",
"title": "Context & Procurement Tactics",
"description": "Demonstrate knowledge of renewal cycles and procurement pushback.",
"is_start": true,
"next_block_ids": ["b454f31a-3045-4c23-a60e-820b43d0e9ce"],
"status": "available",
"created_at": "2026-06-23T13:01:32.691293+00:00"
}
],
"files": []
}- Guest keys (gsk_) may create workspaces; workspace is org-owned and tagged with guest_user_id.
- Requires Teams tier (403 teams_required otherwise).
/api/v2/agent/workspaces/{workspace_id}/blocksworkspaces:read200 OKList assessable blocks in a workspace.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Workspace ID. |
Response body
| Field | Type | Required | Description |
|---|---|---|---|
| blocks | array | — | All blocks for the workspace, ordered by created_at ascending. |
| blocks[].id | uuid | — | Block ID. |
| blocks[].title | string | — | Block title. |
| blocks[].description | string | — | Demonstration objective. |
| blocks[].is_start | boolean | — | Entry block flag. |
| blocks[].next_block_ids | uuid[] | — | Next block IDs. |
| blocks[].status | string | — | available | in_progress | completed |
| blocks[].created_at | ISO-8601 | — | Creation timestamp. |
Response example
{
"blocks": [
{
"id": "e57844a6-1b69-465c-9120-d0812d6339ae",
"title": "Context & Procurement Tactics",
"description": "Demonstrate knowledge of renewal cycles.",
"is_start": true,
"next_block_ids": ["b454f31a-3045-4c23-a60e-820b43d0e9ce"],
"status": "available",
"created_at": "2026-06-23T13:01:32.691293+00:00"
}
]
}- 404 workspace_not_found if the key cannot access the workspace.
/api/v2/agent/workspaces/{workspace_id}/proof-of-work-schemaworkspaces:read200 OKGiven workspace context (blocks, plan files on xAI, proof-of-work metadata) plus an evaluation definition, Grok returns a JSON Schema for the ideal tool proof-of-work payload.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Workspace ID. |
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| definition | string | yes | What to evaluate — rubric text, competency description, or full eval spec from your agent. |
| block_id | uuid | — | Optional: scope schema design to one block. |
| integration_hints | object | — | Optional hints to tailor the schema: tool_name, partner_agent, event_verbs[], goals[]. |
| integration_hints.tool_name | string | — | Tool identifier (e.g. pumadoc, canvas). |
| integration_hints.partner_agent | string | — | Partner agent name for context. |
| integration_hints.event_verbs | string[] | — | Actions your agent serializes (e.g. run_simulation, edit_field). |
| integration_hints.goals | string[] | — | High-level goals to encode (e.g. simulation_completed). |
Request example
{
"definition": "Evaluate whether the learner can articulate a crisp ICP with segment rationale and validation plan",
"block_id": "e57844a6-1b69-465c-9120-d0812d6339ae",
"integration_hints": {
"tool_name": "pumadoc",
"partner_agent": "PumaDoc Customer Agent",
"event_verbs": ["run_simulation", "edit_field", "publish_artifact"],
"goals": ["simulation_completed", "artifact_published"]
}
}Response body
| Field | Type | Required | Description |
|---|---|---|---|
| schema | object | — | JSON Schema (draft-07 style) for the ideal tool proof-of-work payload inside the upload data field. |
| schema_name | string | — | Snake_case identifier, typically prefixed eval_input_. |
| rationale | string | — | Why these fields capture optimal eval signal for this workspace. |
| example_payload | object | — | Example JSON matching the schema conceptually. |
| recommended_mime_type | string | — | Usually application/json for tool proof of work. |
| recommended_proof_of_work_type | string | — | tool | screen | video | eeg |
| required_fields | string[] | — | Top-level field names integrators should always include. |
| optional_fields | string[] | — | Enrichment fields (reflections, media refs, etc.). |
| collection_guidance | string | — | When and how often to upload proof of work for this definition. |
| performance_report_contract | object | — | Formal contract for POST .../performance report mode: overall_score, marker_scores (spider_radar), gap_analysis.gaps, and example_report. |
| workspace_id | uuid | — | Echo of path workspace_id. |
| block_id | uuid | null | — | Echo of request block_id. |
| definition | string | — | Echo of request definition. |
| workspace_summary | object | — | id, title, root_topic. |
| context_counts | object | — | blocks, tap_sessions, proof_of_work_artifacts, linked_sessions, workspace_files. |
| file_ids | string[] | — | xAI file IDs used for generation (workspace JSON + plan files). |
Response example
{
"schema": {
"type": "object",
"properties": {
"events": {
"type": "array",
"items": {
"type": "object",
"properties": {
"verb": { "type": "string" },
"timestamp_ms": { "type": "integer" },
"payload": { "type": "object" }
},
"required": ["verb", "timestamp_ms"]
}
},
"goals_achieved": { "type": "array", "items": { "type": "string" } },
"learner_reflection": { "type": "string" }
},
"required": ["events"]
},
"schema_name": "eval_input_icp_clarity",
"rationale": "Time-ordered events plus goals_achieved give performance analysis enough signal to assess ICP clarity without a fixed rubric.",
"example_payload": {
"events": [
{ "verb": "run_simulation", "timestamp_ms": 1710000000000, "payload": { "simulation_id": "icp-v1" } }
],
"goals_achieved": ["simulation_completed"],
"learner_reflection": "Segment B has stronger willingness-to-pay signals."
},
"recommended_mime_type": "application/json",
"recommended_proof_of_work_type": "tool",
"required_fields": ["events"],
"optional_fields": ["goals_achieved", "learner_reflection"],
"collection_guidance": "Upload after each simulation run or when the learner publishes an ICP artifact.",
"workspace_id": "a3090aa0-3498-4be8-aa87-32a1a6591641",
"block_id": "e57844a6-1b69-465c-9120-d0812d6339ae",
"definition": "Evaluate whether the learner can articulate a crisp ICP...",
"workspace_summary": {
"id": "a3090aa0-3498-4be8-aa87-32a1a6591641",
"title": "Customer Development Mastery",
"root_topic": "Founder ICP validation"
},
"context_counts": {
"blocks": 5,
"tap_sessions": 0,
"proof_of_work_artifacts": 0,
"linked_sessions": 0,
"workspace_files": 2
},
"file_ids": ["file_814439bd-4894-4e11-852d-314e9f777a7f"]
}- Use before POST .../proof-of-work when you want a concrete JSON contract for what your agent should serialize.
- Builds the same workspace context bundle as performance (JSON summary + up to 19 xAI artifact refs).
- 404 block_not_found if block_id is not in this workspace.
- Grok-generated; may take up to ~120s on large workspaces.
/api/v2/agent/workspaces/{workspace_id}/integration-skillworkspaces:read200 OKGenerate a workspace-specific skill.md integration guide via POST .../integration-skill for a custom partner agent.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Workspace ID. |
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| integration_name | string | yes | Partner integration slug or display name (e.g. acme-sales-copilot). |
| partner_description | string | — | What the external agent does; Grok uses this to tailor examples. |
| block_id | uuid | — | Optional: focus the skill on one block. |
| base_url | string | — | Origin for example URLs; default https://openlesson.academy. |
| include_sections | string[] | — | Sections to include. Default: purpose, design_principles, auth, endpoints, proof_of_work_payload, performance, checklist. |
Request example
{
"integration_name": "acme-sales-copilot",
"partner_description": "Guides reps through discovery calls and objection handling",
"base_url": "https://openlesson.academy",
"include_sections": ["purpose", "auth", "endpoints", "proof_of_work_payload", "performance", "checklist"]
}Response body
| Field | Type | Required | Description |
|---|---|---|---|
| skill_md | string | — | Full markdown document with YAML frontmatter (name, description). |
| skill_name | string | — | Derived frontmatter name, e.g. acme-sales-copilot-openlesson-proof-of-work-performance. |
| suggested_share_path | string | — | Suggested public path, e.g. /acme-sales-copilot-skill.md. |
| workspace_summary | object | — | id, title, root_topic, block_count. |
| context_counts | object | null | — | Workspace context counts used during generation. |
| file_ids | string[] | — | xAI file IDs attached during generation. |
Response example
{
"skill_md": "---\nname: acme-sales-copilot-openlesson-proof-of-work-performance\ndescription: Acme Sales Copilot integration skill for OpenLesson proof-of-work upload and performance analysis.\n---\n\n# Acme Sales Copilot — OpenLesson Proof-of-Work & Performance\n\n...",
"skill_name": "acme-sales-copilot-openlesson-proof-of-work-performance",
"suggested_share_path": "/acme-sales-copilot-skill.md",
"workspace_summary": {
"id": "a3090aa0-3498-4be8-aa87-32a1a6591641",
"title": "Discovery Mastery",
"root_topic": "B2B sales discovery",
"block_count": 5
},
"context_counts": {
"blocks": 5,
"tap_sessions": 0,
"proof_of_work_artifacts": 0,
"linked_sessions": 0,
"workspace_files": 1
},
"file_ids": ["file_814439bd-4894-4e11-852d-314e9f777a7f"]
}- Host skill_md at suggested_share_path or inject directly into your agent's skill system.
- References canonical /skill.md and /docs/proof-of-work-api; includes workspace-specific block mapping and payload examples.
- 404 block_not_found if block_id is not in this workspace.
- Grok-generated markdown; may take up to ~120s.
/api/v2/agent/workspaces/{workspace_id}/proof-of-workworkspaces:write201 CreatedUpload tool usage, screenshots, video, or EEG to xAI Files and link to workspace/block/session.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Workspace ID. |
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | yes | tool | screen | screenshot | video | eeg (screenshot aliases to screen). |
| mime_type | string | yes | Must match type (see MIME table below). |
| data | string (base64) | yes | Artifact bytes, max 10 MB. |
| file_name | string | — | Optional filename; default derived from type. |
| block_id | uuid | — | Optional block to scope proof of work. |
| session_id | uuid | — | Optional linked session ID. |
| timestamp_ms | integer | — | Client timestamp; defaults to server time. |
| chunk_index | integer | — | Chunk sequence for streaming artifacts; default 0. |
| metadata | object | — | Arbitrary JSON metadata stored on the proof of work row. |
| tool_name | string | — | For type=tool: tool identifier (e.g. canvas, pumadoc). |
| tool_action | string | — | For type=tool: action name (e.g. draw, step_completed). |
| band_powers | object | — | For type=eeg: band power map (numeric values). |
| device_name | string | — | For type=eeg: device label (e.g. Muse). |
| sample_count | integer | — | For type=eeg: sample count in chunk. |
Request example
{
"type": "tool",
"file_name": "renewal-workbench-trace.json",
"mime_type": "application/json",
"data": "<base64>",
"block_id": "e57844a6-1b69-465c-9120-d0812d6339ae",
"metadata": { "source": "pumadoc-customer-agent" },
"tool_name": "renewal-workbench",
"tool_action": "session_trace"
}Response body
| Field | Type | Required | Description |
|---|---|---|---|
| proof_of_work.id | uuid | — | workspace_proof_of_work.id |
| proof_of_work.workspace_id | uuid | — | Same as workspace_id. |
| proof_of_work.block_id | uuid | null | — | block_id if scoped. |
| proof_of_work.session_id | uuid | null | — | Optional session link. |
| proof_of_work.type | string | — | tool | screen | video | eeg |
| proof_of_work.file_name | string | — | Stored filename. |
| proof_of_work.mime_type | string | — | MIME type. |
| proof_of_work.file_size | integer | — | Decoded byte length. |
| proof_of_work.xai_file_id | string | — | xAI Files API file_id. |
| proof_of_work.timestamp_ms | integer | — | Client or server timestamp. |
| proof_of_work.chunk_index | integer | — | Chunk index. |
| proof_of_work.metadata | object | — | Stored metadata JSON. |
| proof_of_work.tool_name | string | null | — | Tool name when type=tool. |
| proof_of_work.tool_action | string | null | — | Tool action when type=tool. |
| proof_of_work.device_name | string | null | — | EEG device when type=eeg. |
| proof_of_work.sample_count | integer | null | — | EEG samples when type=eeg. |
| proof_of_work.created_at | ISO-8601 | — | Upload timestamp. |
Response example
{
"proof_of_work": {
"id": "3d2a15c4-3e21-4e11-bf9c-c007ef0c82b4",
"workspace_id": "a3090aa0-3498-4be8-aa87-32a1a6591641",
"block_id": "e57844a6-1b69-465c-9120-d0812d6339ae",
"session_id": null,
"type": "tool",
"file_name": "renewal-workbench-trace.json",
"mime_type": "application/json",
"file_size": 992,
"xai_file_id": "file_9f395df1-ecfd-4587-87fd-4f2e8d3cea3d",
"timestamp_ms": 1782219694763,
"chunk_index": 0,
"metadata": { "source": "pumadoc-customer-agent" },
"tool_name": "renewal-workbench",
"tool_action": "session_trace",
"device_name": null,
"sample_count": null,
"created_at": "2026-06-23T13:01:34.791176+00:00"
}
}- MIME by type: tool → application/json, text/plain, text/markdown; screen → image/png, image/jpeg, image/webp; video → video/mp4, video/webm, video/quicktime; eeg → application/json, text/plain.
- 404 block_not_found if block_id is not in this workspace.
/api/v2/agent/workspaces/{workspace_id}/performanceworkspaces:read200 OKAnalyze workspace proof of work, TAP (Think Aloud Protocol) results, ILE practice traces, sessions, and plan files. Report mode (no prompt) or chat mode (with prompt).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Workspace ID. |
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| prompt | string | — | If non-empty → chat mode (markdown response). If omitted or empty → report mode (structured JSON). |
| block_id | uuid | — | Optional: scope analysis to one block. |
| conversation_history | array | — | Chat mode only. Up to 12 prior turns: { role: user|assistant, content: string }. |
| file_ids | string[] | — | Optional xAI file IDs from a prior performance call. Empty → rebuild context bundle. |
Request example
// Report mode
{ "block_id": "e57844a6-1b69-465c-9120-d0812d6339ae" }
// Chat mode
{
"block_id": "e57844a6-1b69-465c-9120-d0812d6339ae",
"prompt": "What is the single biggest readiness gap?",
"file_ids": ["file_814439bd-4894-4e11-852d-314e9f777a7f"]
}Response body
| Field | Type | Required | Description |
|---|---|---|---|
| mode | report | chat | — | Which response shape is populated. |
| report | object | null | — | Present when mode=report. |
| report.overall_score | integer | — | 0–100 readiness score synthesized from proof of work. |
| report.marker_scores | array | — | Spider/radar competency axes: id, label, score (0–100), rationale, optional block_id. |
| report.summary | string | — | Executive summary. |
| report.strengths | string[] | — | Demonstrated strengths. |
| report.growth_areas | string[] | — | Areas needing development. |
| report.gap_analysis.summary | string | — | Gap analysis overview. |
| report.gap_analysis.gaps | array | — | title, proof_of_work, severity (low|medium|high), suggested_repair. |
| report.gap_analysis.next_practice | string[] | — | Recommended practice actions. |
| report.suggestions | string[] | — | Additional recommendations. |
| report.confidence | string | — | emerging | developing | clear | well-connected |
| response | string | null | — | Markdown answer when mode=chat. |
| proof_of_work_summary | object | null | — | Counts used in context (blocks, tap_sessions, proof_of_work_artifacts, linked_sessions, workspace_files). |
| file_ids | string[] | — | xAI file IDs for follow-up calls; pass back as file_ids. |
Response example
{
"mode": "report",
"report": {
"overall_score": 68,
"marker_scores": [
{
"id": "negotiation_prep",
"label": "Negotiation Preparation",
"score": 74,
"rationale": "Used CRM and ROI table before price discussion."
},
{
"id": "risk_quantification",
"label": "Risk Quantification",
"score": 52,
"rationale": "Churn risk was discussed but never modeled numerically."
}
],
"summary": "Learner prepared for renewal negotiation using simulated tool traces.",
"strengths": ["Used CRM and ROI table before price discussion"],
"growth_areas": ["Did not quantify churn risk"],
"gap_analysis": {
"summary": "Missing churn risk quantification.",
"gaps": [
{
"title": "Missing churn risk quantification",
"proof_of_work": "Reflection states churn risk was not modeled.",
"severity": "medium",
"suggested_repair": "Add probability-weighted revenue loss to ROI table."
}
],
"next_practice": ["Run 3 simulated procurement scenarios with churn math"]
},
"suggestions": ["Practice live role-play with procurement pushback"],
"confidence": "emerging"
},
"proof_of_work_summary": {
"blocks": 1,
"tap_sessions": 0,
"proof_of_work_artifacts": 2,
"linked_sessions": 0,
"workspace_files": 0
},
"file_ids": ["file_814439bd-4894-4e11-852d-314e9f777a7f"]
}
// Chat mode response
{
"mode": "chat",
"response": "The biggest readiness gap is **churn risk quantification** — the learner discussed renewal value but never modeled probability-weighted revenue loss.",
"proof_of_work_summary": {
"blocks": 1,
"tap_sessions": 0,
"proof_of_work_artifacts": 2,
"linked_sessions": 0,
"workspace_files": 0
},
"file_ids": ["file_814439bd-4894-4e11-852d-314e9f777a7f"]
}- 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, both modes still return 200 with an empty-data template (report object or chat message).
- Chat mode: pass returned file_ids on follow-up calls to avoid re-uploading the context bundle.
/api/v2/agent/workspaces/{workspace_id}/blocks/{block_id}/tap-linkstap:write201 CreatedCreate a private Think Aloud Protocol (TAP) link for a block (15 or 30 minutes).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Workspace ID. |
| block_id | uuid | yes | Target block ID. |
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| minutes | integer | — | 15 or 30 only; any other value defaults to 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. |
Request example
{
"minutes": 15,
"guest_email": "learner@example.com"
}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 | — | Block ID. |
| tap_link.status | string | — | pending | in_progress | completed |
| tap_link.requested_duration_seconds | integer | — | 900 (15 min) or 1800 (30 min). |
| tap_link.focus_block_ids | uuid[] | — | Focused block IDs (usually the target block). |
| 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": "88a43ad8-62f8-4252-a847-2cbc0b754a57",
"status": "pending",
"requested_duration_seconds": 900,
"focus_block_ids": ["88a43ad8-62f8-4252-a847-2cbc0b754a57"],
"created_at": "2026-06-23T01:29:03.861663+00:00",
"private_url": "https://openlesson.academy/tap/session/E8-ouJ9lErgDEmteyKc4tJ39meJ91vzZFNUiuRauHvw"
}
}- 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/v2/agent/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 | — | Block ID. |
| 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[].overall_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"],
"overall_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/v2/agent/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/v2/agent/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/v2/agent/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 teams_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/v2/agent/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/v2/agent/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.