{
  "openapi": "3.0.0",
  "info": {
    "title": "Speech is Cheap API",
    "description": "Create transcription jobs, check their status, and read their results. Authenticate each request with an API key, or pay for each job with USDC on Base using x402 and no API key.",
    "version": "2.0.0",
    "contact": {
      "name": "Speech is Cheap Support",
      "email": "support@speechischeap.com",
      "url": "https://docs.speechischeap.com"
    }
  },
  "servers": [
    {
      "url": "https://api.speechischeap.com/v2"
    }
  ],
  "tags": [
    {
      "name": "Jobs",
      "description": "Endpoints for managing transcription jobs with an API key."
    },
    {
      "name": "x402",
      "description": "Asynchronous transcription jobs paid per job with native USDC on Base."
    }
  ],
  "paths": {
    "/jobs/": {
      "post": {
        "summary": "Create a New Transcription Job",
        "description": "Creates a transcription job from the URL of an audio or video file. The response is 202 with the new job, or 200 with a Server-Sent Events stream when `can_stream_output` is true.",
        "tags": [ "Jobs" ],
        "security": [
          {
            "BearerAuth": [ ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [ "input_url" ],
                "properties": {
                  "input_url": {
                    "type": "string",
                    "description": "The URL of the audio or video file to transcribe. It must be publicly reachable, which the API checks before it creates the job. The API checks the audio length only after it accepts the job, so a file that is too short or too long fails the job instead of the request. For the limits and other rules, see https://docs.speechischeap.com/input-requirements/. For the accepted containers and codecs, see https://docs.speechischeap.com/types/.",
                    "example": "https://example.com/audio-file.mp3"
                  },
                  "can_label_audio": { "type": "boolean", "default": false, "description": "When enabled, includes an audio classification label in the transcription." },
                  "can_parse_speakers": { "type": "boolean", "default": false, "description": "When enabled, adds `speaker_id` to each segment based on the speaker's voice." },
                  "can_parse_words": { "type": "boolean", "default": false, "description": "When enabled, includes a timecode for every word in the transcription." },
                  "can_stream_output": { "type": "boolean", "default": false, "description": "When enabled, streams progress and chronological transcription segments using SSE, and the response is 200 instead of 202." },
                  "is_private": { "type": "boolean", "default": false, "description": "When enabled, redacts the original `input_url` and deletes the segments 12 hours after the job completes." },
                  "is_split_channel": { "type": "boolean", "default": false, "description": "When enabled, parses speakers from multi-channel audio by assigning one speaker per channel." },
                  "language": {
                    "type": "string",
                    "description": "A code from the supported languages at https://docs.speechischeap.com/languages/, such as `en`. Send it in lowercase. If not provided, the language is detected for each speech segment. An unsupported code fails the job after the API accepts it.",
                    "enum": [ "af", "sq", "am", "ar", "hy", "as", "az", "ba", "eu", "be", "bn", "bs", "br", "bg", "yue", "ca", "zh", "hr", "cs", "da", "nl", "en", "et", "fo", "fi", "fr", "gl", "ka", "de", "el", "gu", "ht", "ha", "haw", "he", "hi", "hu", "is", "id", "it", "ja", "jw", "kn", "kk", "km", "ko", "lo", "la", "lv", "ln", "lt", "lb", "mk", "mg", "ms", "ml", "mt", "mi", "mr", "mn", "my", "ne", "no", "nn", "oc", "ps", "fa", "pl", "pt", "pa", "ro", "ru", "sa", "sr", "sn", "sd", "si", "sk", "sl", "so", "es", "su", "sw", "sv", "tl", "tg", "ta", "tt", "te", "th", "bo", "tr", "tk", "uk", "ur", "uz", "vi", "cy", "yi", "yo" ]
                  },
                  "minimum_confidence": { "type": "number", "format": "float", "minimum": 0, "maximum": 1, "default": 0.5, "description": "Filter out segments that fall below this confidence threshold, from 0 to 1. A value outside that range fails the job after the API accepts it." },
                  "output_format": { "type": "string", "enum": [ "srt", "vtt", "webvtt" ], "description": "Format of the transcript in a streamed response and in the webhook body. `webvtt` is an alias for `vtt`. The value is case-insensitive. Omit it for JSON. `GET /jobs/{job_id}` has its own `output_format` query parameter." },
                  "segment_duration": { "type": "number", "default": 30, "description": "The maximum duration of a transcription segment in seconds. The API adjusts a value outside 6 to 30 to that range instead of rejecting it." },
                  "user_agent": { "type": "string", "description": "Custom user agent header to fetch the audio file." },
                  "webhook_url": { "type": "string", "description": "The URL that receives the job's result in a single POST request." }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The job was created and its progress streams as Server-Sent Events. Returned instead of 202 when `can_stream_output` is true.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "202": {
            "description": "The job was successfully created and is being processed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "400": { "description": "Missing or invalid parameters, or the `input_url` isn't reachable" },
          "401": { "description": "Missing or invalid API key" },
          "402": { "description": "The API key's billing isn't active" },
          "500": { "description": "Server-side error occurred" }
        }
      }
    },
    "/x402/jobs/": {
      "post": {
        "summary": "Pay for one asynchronous transcription",
        "description": "POST URL or upload metadata with a finite numeric input_duration. Without PAYMENT-SIGNATURE, the response is the standard 402 PAYMENT-REQUIRED quote, valid for 300 seconds, and the service doesn't fetch any media. Send the identical JSON again with the signed proof in PAYMENT-SIGNATURE, preserving the quote's resource purchase_id. The authorization's validBefore must be 6 to 360 seconds after the service's clock. After a timeout, or while a payment is pending, replay the same request and proof: a replay never charges again. Request bodies are limited to 16 KB. An upload purchase returns a one-use continuation for https://upload.speechischeap.com/v2/x402/jobs/. Guide: https://docs.speechischeap.com/x402-v2/.",
        "tags": [ "x402" ],
        "security": [ ],
        "parameters": [{
          "name": "PAYMENT-SIGNATURE", "in": "header", "required": false,
          "description": "Standard base64-encoded x402 v2 payment payload. Persist before submitting; replay the same proof and request after response loss.",
          "schema": { "type": "string" }
        }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/X402Request" } } }
        },
        "responses": {
          "202": {
            "description": "The service has the proof. The first paid response usually reports payment_status stripe_pending without a job id: replay the same request and proof until the response has an id (URL job) or READY (upload). A URL purchase whose media can't be fetched is FAILED without a job id, and the service refunds it in full. admission_status dispatch_pending means the job exists but its start isn't confirmed yet, so don't pay or submit again. Replay a pending upload purchase at least once a minute: its 300-second upload window starts when payment is confirmed, even in the background.",
            "headers": { "PAYMENT-RESPONSE": { "description": "Standard settlement response once on-chain settlement is confirmed, including while payment confirmation is pending.", "schema": { "type": "string" } } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/X402Admission" } } }
          },
          "402": {
            "description": "Without a payment, sign the quoted x402 v2 exact Base USDC requirement with the buyer wallet; the server-issued resource.url includes purchase_id. After a payment, a fresh PAYMENT-REQUIRED with error means the proof was rejected and no payment was taken; the quote stays payable until it expires. A PAYMENT-RESPONSE with success false means the facilitator declined the first settlement before broadcasting. The decline is final and no payment was taken; the service never submits that proof again. Discard the proof, then request a new quote and pay it. A new signature for the same quote returns 409. If Base later shows that transfer anyway, the service treats the purchase as paid.",
            "headers": {
              "PAYMENT-REQUIRED": { "description": "Base64-encoded standard x402 declaration for a new quote or a rejected proof.", "schema": { "type": "string" } },
              "PAYMENT-RESPONSE": { "description": "Base64-encoded settlement response with success false and errorReason after a declined first settlement. The decline is final; replays of that proof return it until validBefore passes, then 410 unless Base shows the transfer.", "schema": { "type": "string" } }
            },
            "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/PaymentRequired" }, { "$ref": "#/components/schemas/X402Admission" } ] } } }
          },
          "400": { "description": "Invalid JSON, input duration, option, field length, streaming request, public max_input_duration, or malformed payment proof, as text/plain." },
          "404": { "description": "Unknown quote, or an unpaid quote removed after expiry. No payment was taken; request a new quote." },
          "409": { "description": "The request or proof conflicts with the original purchase. Send the original request with its original proof." },
          "410": { "description": "Quote expired, or the payment authorization ended without settlement. No payment was taken; request a new quote." },
          "413": { "description": "Request body larger than 16 KB" },
          "429": {
            "description": "Too many unpaid quote requests from one IP address, about 20 per minute. Requests with PAYMENT-SIGNATURE are never limited. The body is JSON with a message field.",
            "headers": { "Retry-After": { "description": "Seconds to wait before requesting another quote.", "schema": { "type": "integer" } } }
          },
          "503": { "description": "The text x402 payments are not enabled means x402 is switched off: new quotes and first payments are refused until it is switched on again, and a purchase that is already paid continues. The text x402 payment configuration is incomplete is a service-side problem. Neither is transient. A JSON message that says payment admission is temporarily unavailable is transient: retry the original request and any already-submitted proof, and don't purchase again." }
        },
        "callbacks": {
          "outcome": {
            "{$request.body#/webhook_url}": {
              "post": {
                "description": "Sent when the job completes, fails, or is canceled, or when the purchase ends without a job. The body is the job result as JSON, SRT, or VTT according to output_format, or the failure details as JSON. A purchase that ended before a job existed has purchase_id in place of id. Any 2xx response within 10 seconds acknowledges delivery, and redirects are not followed. Failed deliveries are retried every five minutes at first, backing off to one hour, for at most 12 attempts within 24 hours. A private job's outcome stops delivering 12 hours after completion. Deduplicate by X-SiC-Event-ID.",
                "parameters": [
                  { "name": "X-SiC-Event-ID", "in": "header", "required": true, "schema": { "type": "string" } },
                  { "name": "X-SiC-Event-Type", "in": "header", "required": true, "schema": { "type": "string", "enum": [ "x402.outcome" ] } }
                ],
                "requestBody": {
                  "required": true,
                  "content": {
                    "application/json": { "schema": { "$ref": "#/components/schemas/X402Outcome" } },
                    "application/x-subrip": { "schema": { "type": "string" } },
                    "text/vtt": { "schema": { "type": "string" } }
                  }
                },
                "responses": { "200": { "description": "Acknowledged. Any 2xx response acknowledges delivery." } }
              }
            }
          },
          "paymentUpdate": {
            "{$request.body#/webhook_url}": {
              "post": {
                "description": "Separate refund-status callback. Refund updates are sent within about five minutes and use the same retry schedule as outcomes: at most 12 attempts within 24 hours, or 12 hours for a private job's first pending refund update. Deduplicate by event ID, and use occurred_at to detect stale out-of-order refund status updates.",
                "parameters": [
                  { "name": "X-SiC-Event-ID", "in": "header", "required": true, "schema": { "type": "string" } },
                  { "name": "X-SiC-Event-Type", "in": "header", "required": true, "schema": { "type": "string", "enum": [ "x402.payment" ] } }
                ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/X402RefundEvent" } } } },
                "responses": { "200": { "description": "Acknowledged. Any 2xx response acknowledges delivery." } }
              }
            }
          }
        }
      }
    },
    "/x402/jobs/{job_id}": {
      "delete": {
        "summary": "Cancel a job with the original paying wallet",
        "description": "Auth-only SIWX, not another payment. Request a challenge, sign its exact job/action scope with the original payer, and retry with SIGN-IN-WITH-X. One-use nonce expires after 300 seconds. Confirmation precedes the full gross refund; public job IDs/transaction hashes alone are not authority. An API-key DELETE /jobs/{job_id} returns 403 for an x402 job.",
        "tags": [ "x402" ],
        "security": [ ],
        "parameters": [
          { "name": "job_id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "SIGN-IN-WITH-X", "in": "header", "required": false, "description": "Standard signed SIWX proof for this cancellation challenge. No persistent cancellation token.", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Cancellation confirmed; the full refund is owed, not necessarily received.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/X402Cancellation" } } } },
          "202": { "description": "Cancellation requested but not yet confirmed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/X402Cancellation" } } } },
          "402": {
            "description": "Auth-only challenge with accepts: [] and sign-in-with-x extension. No money is charged.",
            "headers": { "PAYMENT-REQUIRED": { "schema": { "type": "string" }, "description": "Base64-encoded auth-only SIWX declaration." } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaymentRequired" } } }
          },
          "401": { "description": "Invalid, expired, or consumed cancellation challenge/proof. Each verified challenge is single-use." },
          "403": { "description": "Signer is not the original verified payer" },
          "404": { "description": "Paid job not found" },
          "409": { "description": "The job already completed, failed, or was canceled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/X402Cancellation" } } } },
          "503": { "description": "Cancellation recovery temporarily unavailable" }
        }
      }
    },
    "/jobs/health": {
      "get": {
        "summary": "Get API Health",
        "description": "This endpoint allows you to check the health of the Speech is Cheap Jobs API.",
        "tags": [ "Jobs" ],
        "responses": {
          "200": {
            "description": "Healthy",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "Healthy"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/health/inference": {
      "get": {
        "summary": "Get Transcription Health",
        "description": "Reports whether transcription is running normally. This endpoint needs no API key. The response is always 200, including when transcription is slow or unavailable, so read `status` instead of the HTTP status. The API doesn't cache the response.",
        "tags": [ "Jobs" ],
        "responses": {
          "200": {
            "description": "The result of the check",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [ "status", "message", "checkedAt" ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [ "operational", "degraded", "down" ],
                      "description": "`operational` means transcription is running normally. `degraded` means jobs are queuing longer than usual or failing more often than usual, so they can take longer to finish. `down` means the check couldn't reach the transcription service.",
                      "example": "operational"
                    },
                    "message": { "type": "string", "description": "A short reason. It is `OK` when the status is `operational`.", "example": "OK" },
                    "checkedAt": { "type": "string", "format": "date-time", "description": "The time of the check, in UTC.", "example": "2026-10-25T21:57:32.093Z" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/jobs/auth": {
      "get": {
        "summary": "Check an API Key",
        "description": "Checks an API key before you start work. The response is 200 only when the key is valid and its billing is active.",
        "tags": [ "Jobs" ],
        "security": [
          {
            "BearerAuth": [ ]
          }
        ],
        "responses": {
          "200": {
            "description": "The API key is valid",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "Authenticated"
                }
              }
            }
          },
          "401": { "description": "Missing or invalid API key" },
          "402": { "description": "The API key's billing isn't active" }
        }
      }
    },
    "/jobs/{job_id}": {
      "get": {
        "summary": "Get Job Status and Output",
        "description": "Returns a job's status and, once it completes, its result. This endpoint needs no API key. A failed or canceled x402 job returns its error in `output.error`. Don't add a trailing slash: a request to `/jobs/{job_id}/` returns 404.",
        "tags": [ "Jobs" ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The ID of the job to retrieve."
          },
          {
            "name": "output_format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [ "srt", "vtt", "webvtt" ]
            },
            "description": "Returns a completed job's transcript in this format instead of JSON. `webvtt` is an alias for `vtt`. The value is case-insensitive. Jobs that haven't completed always return JSON."
          }
        ],
        "responses": {
          "200": {
            "description": "Request successful",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              },
              "application/x-subrip": {
                "schema": {
                  "type": "string"
                }
              },
              "text/vtt": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": { "description": "Invalid `output_format` for a completed job" },
          "404": { "description": "No job has this ID, or the path ends with a slash" },
          "500": { "description": "Server-side error occurred" }
        }
      },
      "delete": {
        "summary": "Cancel a Pending Job",
        "description": "Cancels a pending transcription job that belongs to the API key's account. An x402 job can't be canceled with an API key. Don't add a trailing slash: a request to `/jobs/{job_id}/` returns 404.",
        "tags": [ "Jobs" ],
        "security": [
          {
            "BearerAuth": [ ]
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The ID of the job to cancel."
          }
        ],
        "responses": {
          "200": {
            "description": "Job was successfully canceled",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "OK"
                }
              }
            }
          },
          "401": { "description": "Missing or invalid API key" },
          "402": { "description": "The API key's billing isn't active" },
          "403": { "description": "The job was paid with x402. Cancel it with the paying wallet at DELETE /x402/jobs/{job_id}." },
          "404": { "description": "No job has this ID, the job belongs to another account, the job already completed, failed, or was canceled, or the path ends with a slash" },
          "500": { "description": "Server-side error occurred" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key, sent as `Authorization: Bearer YOUR_API_KEY`. It is required to create and cancel jobs and to check a key."
      }
    },
    "schemas": {
      "X402Request": {
        "type": "object",
        "additionalProperties": false,
        "required": [ "input_duration" ],
        "oneOf": [
          { "required": [ "input_url" ], "properties": { "input_type": { "enum": [ "url" ] } } },
          { "required": [ "input_type" ], "properties": { "input_type": { "enum": [ "upload" ] } }, "not": { "required": [ "input_url" ] } }
        ],
        "description": "One URL or intended upload. For url mode input_url is required; for upload mode omit it. Processing options and rate snapshot freeze with payment. Streaming and public max_input_duration are unsupported.",
        "properties": {
          "input_type": { "type": "string", "enum": [ "url", "upload" ], "default": "url" },
          "input_url": { "type": "string", "format": "uri", "maxLength": 2048, "description": "HTTP(S) media URL without credentials; required for URL mode and absent for upload mode." },
          "input_duration": { "type": "number", "minimum": 6, "maximum": 86400, "example": 200, "description": "Buyer-supplied finite duration claim in seconds. No unpaid media probing." },
          "webhook_url": { "type": "string", "maxLength": 2048, "description": "Optional HTTP(S) callback without credentials. Receives the outcome and refund updates, with retries." },
          "can_label_audio": { "type": "boolean", "default": false },
          "can_parse_speakers": { "type": "boolean", "default": false },
          "can_parse_words": { "type": "boolean", "default": false },
          "can_stream_output": { "type": "boolean", "enum": [ false ], "default": false },
          "is_private": { "type": "boolean", "default": false },
          "is_split_channel": { "type": "boolean", "default": false },
          "language": { "type": "string", "maxLength": 16, "description": "A code from the supported languages at https://docs.speechischeap.com/languages/, such as `en`. Send it in lowercase. If omitted, the language is detected for each speech segment. An unsupported code fails the job after payment, and the service refunds it in full.", "default": "" },
          "minimum_confidence": { "type": "number", "minimum": 0, "maximum": 1, "description": "Filter out segments that fall below this confidence threshold, from 0 to 1. Send a JSON number. A value outside that range returns 400 before payment.", "default": 0.5 },
          "segment_duration": { "type": "integer", "minimum": 1, "description": "The maximum duration of a transcription segment in seconds, as a positive integer. The service adjusts a value outside 6 to 30 to that range instead of rejecting it.", "default": 30 },
          "user_agent": { "type": "string", "maxLength": 256 },
          "output_format": { "type": "string", "enum": [ "", "srt", "vtt", "webvtt" ], "description": "Format of the transcript in the webhook body. `webvtt` is an alias for `vtt`. The value is case-insensitive.", "default": "" }
        }
      },
      "PaymentRequired": {
        "type": "object", "required": [ "x402Version", "resource", "accepts" ],
        "properties": {
          "x402Version": { "type": "integer", "enum": [ 2 ] },
          "error": { "type": "string" },
          "resource": { "type": "object", "required": [ "url" ], "properties": {
            "url": { "type": "string", "format": "uri" }, "description": { "type": "string" }, "mimeType": { "type": "string" }
          } },
          "accepts": { "type": "array", "description": "Exactly one native Base USDC offer for purchasing; empty for auth-only cancellation.", "items": {
            "type": "object", "required": [ "scheme", "network", "asset", "amount", "payTo", "maxTimeoutSeconds" ],
            "properties": {
              "scheme": { "type": "string", "enum": [ "exact" ] },
              "network": { "type": "string", "enum": [ "eip155:8453" ] },
              "asset": { "type": "string", "enum": [ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" ] },
              "amount": { "type": "string", "pattern": "^[0-9]+$", "description": "USDC atomic amount: paid_cents * 10000.", "example": "10000" },
              "payTo": { "type": "string", "description": "Receiving address for this quote. Always pay the address from the live response." },
              "maxTimeoutSeconds": { "type": "integer", "example": 300 },
              "extra": { "type": "object", "additionalProperties": true }
            }
          } },
          "extensions": { "type": "object", "additionalProperties": true, "description": "Standard sign-in-with-x on auth-only cancellation." }
        }
      },
      "X402Admission": {
        "type": "object", "required": [ "purchase_id", "status" ],
        "description": "Fields depend on admission state. READY requires upload_url/upload_token/expiry/allowance/price; payment recovery has no job ID; accepted jobs have id/output. A purchase that ended before a job existed, including unreachable input media, reports EXPIRED or FAILED without id or upload token.",
        "properties": {
          "purchase_id": { "type": "string", "format": "uuid" },
          "id": { "type": "string" },
          "status": { "type": "string", "enum": [ "PENDING", "READY", "UNCLAIMED", "CLAIMED", "COMPLETED", "FAILED", "CANCELED", "EXPIRED" ] },
          "output": { "type": "object" },
          "payment_status": { "type": "string", "enum": [ "quoted", "settling", "settlement_unknown", "stripe_pending" ] },
          "admission_status": { "type": "string", "enum": [ "dispatch_pending" ] },
          "upload_url": { "type": "string", "format": "uri" },
          "upload_token": { "type": "string", "description": "One-use upload-start credential. Keep private; send only in X-SiC-Upload-Token at upload_url." },
          "admission_expires_at": { "type": "string", "format": "date-time", "description": "300 seconds from readiness, when payment is confirmed, to start; accepted upload can continue one hour. Retries never extend either deadline." },
          "max_input_duration": { "type": "number", "minimum": 6, "maximum": 86400, "description": "Server-derived frozen purchased allowance. Never a request parameter." },
          "paid_cents": { "type": "integer", "minimum": 1 }
        }
      },
      "X402Cancellation": {
        "type": "object", "required": [ "id", "status", "cancellation_status" ],
        "properties": {
          "id": { "type": "string" },
          "status": { "type": "string", "enum": [ "PENDING", "CANCELED", "COMPLETED", "FAILED" ] },
          "output": { "type": "object" },
          "cancellation_status": { "type": "string", "enum": [ "requested", "confirmed", "terminal" ] }
        }
      },
      "X402Outcome": {
        "type": "object", "required": [ "status", "output" ],
        "description": "A job's final result, or the failure details of a job or purchase. A purchase that ended before a job existed has purchase_id and no id.",
        "properties": {
          "id": { "type": "string" },
          "purchase_id": { "type": "string", "format": "uuid" },
          "status": { "type": "string", "enum": [ "COMPLETED", "FAILED", "CANCELED", "EXPIRED" ] },
          "output": { "$ref": "#/components/schemas/JobOutput" }
        }
      },
      "X402RefundEvent": {
        "type": "object", "required": [ "type", "event_id", "occurred_at", "purchase_id", "payment_status", "paid_cents", "retained_cents", "refund" ],
        "properties": {
          "type": { "type": "string", "enum": [ "x402.payment" ] },
          "event_id": { "type": "string", "description": "Opaque ID for one durable status transition. Deduplicate retries; a later transition has a new ID even if its status occurred previously." },
          "occurred_at": { "type": "string", "format": "date-time", "description": "Immutable event time; status retries may arrive out of order." },
          "purchase_id": { "type": "string", "format": "uuid" },
          "job_id": { "type": "string", "description": "Omitted when the purchase ended before a job was created." },
          "payment_status": { "type": "string", "enum": [ "paid" ] },
          "paid_cents": { "type": "integer", "minimum": 1 },
          "retained_cents": { "type": "integer", "minimum": 0 },
          "refund": { "type": "object", "required": [ "id", "amount_cents", "status" ], "properties": {
            "id": { "type": "string" }, "amount_cents": { "type": "integer", "minimum": 1 },
            "status": { "type": "string", "enum": [ "pending", "succeeded", "failed" ] }
          } }
        }
      },
      "DurationError": {
        "type": "object", "required": [ "code", "min_input_duration", "max_input_duration", "actual_input_duration", "actual_duration_is_lower_bound" ],
        "properties": {
          "code": { "type": "string", "enum": [ "INPUT_DURATION_MISMATCH" ] },
          "min_input_duration": { "type": "number", "example": 6 },
          "max_input_duration": { "type": "number", "example": 300 },
          "actual_input_duration": { "type": "number", "example": 301 },
          "actual_duration_is_lower_bound": { "type": "boolean", "description": "True when bounded decoding stopped early. Not a complete successful duration." }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "00000000-1111-7222-b333-444444444444-sic"
          },
          "status": {
            "type": "string",
            "enum": [ "PENDING", "COMPLETED", "FAILED", "CANCELED" ],
            "example": "PENDING"
          },
          "output": {
            "$ref": "#/components/schemas/JobOutput"
          }
        }
      },
      "JobOutput": {
        "type": "object",
        "properties": {
          "duration": {
            "type": "number",
            "example": 24.512,
            "description": "The length of the audio in seconds."
          },
          "request": {
            "type": "object",
            "description": "The options the job ran with. It repeats the request, but not `webhook_url`, `output_format`, `is_private`, or `user_agent`. For a private job, `input_url` is `https://speechischeap.com/redacted`.",
            "properties": {
              "can_label_audio": { "type": "boolean" },
              "can_parse_speakers": { "type": "boolean" },
              "can_parse_words": { "type": "boolean" },
              "can_stream_output": { "type": "boolean" },
              "input_url": { "type": "string", "example": "https://example.com/audio-file.mp3" },
              "is_split_channel": { "type": "boolean" },
              "language": { "type": "string", "nullable": true, "description": "`null` when the request has no `language`.", "example": "en" },
              "minimum_confidence": { "type": "number", "format": "float", "example": 0.5 },
              "segment_duration": { "type": "number", "example": 30 }
            }
          },
          "segments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Segment"
            }
          },
          "error": {
            "type": "string",
            "example": "Some error message"
          },
          "duration_error": {
            "$ref": "#/components/schemas/DurationError"
          }
        }
      },
      "Segment": {
        "type": "object",
        "properties": {
          "id": { "type": "integer", "example": 1 },
          "start": { "type": "number", "format": "float", "example": 1.234 },
          "end": { "type": "number", "format": "float", "example": 12.345 },
          "text": { "type": "string", "example": "This is an example of some transcribed text output." },
          "confidence": { "type": "number", "format": "float", "example": 0.987 },
          "language": { "type": "string", "nullable": true, "example": "en (99.95%)", "description": "The detected language and its probability, such as `en (99.95%)`. When you set the `language` parameter, the probability is always `100.00%`. `null` for non-speech segments." },
          "processing_duration_in_s": { "type": "number", "format": "float", "example": 0.321 },
          "speaker_id": { "type": "string", "example": "A", "description": "The speaker, such as `A` or `B`. Only segments with speech have it, and only with `can_parse_speakers` or `is_split_channel`." },
          "label": { "type": "string", "example": "music", "description": "With `can_label_audio`, `speech`, `silence`, or the name of a sound, such as `music`. Without it, segments have no label." },
          "words": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Word"
            },
            "description": "With `can_parse_words`, the words of a segment with speech. Otherwise an empty array."
          }
        }
      },
      "Word": {
        "type": "object",
        "properties": {
          "start": { "type": "number", "format": "float", "example": 2.345 },
          "end": { "type": "number", "format": "float", "example": 2.567 },
          "text": { "type": "string", "example": "hi" }
        }
      }
    }
  }
}
