openapi: 3.2.0 info: title: Media Caption Public Transcripts 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: Transcripts description: Synchronous and async public YouTube transcript endpoints. paths: /transcripts: post: tags: - Transcripts summary: Fetch one public YouTube transcript description: Synchronously fetches a public YouTube transcript, reserves 1 credit before processing, and charges 1 credit on success. operationId: createTranscript security: - bearerApiKey: [] - headerApiKey: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TranscriptRequest' examples: url: value: url: https://www.youtube.com/watch?v=dQw4w9WgXcQ language: en videoId: value: videoId: dQw4w9WgXcQ responses: '200': description: Transcript fetched headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-RateLimit-Limit: $ref: '#/components/headers/XRateLimitLimit' X-RateLimit-Remaining: $ref: '#/components/headers/XRateLimitRemaining' X-RateLimit-Reset: $ref: '#/components/headers/XRateLimitReset' content: application/json: schema: $ref: '#/components/schemas/TranscriptResponse' '400': $ref: '#/components/responses/InvalidRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientCredits' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' /transcripts/bulk: post: tags: - Transcripts summary: Create a bulk public transcript job description: Creates an async job for 1 to 100 public YouTube transcript lookups and requires an active dashboard webhook ID for the final job-level event. The API reserves 1 credit per item when the job is accepted and settles credits after all items finish. operationId: createBulkTranscriptJob security: - bearerApiKey: [] - headerApiKey: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BulkTranscriptRequest' responses: '202': description: Job accepted headers: Location: description: Relative URL for polling the accepted job. schema: type: string example: /v1/jobs/job_01jz7m91bxkpbqha0wt8whm76r X-Request-Id: $ref: '#/components/headers/XRequestId' X-RateLimit-Limit: $ref: '#/components/headers/XRateLimitLimit' X-RateLimit-Remaining: $ref: '#/components/headers/XRateLimitRemaining' X-RateLimit-Reset: $ref: '#/components/headers/XRateLimitReset' content: application/json: schema: $ref: '#/components/schemas/BulkTranscriptAcceptedResponse' '400': $ref: '#/components/responses/InvalidRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientCredits' '429': $ref: '#/components/responses/ConcurrentJobLimitExceeded' '500': $ref: '#/components/responses/InternalError' components: 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' ConcurrentJobLimitExceeded: description: Concurrent bulk job limit exceeded headers: Retry-After: $ref: '#/components/headers/RetryAfter' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: concurrent_job_limit_exceeded message: Concurrent API job limit exceeded. Please wait for active jobs to finish. 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' Forbidden: description: Forbidden request content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' schemas: TranscriptSegment: type: object additionalProperties: false required: - startSec - durationSec - text properties: startSec: type: number minimum: 0 example: 0 durationSec: type: number minimum: 0 example: 2.4 text: type: string example: Example transcript text 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 BulkTranscriptAcceptedResponse: type: object additionalProperties: false required: - jobId - type - status - itemCount - creditsFrozen properties: jobId: type: string example: job_01jz7m91bxkpbqha0wt8whm76r type: type: string enum: - public_transcripts_bulk status: $ref: '#/components/schemas/JobStatus' itemCount: type: integer example: 2 creditsFrozen: type: integer example: 2 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 LanguageTag: type: string pattern: ^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$ example: en JobStatus: type: string enum: - queued - processing - completed - completed_with_errors - failed - canceled TranscriptRequest: type: object additionalProperties: false properties: url: type: string format: uri description: YouTube video URL. Required when videoId is omitted. videoId: type: string description: YouTube video ID. Required when url is omitted. example: dQw4w9WgXcQ language: $ref: '#/components/schemas/LanguageTag' anyOf: - required: - url - required: - videoId BulkTranscriptItemRequest: type: object additionalProperties: false properties: externalId: type: string description: Customer-provided item ID returned in job results and webhooks. url: type: string format: uri videoId: type: string language: $ref: '#/components/schemas/LanguageTag' anyOf: - required: - url - required: - videoId BulkTranscriptRequest: type: object additionalProperties: false required: - webhookId - items properties: webhookId: type: string description: '**Required** active dashboard webhook ID for one final job-level event.' example: wh_01jz7m8a3yk9pnx2g6bc8n4a8m items: type: array description: '**Required** array of public transcript requests.' minItems: 1 maxItems: 100 items: $ref: '#/components/schemas/BulkTranscriptItemRequest' TranscriptResponse: type: object additionalProperties: false required: - transcriptionId - source - language - durationSec - expiresAt - transcript - creditsFrozen - creditsCharged properties: transcriptionId: type: string example: tr_3f6e5c6a-0d31-4f8c-9b1d-7f3b9e6a2c11 source: type: string enum: - public language: type: string example: en durationSec: type: number example: 184 expiresAt: type: string format: date-time transcript: type: array items: $ref: '#/components/schemas/TranscriptSegment' creditsFrozen: type: integer example: 1 creditsCharged: type: integer example: 1 headers: XRequestId: description: Request ID for troubleshooting. schema: type: string example: req_01jz7mb36gp7h2nm5rwd1ah4zz XRateLimitReset: description: ISO 8601 timestamp when the current fixed window resets. schema: type: string format: date-time example: '2026-05-11T10:01:00.000Z' RetryAfter: description: Seconds to wait before retrying. schema: type: integer example: 30 XRateLimitLimit: description: Maximum requests allowed in the current fixed window. schema: type: integer example: 60 XRateLimitRemaining: description: Requests remaining in the current fixed window. schema: type: integer example: 58 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.