openapi: 3.2.0 info: title: Media Caption Public Uploads API version: 1.0.0 description: 'Public API for reading account and credit balance details, fetching YouTube transcripts, creating bulk transcript jobs, polling jobs, retrieving retained transcriptions, and receiving job-level webhooks.' contact: name: Media Caption url: https://mediacaption.io license: name: Proprietary url: https://mediacaption.io/terms servers: - url: https://api.mediacaption.io/v1 description: Production security: - bearerApiKey: [] - headerApiKey: [] tags: - name: Uploads description: Multipart media upload and AI transcription endpoints. paths: /uploads: post: tags: - Uploads summary: Preflight a media upload description: 'Reserves transcription credits before creating any storage upload. The declared duration determines the initial reservation; the server measures the uploaded media before transcription and reconciles the final cost. Files may be at most 3 GB (3,000,000,000 bytes). End-to-end local-file flow: 1. Create an upload with `POST /uploads`. 2. For each numbered file part, request a signed URL from `POST /uploads/{id}/parts`, then `PUT` that part directly to the returned S3 URL and retain its `ETag` response header. 3. Submit the ordered part numbers and ETags to `POST /uploads/{id}/complete`. 4. Poll `GET /uploads/{id}`. If it returns `awaiting_credits`, add credits and call `POST /uploads/{id}/resume`. When it returns `completed`, fetch the returned `transcriptionUrl`. The Media Caption API key must not be sent to the signed S3 part URL.' operationId: createUpload requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UploadRequest' example: filename: meeting.mp4 contentType: video/mp4 sizeBytes: 16777216 durationSec: 600 responses: '201': description: Credit reservation and multipart upload created headers: Location: schema: type: string X-Request-Id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/UploadCreatedResponse' '400': $ref: '#/components/responses/InvalidRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientCredits' '413': description: File exceeds the 3 GB limit content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' /uploads/{id}: parameters: - $ref: '#/components/parameters/UploadId' get: tags: - Uploads summary: Fetch upload and transcription status operationId: getUpload responses: '200': description: Upload found content: application/json: schema: $ref: '#/components/schemas/UploadStatusResponse' '400': $ref: '#/components/responses/InvalidRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' delete: tags: - Uploads summary: Cancel an upload before processing operationId: cancelUpload responses: '200': description: Upload cancelled and reserved credits refunded content: application/json: schema: $ref: '#/components/schemas/UploadOperationResponse' '400': $ref: '#/components/responses/InvalidRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' /uploads/{id}/parts: parameters: - $ref: '#/components/parameters/UploadId' post: tags: - Uploads summary: Create a signed multipart part URL description: Request this immediately before uploading the numbered part. The signed URL expires after 15 minutes. Upload the raw bytes with `PUT`, without a Media Caption API key, and retain the S3 `ETag` response header. operationId: createUploadPartUrl requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UploadPartUrlRequest' responses: '200': description: Signed part URL created content: application/json: schema: $ref: '#/components/schemas/UploadPartUrlResponse' '400': $ref: '#/components/responses/InvalidRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' /uploads/{id}/complete: parameters: - $ref: '#/components/parameters/UploadId' post: tags: - Uploads summary: Complete upload and start transcription description: Submit every part number and S3 ETag in ascending order. The API completes the S3 multipart upload, verifies the actual object size, and queues ElevenLabs Scribe transcription. operationId: completeUpload requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UploadCompleteRequest' responses: '202': description: Upload accepted for transcription content: application/json: schema: $ref: '#/components/schemas/UploadOperationResponse' '400': $ref: '#/components/responses/InvalidRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '413': description: Uploaded object does not match the declared size or exceeds 3 GB content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' /uploads/{id}/resume: parameters: - $ref: '#/components/parameters/UploadId' post: tags: - Uploads summary: Resume transcription after adding credits description: Rechecks the additional credits required after server-side duration measurement, then retries processing. operationId: resumeUpload responses: '200': description: Upload was already completed content: application/json: schema: $ref: '#/components/schemas/UploadOperationResponse' '202': description: Upload transcription resumed content: application/json: schema: $ref: '#/components/schemas/UploadOperationResponse' '400': $ref: '#/components/responses/InvalidRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientCredits' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' components: schemas: UploadPartUrlRequest: type: object additionalProperties: false required: - partNumber properties: partNumber: type: integer minimum: 1 maximum: 10000 ErrorCode: type: string enum: - concurrent_job_limit_exceeded - geo_restricted - invalid_api_key - invalid_request - insufficient_credits - internal_error - missing_api_key - not_found - public_api_rate_limited - single_transcript_rate_limited - transcription_expired - transcript_unavailable - video_unavailable - webhook_not_found - youtube_blocked - youtube_rate_limited UploadStatusResponse: type: object additionalProperties: false required: - id - filename - sizeBytes - durationSec - requiredCredits - reservedCredits - status - progress - stage - error - createdAt - completedAt properties: id: type: string filename: type: - string - 'null' sizeBytes: type: - integer - 'null' durationSec: type: - integer - 'null' requiredCredits: type: - integer - 'null' reservedCredits: type: - integer - 'null' status: $ref: '#/components/schemas/UploadStatus' progress: type: - integer - 'null' stage: type: - string - 'null' error: type: - string - 'null' createdAt: type: string format: date-time completedAt: type: - string - 'null' format: date-time transcriptionId: type: string transcriptionUrl: type: string UploadCreatedResponse: type: object additionalProperties: false required: - id - status - requiredCredits - minPartSizeBytes - partUrl - completeUrl - statusUrl - expiresAt properties: id: type: string status: type: string enum: - uploading requiredCredits: type: integer minPartSizeBytes: type: integer partUrl: type: string completeUrl: type: string statusUrl: type: string expiresAt: type: string format: date-time ErrorResponse: type: object additionalProperties: false required: - error properties: error: type: object additionalProperties: false required: - code - message properties: code: $ref: '#/components/schemas/ErrorCode' message: type: string UploadPartUrlResponse: type: object additionalProperties: false required: - partNumber - uploadUrl - expiresInSeconds properties: partNumber: type: integer uploadUrl: type: string format: uri expiresInSeconds: type: integer UploadCompleteRequest: type: object additionalProperties: false required: - parts properties: parts: type: array minItems: 1 maxItems: 10000 items: type: object additionalProperties: false required: - partNumber - etag properties: partNumber: type: integer minimum: 1 maximum: 10000 etag: type: string UploadOperationResponse: type: object additionalProperties: true required: - id - status properties: id: type: string status: $ref: '#/components/schemas/UploadStatus' requiredCredits: type: integer additionalCredits: type: integer UploadRequest: type: object additionalProperties: false required: - filename - contentType - sizeBytes - durationSec properties: filename: type: string maxLength: 400 example: meeting.mp4 contentType: type: string description: An audio/* or video/* media type. example: video/mp4 sizeBytes: type: integer minimum: 1 maximum: 3000000000 durationSec: type: integer minimum: 1 description: Client-measured duration used for the pre-upload credit reservation. UploadStatus: type: string enum: - initiated - uploading - uploaded - processing - awaiting_credits - completed - failed - aborted responses: NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' RateLimited: description: Rate limit exceeded headers: Retry-After: $ref: '#/components/headers/RetryAfter' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Unauthorized: description: Missing or invalid API key content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: missing: value: error: code: missing_api_key message: Missing API key. invalid: value: error: code: invalid_api_key message: Invalid API key. InternalError: description: Internal server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' InsufficientCredits: description: Not enough credits to start processing content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: insufficient_credits message: Insufficient credits. InvalidRequest: description: Invalid request content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' headers: XRequestId: description: Request ID for troubleshooting. schema: type: string example: req_01jz7mb36gp7h2nm5rwd1ah4zz RetryAfter: description: Seconds to wait before retrying. schema: type: integer example: 30 parameters: UploadId: name: id in: path required: true schema: type: string pattern: ^upl_[0-9a-fA-F-]{36}$ example: upl_3f6e5c6a-0d31-4f8c-9b1d-7f3b9e6a2c11 securitySchemes: bearerApiKey: type: http scheme: bearer bearerFormat: Media Caption API key description: 'Use `Authorization: Bearer mc_live_xxx`.' headerApiKey: type: apiKey in: header name: X-API-Key description: Alternative API key header.