{"openapi": "3.1.0", "info": {"title": "Voicecape API", "version": "1.0.0", "description": "REST API v1 for Voicecape. Read summaries, transcripts, action items, and decisions, mark work done, and ask questions over your meetings. Authenticate with an rp_ API key created in Console > Integrations > AI and API access. API access is included on all plans, including Free. Scopes are endpoint gates, not response-field redaction. Every response carries an x-request-id header. Additive changes are non-breaking and ship to /v1; breaking changes go to /v2.", "contact": {"email": "contact@voicecape.com", "url": "https://voicecape.com/support"}}, "servers": [{"url": "https://api.recordport.app"}], "security": [{"bearerAuth": []}], "tags": [{"name": "meta", "description": "Key introspection"}, {"name": "summaries", "description": "Meeting summaries and transcripts"}, {"name": "action-items", "description": "Action items extracted from summaries"}, {"name": "decisions", "description": "Decisions extracted from summaries"}, {"name": "ask", "description": "Question answering over your meetings"}], "paths": {"/v1/me": {"get": {"operationId": "getMe", "tags": ["meta"], "summary": "Describe the calling API key", "description": "Returns the organization and key metadata for the calling key. Requires only a valid key; no scope. scopes: null means a legacy key with full access.", "responses": {"200": {"description": "Key introspection", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MeResponse"}, "example": {"org": {"id": "0b6a4c2e-1d3f-4a5b-8c7d-9e0f1a2b3c4d", "name": "Acme Inc", "plan": "team"}, "key": {"name": "Reporting bot", "scopes": ["summaries:read", "transcripts:read", "action_items:read", "decisions:read"], "dept_id": null, "user_scoped": false, "expires_at": "2027-08-16T00:00:00.000Z", "created_at": "2026-08-16T09:12:00.000Z"}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/v1/summaries": {"get": {"operationId": "listSummaries", "tags": ["summaries"], "summary": "List summaries", "description": "Lists summaries the key can read, newest first by meeting date. Requires the summaries:read scope. Read rate limit: 120 requests per minute per key.", "parameters": [{"$ref": "#/components/parameters/Limit"}, {"$ref": "#/components/parameters/Cursor"}, {"name": "from", "in": "query", "description": "Earliest meeting date to include (inclusive), YYYY-MM-DD.", "schema": {"type": "string", "format": "date"}}, {"name": "to", "in": "query", "description": "Latest meeting date to include (inclusive), YYYY-MM-DD.", "schema": {"type": "string", "format": "date"}}, {"name": "dept_id", "in": "query", "description": "Only summaries assigned to this department (UUID).", "schema": {"type": "string", "format": "uuid"}}], "responses": {"200": {"description": "A page of summaries", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "next_cursor"], "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Summary"}}, "next_cursor": {"$ref": "#/components/schemas/NextCursor"}}}, "example": {"data": [{"id": "7d4e9b2a-1c3f-4e5a-9b6d-2f8a1c0e7b3d", "recording_id": "3c2b1a0d-9e8f-4a7b-8c6d-5e4f3a2b1c0d", "title": "Weekly sales sync", "occurred_on": "2026-08-12", "dept_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "visibility": "dept", "needs_review": false, "model": "gpt-5-mini", "summary_md": "## Weekly sales sync\n\nPipeline coverage recovered to 3.1x\u2026", "key_points": ["Pipeline coverage recovered to 3.1x for Q3"], "decisions": ["Move the pricing update to the July release"], "action_items": ["Send the security overview to both trial accounts (Dana, 2026-08-20)"], "participants": ["Dana", "Minho"], "action_items_struct": [{"text": "Send the security overview to both trial accounts", "assignee": "Dana", "due": "2026-08-20"}], "decisions_struct": [{"text": "Move the pricing update to the July release", "status": "confirmed", "decided_on": "2026-08-12"}]}], "next_cursor": "MjAyNi0wOC0xMnw3ZDRlOWIyYS0xYzNmLTRlNWEtOWI2ZC0yZjhhMWMwZTdiM2Q"}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/InsufficientScope"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/v1/summaries/{id}": {"get": {"operationId": "getSummary", "tags": ["summaries"], "summary": "Get one summary", "description": "Returns one summary in the list shape plus chapters. Requires the summaries:read scope. A summary that exists but is outside the key's scope returns the same 404 as one that does not exist.", "parameters": [{"$ref": "#/components/parameters/SummaryId"}], "responses": {"200": {"description": "The summary with chapters", "content": {"application/json": {"schema": {"type": "object", "required": ["data"], "properties": {"data": {"allOf": [{"$ref": "#/components/schemas/Summary"}, {"type": "object", "required": ["chapters"], "properties": {"chapters": {"type": "array", "description": "Chapter markers for the recording. Empty array when the summary has no chapters.", "items": {"type": "object"}}}}]}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/InsufficientScope"}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/v1/summaries/{id}/transcript": {"get": {"operationId": "getSummaryTranscript", "tags": ["summaries"], "summary": "Get the transcript behind a summary", "description": "Returns the full transcript for the recording behind a summary, with speaker names applied where labeled. Requires the transcripts:read scope. A missing transcript and an out-of-scope summary both return the same 404 envelope.", "parameters": [{"$ref": "#/components/parameters/SummaryId"}], "responses": {"200": {"description": "The transcript", "content": {"application/json": {"schema": {"type": "object", "required": ["data"], "properties": {"data": {"type": "object", "required": ["summary_id", "recording_id", "text"], "properties": {"summary_id": {"type": "string", "format": "uuid"}, "recording_id": {"type": "string", "format": "uuid"}, "text": {"type": "string", "description": "Full transcript text with speaker names applied."}}}}}, "example": {"data": {"summary_id": "7d4e9b2a-1c3f-4e5a-9b6d-2f8a1c0e7b3d", "recording_id": "3c2b1a0d-9e8f-4a7b-8c6d-5e4f3a2b1c0d", "text": "Dana: Pipeline coverage is back to 3.1x for Q3.\nMinho: Both enterprise trials asked for a security review\u2026"}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/InsufficientScope"}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/v1/action-items": {"get": {"operationId": "listActionItems", "tags": ["action-items"], "summary": "List action items", "description": "Lists action items across summaries the key can read, newest first. Requires the action_items:read scope.", "parameters": [{"$ref": "#/components/parameters/Limit"}, {"$ref": "#/components/parameters/Cursor"}, {"name": "done", "in": "query", "description": "Filter by completion state.", "schema": {"type": "string", "enum": ["true", "false"]}}, {"name": "summary_id", "in": "query", "description": "Only action items from this summary (UUID).", "schema": {"type": "string", "format": "uuid"}}, {"name": "due_before", "in": "query", "description": "Only items due on or before this date, YYYY-MM-DD.", "schema": {"type": "string", "format": "date"}}, {"name": "due_after", "in": "query", "description": "Only items due on or after this date, YYYY-MM-DD.", "schema": {"type": "string", "format": "date"}}], "responses": {"200": {"description": "A page of action items", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "next_cursor"], "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/ActionItem"}}, "next_cursor": {"$ref": "#/components/schemas/NextCursor"}}}, "example": {"data": [{"id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9", "summary_id": "7d4e9b2a-1c3f-4e5a-9b6d-2f8a1c0e7b3d", "recording_id": "3c2b1a0d-9e8f-4a7b-8c6d-5e4f3a2b1c0d", "text": "Send the security overview to both trial accounts", "done": false, "done_at": null, "due_date": "2026-08-20", "assignee_user_id": "9f8e7d6c-5b4a-4c3d-8e2f-1a0b9c8d7e6f", "occurred_on": "2026-08-12", "created_at": "2026-08-12T10:02:33.512041+00:00"}], "next_cursor": null}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/InsufficientScope"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/v1/action-items/{id}": {"patch": {"operationId": "updateActionItem", "tags": ["action-items"], "summary": "Mark an action item done or not done", "description": "Requires the action_items:write scope. Write rate limit: 60 requests per minute per key. When completed via a personal key, that member is recorded as the completer; org keys complete without attributing an actor.", "parameters": [{"name": "id", "in": "path", "required": true, "description": "Action item UUID.", "schema": {"type": "string", "format": "uuid"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["done"], "properties": {"done": {"type": "boolean"}}}, "example": {"done": true}}}}, "responses": {"200": {"description": "The updated state", "content": {"application/json": {"schema": {"type": "object", "required": ["data"], "properties": {"data": {"type": "object", "required": ["id", "done"], "properties": {"id": {"type": "string", "format": "uuid"}, "done": {"type": "boolean"}}}}}, "example": {"data": {"id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9", "done": true}}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/InsufficientScope"}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/v1/decisions": {"get": {"operationId": "listDecisions", "tags": ["decisions"], "summary": "List decisions", "description": "Lists decisions across summaries the key can read, newest first. Requires the decisions:read scope.", "parameters": [{"$ref": "#/components/parameters/Limit"}, {"$ref": "#/components/parameters/Cursor"}, {"name": "status", "in": "query", "description": "Filter by decision status.", "schema": {"type": "string", "enum": ["confirmed", "revisit", "hold"]}}], "responses": {"200": {"description": "A page of decisions", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "next_cursor"], "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Decision"}}, "next_cursor": {"$ref": "#/components/schemas/NextCursor"}}}, "example": {"data": [{"id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d", "summary_id": "7d4e9b2a-1c3f-4e5a-9b6d-2f8a1c0e7b3d", "recording_id": "3c2b1a0d-9e8f-4a7b-8c6d-5e4f3a2b1c0d", "text": "Move the pricing update to the July release", "status": "confirmed", "decided_on": "2026-08-12", "dept_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "occurred_on": "2026-08-12", "created_at": "2026-08-12T10:02:33.512041+00:00"}], "next_cursor": null}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/InsufficientScope"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/v1/decisions/{id}": {"patch": {"operationId": "updateDecision", "tags": ["decisions"], "summary": "Update a decision's status", "description": "Requires the decisions:write scope. Write rate limit: 60 requests per minute per key. Status must be confirmed, revisit, or hold.", "parameters": [{"name": "id", "in": "path", "required": true, "description": "Decision UUID.", "schema": {"type": "string", "format": "uuid"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["status"], "properties": {"status": {"type": "string", "enum": ["confirmed", "revisit", "hold"]}}}, "example": {"status": "revisit"}}}}, "responses": {"200": {"description": "The updated state", "content": {"application/json": {"schema": {"type": "object", "required": ["data"], "properties": {"data": {"type": "object", "required": ["id", "status"], "properties": {"id": {"type": "string", "format": "uuid"}, "status": {"type": "string", "enum": ["confirmed", "revisit", "hold"]}}}}}, "example": {"data": {"id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d", "status": "revisit"}}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/InsufficientScope"}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/v1/ask": {"post": {"operationId": "ask", "tags": ["ask"], "summary": "Ask a question over your meetings", "description": "Answers a natural-language question over the meetings the key can read, with citations. Requires the ask:run scope. Rate limit: 5 requests per minute per key; plan quotas also apply. If the ask engine declines (for example, the plan quota is exhausted), the response is a 400 with code ask_failed.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["question"], "properties": {"question": {"type": "string", "minLength": 1}}}, "example": {"question": "What did we decide about pricing this month?"}}}}, "responses": {"200": {"description": "The answer", "content": {"application/json": {"schema": {"type": "object", "required": ["data"], "properties": {"data": {"type": "object", "required": ["answer"], "properties": {"answer": {"type": "string"}}}}}, "example": {"data": {"answer": "The pricing update was moved to the July release, confirmed in the Weekly sales sync on 2026-08-12\u2026"}}}}}, "400": {"description": "invalid_request (empty question) or ask_failed (the ask engine declined)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/InsufficientScope"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}}, "components": {"securitySchemes": {"bearerAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "rp_ API key", "description": "API key created in Console > Integrations > AI and API access, sent as Authorization: Bearer rp_your_key. Org keys read across the organization excluding other members' private recordings; personal keys return only the owner's own summaries (including their private ones); department-scoped keys return only that department's summaries. Keys carry scopes and an optional expiry."}}, "parameters": {"Limit": {"name": "limit", "in": "query", "description": "Page size, 1-100.", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 25}}, "Cursor": {"name": "cursor", "in": "query", "description": "Opaque pagination cursor from a previous response's next_cursor. Pass it back unchanged. A malformed cursor returns 400 invalid_cursor.", "schema": {"type": "string"}}, "SummaryId": {"name": "id", "in": "path", "required": true, "description": "Summary UUID.", "schema": {"type": "string", "format": "uuid"}}}, "responses": {"BadRequest": {"description": "invalid_request (a parameter or body field failed validation) or invalid_cursor (malformed cursor)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}, "example": {"error": {"code": "invalid_request", "message": "Invalid request.", "request_id": "1f6c2a3e-9d4b-4c7e-8a21-0b5d9e6f3c11"}}}}}, "Unauthorized": {"description": "Missing, invalid, revoked, or expired API key", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}, "example": {"error": {"code": "unauthorized", "message": "Provide a valid API key via Authorization: Bearer rp_\u2026", "request_id": "1f6c2a3e-9d4b-4c7e-8a21-0b5d9e6f3c11"}}}}}, "InsufficientScope": {"description": "The key does not carry the scope this endpoint requires", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}, "example": {"error": {"code": "insufficient_scope", "message": "This API key does not have the required scope.", "request_id": "1f6c2a3e-9d4b-4c7e-8a21-0b5d9e6f3c11"}}}}}, "NotFound": {"description": "No such resource, or the resource is outside this key's scope (existence is not revealed)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}, "example": {"error": {"code": "not_found", "message": "Not found.", "request_id": "1f6c2a3e-9d4b-4c7e-8a21-0b5d9e6f3c11"}}}}}, "RateLimited": {"description": "Rate limit exceeded for this API key (read 120/min, write 60/min, ask 5/min). Retry after the Retry-After header.", "headers": {"Retry-After": {"description": "Seconds to wait before retrying (30).", "schema": {"type": "string"}}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}, "example": {"error": {"code": "rate_limited", "message": "Rate limit exceeded for this API key.", "request_id": "1f6c2a3e-9d4b-4c7e-8a21-0b5d9e6f3c11"}}}}}}, "schemas": {"ErrorResponse": {"type": "object", "required": ["error"], "properties": {"error": {"type": "object", "required": ["code", "message", "request_id"], "properties": {"code": {"type": "string", "description": "Machine-readable error code.", "enum": ["unauthorized", "insufficient_scope", "rate_limited", "not_found", "invalid_cursor", "invalid_request", "ask_failed", "server_not_configured", "query_failed"]}, "message": {"type": "string"}, "request_id": {"type": "string", "description": "Matches the x-request-id response header."}}}}}, "NextCursor": {"type": ["string", "null"], "description": "Opaque cursor for the next page, or null when there are no more results."}, "ActionItemStruct": {"type": "object", "required": ["text", "assignee", "due"], "properties": {"text": {"type": "string"}, "assignee": {"type": ["string", "null"]}, "due": {"type": ["string", "null"], "description": "Due date, YYYY-MM-DD, when known."}}}, "DecisionStruct": {"type": "object", "required": ["text", "status", "decided_on"], "properties": {"text": {"type": "string"}, "status": {"type": ["string", "null"]}, "decided_on": {"type": ["string", "null"], "description": "Decision date, YYYY-MM-DD, when known."}}}, "Summary": {"type": "object", "required": ["id", "recording_id", "title", "occurred_on", "dept_id", "visibility", "needs_review", "model", "summary_md", "key_points", "decisions", "action_items", "participants", "action_items_struct", "decisions_struct"], "properties": {"id": {"type": "string", "format": "uuid"}, "recording_id": {"type": "string", "format": "uuid"}, "title": {"type": ["string", "null"]}, "occurred_on": {"type": "string", "format": "date", "description": "Meeting date, YYYY-MM-DD."}, "dept_id": {"type": ["string", "null"], "description": "Department UUID, or null when unassigned."}, "visibility": {"type": ["string", "null"], "description": "Recording visibility: private, dept, or org."}, "needs_review": {"type": "boolean"}, "model": {"type": ["string", "null"], "description": "Model that produced the summary."}, "summary_md": {"type": ["string", "null"], "description": "The summary body as Markdown."}, "key_points": {"type": "array", "items": {"type": "string"}, "description": "Flattened display strings."}, "decisions": {"type": "array", "items": {"type": "string"}, "description": "Flattened display strings."}, "action_items": {"type": "array", "items": {"type": "string"}, "description": "Flattened display strings."}, "participants": {"type": "array", "items": {"type": "string"}}, "action_items_struct": {"type": "array", "items": {"$ref": "#/components/schemas/ActionItemStruct"}, "description": "Structured originals. Always present; [] when empty."}, "decisions_struct": {"type": "array", "items": {"$ref": "#/components/schemas/DecisionStruct"}, "description": "Structured originals. Always present; [] when empty."}}}, "ActionItem": {"type": "object", "required": ["id", "summary_id", "recording_id", "text", "done", "done_at", "due_date", "assignee_user_id", "occurred_on", "created_at"], "properties": {"id": {"type": "string", "format": "uuid"}, "summary_id": {"type": "string", "format": "uuid"}, "recording_id": {"type": "string", "format": "uuid"}, "text": {"type": "string"}, "done": {"type": "boolean"}, "done_at": {"type": ["string", "null"], "format": "date-time"}, "due_date": {"type": ["string", "null"], "description": "Due date, YYYY-MM-DD, when known."}, "assignee_user_id": {"type": ["string", "null"], "description": "Assignee's user UUID, when matched to a member."}, "occurred_on": {"type": ["string", "null"], "description": "Meeting date of the parent summary, YYYY-MM-DD."}, "created_at": {"type": "string", "format": "date-time"}}}, "Decision": {"type": "object", "required": ["id", "summary_id", "recording_id", "text", "status", "decided_on", "dept_id", "occurred_on", "created_at"], "properties": {"id": {"type": "string", "format": "uuid"}, "summary_id": {"type": "string", "format": "uuid"}, "recording_id": {"type": "string", "format": "uuid"}, "text": {"type": "string"}, "status": {"type": "string", "enum": ["confirmed", "revisit", "hold"]}, "decided_on": {"type": ["string", "null"], "description": "Decision date, YYYY-MM-DD, when known."}, "dept_id": {"type": ["string", "null"]}, "occurred_on": {"type": ["string", "null"], "description": "Meeting date of the parent summary, YYYY-MM-DD."}, "created_at": {"type": "string", "format": "date-time"}}}, "MeResponse": {"type": "object", "required": ["org", "key"], "properties": {"org": {"type": "object", "required": ["id", "name", "plan"], "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": ["string", "null"]}, "plan": {"type": ["string", "null"]}}}, "key": {"type": "object", "required": ["name", "scopes", "dept_id", "user_scoped", "expires_at", "created_at"], "properties": {"name": {"type": ["string", "null"]}, "scopes": {"type": ["array", "null"], "items": {"type": "string", "enum": ["summaries:read", "transcripts:read", "action_items:read", "action_items:write", "decisions:read", "decisions:write", "ask:run"]}, "description": "Scopes granted to this key. null means a legacy key with full access."}, "dept_id": {"type": ["string", "null"], "description": "Department binding, or null for org-wide keys."}, "user_scoped": {"type": "boolean", "description": "true for personal keys bound to the member who created them."}, "expires_at": {"type": ["string", "null"], "format": "date-time"}, "created_at": {"type": ["string", "null"], "format": "date-time"}}}}}}}}