{
  "openapi": "3.1.0",
  "info": {
    "title": "Scriptivox Transcription API",
    "version": "1.0.0",
    "summary": "Transcribe recorded audio and video into text with word-level timestamps and speaker labels.",
    "description": "The Scriptivox API turns recorded audio and video into text. Submit a file by public URL or by\npresigned upload, poll for the result or receive a webhook, and read back a structured transcript\nwith per-utterance and per-word timestamps, optional speaker diarization, and SRT/WebVTT/plain-text\nexports.\n\n**When to use it.** Batch or on-demand transcription of *recorded* media: meeting and interview\nrecordings, podcasts, lecture capture, call archives, video libraries that need captions, and\npipelines that need word-level timestamps for clip search. 119 languages. It is not a live\nstreaming or real-time dictation API — every job takes a complete file.\n\n**Shape of an integration.** `POST /v1/transcribe` returns immediately with `status: \"created\"`;\nthe file is downloaded and validated in the background, so input errors surface later on\n`GET /v1/transcribe/{id}` with `status: \"failed\"`, not on the submit call. A correct client must\nhandle failures on the poll path.\n\n**Limits.** 5 GB per file, 1 second to 10 hours of audio, 25 accepted container formats. Source\naudio is deleted 24 hours after the job ends; transcripts stay retrievable.\n\n**Billing.** $0.20 per hour of audio, pay as you go. Cost is reserved once the duration is known\n(after download or validation), not at submit time. Failed and cancelled jobs are free — the\nreservation is released.\n\n**Errors.** Every error response is `{\"error\": {\"code\", \"message\"}}` with a machine-readable code.\nSee https://platform.scriptivox.com/docs/api-reference#error-codes.",
    "termsOfService": "https://www.scriptivox.com/termsofuse",
    "contact": {
      "name": "Scriptivox API support",
      "email": "support@scriptivox.com",
      "url": "https://platform.scriptivox.com/docs"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.scriptivox.com/termsofuse"
    }
  },
  "servers": [
    {
      "url": "https://api.scriptivox.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Full API reference, guides and code examples",
    "url": "https://platform.scriptivox.com/docs/api-reference"
  },
  "security": [
    {
      "ApiKeyAuth": []
    },
    {
      "XApiKey": []
    }
  ],
  "tags": [
    {
      "name": "Transcriptions",
      "description": "Submit, poll, list, cancel and delete transcription jobs."
    },
    {
      "name": "Uploads",
      "description": "Presigned upload URLs for files that are not reachable at a public URL."
    },
    {
      "name": "Account",
      "description": "Balance and pricing information for the calling API key."
    }
  ],
  "paths": {
    "/v1/upload": {
      "post": {
        "operationId": "createUpload",
        "tags": [
          "Uploads"
        ],
        "summary": "Create a presigned upload URL",
        "description": "Returns a presigned URL to PUT an audio or video file to, for files that are not reachable at a public URL. The URL expires in 1 hour (`expires_in` seconds). Upload the bytes with a PUT request using the returned `method` and `headers`, then pass the returned `upload_id` to POST /v1/transcribe. Requires a non-zero balance: returns 402 ZERO_BALANCE at $0.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "XApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Opaque key, 1-255 printable ASCII characters. When supplied, the 200 response is cached for 24 hours and replayed byte-for-byte for the same key and body (the replay carries an `Idempotent-Replay: true` header). Reusing the key with a different body returns 422 IDEMPOTENCY_KEY_CONFLICT; reusing it while the first request is still in flight returns 409 IDEMPOTENCY_KEY_LOCKED. 4xx and 5xx responses are never cached.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUploadRequest"
              },
              "example": {
                "filename": "meeting.mp3"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Presigned upload created.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                },
                "example": {
                  "upload_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                  "upload_url": "https://storage.supabase.co/...",
                  "expires_in": 3600,
                  "method": "PUT",
                  "headers": {
                    "Content-Type": "audio/mpeg"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or filename (INVALID_REQUEST, INVALID_FILENAME, INVALID_MEDIA_FORMAT). Filenames must be ASCII, 255 characters or fewer, free of path separators, and carry a supported extension.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key (INVALID_API_KEY, API_KEY_REVOKED).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Balance is $0 (ZERO_BALANCE). Deposit before uploading.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The path exists but only accepts a different HTTP method. The response carries an `Allow` header naming the accepted method.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Allow": {
                "description": "The HTTP method this path accepts.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Another request with the same Idempotency-Key is in flight (IDEMPOTENCY_KEY_LOCKED). Retry after a few seconds; a Retry-After header is sent.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request body exceeds 100KB (PAYLOAD_TOO_LARGE).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "A request body was sent without `Content-Type: application/json` (UNSUPPORTED_MEDIA_TYPE). The response carries an `Accept-Post: application/json` header. Parameters are allowed, e.g. `application/json; charset=utf-8`.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Accept-Post": {
                "description": "The media type this endpoint accepts on a request body.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key reused with a different request body (IDEMPOTENCY_KEY_CONFLICT).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (RATE_LIMIT_EXCEEDED). Limits use a sliding 60-second window, per API key per endpoint, plus a 300/min per-IP cap across all endpoints.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error (INTERNAL_ERROR). Safe to retry.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-rateLimit": {
          "requestsPerMinute": 60,
          "scope": "api-key"
        }
      }
    },
    "/v1/transcribe": {
      "post": {
        "operationId": "createTranscription",
        "tags": [
          "Transcriptions"
        ],
        "summary": "Start a transcription",
        "description": "Accepts a job and returns immediately with status \"created\". Supply exactly one of `url` (we download the file) or `upload_id` (from POST /v1/upload). Download and validation happen in the background, so input problems surface on GET /v1/transcribe/{id} with status \"failed\" — not here. Duration and cost are determined after download or validation. Requires a non-zero balance: returns 402 ZERO_BALANCE at $0.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "XApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Opaque key, 1-255 printable ASCII characters. When supplied, the 200 response is cached for 24 hours and replayed byte-for-byte for the same key and body (the replay carries an `Idempotent-Replay: true` header). Reusing the key with a different body returns 422 IDEMPOTENCY_KEY_CONFLICT; reusing it while the first request is still in flight returns 409 IDEMPOTENCY_KEY_LOCKED. 4xx and 5xx responses are never cached.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/CreateTranscriptionFromUrl"
                  },
                  {
                    "$ref": "#/components/schemas/CreateTranscriptionFromUpload"
                  }
                ]
              },
              "examples": {
                "fromUrl": {
                  "summary": "From a public URL",
                  "value": {
                    "url": "https://example.com/podcast-episode.mp3",
                    "language": "en",
                    "diarize": true
                  }
                },
                "fromUpload": {
                  "summary": "From a presigned upload",
                  "value": {
                    "upload_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                    "language": "en",
                    "diarize": true,
                    "speaker_count": 2
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job accepted. Poll GET /v1/transcribe/{id} or wait for the webhook.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptionCreated"
                },
                "example": {
                  "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
                  "status": "created",
                  "message": "Transcription created. The file will be downloaded and processed. Poll GET /v1/transcribe/{id} for status updates."
                }
              }
            }
          },
          "400": {
            "description": "Invalid body (INVALID_REQUEST), unsupported media (INVALID_MEDIA_FORMAT), or an upload that was never completed, already consumed, or expired (FILE_NOT_UPLOADED, UPLOAD_ALREADY_USED, UPLOAD_EXPIRED, FILE_TOO_LARGE). Unknown top-level fields are rejected rather than ignored.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key (INVALID_API_KEY, API_KEY_REVOKED).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Balance is $0 or too low for this job (ZERO_BALANCE, INSUFFICIENT_BALANCE).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The upload_id does not exist (UPLOAD_NOT_FOUND).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The path exists but only accepts a different HTTP method. The response carries an `Allow` header naming the accepted method.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Allow": {
                "description": "The HTTP method this path accepts.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Another request with the same Idempotency-Key is in flight (IDEMPOTENCY_KEY_LOCKED).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request body exceeds 100KB (PAYLOAD_TOO_LARGE).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "A request body was sent without `Content-Type: application/json` (UNSUPPORTED_MEDIA_TYPE). The response carries an `Accept-Post: application/json` header. Parameters are allowed, e.g. `application/json; charset=utf-8`.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Accept-Post": {
                "description": "The media type this endpoint accepts on a request body.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key reused with a different request body (IDEMPOTENCY_KEY_CONFLICT).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (RATE_LIMIT_EXCEEDED). Limits use a sliding 60-second window, per API key per endpoint, plus a 300/min per-IP cap across all endpoints.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error (INTERNAL_ERROR). Safe to retry.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-rateLimit": {
          "requestsPerMinute": 60,
          "scope": "api-key"
        }
      }
    },
    "/v1/transcribe/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Transcription ID returned by POST /v1/transcribe.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "operationId": "getTranscription",
        "tags": [
          "Transcriptions"
        ],
        "summary": "Get a transcription",
        "description": "Returns the status and, once complete, the transcript. Poll until status is \"completed\" or \"failed\", or configure a webhook instead. The status lifecycle is created -> downloading (URL flow only) -> processing -> completed | failed. When status is \"failed\", `error.code` names the cause. Pass ?format=srt|vtt|text to export captions or plain text directly instead of JSON.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "XApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Output format. `json` (default) returns the full structured response and works at any status. `srt`, `vtt` and `text` return the transcript directly with the matching Content-Type and require status=completed; otherwise they return 400 INVALID_REQUEST.",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "srt",
                "vtt",
                "text"
              ],
              "default": "json"
            }
          },
          {
            "name": "max_words",
            "in": "query",
            "required": false,
            "description": "Caption segmentation: maximum words per cue. Only applies to format=srt|vtt|text. Whichever of max_words, max_chars and max_duration is exceeded first ends the cue.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 4
            }
          },
          {
            "name": "max_chars",
            "in": "query",
            "required": false,
            "description": "Caption segmentation: maximum characters per cue. The subtitle convention is about 80 (roughly 37 characters across two lines).",
            "schema": {
              "type": "integer",
              "minimum": 10,
              "maximum": 500,
              "default": 80
            }
          },
          {
            "name": "max_duration",
            "in": "query",
            "required": false,
            "description": "Caption segmentation: maximum seconds a single cue stays on screen.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 60,
              "default": 10
            }
          },
          {
            "name": "sentence_aware",
            "in": "query",
            "required": false,
            "description": "Caption segmentation: end a cue at sentence-ending punctuation (. ! ?). Produces more natural breaks at the cost of more variable cue lengths.",
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "include_speakers",
            "in": "query",
            "required": false,
            "description": "Whether cues are prefixed with the speaker label. `auto` includes labels only when the job has more than one distinct speaker. Only meaningful for jobs created with diarize: true.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false",
                "auto"
              ],
              "default": "auto"
            }
          },
          {
            "name": "strip_chars",
            "in": "query",
            "required": false,
            "description": "Characters to remove from cue text before output, e.g. `strip_chars=,.` drops all commas and periods.",
            "schema": {
              "type": "string",
              "maxLength": 32
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The transcription. The Content-Type depends on the `format` parameter.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transcription"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "1\n00:00:00,500 --> 00:00:03,200\nHello, thanks for joining the call today.\n"
              },
              "text/vtt": {
                "schema": {
                  "type": "string"
                },
                "example": "WEBVTT\n\n00:00:00.500 --> 00:00:03.200\n<v SPEAKER 1>Hello, thanks for joining the call today.\n"
              }
            }
          },
          "400": {
            "description": "A caption format was requested for a job that is not completed (INVALID_REQUEST), or a segmentation parameter is out of range.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key (INVALID_API_KEY, API_KEY_REVOKED).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No transcription with this ID belongs to the calling key (TRANSCRIPTION_NOT_FOUND).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The path exists but only accepts a different HTTP method. The response carries an `Allow` header naming the accepted method.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Allow": {
                "description": "The HTTP method this path accepts.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (RATE_LIMIT_EXCEEDED). Limits use a sliding 60-second window, per API key per endpoint, plus a 300/min per-IP cap across all endpoints.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error (INTERNAL_ERROR). Safe to retry.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-rateLimit": {
          "requestsPerMinute": 200,
          "scope": "api-key"
        }
      },
      "delete": {
        "operationId": "deleteTranscription",
        "tags": [
          "Transcriptions"
        ],
        "summary": "Delete a transcription",
        "description": "Soft-deletes a completed or failed transcription and removes the stored transcript. The job record is kept for 7 days for audit, then hard-deleted. Idempotent: deleting an already-deleted transcription also returns 204. In-flight jobs cannot be deleted — cancel them first.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "XApiKey": []
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted. Empty body.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key (INVALID_API_KEY, API_KEY_REVOKED).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Transcription not found (TRANSCRIPTION_NOT_FOUND).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The path exists but only accepts a different HTTP method. The response carries an `Allow` header naming the accepted method.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Allow": {
                "description": "The HTTP method this path accepts.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The job is still in flight (CONFLICT). Cancel it first.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (RATE_LIMIT_EXCEEDED). Limits use a sliding 60-second window, per API key per endpoint, plus a 300/min per-IP cap across all endpoints.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error (INTERNAL_ERROR). Safe to retry.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-rateLimit": {
          "requestsPerMinute": 30,
          "scope": "api-key"
        }
      }
    },
    "/v1/transcribe/{id}/cancel": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Transcription ID returned by POST /v1/transcribe.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "operationId": "cancelTranscription",
        "tags": [
          "Transcriptions"
        ],
        "summary": "Cancel an in-flight transcription",
        "description": "Stops a job in created, downloading, pending or processing state, releases the reserved balance, and fires the transcription.failed webhook with error.code=CANCELLED. Idempotent: cancelling an already-cancelled job returns the same response. Cancellation is best-effort against the GPU — the model may finish shortly after, but the result is discarded and you are not charged. Cancelling a completed or failed job returns 409 CONFLICT.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "XApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled. The reserved balance was released.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptionCancelled"
                },
                "example": {
                  "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
                  "status": "failed",
                  "error": {
                    "code": "CANCELLED",
                    "message": "Cancelled by customer"
                  },
                  "released_cents": 0.5
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key (INVALID_API_KEY, API_KEY_REVOKED).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Transcription not found (TRANSCRIPTION_NOT_FOUND).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The path exists but only accepts a different HTTP method. The response carries an `Allow` header naming the accepted method.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Allow": {
                "description": "The HTTP method this path accepts.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The job already reached a terminal state (CONFLICT).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (RATE_LIMIT_EXCEEDED). Limits use a sliding 60-second window, per API key per endpoint, plus a 300/min per-IP cap across all endpoints.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error (INTERNAL_ERROR). Safe to retry.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-rateLimit": {
          "requestsPerMinute": 30,
          "scope": "api-key"
        }
      }
    },
    "/v1/transcriptions": {
      "get": {
        "operationId": "listTranscriptions",
        "tags": [
          "Transcriptions"
        ],
        "summary": "List transcriptions",
        "description": "Lists the calling key’s transcriptions, newest first by default, with optional filters and cursor pagination. Soft-deleted transcriptions are excluded. Each item has the same shape as GET /v1/transcribe/{id} except that the heavy `result` object is omitted — fetch a transcription individually for the transcript. Pagination is stable across new inserts: pass `next_cursor` back in as `cursor`. `next_cursor` is null on the last page.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "XApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only return jobs in this status.",
            "schema": {
              "$ref": "#/components/schemas/TranscriptionStatus"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on created_at.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Exclusive upper bound on created_at.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction on created_at.",
            "schema": {
              "type": "string",
              "enum": [
                "desc",
                "asc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from the next_cursor field of a previous response.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of transcriptions.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptionPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid filter or cursor (INVALID_REQUEST).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key (INVALID_API_KEY, API_KEY_REVOKED).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The path exists but only accepts a different HTTP method. The response carries an `Allow` header naming the accepted method.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Allow": {
                "description": "The HTTP method this path accepts.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (RATE_LIMIT_EXCEEDED). Limits use a sliding 60-second window, per API key per endpoint, plus a 300/min per-IP cap across all endpoints.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error (INTERNAL_ERROR). Safe to retry.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-rateLimit": {
          "requestsPerMinute": 60,
          "scope": "api-key"
        }
      }
    },
    "/v1/balance": {
      "get": {
        "operationId": "getBalance",
        "tags": [
          "Account"
        ],
        "summary": "Get account balance",
        "description": "Returns the balance for the account owning the calling API key, in cents: the total, the amount reserved for in-progress transcriptions, the amount available for new jobs, and an estimate of remaining audio hours at the current per-hour price. A non-zero available balance is required to start an upload or submit a transcription.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "XApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current balance.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                },
                "example": {
                  "balance_cents": 1500,
                  "reserved_cents": 100,
                  "available_cents": 1400,
                  "price_per_hour_cents": 20,
                  "estimated_hours_available": 93.3,
                  "deposit_url": "https://platform.scriptivox.com/billing",
                  "updated_at": "2025-01-15T10:30:00Z"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key (INVALID_API_KEY, API_KEY_REVOKED).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The path exists but only accepts a different HTTP method. The response carries an `Allow` header naming the accepted method.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Allow": {
                "description": "The HTTP method this path accepts.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (RATE_LIMIT_EXCEEDED). Limits use a sliding 60-second window, per API key per endpoint, plus a 300/min per-IP cap across all endpoints.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Server error (INTERNAL_ERROR). Safe to retry.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Maximum requests allowed per minute for this endpoint.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current rolling 60-second window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) at which the window fully resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-rateLimit": {
          "requestsPerMinute": 100,
          "scope": "api-key"
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Your API key, e.g. `Authorization: sk_live_...`. A `Bearer ` prefix is accepted but not required. Header names are case-insensitive. Create keys at https://platform.scriptivox.com/keys — at most 5 active keys per account."
      },
      "XApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Alternative to the Authorization header: `X-Api-Key: sk_live_...`."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "title": "Error",
        "description": "Every failed request returns this shape. `code` is stable and machine-readable; `message` is a sanitized, customer-safe string and never a raw stack trace.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "INVALID_REQUEST",
                  "INVALID_FILENAME",
                  "INVALID_MEDIA_FORMAT",
                  "FILE_NOT_UPLOADED",
                  "FILE_TOO_LARGE",
                  "UPLOAD_ALREADY_USED",
                  "UPLOAD_EXPIRED",
                  "INVALID_API_KEY",
                  "API_KEY_REVOKED",
                  "INSUFFICIENT_BALANCE",
                  "ZERO_BALANCE",
                  "UPLOAD_NOT_FOUND",
                  "TRANSCRIPTION_NOT_FOUND",
                  "NOT_FOUND",
                  "FORBIDDEN",
                  "METHOD_NOT_ALLOWED",
                  "CONFLICT",
                  "PAYLOAD_TOO_LARGE",
                  "UNSUPPORTED_MEDIA_TYPE",
                  "IDEMPOTENCY_KEY_LOCKED",
                  "IDEMPOTENCY_KEY_CONFLICT",
                  "RATE_LIMIT_EXCEEDED",
                  "INTERNAL_ERROR"
                ],
                "description": "Machine-readable error code."
              },
              "message": {
                "type": "string",
                "description": "Human-readable description of what went wrong."
              },
              "docs_url": {
                "type": "string",
                "format": "uri",
                "description": "Documentation for this error code."
              }
            }
          },
          "retry_after": {
            "type": "integer",
            "description": "Seconds to wait before retrying. Present on 429 responses alongside the Retry-After header."
          }
        },
        "example": {
          "error": {
            "code": "INVALID_API_KEY",
            "message": "Missing API key. Provide via Authorization header or x-api-key header.",
            "docs_url": "https://platform.scriptivox.com/docs/api-reference#error-codes"
          }
        }
      },
      "CreateUploadRequest": {
        "type": "object",
        "title": "CreateUploadRequest",
        "required": [
          "filename"
        ],
        "additionalProperties": false,
        "properties": {
          "filename": {
            "type": "string",
            "maxLength": 255,
            "description": "Name of the file being uploaded, e.g. \"meeting.mp3\". ASCII only, no path separators, and it must carry a supported extension.",
            "example": "meeting.mp3"
          }
        }
      },
      "Upload": {
        "type": "object",
        "title": "Upload",
        "required": [
          "upload_id",
          "upload_url",
          "expires_in",
          "method",
          "headers"
        ],
        "properties": {
          "upload_id": {
            "type": "string",
            "format": "uuid",
            "description": "Pass this to POST /v1/transcribe once the PUT succeeds."
          },
          "upload_url": {
            "type": "string",
            "format": "uri",
            "description": "Presigned URL to PUT the file bytes to."
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds until upload_url expires. Currently 3600."
          },
          "method": {
            "type": "string",
            "enum": [
              "PUT"
            ],
            "description": "HTTP method to use against upload_url."
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Headers that must be sent with the PUT request."
          }
        }
      },
      "CreateTranscriptionFromUrl": {
        "type": "object",
        "title": "CreateTranscriptionFromUrl",
        "description": "Transcribe a file we download from a public URL.",
        "required": [
          "url"
        ],
        "additionalProperties": false,
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Anonymously accessible http or https URL. An anonymous GET must return the raw file bytes, not an HTML preview, login form or password page. Direct and presigned URLs (S3, GCS, Azure SAS) always work. Share links work only when set to \"anyone with the link can view\": Google Drive, Dropbox files (not /scl/fo/ folder links), and OneDrive personal /redir or onedrive.live.com links. OneDrive 1drv.ms/v/c/ and /p/c/ photo shares cannot be used — Microsoft routes them through SharePoint Photos, which requires sign-in.",
            "example": "https://example.com/podcast-episode.mp3"
          },
          "language": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 639-1 language code (with BCP-47 fallbacks such as `yue` and `kea`), e.g. \"en\", \"es\", \"fr\". Omit or pass null for auto-detection. Passing the language explicitly is recommended: it is faster (the model skips its detection pass) and avoids known auto-detect failure modes on short clips, code-switched audio, and files that open with music. Forcing a language the audio is not in can produce a translation rather than a transcription. Invalid codes return 400 INVALID_REQUEST.",
            "example": "en"
          },
          "diarize": {
            "type": "boolean",
            "default": false,
            "description": "Identify who said what and attach a speaker label to every utterance. Implies align: true, because speaker assignment needs word-level alignment."
          },
          "speaker_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "description": "Expected number of speakers. Requires diarize: true; passing it without diarize returns 400 INVALID_REQUEST. This is a soft prior, not a hard ceiling — the result may contain slightly more or fewer labels. Strongly recommended whenever the count is known: it measurably reduces over- and under-segmentation."
          },
          "align": {
            "type": "boolean",
            "default": true,
            "description": "Produce word-level start/end timestamps plus per-word confidence where the alignment model supports it. Confidence is language-dependent and may be null. Ignored (treated as true) when diarize is true."
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "URL to receive the completion or failure webhook. HTTPS recommended. Payloads are HMAC-signed — see https://platform.scriptivox.com/docs/webhooks."
          }
        }
      },
      "CreateTranscriptionFromUpload": {
        "type": "object",
        "title": "CreateTranscriptionFromUpload",
        "description": "Transcribe a file previously sent to a presigned upload URL.",
        "required": [
          "upload_id"
        ],
        "additionalProperties": false,
        "properties": {
          "upload_id": {
            "type": "string",
            "format": "uuid",
            "description": "The upload_id returned by POST /v1/upload.",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          "language": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 639-1 language code (with BCP-47 fallbacks such as `yue` and `kea`), e.g. \"en\", \"es\", \"fr\". Omit or pass null for auto-detection. Passing the language explicitly is recommended: it is faster (the model skips its detection pass) and avoids known auto-detect failure modes on short clips, code-switched audio, and files that open with music. Forcing a language the audio is not in can produce a translation rather than a transcription. Invalid codes return 400 INVALID_REQUEST.",
            "example": "en"
          },
          "diarize": {
            "type": "boolean",
            "default": false,
            "description": "Identify who said what and attach a speaker label to every utterance. Implies align: true, because speaker assignment needs word-level alignment."
          },
          "speaker_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "description": "Expected number of speakers. Requires diarize: true; passing it without diarize returns 400 INVALID_REQUEST. This is a soft prior, not a hard ceiling — the result may contain slightly more or fewer labels. Strongly recommended whenever the count is known: it measurably reduces over- and under-segmentation."
          },
          "align": {
            "type": "boolean",
            "default": true,
            "description": "Produce word-level start/end timestamps plus per-word confidence where the alignment model supports it. Confidence is language-dependent and may be null. Ignored (treated as true) when diarize is true."
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "URL to receive the completion or failure webhook. HTTPS recommended. Payloads are HMAC-signed — see https://platform.scriptivox.com/docs/webhooks."
          }
        }
      },
      "TranscriptionCreated": {
        "type": "object",
        "title": "TranscriptionCreated",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "created"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "TranscriptionCancelled": {
        "type": "object",
        "title": "TranscriptionCancelled",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "failed"
            ]
          },
          "error": {
            "$ref": "#/components/schemas/TranscriptionError"
          },
          "released_cents": {
            "type": "number",
            "description": "Reserved balance returned to the account, in cents."
          }
        }
      },
      "TranscriptionStatus": {
        "type": "string",
        "title": "TranscriptionStatus",
        "enum": [
          "created",
          "downloading",
          "pending",
          "processing",
          "completed",
          "failed"
        ],
        "description": "created: accepted, about to download or validate. downloading: URL flow only. processing: transcribing on a GPU worker. completed: result is populated. failed: inspect error.code."
      },
      "TranscriptionError": {
        "type": "object",
        "title": "TranscriptionError",
        "description": "Present when status is \"failed\". These codes surface only on GET /v1/transcribe/{id}, never on the submit call. Failed jobs are free — the reservation is released.",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "URL_NOT_ACCESSIBLE",
              "DOWNLOAD_FAILED",
              "INVALID_MEDIA_FORMAT",
              "DURATION_TOO_LONG",
              "PROCESSING_ERROR",
              "CREATED_TIMEOUT",
              "DOWNLOAD_TIMEOUT",
              "PROCESSING_TIMEOUT",
              "BILLING_ERROR",
              "INTERNAL_ERROR",
              "CANCELLED"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Word": {
        "type": "object",
        "title": "Word",
        "description": "Present only when align is effectively true. Leading whitespace is stripped — reconstruct spacing from utterance.text if you need it exactly.",
        "properties": {
          "word": {
            "type": "string"
          },
          "start": {
            "type": "number",
            "description": "Seconds from the start of the audio."
          },
          "end": {
            "type": "number",
            "description": "Seconds from the start of the audio."
          },
          "confidence": {
            "type": [
              "number",
              "null"
            ],
            "description": "Null for languages whose alignment model does not report confidence. Handle null defensively."
          },
          "speaker": {
            "type": "string",
            "description": "Absent when diarize is false."
          }
        }
      },
      "Utterance": {
        "type": "object",
        "title": "Utterance",
        "properties": {
          "start": {
            "type": "number"
          },
          "end": {
            "type": "number"
          },
          "text": {
            "type": "string"
          },
          "speaker": {
            "type": [
              "string",
              "null"
            ],
            "description": "Labels are \"SPEAKER 1\", \"SPEAKER 2\", ... (space, 1-indexed). Null when diarize is false."
          },
          "confidence": {
            "type": [
              "number",
              "null"
            ]
          },
          "words": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Word"
            },
            "description": "Empty array when align is false."
          }
        }
      },
      "TranscriptionResult": {
        "type": "object",
        "title": "TranscriptionResult",
        "description": "Populated once status is \"completed\".",
        "properties": {
          "full_transcript": {
            "type": "string"
          },
          "language": {
            "type": "string",
            "description": "The language actually used, detected or supplied."
          },
          "duration_seconds": {
            "type": "number"
          },
          "speakers": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Null when diarize is false."
          },
          "utterances": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Utterance"
            }
          }
        }
      },
      "TranscriptionSummary": {
        "type": "object",
        "title": "TranscriptionSummary",
        "description": "A transcription without its transcript. This is what GET /v1/transcriptions returns per item.",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/TranscriptionStatus"
          },
          "audio_duration_seconds": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Rounded to the nearest whole second. Null until the file has been probed."
          },
          "file_size_bytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "language": {
            "type": [
              "string",
              "null"
            ]
          },
          "diarize": {
            "type": "boolean"
          },
          "speaker_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "align": {
            "type": "boolean",
            "description": "The effective value. Always true when diarize is true, even if the request passed align: false."
          },
          "cost_cents": {
            "type": [
              "number",
              "null"
            ],
            "description": "Fractional and exact, e.g. 0.061111 for an 11-second clip at $0.20/hour. The balance is debited against this precise value, not a rounded one."
          },
          "source_url": {
            "type": "string",
            "description": "Present only on URL-flow transcriptions."
          },
          "progress": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "error": {
            "$ref": "#/components/schemas/TranscriptionError"
          }
        }
      },
      "Transcription": {
        "title": "Transcription",
        "description": "A transcription including its result once complete. Some result fields depend on the flags the job was created with: result.speakers and utterances[].speaker are null when diarize is false, and utterances[].words is empty when align is false.",
        "allOf": [
          {
            "$ref": "#/components/schemas/TranscriptionSummary"
          },
          {
            "type": "object",
            "properties": {
              "result": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/TranscriptionResult"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Null until status is \"completed\"."
              }
            }
          }
        ]
      },
      "TranscriptionPage": {
        "type": "object",
        "title": "TranscriptionPage",
        "required": [
          "items",
          "next_cursor"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TranscriptionSummary"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass back as the cursor query parameter for the next page. Null on the last page."
          }
        }
      },
      "Balance": {
        "type": "object",
        "title": "Balance",
        "required": [
          "balance_cents",
          "reserved_cents",
          "available_cents",
          "price_per_hour_cents"
        ],
        "properties": {
          "balance_cents": {
            "type": "number",
            "description": "Total balance, in cents."
          },
          "reserved_cents": {
            "type": "number",
            "description": "Held against in-progress transcriptions, in cents."
          },
          "available_cents": {
            "type": "number",
            "description": "balance_cents minus reserved_cents. Must be above zero to start a job."
          },
          "price_per_hour_cents": {
            "type": "number",
            "description": "Current price per hour of audio, in cents."
          },
          "estimated_hours_available": {
            "type": "number",
            "description": "available_cents divided by price_per_hour_cents."
          },
          "deposit_url": {
            "type": "string",
            "format": "uri"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}