{
  "openapi": "3.0.0",
  "info": {
    "title": "Speech is Cheap Edge API",
    "description": "Transcribe a short audio or video file in one request. Upload the file and the transcript comes back in the same response, so there's nothing to poll and no webhook to receive. Authenticate each request with an API key, the same key the Jobs API accepts. A failed transcription still returns HTTP 200, so check `status`. Edge allows requests from any origin, so browsers can call it directly. Anyone who loads the page can read an API key placed in its code. For long or large files, use the Jobs API. Guide: https://docs.speechischeap.com/edge-v2/.",
    "version": "2.0.0",
    "contact": {
      "name": "Speech is Cheap Support",
      "email": "support@speechischeap.com",
      "url": "https://docs.speechischeap.com"
    }
  },
  "servers": [
    {
      "url": "https://edge.speechischeap.com/v2"
    }
  ],
  "tags": [
    {
      "name": "Edge",
      "description": "Synchronous transcription of a short file, with the transcript in the response to the upload."
    }
  ],
  "paths": {
    "/jobs/": {
      "post": {
        "summary": "Upload a File and Get Its Transcript",
        "description": "Uploads an audio or video file and returns its transcript in the response. The connection stays open until transcription finishes, so use it for short recordings. The response is 200 whether the transcription completed or failed, so check `status` before you read `output`. A failed transcription has `status` set to `FAILED` and the reason in `output.error`. Every other error is plain text, not JSON. Edge doesn't check a file's type, size, or duration before it transcribes. Keep the trailing slash. A request to `/jobs` without it returns 404. Each request rounds the file's duration up to whole minutes, and `can_parse_words` adds a charge for the same minutes. You're charged only when `status` is `COMPLETED`. See https://speechischeap.com/pricing/ for the rates. For every error message and how to recover, see https://docs.speechischeap.com/errors/#edge-api.",
        "tags": [ "Edge" ],
        "security": [
          {
            "BearerAuth": [ ]
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Send the fields as `multipart/form-data`. Enable a boolean with the exact value `true`. Any other value leaves it off. Edge ignores fields that aren't listed here, such as `webhook_url` and `output_format`, without an error.",
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [ "input_file" ],
                "properties": {
                  "input_file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The audio or video file to transcribe, sent as a file part rather than a text field. Edge accepts the same file types as the Jobs API, listed at https://docs.speechischeap.com/types/. It doesn't check the file's type, size, or duration, so a file it can't decode returns `FAILED`. Edge reads the whole file into memory, so use it for short recordings. Any file name works."
                  },
                  "can_parse_words": { "type": "boolean", "default": false, "description": "When enabled, includes a timecode for every word in the transcription, in each segment's `words`. It adds a per-minute charge." },
                  "is_private": { "type": "boolean", "default": false, "description": "When enabled, the response shows `redacted` instead of the file name in `output.request.input_file`, and Edge saves the job without its transcript." },
                  "language": {
                    "type": "string",
                    "default": "",
                    "example": "en",
                    "description": "A code from the supported languages at https://docs.speechischeap.com/languages/, such as `en`. Send it in lowercase. If not provided, Edge detects the language once for the whole file. An empty value counts as not provided. Edge doesn't check the code against that list, so use a listed code. Send it as a text field, because a file part returns 400."
                  },
                  "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. Edge removes them before it numbers the remaining segments. A value outside that range, an empty value, or one that isn't a number returns 400." }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Edge processed the request. Check `status` to see whether the transcription completed or failed. A failed transcription is still HTTP 200, with `status` set to `FAILED` and the reason in `output.error`. You aren't charged for it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EdgeJob"
                },
                "examples": {
                  "completed": {
                    "summary": "The transcription completed",
                    "description": "Created with `can_parse_words` enabled and no `language`.",
                    "value": {
                      "id": "00000000-1111-7222-b333-444444444444-sic",
                      "output": {
                        "duration": 2.944,
                        "request": {
                          "can_parse_words": true,
                          "input_file": "audio-file.mp3",
                          "is_private": false,
                          "language": null,
                          "minimum_confidence": 0.5
                        },
                        "segments": [
                          {
                            "confidence": 0.987,
                            "end": 2.24,
                            "id": 1,
                            "language": "en (99.95%)",
                            "processing_duration_in_s": 0.321,
                            "start": 0.32,
                            "text": "Hello, and welcome.",
                            "words": [
                              { "end": 0.8, "start": 0.32, "text": "Hello," },
                              { "end": 1.12, "start": 0.8, "text": "and" },
                              { "end": 2.24, "start": 1.12, "text": "welcome." }
                            ]
                          }
                        ]
                      },
                      "status": "COMPLETED"
                    }
                  },
                  "failed": {
                    "summary": "The transcription failed",
                    "description": "Edge answers a failed transcription with HTTP 200, for example when it can't decode the file.",
                    "value": {
                      "id": "00000000-1111-7222-b333-444444444444-sic",
                      "output": {
                        "error": "Failed to decode audio file"
                      },
                      "status": "FAILED"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing `input_file`, a body that isn't `multipart/form-data`, or an invalid parameter. The body is plain text.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "examples": {
                  "invalid_body": {
                    "summary": "The body isn't `multipart/form-data`",
                    "value": "Failed to parse request body. Please use multipart/form-data."
                  },
                  "missing_input_file": {
                    "summary": "No `input_file`, or it holds text instead of a file",
                    "value": "Missing or invalid \"input_file\". Please upload an audio file."
                  },
                  "invalid_language": {
                    "summary": "`language` was sent as a file",
                    "value": "Invalid \"language\" parameter. It must be an ISO 639-1 string."
                  },
                  "invalid_minimum_confidence": {
                    "summary": "`minimum_confidence` isn't a number from 0 to 1",
                    "value": "Invalid \"minimum_confidence\" parameter. It must be between 0.0 and 1.0."
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "500": {
            "description": "Edge couldn't determine the audio's duration, or hit an unexpected error. The body is plain text.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "examples": {
                  "unable_to_calculate_duration": {
                    "summary": "The transcription engine didn't report the audio's length. The message is spelled this way.",
                    "value": "Unable to calcuate audio duration."
                  },
                  "something_went_wrong": {
                    "summary": "An unexpected error occurred",
                    "value": "Something went wrong"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/jobs/health": {
      "get": {
        "summary": "Get API Health",
        "description": "Returns `Healthy` when the Edge API is reachable. It doesn't test transcription. This endpoint needs no API key, and the request isn't billed. Don't add a trailing slash. A request to `/jobs/health/` returns 404.",
        "tags": [ "Edge" ],
        "security": [ ],
        "responses": {
          "200": {
            "description": "Healthy",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "Healthy"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/auth": {
      "get": {
        "summary": "Check an API Key",
        "description": "Checks an API key before you upload a file. The response is 200 only when the key is valid and its billing is active. The request isn't billed. Don't add a trailing slash. A request to `/jobs/auth/` returns 404.",
        "tags": [ "Edge" ],
        "security": [
          {
            "BearerAuth": [ ]
          }
        ],
        "responses": {
          "200": {
            "description": "The API key is valid and its billing is active",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "Authenticated"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key, sent as `Authorization: Bearer YOUR_API_KEY`. Edge accepts the same keys as the Jobs API. It is required to upload a file and to check a key."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, invalid, expired, or revoked API key. The body is plain text.",
        "content": {
          "text/plain": {
            "schema": {
              "type": "string"
            },
            "examples": {
              "unauthorized_api_key": {
                "summary": "No API key, or one that isn't valid",
                "value": "Speech is Cheap: Unauthorized API Key. Sign up at https://speechischeap.com/ and use code E401 for a special discount while supplies last."
              },
              "invalid_authorization_header": {
                "summary": "The `Authorization` header isn't `Bearer` followed by a key",
                "value": "Speech is Cheap: Invalid authorization header. Format: \"Authorization: Bearer <key>\""
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The API key is known, but its billing isn't active. The body is plain text.",
        "content": {
          "text/plain": {
            "schema": {
              "type": "string"
            },
            "examples": {
              "missing_subscription": {
                "summary": "The key's billing isn't active",
                "value": "Speech is Cheap: Missing subscription. Manage your subscriptions at https://billing.speechischeap.com/."
              }
            }
          }
        }
      }
    },
    "schemas": {
      "EdgeJob": {
        "description": "The result of an upload. Check `status` first. `COMPLETED` has the transcript in `output`, and `FAILED` has only `output.error`. Edge answers both with HTTP 200.",
        "oneOf": [
          { "$ref": "#/components/schemas/EdgeCompletedJob" },
          { "$ref": "#/components/schemas/EdgeFailedJob" }
        ],
        "discriminator": {
          "propertyName": "status",
          "mapping": {
            "COMPLETED": "#/components/schemas/EdgeCompletedJob",
            "FAILED": "#/components/schemas/EdgeFailedJob"
          }
        }
      },
      "EdgeCompletedJob": {
        "type": "object",
        "required": [ "id", "status", "output" ],
        "properties": {
          "id": {
            "type": "string",
            "example": "00000000-1111-7222-b333-444444444444-sic",
            "description": "The ID of the job. It ends in `-sic`. Edge has no endpoint that reads a job later, so keep the response."
          },
          "status": {
            "type": "string",
            "enum": [ "COMPLETED" ],
            "description": "The transcription finished. The transcript is in `output`."
          },
          "output": {
            "$ref": "#/components/schemas/EdgeJobOutput"
          }
        }
      },
      "EdgeFailedJob": {
        "type": "object",
        "required": [ "id", "status", "output" ],
        "properties": {
          "id": {
            "type": "string",
            "example": "00000000-1111-7222-b333-444444444444-sic",
            "description": "The ID of the job."
          },
          "status": {
            "type": "string",
            "enum": [ "FAILED" ],
            "description": "The transcription failed. `output.error` says why. You aren't charged."
          },
          "output": {
            "type": "object",
            "required": [ "error" ],
            "properties": {
              "error": {
                "type": "string",
                "example": "Failed to decode audio file",
                "description": "The message from the transcription engine, so its wording can vary. A failure without a message reads `Transcription failed`."
              }
            }
          }
        }
      },
      "EdgeJobOutput": {
        "type": "object",
        "required": [ "duration", "request", "segments" ],
        "properties": {
          "duration": {
            "type": "number",
            "example": 2.944,
            "description": "The length of the audio in seconds."
          },
          "request": {
            "$ref": "#/components/schemas/EdgeJobRequest"
          },
          "segments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EdgeSegment"
            },
            "description": "The transcribed segments, in order of their start time. The array is empty when the transcription has no segments or when `minimum_confidence` removes them all."
          }
        }
      },
      "EdgeJobRequest": {
        "type": "object",
        "description": "The options the request ran with, with the defaults filled in. It leaves out the fields Edge ignores, such as `webhook_url`.",
        "required": [ "can_parse_words", "input_file", "is_private", "language", "minimum_confidence" ],
        "properties": {
          "can_parse_words": { "type": "boolean" },
          "input_file": { "type": "string", "example": "audio-file.mp3", "description": "The uploaded file's name, or `redacted` when `is_private` is `true`." },
          "is_private": { "type": "boolean" },
          "language": { "type": "string", "nullable": true, "example": "en", "description": "The `language` you sent. `null` when you sent none or an empty value." },
          "minimum_confidence": { "type": "number", "format": "float", "example": 0.5 }
        }
      },
      "EdgeSegment": {
        "type": "object",
        "required": [ "confidence", "end", "id", "language", "processing_duration_in_s", "start", "text", "words" ],
        "properties": {
          "id": { "type": "integer", "example": 1, "description": "The segment's position in the transcript, counting up from 1 with no gaps. Edge removes segments below `minimum_confidence` before it numbers the rest." },
          "start": { "type": "number", "format": "float", "example": 0.32, "description": "The start of the segment, in seconds." },
          "end": { "type": "number", "format": "float", "example": 2.24, "description": "The end of the segment, in seconds." },
          "text": { "type": "string", "example": "Hello, and welcome.", "description": "The transcribed text." },
          "confidence": { "type": "number", "format": "float", "example": 0.987, "description": "The confidence from 0 to 1." },
          "language": { "type": "string", "example": "en (99.95%)", "description": "The detected language and its probability, such as `en (99.95%)`. It's the same in every segment, because Edge detects the language once for the whole file." },
          "processing_duration_in_s": { "type": "number", "format": "float", "example": 0.321, "description": "The total transcription time in seconds divided by the number of segments before low-confidence segments are removed. It's the same in every segment." },
          "words": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EdgeWord"
            },
            "description": "With `can_parse_words`, the words of the segment. Otherwise an empty array."
          }
        }
      },
      "EdgeWord": {
        "type": "object",
        "required": [ "end", "start", "text" ],
        "properties": {
          "start": { "type": "number", "format": "float", "example": 0.32, "description": "The start of the word, in seconds." },
          "end": { "type": "number", "format": "float", "example": 0.8, "description": "The end of the word, in seconds." },
          "text": { "type": "string", "example": "Hello,", "description": "The word, with its punctuation attached." }
        }
      }
    }
  }
}
