docs(memoro/server): add OpenAPI 3.1 spec and update ManaScore to 79

- Add openapi.yaml with all 50+ endpoints, schemas, and auth methods
- Update ManaScore: 76→79 (testing 45→55, documentation 78→82)
- 210 tests total (main-server 185 + audio-server 25)
- API conformity: documentation now true

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-01 16:31:44 +02:00
parent c582f164ba
commit 9aedc89ce5
2 changed files with 943 additions and 19 deletions

View file

@ -0,0 +1,926 @@
openapi: 3.1.0
info:
title: Memoro Server API
description: AI-powered voice memo management — memo processing, spaces, credits, meetings, settings.
version: 1.0.0
contact:
name: ManaCore
url: https://mana.how
servers:
- url: http://localhost:3015
description: Local development
- url: https://memoro.mana.how
description: Production
tags:
- name: Health
description: Health check and public endpoints
- name: Memos
description: Memo creation, transcription, and AI operations
- name: Spaces
description: Collaborative workspaces and invitations
- name: Invites
description: Pending invite management
- name: Credits
description: Mana credit balance, validation, and consumption
- name: Settings
description: User profile and app settings
- name: Meetings
description: Meeting bot and recording management
- name: Internal
description: Service-to-service callbacks (X-Service-Key auth)
- name: Cleanup
description: Audio file cleanup (X-Internal-API-Key auth)
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
serviceKey:
type: apiKey
in: header
name: X-Service-Key
internalKey:
type: apiKey
in: header
name: X-Internal-API-Key
schemas:
ApiResult:
type: object
properties:
success:
type: boolean
error:
type: string
required: [success]
PaginationQuery:
type: object
properties:
limit:
type: integer
minimum: 1
maximum: 100
default: 50
offset:
type: integer
minimum: 0
default: 0
CreateMemoBody:
type: object
required: [filePath, duration]
properties:
filePath:
type: string
minLength: 1
duration:
type: number
minimum: 0
spaceId:
type: string
format: uuid
blueprintId:
type: string
format: uuid
memoId:
type: string
format: uuid
recordingStartedAt:
type: string
mediaType:
type: string
AppendMemoBody:
type: object
required: [filePath, duration]
properties:
filePath:
type: string
minLength: 1
duration:
type: number
minimum: 0
recordingIndex:
type: integer
minimum: 0
recordingLanguages:
type: array
items:
type: string
enableDiarization:
type: boolean
CombineMemoBody:
type: object
required: [memoIds]
properties:
memoIds:
type: array
items:
type: string
format: uuid
minItems: 2
QuestionMemoBody:
type: object
required: [question]
properties:
question:
type: string
minLength: 1
CreateSpaceBody:
type: object
required: [name]
properties:
name:
type: string
minLength: 1
description:
type: string
LinkMemoBody:
type: object
required: [memoId]
properties:
memoId:
type: string
format: uuid
InviteBody:
type: object
required: [email]
properties:
email:
type: string
format: email
InviteActionBody:
type: object
required: [inviteId]
properties:
inviteId:
type: string
format: uuid
CreateBotBody:
type: object
required: [meeting_url]
properties:
meeting_url:
type: string
pattern: '^https://(teams\.microsoft\.com|meet\.google\.com|[\w-]+\.zoom\.us)/'
space_id:
type: string
format: uuid
CheckCreditsBody:
type: object
required: [operation, amount]
properties:
operation:
type: string
minLength: 1
amount:
type: number
minimum: 0
ConsumeCreditsBody:
type: object
required: [operation, amount, description]
properties:
operation:
type: string
minLength: 1
amount:
type: number
minimum: 0
description:
type: string
minLength: 1
metadata:
type: object
UpdateProfileBody:
type: object
properties:
display_name:
type: string
avatar_url:
type: string
format: uri
bio:
type: string
maxLength: 500
UpdateDataUsageBody:
type: object
required: [accepted]
properties:
accepted:
type: boolean
TranscriptionCompletedBody:
type: object
required: [memoId, userId, success]
properties:
memoId:
type: string
userId:
type: string
success:
type: boolean
transcriptionResult:
type: object
properties:
transcript:
type: string
utterances:
type: array
items:
type: object
properties:
offset: { type: number }
duration: { type: number }
text: { type: string }
speaker: { type: string }
languages:
type: array
items: { type: string }
primary_language:
type: string
duration:
type: number
route:
type: string
error:
type: string
fallbackStage:
type: string
ManualCleanupBody:
type: object
properties:
userIds:
type: array
items:
type: string
format: uuid
CreditCosts:
type: object
properties:
TRANSCRIPTION_PER_MINUTE: { type: number, example: 2 }
HEADLINE_GENERATION: { type: number, example: 10 }
MEMORY_CREATION: { type: number, example: 10 }
BLUEPRINT_PROCESSING: { type: number, example: 5 }
QUESTION_MEMO: { type: number, example: 5 }
MEMO_COMBINE: { type: number, example: 5 }
MEETING_RECORDING_PER_MINUTE: { type: number, example: 2 }
paths:
/health:
get:
tags: [Health]
summary: Health check with dependency status
responses:
'200':
description: All systems operational
content:
application/json:
schema:
type: object
properties:
status: { type: string, enum: [ok, degraded] }
service: { type: string }
runtime: { type: string }
timestamp: { type: string, format: date-time }
checks:
type: object
properties:
supabase: { type: string, enum: [ok, error] }
'503':
description: One or more dependencies degraded
/api/v1/credits/pricing:
get:
tags: [Credits]
summary: Get credit cost constants (public, no auth)
responses:
'200':
description: Cost constants
content:
application/json:
schema:
type: object
properties:
costs:
$ref: '#/components/schemas/CreditCosts'
# ── Memos ────────────────────────────────────────────────────────
/api/v1/memos:
post:
tags: [Memos]
summary: Create memo from uploaded file
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateMemoBody'
responses:
'201': { description: Memo created, transcription started }
'400': { description: Validation error }
'402': { description: Insufficient credits }
/api/v1/memos/{id}/append:
post:
tags: [Memos]
summary: Append transcription to existing memo
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AppendMemoBody'
responses:
'200': { description: Append transcription started }
'400': { description: Validation error }
'402': { description: Insufficient credits }
'404': { description: Memo not found }
/api/v1/memos/{id}/retry-transcription:
post:
tags: [Memos]
summary: Retry failed transcription
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Transcription retry started }
'400': { description: No audio file }
'404': { description: Memo not found }
/api/v1/memos/{id}/retry-headline:
post:
tags: [Memos]
summary: Retry headline generation
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Headline regenerated }
'404': { description: Memo not found }
'500': { description: Generation failed }
/api/v1/memos/combine:
post:
tags: [Memos]
summary: Combine multiple memos with AI
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CombineMemoBody'
responses:
'200': { description: Memos combined }
'400': { description: Validation error }
'402': { description: Insufficient credits }
'404': { description: One or more memos not found }
/api/v1/memos/{id}/question:
post:
tags: [Memos]
summary: Ask a question about memo transcript
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/QuestionMemoBody'
responses:
'200': { description: Answer generated }
'400': { description: Validation error or no transcript }
'402': { description: Insufficient credits }
'404': { description: Memo not found }
# ── Spaces ───────────────────────────────────────────────────────
/api/v1/spaces:
get:
tags: [Spaces]
summary: List user's spaces
security: [{ bearerAuth: [] }]
parameters:
- name: limit
in: query
schema: { type: integer, default: 50, maximum: 100 }
- name: offset
in: query
schema: { type: integer, default: 0 }
responses:
'200': { description: List of spaces with pagination }
post:
tags: [Spaces]
summary: Create a space
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSpaceBody'
responses:
'201': { description: Space created }
'400': { description: Validation error }
/api/v1/spaces/{id}:
get:
tags: [Spaces]
summary: Get space details
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Space details }
'403': { description: Access denied }
'404': { description: Space not found }
delete:
tags: [Spaces]
summary: Delete space (owner only)
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Space deleted }
'403': { description: Not the owner }
'404': { description: Space not found }
/api/v1/spaces/{id}/leave:
post:
tags: [Spaces]
summary: Leave a space (non-owner)
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Left space }
'400': { description: Owner cannot leave }
'403': { description: Not a member }
/api/v1/spaces/{id}/memos/link:
post:
tags: [Spaces]
summary: Link memo to space
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LinkMemoBody'
responses:
'200': { description: Memo linked }
'403': { description: Not a member }
/api/v1/spaces/{id}/memos/unlink:
post:
tags: [Spaces]
summary: Unlink memo from space
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LinkMemoBody'
responses:
'200': { description: Memo unlinked }
/api/v1/spaces/{id}/memos:
get:
tags: [Spaces]
summary: List memos in space
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
- name: limit
in: query
schema: { type: integer, default: 50 }
- name: offset
in: query
schema: { type: integer, default: 0 }
responses:
'200': { description: Paginated memo list }
'403': { description: Not a member }
/api/v1/spaces/{id}/invites:
get:
tags: [Spaces]
summary: List space invites
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: List of invites }
/api/v1/spaces/{id}/invite:
post:
tags: [Spaces]
summary: Send invitation to email
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/InviteBody'
responses:
'201': { description: Invite created and email sent }
'400': { description: Invalid email }
'403': { description: Not a member }
# ── Invites ──────────────────────────────────────────────────────
/api/v1/invites/pending:
get:
tags: [Invites]
summary: List pending invites for current user
security: [{ bearerAuth: [] }]
responses:
'200': { description: List of pending invites }
/api/v1/invites/accept:
post:
tags: [Invites]
summary: Accept an invite
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/InviteActionBody'
responses:
'200': { description: Invite accepted }
'404': { description: Invite not found }
/api/v1/invites/decline:
post:
tags: [Invites]
summary: Decline an invite
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/InviteActionBody'
responses:
'200': { description: Invite declined }
'404': { description: Invite not found }
# ── Credits ──────────────────────────────────────────────────────
/api/v1/credits/balance:
get:
tags: [Credits]
summary: Get credit balance
security: [{ bearerAuth: [] }]
responses:
'200':
description: Credit balance
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
credits: { type: number }
totalEarned: { type: number }
totalSpent: { type: number }
/api/v1/credits/check:
post:
tags: [Credits]
summary: Check if user has enough credits
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CheckCreditsBody'
responses:
'200': { description: Credit validation result }
/api/v1/credits/consume:
post:
tags: [Credits]
summary: Consume credits
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ConsumeCreditsBody'
responses:
'200': { description: Credits consumed }
# ── Settings ─────────────────────────────────────────────────────
/api/v1/settings:
get:
tags: [Settings]
summary: Get all user settings
security: [{ bearerAuth: [] }]
responses:
'200': { description: User settings }
/api/v1/settings/memoro:
get:
tags: [Settings]
summary: Get memoro-specific settings
security: [{ bearerAuth: [] }]
responses:
'200': { description: Memoro settings }
patch:
tags: [Settings]
summary: Update memoro settings
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
type: object
minProperties: 1
responses:
'200': { description: Settings updated }
'400': { description: Empty body }
/api/v1/settings/memoro/data-usage:
patch:
tags: [Settings]
summary: Update data usage acceptance
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateDataUsageBody'
responses:
'200': { description: Data usage updated }
/api/v1/settings/profile:
patch:
tags: [Settings]
summary: Update user profile
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateProfileBody'
responses:
'200': { description: Profile updated }
'400': { description: Validation error }
# ── Meetings ─────────────────────────────────────────────────────
/api/v1/meetings/bots:
post:
tags: [Meetings]
summary: Create meeting bot
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateBotBody'
responses:
'200': { description: Bot created }
'400': { description: Invalid meeting URL }
'402': { description: Insufficient credits }
get:
tags: [Meetings]
summary: List meeting bots
security: [{ bearerAuth: [] }]
parameters:
- name: space_id
in: query
schema: { type: string }
- name: limit
in: query
schema: { type: integer, default: 50 }
- name: offset
in: query
schema: { type: integer, default: 0 }
responses:
'200': { description: List of bots }
/api/v1/meetings/bots/{id}:
get:
tags: [Meetings]
summary: Get bot by ID
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Bot details }
'404': { description: Bot not found }
/api/v1/meetings/bots/{id}/stop:
post:
tags: [Meetings]
summary: Stop a meeting bot
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Bot stop signal sent }
'404': { description: Bot not found }
/api/v1/meetings/recordings:
get:
tags: [Meetings]
summary: List recordings
security: [{ bearerAuth: [] }]
parameters:
- name: space_id
in: query
schema: { type: string }
- name: limit
in: query
schema: { type: integer, default: 50 }
- name: offset
in: query
schema: { type: integer, default: 0 }
responses:
'200': { description: List of recordings }
/api/v1/meetings/recordings/{id}:
get:
tags: [Meetings]
summary: Get recording by ID
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Recording details }
'404': { description: Recording not found }
/api/v1/meetings/recordings/{id}/to-memo:
post:
tags: [Meetings]
summary: Convert recording to memo
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Memo created from recording }
'400': { description: No audio/video file }
'404': { description: Recording not found }
# ── Internal ─────────────────────────────────────────────────────
/api/v1/internal/transcription-completed:
post:
tags: [Internal]
summary: Transcription completion callback
security: [{ serviceKey: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TranscriptionCompletedBody'
responses:
'200': { description: Callback processed }
/api/v1/internal/batch-metadata:
post:
tags: [Internal]
summary: Update batch job metadata
security: [{ serviceKey: [] }]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [memoId, jobId]
properties:
memoId: { type: string }
jobId: { type: string }
responses:
'200': { description: Metadata updated }
'404': { description: Memo not found }
# ── Cleanup ──────────────────────────────────────────────────────
/api/v1/cleanup/run:
post:
tags: [Cleanup]
summary: Trigger async audio cleanup
security: [{ internalKey: [] }]
responses:
'200': { description: Cleanup started }
/api/v1/cleanup/manual:
post:
tags: [Cleanup]
summary: Manual cleanup with optional user IDs
security: [{ internalKey: [] }]
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ManualCleanupBody'
responses:
'200': { description: Cleanup result }
# ── Webhooks ─────────────────────────────────────────────────────
/meetings/webhooks/bot-events:
post:
tags: [Meetings]
summary: Meeting bot webhook (HMAC-verified)
description: Receives recording.completed and recording.failed events from meeting bot service.
responses:
'200': { description: Event processed }
'400': { description: Invalid JSON }
'401': { description: Invalid HMAC signature }