{"openapi":"3.0.3","info":{"title":"La Sintesi – Call Analysis API","version":"1.0.0","description":"REST API for the La Sintesi Call Analysis platform. Supports session-based (cookie) and API-key authentication.\n\n## Authentication\nAll endpoints except **Sign Up** and **Sign In** require authentication.\n\n| Method | Header | Description |\n|--------|--------|-------------|\n| Session | `Cookie: next-auth.session-token=<token>` | Automatic after browser sign-in |\n| API Key | `X-API-Key: <key>` | For external integrations |\n\n## Rate Limiting\nResponses include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers.\n\n| Tier | Limit | Used by |\n|------|-------|---------|\n| api | 100 req/min | Most GET endpoints |\n| auth | 20 req/min | Sign up |\n| heavy | 10 req/min | Analysis, upload, upsert |\n\n## Error Format\n```json\n{ \"error\": { \"code\": \"BAD_REQUEST\", \"message\": \"descriptive message\", \"details\": {} } }\n```","contact":{"name":"La Sintesi Support"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://la-sintesi.intelhouse.net","description":"API Server"}],"tags":[{"name":"Authentication","description":"User signup and sign-in via NextAuth credentials"},{"name":"API Keys","description":"Create, list, and revoke API keys for external integrations"},{"name":"Call Analysis","description":"Submit calls for AI-powered analysis and retrieve analytics"},{"name":"Call Records","description":"Paginated call record retrieval with filtering"},{"name":"Call Analysis Detail","description":"Upsert and retrieve individual call analysis records"},{"name":"Agent Statistics","description":"Aggregated performance statistics per agent"},{"name":"File Upload","description":"Upload audio/video recordings for analysis"},{"name":"Credits","description":"Credit balance and usage information"},{"name":"Shared Views","description":"Create, list, and access password-protected filtered views of call data"}],"components":{"securitySchemes":{"sessionAuth":{"type":"apiKey","in":"cookie","name":"next-auth.session-token","description":"Session cookie set after signing in via the web UI."},"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key created in the admin panel."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","example":"BAD_REQUEST"},"message":{"type":"string","example":"callUrl is required"},"details":{"description":"Optional additional context"}}}}},"PaginationMeta":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of matching records"},"page":{"type":"integer","description":"Current page number"},"limit":{"type":"integer","description":"Records per page"},"totalPages":{"type":"integer","description":"Total number of pages"},"hasMore":{"type":"boolean","description":"Whether more pages exist"}}},"User":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["customer","admin","superadmin"]},"provider":{"type":"string"},"image":{"type":"string","nullable":true},"isApproved":{"type":"boolean"},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"ApiKey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"keyPrefix":{"type":"string"},"isActive":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time","nullable":true},"lastUsedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"}}}}},"CallAnalysis":{"type":"object","description":"A call analysis record stored in the database.","properties":{"id":{"type":"string","format":"uuid"},"callId":{"type":"string","format":"uuid"},"agentId":{"type":"string","nullable":true},"callUrl":{"type":"string","format":"uri"},"transcript":{"type":"string"},"chronological_utterances":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/ChronologicalUtterance"}},"sentiment":{"type":"string","nullable":true,"description":"Sentiment label assigned by analysis (e.g. positive, negative, neutral, General Inquiry)"},"summary":{"type":"string","nullable":true},"duration":{"type":"integer","nullable":true,"description":"Duration in seconds"},"connectedAt":{"type":"string","format":"date-time","nullable":true},"callScore":{"type":"integer","nullable":true},"callRemarks":{"type":"string","nullable":true},"status":{"type":"string","enum":["pending","processing","completed","failed"]},"cost":{"type":"number","nullable":true,"description":"Analysis cost in USD"},"s3Key":{"type":"string","nullable":true},"s3Bucket":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"ChronologicalUtterance":{"type":"object","properties":{"speaker":{"type":"string","description":"Speaker label (e.g. Agent, Customer)"},"text":{"type":"string"},"start_time":{"type":"string","description":"Start time (e.g. \"0:03\")"},"end_time":{"type":"string","description":"End time (e.g. \"0:07\")"},"duration":{"type":"string","description":"Utterance duration (e.g. \"0:04\")"},"word_count":{"type":"integer"},"confidence":{"type":"number","description":"ASR confidence score 0–1"}}},"CreditBalance":{"type":"object","properties":{"creditBalance":{"type":"number","description":"Remaining balance in USD"},"estimatedMinutesRemaining":{"type":"integer","description":"Minutes of analysis remaining at current rate"},"totalSpent":{"type":"number","description":"Total USD debited for analyses"},"pricePerMinute":{"type":"number","description":"Current per-minute rate in USD"}}},"SentimentSummary":{"type":"object","description":"Aggregated sentiment statistics over a set of calls.","properties":{"totalCalls":{"type":"integer"},"positiveCalls":{"type":"integer"},"negativeCalls":{"type":"integer"},"neutralCalls":{"type":"integer"},"positivePercentage":{"type":"integer"},"negativePercentage":{"type":"integer"},"neutralPercentage":{"type":"integer"},"avgDuration":{"type":"integer","description":"Average call duration in seconds"},"totalDuration":{"type":"integer","description":"Sum of all call durations in seconds"}}},"CallRecord":{"type":"object","description":"A summarised call record returned in list responses.","properties":{"callId":{"type":"string"},"sentiment":{"type":"string"},"duration":{"type":"integer"},"connectedAt":{"type":"string","format":"date-time"},"summary":{"type":"string"},"agentId":{"type":"string","nullable":true},"status":{"type":"string"},"callScore":{"type":"integer","nullable":true},"callRemarks":{"type":"string","nullable":true},"cost":{"type":"number","nullable":true}}}}},"paths":{"/api/auth/signup":{"post":{"tags":["Authentication"],"summary":"Sign Up","operationId":"authSignup","description":"Register a new user account. New accounts require admin approval before the user can log in. Rate-limited to auth tier (20 req/min).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","password"],"properties":{"name":{"type":"string","minLength":2,"example":"Jane Doe"},"email":{"type":"string","format":"email","example":"jane@example.com"},"password":{"type":"string","minLength":8,"example":"securePass123"}}}}}},"responses":{"201":{"description":"Account created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"user":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string"},"role":{"type":"string"}}}}},"example":{"message":"Account created successfully. Your account is pending admin approval before you can log in.","user":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"Jane Doe","email":"jane@example.com","role":"customer"}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"BAD_REQUEST","message":"name, email, and password are all required."}}}}},"409":{"description":"Email already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"CONFLICT","message":"An account with this email already exists."}}}}},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/callback/credentials":{"post":{"tags":["Authentication"],"summary":"Sign In (NextAuth Credentials)","operationId":"authSignin","description":"Authenticate via NextAuth credentials provider. On success a session cookie is set. Account must be approved and active.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email","example":"jane@example.com"},"password":{"type":"string","example":"securePass123"}}}}}},"responses":{"200":{"description":"Session cookie set, redirect to dashboard"},"401":{"description":"Invalid credentials or account not approved/active"}}}},"/api/api-keys":{"get":{"tags":["API Keys"],"summary":"List API Keys","operationId":"listApiKeys","description":"Retrieve API keys with metadata. Admins see all keys; customers see only their own. The full key value is never returned after creation.","security":[{"sessionAuth":[]}],"responses":{"200":{"description":"List of API keys","content":{"application/json":{"schema":{"type":"object","properties":{"apiKeys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}},"example":{"apiKeys":[{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"Workflow Integration","keyPrefix":"cad_abc1","isActive":true,"expiresAt":"2025-12-31T23:59:59.000Z","lastUsedAt":"2025-06-01T12:00:00.000Z","createdAt":"2025-01-15T10:30:00.000Z","createdBy":{"id":"u-001","name":"Admin User","email":"admin@example.com"}}]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"UNAUTHORIZED","message":"Valid session or API key required"}}}}}}},"post":{"tags":["API Keys"],"summary":"Create API Key","operationId":"createApiKey","description":"Generate a new API key. The full key is returned **only once** in the response — store it securely.","security":[{"sessionAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":2,"example":"Workflow Integration"},"expiresAt":{"type":"string","format":"date-time","description":"Optional expiration date","example":"2025-12-31T23:59:59.000Z"}}}}}},"responses":{"201":{"description":"API key created","content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"key":{"type":"string","description":"Full key – shown only once"},"keyPrefix":{"type":"string"},"expiresAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"message":{"type":"string"}}},"example":{"apiKey":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"Workflow Integration","key":"cad_abc123def456ghi789jkl012mno345pqr678stu901vwx234yz","keyPrefix":"cad_abc1","expiresAt":"2025-12-31T23:59:59.000Z","createdAt":"2025-01-15T10:30:00.000Z"},"message":"API key created. Save this key — it will not be shown again."}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"BAD_REQUEST","message":"Name is required and must be at least 2 characters."}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"UNAUTHORIZED","message":"Valid session or API key required"}}}}}}}},"/api/api-keys/{id}":{"delete":{"tags":["API Keys"],"summary":"Revoke API Key","operationId":"revokeApiKey","description":"Deactivate an API key so it can no longer be used for authentication. Admins can revoke any key; customers can only revoke their own.","security":[{"sessionAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"API key ID"}],"responses":{"200":{"description":"Key revoked","content":{"application/json":{"example":{"message":"API key revoked successfully"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"UNAUTHORIZED","message":"Valid session or API key required"}}}}},"403":{"description":"Forbidden — customers can only revoke their own keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"FORBIDDEN","message":"You can only revoke your own API keys"}}}}},"404":{"description":"API key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"NOT_FOUND","message":"API key not found"}}}}}}}},"/api/analyze":{"post":{"tags":["Call Analysis"],"summary":"Submit Call for Analysis","operationId":"submitCallAnalysis","description":"Submit a call recording URL for AI-powered analysis. Returns cached results if callId already exists. If s3Key and s3Bucket are provided (from a prior upload), they are stored with the record. Rate-limited to heavy tier (10 req/min).","security":[{"sessionAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["callUrl"],"properties":{"callUrl":{"type":"string","format":"uri","description":"URL of the audio/video recording to analyze","example":"https://storage.example.com/recordings/call-001.mp3"},"callId":{"type":"string","description":"Optional custom call ID. A UUID is auto-generated if omitted.","example":"call-001"},"agentId":{"type":"string","description":"Agent identifier to associate with this call","example":"agent-1"},"s3Key":{"type":"string","description":"S3 object key if the file was uploaded via /api/upload","example":"recordings/a1b2c3d4.mp3"},"s3Bucket":{"type":"string","description":"S3 bucket name if the file was uploaded via /api/upload","example":"my-bucket"}}}}}},"responses":{"200":{"description":"Analysis started or cached result returned","content":{"application/json":{"examples":{"cached":{"summary":"Cached result (callId already exists)","value":{"callRecords":[{"callId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","sentiment":"positive","duration":180,"connectedAt":"2025-06-01T12:00:00.000Z","transcriptExcerpt":"Hello, thank you for calling...","summary":"Customer inquiry with positive engagement.","chronological_utterances":[]}],"cached":true,"status":"completed"}},"processing":{"summary":"Analysis submitted for processing","value":{"callRecords":[],"message":"Analysis started. Results will be saved once processing is complete.","status":"processing","callId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"BAD_REQUEST","message":"callUrl is required"}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"UNAUTHORIZED","message":"Valid session or API key required"}}}}},"402":{"description":"Insufficient credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"PAYMENT_REQUIRED","message":"Insufficient credits. Your balance is $0.00. Please top up your account.","details":{"balance":0,"currency":"USD"}}}}}},"403":{"description":"Max recordings limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"FORBIDDEN","message":"Maximum number of recordings (1000) reached."}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"example":{"error":{"code":"RATE_LIMIT_EXCEEDED","message":"Rate limit: maximum 30 analyses per hour exceeded."}}}}}}},"get":{"tags":["Call Analysis"],"summary":"Get Call Analytics Dashboard","operationId":"getCallAnalytics","description":"Retrieve aggregated call analytics including summary statistics and individual call records with pagination. Customers see only their own calls (filtered by userId); admins and API key clients see all calls.","security":[{"sessionAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1},"description":"Page number"},{"name":"limit","in":"query","schema":{"type":"integer","default":100,"maximum":500},"description":"Records per page (max 500)"}],"responses":{"200":{"description":"Analytics data with pagination","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"object","properties":{"groupId":{"type":"string","example":"all"},"groupName":{"type":"string","example":"All Calls"},"totalCalls":{"type":"integer"},"positiveCalls":{"type":"integer"},"negativeCalls":{"type":"integer"},"neutralCalls":{"type":"integer"},"positivePercentage":{"type":"integer","description":"Rounded percentage"},"negativePercentage":{"type":"integer"},"neutralPercentage":{"type":"integer"},"avgDuration":{"type":"integer","description":"Average duration in seconds"},"totalDuration":{"type":"integer","description":"Sum of all durations in seconds"}}},"trends":{"type":"array","items":{"type":"object"}},"calls":{"type":"array","items":{"type":"object","properties":{"callId":{"type":"string"},"sentiment":{"type":"string"},"duration":{"type":"integer"},"connectedAt":{"type":"string","format":"date-time"},"transcriptExcerpt":{"type":"string","description":"First 150 chars of transcript"},"summary":{"type":"string"},"agentId":{"type":"string"},"status":{"type":"string"},"callScore":{"type":"integer","nullable":true},"callRemarks":{"type":"string","nullable":true},"cost":{"type":"number","nullable":true},"s3Key":{"type":"string","nullable":true},"s3Bucket":{"type":"string","nullable":true},"chronological_utterances":{"type":"array","items":{"$ref":"#/components/schemas/ChronologicalUtterance"}}}}},"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}},"example":{"summary":{"groupId":"all","groupName":"All Calls","totalCalls":42,"positiveCalls":30,"negativeCalls":5,"neutralCalls":7,"positivePercentage":71,"negativePercentage":12,"neutralPercentage":17,"avgDuration":180,"totalDuration":7560},"trends":[],"calls":[{"callId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","sentiment":"positive","duration":180,"connectedAt":"2025-06-01T12:00:00.000Z","transcriptExcerpt":"Hello, thank you for calling...","summary":"Customer inquiry with positive engagement.","agentId":"agent-1","status":"completed","callScore":85,"callRemarks":"Good call handling.","cost":0.15,"chronological_utterances":[]}],"pagination":{"total":42,"page":1,"limit":100,"totalPages":1,"hasMore":false}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"UNAUTHORIZED","message":"Valid session or API key required"}}}}}}}},"/api/calls":{"get":{"tags":["Call Records"],"summary":"List Call Records","operationId":"listCalls","description":"Retrieve paginated call records with optional filtering by agent, sentiment, or status. Supports both page-based and legacy offset-based pagination. Requires API key authentication.","security":[{"apiKeyAuth":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1},"description":"Page number (default 1)"},{"name":"limit","in":"query","schema":{"type":"integer","default":100,"maximum":500},"description":"Records per page (default 100, max 500)"},{"name":"offset","in":"query","schema":{"type":"integer","default":0},"description":"Legacy pagination offset (alternative to page)"},{"name":"agentId","in":"query","schema":{"type":"string"},"description":"Filter by agent ID"},{"name":"sentiment","in":"query","schema":{"type":"string"},"description":"Filter by sentiment value"},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","processing","completed","failed"]},"description":"Filter by analysis status"}],"responses":{"200":{"description":"Paginated call records with summary statistics","content":{"application/json":{"schema":{"type":"object","properties":{"pagination":{"$ref":"#/components/schemas/PaginationMeta"},"summary":{"type":"object","properties":{"totalCalls":{"type":"integer"},"positiveCalls":{"type":"integer"},"negativeCalls":{"type":"integer"},"neutralCalls":{"type":"integer"},"positivePercentage":{"type":"integer"},"negativePercentage":{"type":"integer"},"neutralPercentage":{"type":"integer"},"avgDuration":{"type":"integer"},"totalDuration":{"type":"integer"}}},"calls":{"type":"array","items":{"type":"object","properties":{"callId":{"type":"string"},"agentId":{"type":"string","nullable":true},"sentiment":{"type":"string"},"summary":{"type":"string"},"transcriptExcerpt":{"type":"string"},"duration":{"type":"integer"},"connectedAt":{"type":"string","format":"date-time","nullable":true},"callScore":{"type":"integer","nullable":true},"callRemarks":{"type":"string","nullable":true},"status":{"type":"string"},"s3Key":{"type":"string","nullable":true},"s3Bucket":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}},"example":{"pagination":{"total":150,"page":1,"limit":100,"totalPages":2,"hasMore":true},"summary":{"totalCalls":42,"positiveCalls":30,"negativeCalls":5,"neutralCalls":7,"positivePercentage":71,"negativePercentage":12,"neutralPercentage":17,"avgDuration":180,"totalDuration":7560},"calls":[{"callId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","agentId":"agent-1","sentiment":"positive","summary":"Customer inquiry with positive engagement.","transcriptExcerpt":"Hello, thank you for calling...","duration":180,"connectedAt":"2025-06-01T12:00:00.000Z","callScore":85,"callRemarks":"Good call handling.","status":"completed","s3Key":"recordings/a1b2c3d4.mp3","s3Bucket":"my-bucket","createdAt":"2025-06-01T11:55:00.000Z","updatedAt":"2025-06-01T12:05:00.000Z"}]}}}},"400":{"description":"Invalid filter value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"BAD_REQUEST","message":"Invalid status filter. Must be one of: pending, processing, completed, failed"}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid or missing API key"}}}}}}}},"/api/call-analysis/by-call/{callId}":{"get":{"tags":["Call Analysis Detail"],"summary":"Get Call Analysis by Call ID","operationId":"getCallAnalysisByCallId","description":"Retrieve the full analysis record for a specific call. Requires a valid UUID callId.","security":[{"apiKeyAuth":[]}],"parameters":[{"name":"callId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Call ID (UUID format)"}],"responses":{"200":{"description":"Call analysis record","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"callId":{"type":"string","format":"uuid"},"transcript":{"type":"string"},"chronological_utterances":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/ChronologicalUtterance"}},"sentiment":{"type":"string","nullable":true},"summary":{"type":"string","nullable":true},"duration":{"type":"integer","nullable":true},"connectedAt":{"type":"string","format":"date-time","nullable":true},"status":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"example":{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","callId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","transcript":"Agent: Hello, thank you for calling.\nCustomer: Hi, I need help with my account.","chronological_utterances":[{"speaker":"Agent","text":"Hello, thank you for calling.","start_time":"0:00","end_time":"0:03","duration":"0:03","word_count":5,"confidence":0.98}],"sentiment":"positive","summary":"Customer inquiry resolved successfully.","duration":180,"connectedAt":"2025-06-01T12:00:00.000Z","status":"completed","createdAt":"2025-06-01T11:55:00.000Z","updatedAt":"2025-06-01T12:05:00.000Z"}}}},"400":{"description":"Invalid callId format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"BAD_REQUEST","message":"Invalid callId format: expected UUID, got \"abc\""}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid or missing API key"}}}}},"404":{"description":"Call not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"NOT_FOUND","message":"Call analysis not found for callId: a1b2c3d4-e5f6-7890-abcd-ef1234567890"}}}}}}},"post":{"tags":["Call Analysis Detail"],"summary":"Create or Update Call Analysis","operationId":"upsertCallAnalysis","description":"Upsert a call analysis record. Creates a new record if none exists for the callId, or updates an existing one (only if status is 'processing' or incoming status is 'completed'). Returns 201 for create, 200 for update. A PUT alias is also available. Rate-limited to heavy tier (10 req/min).","security":[{"apiKeyAuth":[]}],"parameters":[{"name":"callId","in":"path","required":true,"schema":{"type":"string"},"description":"Call ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"callUrl":{"type":"string","format":"uri"},"transcript":{"type":"string"},"chronological_utterances":{"type":"array","items":{"$ref":"#/components/schemas/ChronologicalUtterance"}},"sentiment":{"type":"string"},"summary":{"type":"string"},"duration":{"type":"integer","description":"Duration in seconds"},"connectedAt":{"type":"string","format":"date-time"},"callScore":{"type":"integer"},"callRemarks":{"type":"string"},"status":{"type":"string","enum":["pending","processing","completed","failed"]}}}}}},"responses":{"200":{"description":"Record updated","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"callId":{"type":"string"},"transcript":{"type":"string"},"chronological_utterances":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/ChronologicalUtterance"}},"sentiment":{"type":"string","nullable":true},"summary":{"type":"string","nullable":true},"duration":{"type":"integer","nullable":true},"connectedAt":{"type":"string","format":"date-time","nullable":true},"callScore":{"type":"integer","nullable":true},"callRemarks":{"type":"string","nullable":true},"cost":{"type":"number","nullable":true},"status":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}},"201":{"description":"Record created","content":{"application/json":{"example":{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","callId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","transcript":"Agent: Hello...","sentiment":"positive","summary":"Customer inquiry resolved.","duration":180,"connectedAt":"2025-06-01T12:00:00.000Z","callScore":85,"callRemarks":"Good call handling.","cost":0.15,"status":"completed","createdAt":"2025-06-01T11:55:00.000Z","updatedAt":"2025-06-01T12:05:00.000Z"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid or missing API key"}}}}}}},"put":{"tags":["Call Analysis Detail"],"summary":"Create or Update Call Analysis (PUT alias)","operationId":"upsertCallAnalysisPut","description":"Alias for POST /api/call-analysis/by-call/{callId}. Accepts the same body and returns the same responses.","security":[{"apiKeyAuth":[]}],"parameters":[{"name":"callId","in":"path","required":true,"schema":{"type":"string"},"description":"Call ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"callUrl":{"type":"string","format":"uri"},"transcript":{"type":"string"},"chronological_utterances":{"type":"array","items":{"$ref":"#/components/schemas/ChronologicalUtterance"}},"sentiment":{"type":"string"},"summary":{"type":"string"},"duration":{"type":"integer"},"connectedAt":{"type":"string","format":"date-time"},"callScore":{"type":"integer"},"callRemarks":{"type":"string"},"status":{"type":"string","enum":["pending","processing","completed","failed"]}}}}}},"responses":{"200":{"description":"Record updated (same as POST)"},"201":{"description":"Record created (same as POST)"},"401":{"description":"Unauthorized"}}}},"/api/agents":{"get":{"tags":["Agent Statistics"],"summary":"Get Agent Statistics","operationId":"getAgentStatistics","description":"Retrieve aggregated statistics for all agents, or filter for a single agent using the agentId query parameter. Only completed calls with sentiment are included. Results are paginated.","security":[{"apiKeyAuth":[]}],"parameters":[{"name":"agentId","in":"query","schema":{"type":"string"},"description":"Filter to a single agent"},{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1},"description":"Page number"},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":100},"description":"Agents per page (max 100)"}],"responses":{"200":{"description":"Agent statistics","content":{"application/json":{"schema":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"object","properties":{"agentId":{"type":"string"},"totalCalls":{"type":"integer"},"positiveCalls":{"type":"integer"},"negativeCalls":{"type":"integer"},"neutralCalls":{"type":"integer"},"positivePercentage":{"type":"integer"},"negativePercentage":{"type":"integer"},"neutralPercentage":{"type":"integer"},"totalDuration":{"type":"integer"},"averageDuration":{"type":"integer"},"averageScore":{"type":"integer","nullable":true},"sentimentBreakdown":{"type":"object","additionalProperties":{"type":"integer"}},"recentCalls":{"type":"array","description":"Up to 20 most recent calls","items":{"type":"object","properties":{"callId":{"type":"string"},"sentiment":{"type":"string"},"duration":{"type":"integer"},"connectedAt":{"type":"string","format":"date-time","nullable":true},"callScore":{"type":"integer","nullable":true},"summary":{"type":"string"}}}}}}},"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}},"example":{"agents":[{"agentId":"agent-1","totalCalls":50,"positiveCalls":30,"negativeCalls":10,"neutralCalls":10,"positivePercentage":60,"negativePercentage":20,"neutralPercentage":20,"totalDuration":9000,"averageDuration":180,"averageScore":82,"sentimentBreakdown":{"Positive":30,"Negative":10,"Neutral":10},"recentCalls":[{"callId":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","sentiment":"positive","duration":180,"connectedAt":"2025-06-01T12:00:00.000Z","callScore":85,"summary":"Customer inquired about billing."}]}],"pagination":{"total":5,"page":1,"limit":50,"totalPages":1,"hasMore":false}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid or missing API key"}}}}},"404":{"description":"Agent not found (when filtering by agentId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"NOT_FOUND","message":"No completed calls found for agent: agent-99"}}}}}}}},"/api/upload":{"post":{"tags":["File Upload"],"summary":"Upload Recording","operationId":"uploadRecording","description":"Upload an audio file via multipart/form-data. Supported formats: MP3, WAV, M4A, FLAC, MP4 audio. Files are uploaded to S3 if configured, otherwise stored locally. Rate-limited to heavy tier (10 req/min).","security":[{"sessionAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"Audio file (MP3, WAV, M4A, FLAC, or MP4 audio)"}}}}}},"responses":{"200":{"description":"File uploaded successfully","content":{"application/json":{"examples":{"s3":{"summary":"Uploaded to S3","value":{"success":true,"filename":"a1b2c3d4.mp3","url":"https://s3.amazonaws.com/my-bucket/recordings/a1b2c3d4.mp3","bucket":"my-bucket","key":"recordings/a1b2c3d4.mp3","size":1048576,"type":"audio/mpeg","storage":"s3"}},"local":{"summary":"Stored locally (S3 not configured)","value":{"success":true,"filename":"a1b2c3d4.mp3","url":"http://localhost:3001/uploads/a1b2c3d4.mp3","localPath":"/uploads/a1b2c3d4.mp3","size":1048576,"type":"audio/mpeg","storage":"local"}}}}}},"400":{"description":"Invalid file type or size exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"noFile":{"summary":"No file provided","value":{"error":{"code":"BAD_REQUEST","message":"No file provided. Include a file in the \"file\" form field."}}},"badType":{"summary":"Unsupported file type","value":{"error":{"code":"BAD_REQUEST","message":"Unsupported file type: video/mp4. Accepted formats: MP3, WAV, M4A, FLAC, MP4 audio."}}},"tooLarge":{"summary":"File too large","value":{"error":{"code":"BAD_REQUEST","message":"File too large (150MB). Maximum size: 100MB."}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"UNAUTHORIZED","message":"Valid session or API key required"}}}}},"403":{"description":"Max recordings limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"FORBIDDEN","message":"Maximum number of recordings (1000) reached."}}}}}}}},"/api/credits/balance":{"get":{"tags":["Credits"],"summary":"Get Credit Balance","operationId":"getCreditBalance","description":"Returns the authenticated user's current credit balance, estimated minutes remaining, total amount spent, and current per-minute rate.","security":[{"sessionAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"Credit balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditBalance"},"example":{"creditBalance":12.5,"estimatedMinutesRemaining":250,"totalSpent":3.75,"pricePerMinute":0.05}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"UNAUTHORIZED","message":"Valid session or API key required"}}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"NOT_FOUND","message":"User not found"}}}}}}}},"/api/shared-views":{"post":{"tags":["Shared Views"],"summary":"Create Shared View","operationId":"createSharedView","description":"Create a new shared view with filter criteria and a password. Anyone with the link and password can access the filtered call data.","security":[{"sessionAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","password","filters"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Display name for the shared view"},"password":{"type":"string","minLength":4,"description":"Password to protect the shared view"},"filters":{"type":"object","description":"Filter criteria. At least one filter must be active (not 'all').","properties":{"callId":{"type":"string","description":"Filter by call ID (partial match)"},"agent":{"type":"string","description":"Filter by agent ID"},"sentiment":{"type":"string","description":"Filter by sentiment value"},"score":{"type":"string","description":"Filter by call score"}}}}},"example":{"name":"Q1 Sales Review","password":"securepass","filters":{"agent":"agent-123","sentiment":"positive"}}}}},"responses":{"201":{"description":"Shared view created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"filters":{"type":"object"},"createdAt":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Shared Views"],"summary":"List Shared Views","operationId":"listSharedViews","description":"List all shared views created by the authenticated user.","security":[{"sessionAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"List of shared views","content":{"application/json":{"schema":{"type":"object","properties":{"views":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"filters":{"type":"object"},"createdAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/shared-views/{id}":{"post":{"tags":["Shared Views"],"summary":"Access Shared View","operationId":"accessSharedView","description":"Access a shared view by providing its password. Returns filtered call data with sentiment summary. This is a public endpoint — no authentication required.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Shared view ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"type":"string","description":"Password for the shared view"}}},"example":{"password":"securepass"}}}},"responses":{"200":{"description":"Shared view data with filtered calls and sentiment summary","content":{"application/json":{"schema":{"type":"object","properties":{"view":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"filters":{"type":"object"}}},"summary":{"$ref":"#/components/schemas/SentimentSummary"},"calls":{"type":"array","items":{"$ref":"#/components/schemas/CallRecord"}}}}}}},"400":{"description":"Missing password","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Invalid password","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Shared view not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Shared Views"],"summary":"Delete Shared View","operationId":"deleteSharedView","description":"Delete a shared view. Only the creator or an admin/superadmin can delete.","security":[{"sessionAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Shared view ID"}],"responses":{"200":{"description":"Shared view deleted","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Shared view deleted"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — not the owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Shared view not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}