openLesson
UpgradeDashboard

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.

Agent skill file →Get API key

Authentication

Authorization: Bearer <api_key>
Content-Type: application/json

Key types

FieldTypeRequiredDescription
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

FieldTypeRequiredDescription
workspaces:readscope—List blocks; generate proof-of-work schemas and integration skills; run performance analysis (report or chat).
workspaces:writescope—Create workspaces; upload proof of work.
tap:readscope—List TAP links and poll completion status (score via POST .../performance).
tap:writescope—Create Think Aloud Protocol (TAP) links for blocks.
org:readscope—Reserved for org admin keys (future org read endpoints).
org:writescope—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
POST/api/v2/agent/workspacesworkspaces:write201 Created

Create a Workspace from an initial prompt and optional seed files.

Request body (JSON)

FieldTypeRequiredDescription
initial_promptstringyesTask or learning goal used to generate workspace title and blocks.
filesarray<object>—Optional seed files (max 5). Each item: name (string), mime_type (string), data (base64 string).
files[].namestringyesFilename including extension.
files[].mime_typestringyesapplication/pdf, text/plain, text/markdown, image/jpeg, image/png, image/webp.
files[].datastring (base64)yesFile 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

FieldTypeRequiredDescription
workspace.iduuid—Workspace ID (workspaces.id).
workspace.titlestring—Generated workspace title.
workspace.root_topicstring—Truncated prompt summary.
workspace.statusstring—active
workspace.notesstring—Full initial prompt stored as notes.
workspace.created_atISO-8601—Creation timestamp.
workspace.updated_atISO-8601—Last update timestamp.
blocksarray—Generated assessable blocks (3–8).
blocks[].iduuid—Block ID (blocks.id).
blocks[].titlestring—Block title.
blocks[].descriptionstring—What the learner should demonstrate.
blocks[].is_startboolean—True for the entry block.
blocks[].next_block_idsuuid[]—Linked next blocks in the graph.
blocks[].statusstring—available
blocks[].created_atISO-8601—Block creation timestamp.
filesarray—Uploaded workspace_files records (empty if none).
files[].iduuid—workspace_files.id
files[].file_namestring—Original filename.
files[].file_sizeinteger—Bytes.
files[].mime_typestring—MIME type.
files[].created_atISO-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).
GET/api/v2/agent/workspaces/{workspace_id}/blocksworkspaces:read200 OK

List assessable blocks in a workspace.

Path parameters

FieldTypeRequiredDescription
workspace_iduuidyesWorkspace ID.

Response body

FieldTypeRequiredDescription
blocksarray—All blocks for the workspace, ordered by created_at ascending.
blocks[].iduuid—Block ID.
blocks[].titlestring—Block title.
blocks[].descriptionstring—Demonstration objective.
blocks[].is_startboolean—Entry block flag.
blocks[].next_block_idsuuid[]—Next block IDs.
blocks[].statusstring—available | in_progress | completed
blocks[].created_atISO-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.
POST/api/v2/agent/workspaces/{workspace_id}/proof-of-work-schemaworkspaces:read200 OK

Given 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

FieldTypeRequiredDescription
workspace_iduuidyesWorkspace ID.

Request body (JSON)

FieldTypeRequiredDescription
definitionstringyesWhat to evaluate — rubric text, competency description, or full eval spec from your agent.
block_iduuid—Optional: scope schema design to one block.
integration_hintsobject—Optional hints to tailor the schema: tool_name, partner_agent, event_verbs[], goals[].
integration_hints.tool_namestring—Tool identifier (e.g. pumadoc, canvas).
integration_hints.partner_agentstring—Partner agent name for context.
integration_hints.event_verbsstring[]—Actions your agent serializes (e.g. run_simulation, edit_field).
integration_hints.goalsstring[]—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

FieldTypeRequiredDescription
schemaobject—JSON Schema (draft-07 style) for the ideal tool proof-of-work payload inside the upload data field.
schema_namestring—Snake_case identifier, typically prefixed eval_input_.
rationalestring—Why these fields capture optimal eval signal for this workspace.
example_payloadobject—Example JSON matching the schema conceptually.
recommended_mime_typestring—Usually application/json for tool proof of work.
recommended_proof_of_work_typestring—tool | screen | video | eeg
required_fieldsstring[]—Top-level field names integrators should always include.
optional_fieldsstring[]—Enrichment fields (reflections, media refs, etc.).
collection_guidancestring—When and how often to upload proof of work for this definition.
performance_report_contractobject—Formal contract for POST .../performance report mode: overall_score, marker_scores (spider_radar), gap_analysis.gaps, and example_report.
workspace_iduuid—Echo of path workspace_id.
block_iduuid | null—Echo of request block_id.
definitionstring—Echo of request definition.
workspace_summaryobject—id, title, root_topic.
context_countsobject—blocks, tap_sessions, proof_of_work_artifacts, linked_sessions, workspace_files.
file_idsstring[]—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.
POST/api/v2/agent/workspaces/{workspace_id}/integration-skillworkspaces:read200 OK

Generate a workspace-specific skill.md integration guide via POST .../integration-skill for a custom partner agent.

Path parameters

FieldTypeRequiredDescription
workspace_iduuidyesWorkspace ID.

Request body (JSON)

FieldTypeRequiredDescription
integration_namestringyesPartner integration slug or display name (e.g. acme-sales-copilot).
partner_descriptionstring—What the external agent does; Grok uses this to tailor examples.
block_iduuid—Optional: focus the skill on one block.
base_urlstring—Origin for example URLs; default https://openlesson.academy.
include_sectionsstring[]—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

FieldTypeRequiredDescription
skill_mdstring—Full markdown document with YAML frontmatter (name, description).
skill_namestring—Derived frontmatter name, e.g. acme-sales-copilot-openlesson-proof-of-work-performance.
suggested_share_pathstring—Suggested public path, e.g. /acme-sales-copilot-skill.md.
workspace_summaryobject—id, title, root_topic, block_count.
context_countsobject | null—Workspace context counts used during generation.
file_idsstring[]—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.
POST/api/v2/agent/workspaces/{workspace_id}/proof-of-workworkspaces:write201 Created

Upload tool usage, screenshots, video, or EEG to xAI Files and link to workspace/block/session.

Path parameters

FieldTypeRequiredDescription
workspace_iduuidyesWorkspace ID.

Request body (JSON)

FieldTypeRequiredDescription
typestringyestool | screen | screenshot | video | eeg (screenshot aliases to screen).
mime_typestringyesMust match type (see MIME table below).
datastring (base64)yesArtifact bytes, max 10 MB.
file_namestring—Optional filename; default derived from type.
block_iduuid—Optional block to scope proof of work.
session_iduuid—Optional linked session ID.
timestamp_msinteger—Client timestamp; defaults to server time.
chunk_indexinteger—Chunk sequence for streaming artifacts; default 0.
metadataobject—Arbitrary JSON metadata stored on the proof of work row.
tool_namestring—For type=tool: tool identifier (e.g. canvas, pumadoc).
tool_actionstring—For type=tool: action name (e.g. draw, step_completed).
band_powersobject—For type=eeg: band power map (numeric values).
device_namestring—For type=eeg: device label (e.g. Muse).
sample_countinteger—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

FieldTypeRequiredDescription
proof_of_work.iduuid—workspace_proof_of_work.id
proof_of_work.workspace_iduuid—Same as workspace_id.
proof_of_work.block_iduuid | null—block_id if scoped.
proof_of_work.session_iduuid | null—Optional session link.
proof_of_work.typestring—tool | screen | video | eeg
proof_of_work.file_namestring—Stored filename.
proof_of_work.mime_typestring—MIME type.
proof_of_work.file_sizeinteger—Decoded byte length.
proof_of_work.xai_file_idstring—xAI Files API file_id.
proof_of_work.timestamp_msinteger—Client or server timestamp.
proof_of_work.chunk_indexinteger—Chunk index.
proof_of_work.metadataobject—Stored metadata JSON.
proof_of_work.tool_namestring | null—Tool name when type=tool.
proof_of_work.tool_actionstring | null—Tool action when type=tool.
proof_of_work.device_namestring | null—EEG device when type=eeg.
proof_of_work.sample_countinteger | null—EEG samples when type=eeg.
proof_of_work.created_atISO-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.
POST/api/v2/agent/workspaces/{workspace_id}/performanceworkspaces:read200 OK

Analyze 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

FieldTypeRequiredDescription
workspace_iduuidyesWorkspace ID.

Request body (JSON)

FieldTypeRequiredDescription
promptstring—If non-empty → chat mode (markdown response). If omitted or empty → report mode (structured JSON).
block_iduuid—Optional: scope analysis to one block.
conversation_historyarray—Chat mode only. Up to 12 prior turns: { role: user|assistant, content: string }.
file_idsstring[]—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

FieldTypeRequiredDescription
modereport | chat—Which response shape is populated.
reportobject | null—Present when mode=report.
report.overall_scoreinteger—0–100 readiness score synthesized from proof of work.
report.marker_scoresarray—Spider/radar competency axes: id, label, score (0–100), rationale, optional block_id.
report.summarystring—Executive summary.
report.strengthsstring[]—Demonstrated strengths.
report.growth_areasstring[]—Areas needing development.
report.gap_analysis.summarystring—Gap analysis overview.
report.gap_analysis.gapsarray—title, proof_of_work, severity (low|medium|high), suggested_repair.
report.gap_analysis.next_practicestring[]—Recommended practice actions.
report.suggestionsstring[]—Additional recommendations.
report.confidencestring—emerging | developing | clear | well-connected
responsestring | null—Markdown answer when mode=chat.
proof_of_work_summaryobject | null—Counts used in context (blocks, tap_sessions, proof_of_work_artifacts, linked_sessions, workspace_files).
file_idsstring[]—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.
POST/api/v2/agent/workspaces/{workspace_id}/blocks/{block_id}/tap-linkstap:write201 Created

Create a private Think Aloud Protocol (TAP) link for a block (15 or 30 minutes).

Path parameters

FieldTypeRequiredDescription
workspace_iduuidyesWorkspace ID.
block_iduuidyesTarget block ID.

Request body (JSON)

FieldTypeRequiredDescription
minutesinteger—15 or 30 only; any other value defaults to 15.
guest_user_iduuid—Org admin only: assign link to a guest by ID.
guest_emailstring—Org admin only: assign link to a guest by email.

Request example

{
  "minutes": 15,
  "guest_email": "learner@example.com"
}

Response body

FieldTypeRequiredDescription
tap_link.iduuid—TAP link / session row ID.
tap_link.workspace_iduuid—Workspace ID.
tap_link.block_iduuid—Block ID.
tap_link.statusstring—pending | in_progress | completed
tap_link.requested_duration_secondsinteger—900 (15 min) or 1800 (30 min).
tap_link.focus_block_idsuuid[]—Focused block IDs (usually the target block).
tap_link.created_atISO-8601—Link creation time.
tap_link.private_urlstring—Bearer URL: /tap/session/{token}. No login required.
interruptionobject | 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.
GET/api/v2/agent/workspaces/{workspace_id}/tap-linkstap:read200 OK

List TAP links for a workspace (filtered by caller role).

Path parameters

FieldTypeRequiredDescription
workspace_iduuidyesWorkspace ID.

Response body

FieldTypeRequiredDescription
tap_linksarray—Sessions ordered by created_at descending.
tap_links[].iduuid—Link ID.
tap_links[].workspace_iduuid—Workspace ID.
tap_links[].block_iduuid—Block ID.
tap_links[].statusstring—pending | in_progress | completed
tap_links[].requested_duration_secondsinteger—Requested duration.
tap_links[].duration_secondsinteger—Actual duration (0 until completed).
tap_links[].focus_block_idsuuid[]—Focused blocks.
tap_links[].overall_scoreinteger | null—Score when completed.
tap_links[].created_atISO-8601—Created at.
tap_links[].started_atISO-8601 | null—Started at.
tap_links[].completed_atISO-8601 | null—Completed at.
interruptionobject | 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.
POST/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)

FieldTypeRequiredDescription
emailstringyesGuest email address (normalized to lowercase).

Request example

{ "email": "learner@example.com" }

Response body

FieldTypeRequiredDescription
guest_user.iduuid—organization_guest_users.id
guest_user.organization_iduuid—Org the guest belongs to.
guest_user.emailstring—Guest email.
guest_user.statusstring—active | claimed | revoked
guest_user.claimed_by_user_iduuid | null—Set when guest signs up with same email.
guest_user.claimed_atISO-8601 | null—Claim timestamp.
guest_user.created_atISO-8601—Guest record created at.
api_keystring—Raw gsk_ key — shown once; store securely.
key.iduuid—agent_api_keys.id
key.key_prefixstring—First 13 chars of key for identification.
key.scopesstring[]—workspaces:read, workspaces:write, tap:read, tap:write
key.rate_limitinteger—Requests per minute (default 120).
key.created_atISO-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.
GET/api/v2/agent/keysbrowser session200 OK

List API keys for the signed-in dashboard user. Uses Supabase session cookies — not Bearer API key auth.

Response body

FieldTypeRequiredDescription
keysarray—All agent_api_keys for the authenticated user, newest first.
keys[].iduuid—Key ID.
keys[].labelstring | null—Optional label set at creation.
keys[].key_prefixstring—First 12 characters of sk_ key (identification only).
keys[].scopesstring[]—Assigned scopes.
keys[].rate_limitinteger—Requests per minute (default 120).
keys[].is_activeboolean—False after revocation.
keys[].created_atISO-8601—Creation timestamp.
keys[].last_used_atISO-8601 | null—Last successful API call.
keys[].expires_atISO-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.
POST/api/v2/agent/keysbrowser session201 Created

Create a new sk_ API key for the signed-in user. Raw key returned once.

Request body (JSON)

FieldTypeRequiredDescription
labelstring—Optional label, max 128 characters.
scopesstring[]—Optional. Default: workspaces:read, workspaces:write, tap:read, tap:write. org:read/org:write require org admin.
expires_in_daysinteger—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

FieldTypeRequiredDescription
key.iduuid—agent_api_keys.id
key.labelstring | null—Label if provided.
key.key_prefixstring—First 12 chars of sk_ key.
key.scopesstring[]—Assigned scopes.
key.rate_limitinteger—Default 120.
key.created_atISO-8601—Creation time.
key.expires_atISO-8601 | null—Expiry if set.
api_keystring—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.
DELETE/api/v2/agent/keys/{key_id}browser session200 OK

Revoke (soft-delete) an API key owned by the signed-in user.

Path parameters

FieldTypeRequiredDescription
key_iduuidyesagent_api_keys.id from list or create.

Response body

FieldTypeRequiredDescription
deletedboolean—True on success.
key_iduuid—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.
PATCH/api/v2/agent/keys/{key_id}/scopesbrowser session200 OK

Replace scopes on an active API key.

Path parameters

FieldTypeRequiredDescription
key_iduuidyesagent_api_keys.id.

Request body (JSON)

FieldTypeRequiredDescription
scopesstring[]yesNon-empty array of valid scope strings.

Request example

{
  "scopes": ["workspaces:read", "tap:read"]
}

Response body

FieldTypeRequiredDescription
key.iduuid—Updated key ID.
key.scopesstring[]—New scope list.
key.updated_atISO-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

FieldTypeRequiredDescription
privateTokenstringyesToken from private_url path.
thoughtstringyesLearner thought fragment.
messagesarray—Optional prior chat messages.

POST /api/workspace-tap-score/complete

FieldTypeRequiredDescription
privateTokenstringyesToken from private_url path.
transcriptarrayyesSession transcript entries with role and text.
durationSecondsinteger—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.

openLesson

A knowledge workspace with software tools that verify and augment learning for humans and AI agents.

Product

  • Platform
  • Pricing
  • Proof-of-Work API

Workspace

  • Create workspace
  • Agent skill file

Resources

  • GitHub

Legal

  • Privacy
  • Terms
  • Cookies
  • Legal Notice
@uncertainsysdaniel@uncertain.systems

© 2026 Uncertain Systems (Daniel Colomer). All rights reserved.

Building the open stack for educational technology