OpenLesson Agentic API v2
Performance Workspace API Reference
Full request and response specifications for every Agentic API endpoint: workspaces, evidence, performance analysis, GHL links, 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, ghl_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; run performance analysis (report or chat). |
| workspaces:write | scope | — | Create workspaces; upload evidence. |
| ghl:read | scope | — | List GHL links; poll results. |
| ghl:write | scope | — | Create GHL Score 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, ghl:read, ghl: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}/evidence
- POST /api/v2/agent/workspaces/{workspace_id}/performance
- POST /api/v2/agent/workspaces/{workspace_id}/blocks/{block_id}/ghl-links
- GET /api/v2/agent/workspaces/{workspace_id}/ghl-links
- GET /api/v2/agent/workspaces/{workspace_id}/ghl-links/{link_id}/results
- 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 Performance 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 (learning_plans.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 (plan_nodes.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_node_ids | uuid[] | — | Linked next blocks in the graph. |
| blocks[].status | string | — | available |
| blocks[].created_at | ISO-8601 | — | Block creation timestamp. |
| files | array | — | Uploaded plan_files records (empty if none). |
| files[].id | uuid | — | plan_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_node_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 | Performance Workspace ID. |
Response body
| Field | Type | Required | Description |
|---|---|---|---|
| blocks | array | — | All plan_nodes 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_node_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_node_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}/evidenceworkspaces: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 | Performance 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 evidence. |
| 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 evidence 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 |
|---|---|---|---|
| evidence.id | uuid | — | workspace_evidence.id |
| evidence.workspace_id | uuid | — | Same as plan_id. |
| evidence.block_id | uuid | null | — | plan_node_id if scoped. |
| evidence.session_id | uuid | null | — | Optional session link. |
| evidence.type | string | — | tool | screen | video | eeg |
| evidence.file_name | string | — | Stored filename. |
| evidence.mime_type | string | — | MIME type. |
| evidence.file_size | integer | — | Decoded byte length. |
| evidence.xai_file_id | string | — | xAI Files API file_id. |
| evidence.timestamp_ms | integer | — | Client or server timestamp. |
| evidence.chunk_index | integer | — | Chunk index. |
| evidence.metadata | object | — | Stored metadata JSON. |
| evidence.tool_name | string | null | — | Tool name when type=tool. |
| evidence.tool_action | string | null | — | Tool action when type=tool. |
| evidence.device_name | string | null | — | EEG device when type=eeg. |
| evidence.sample_count | integer | null | — | EEG samples when type=eeg. |
| evidence.created_at | ISO-8601 | — | Upload timestamp. |
Response example
{
"evidence": {
"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 evidence, GHL results, sessions, and plan files. Report mode (no prompt) or chat mode (with prompt).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Performance 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.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, evidence, 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. |
| evidence_summary | object | null | — | Counts used in context (blocks, ghl_sessions, evidence_artifacts, linked_sessions, plan_files). |
| file_ids | string[] | — | xAI file IDs for follow-up calls; pass back as file_ids. |
Response example
{
"mode": "report",
"report": {
"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",
"evidence": "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"
},
"evidence_summary": {
"blocks": 1,
"ghl_sessions": 0,
"evidence_artifacts": 2,
"linked_sessions": 0,
"plan_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.",
"evidence_summary": {
"blocks": 1,
"ghl_sessions": 0,
"evidence_artifacts": 2,
"linked_sessions": 0,
"plan_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 evidence 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}/ghl-linksghl:write201 CreatedCreate a private GHL Score link for a block (15 or 30 minutes).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Performance 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 |
|---|---|---|---|
| ghl_link.id | uuid | — | GHL link / session row ID. |
| ghl_link.plan_id | uuid | — | Workspace ID. |
| ghl_link.plan_node_id | uuid | — | Block ID. |
| ghl_link.status | string | — | pending | in_progress | completed |
| ghl_link.requested_duration_seconds | integer | — | 900 (15 min) or 1800 (30 min). |
| ghl_link.focus_node_ids | uuid[] | — | Focused block IDs (usually the target block). |
| ghl_link.created_at | ISO-8601 | — | Link creation time. |
| ghl_link.private_url | string | — | Bearer URL: /ghl-score/session/{token}. No login required. |
Response example
{
"ghl_link": {
"id": "ae0cc774-1832-4bb5-bc7d-bf119ddf759f",
"plan_id": "75b3b4ef-4e47-4f39-bb09-f61406603d75",
"plan_node_id": "88a43ad8-62f8-4252-a847-2cbc0b754a57",
"status": "pending",
"requested_duration_seconds": 900,
"focus_node_ids": ["88a43ad8-62f8-4252-a847-2cbc0b754a57"],
"created_at": "2026-06-23T01:29:03.861663+00:00",
"private_url": "https://openlesson.academy/ghl-score/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}/ghl-linksghl:read200 OKList GHL links for a workspace (filtered by caller role).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Performance Workspace ID. |
Response body
| Field | Type | Required | Description |
|---|---|---|---|
| ghl_links | array | — | Sessions ordered by created_at descending. |
| ghl_links[].id | uuid | — | Link ID. |
| ghl_links[].plan_id | uuid | — | Workspace ID. |
| ghl_links[].plan_node_id | uuid | — | Block ID. |
| ghl_links[].status | string | — | pending | in_progress | completed |
| ghl_links[].requested_duration_seconds | integer | — | Requested duration. |
| ghl_links[].duration_seconds | integer | — | Actual duration (0 until completed). |
| ghl_links[].focus_node_ids | uuid[] | — | Focused blocks. |
| ghl_links[].overall_score | integer | null | — | Score when completed. |
| ghl_links[].created_at | ISO-8601 | — | Created at. |
| ghl_links[].started_at | ISO-8601 | null | — | Started at. |
| ghl_links[].completed_at | ISO-8601 | null | — | Completed at. |
Response example
{
"ghl_links": [
{
"id": "ae0cc774-1832-4bb5-bc7d-bf119ddf759f",
"plan_id": "75b3b4ef-4e47-4f39-bb09-f61406603d75",
"plan_node_id": "88a43ad8-62f8-4252-a847-2cbc0b754a57",
"status": "completed",
"requested_duration_seconds": 900,
"duration_seconds": 120,
"focus_node_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/workspaces/{workspace_id}/ghl-links/{link_id}/resultsghl:read200 OKPoll GHL link completion and read scores, markers, and gap analysis.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| workspace_id | uuid | yes | Performance Workspace ID. |
| link_id | uuid | yes | GHL link ID from create or list. |
Response body
| Field | Type | Required | Description |
|---|---|---|---|
| ghl_result.id | uuid | — | Link ID. |
| ghl_result.workspace_id | uuid | — | Workspace ID. |
| ghl_result.block_id | uuid | — | Block ID. |
| ghl_result.xai_file_id | string | null | — | xAI artifact file when completed. |
| ghl_result.status | string | — | pending | in_progress | completed |
| ghl_result.completed | boolean | — | True when status=completed. |
| ghl_result.duration_seconds | integer | — | Actual session duration. |
| ghl_result.requested_duration_seconds | integer | — | Requested duration. |
| ghl_result.focus_block_ids | uuid[] | — | Focused blocks. |
| ghl_result.summary | string | null | — | Overall reflection when completed. |
| ghl_result.overall_score | integer | null | — | 0–100 when completed. |
| ghl_result.marker_scores | array | null | — | id, label, score, rationale per marker when completed. |
| ghl_result.gap_analysis | object | null | — | summary, gaps[], next_practice[] when completed. |
| ghl_result.analysis | object | null | — | Full analysis JSON when completed. |
| ghl_result.created_at | ISO-8601 | — | Created at. |
| ghl_result.started_at | ISO-8601 | null | — | Started at. |
| ghl_result.completed_at | ISO-8601 | null | — | Completed at. |
Response example
{
"ghl_result": {
"id": "ae0cc774-1832-4bb5-bc7d-bf119ddf759f",
"workspace_id": "75b3b4ef-4e47-4f39-bb09-f61406603d75",
"block_id": "88a43ad8-62f8-4252-a847-2cbc0b754a57",
"xai_file_id": "file_1f27f79e-81c2-4ef9-9cb5-28c36a5227c4",
"status": "completed",
"completed": true,
"duration_seconds": 120,
"requested_duration_seconds": 900,
"focus_block_ids": ["88a43ad8-62f8-4252-a847-2cbc0b754a57"],
"summary": "Learner demonstrated basic RAG concepts with room to improve causal links.",
"overall_score": 72,
"marker_scores": [
{
"id": "conceptual_clarity",
"label": "Conceptual Clarity",
"score": 78,
"rationale": "Defined retrieval and generation stages clearly."
}
],
"gap_analysis": {
"summary": "Integration between retrieval quality and answer faithfulness needs work.",
"gaps": [
{
"title": "Weak causal link retrieval→accuracy",
"evidence": "Could not explain how bad retrieval causes hallucinations.",
"severity": "medium",
"suggested_repair": "Practice explaining failure modes with a concrete bad-retrieval example."
}
],
"next_practice": ["Define faithfulness vs fluency tradeoff"]
},
"analysis": { },
"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"
}
}- Pending/in_progress: summary, overall_score, marker_scores, gap_analysis, and analysis are null.
- 404 ghl_link_not_found if link does not exist or caller cannot access it.
/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, ghl:read, ghl: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", "ghl:read", "ghl: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", "ghl:read", "ghl: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, ghl:read, ghl: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", "ghl:read", "ghl: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", "ghl:read", "ghl: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, ghl:read, ghl: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", "ghl: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", "ghl:read"],
"updated_at": "2026-06-23T13:10:00+00:00"
}
}- Cannot update revoked keys. org:read/org:write require organization admin.
GHL session completion (learner-facing)
Learners open private_url without an API key. Completion uses web APIs (not Agentic API):
POST /api/workspace-ghl-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-ghl-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. PumaDoc evidence integration: /pumadoc-evidence-performance-skill.md.