{
  "openapi": "3.1.0",
  "info": {
    "title": "Kalemio Transcription API",
    "version": "1.0.0",
    "description": "Asynchronous Arabic transcription with word timestamps, speaker labels, JSON results, and SRT subtitles. Uploads are authenticated with an API key. Prepaid accounts are billed by recorded duration rounded up to the next second; usage-based accounts are billed at US$0.09 per processed audio minute, rounded up to one minute.",
    "termsOfService": "https://kalemio.app/terms/",
    "contact": {
      "email": "support@kalemio.app",
      "url": "https://kalemio.app/support/"
    }
  },
  "servers": [
    {
      "url": "https://kalemio.app",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Human-readable API documentation",
    "url": "https://kalemio.app/developers/docs/"
  },
  "tags": [
    {
      "name": "Account",
      "description": "Inspect the authenticated account's API balance."
    },
    {
      "name": "Transcriptions",
      "description": "Submit audio or video and retrieve its transcription."
    }
  ],
  "security": [
    {
      "apiKeyAuth": []
    }
  ],
  "paths": {
    "/v1/account": {
      "get": {
        "operationId": "getAccount",
        "summary": "Get account balance",
        "description": "Returns the account mode and available transcription credit. Values are expressed in seconds. Requires the `usage:read` key permission.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Account balance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/transcriptions": {
      "post": {
        "operationId": "createTranscription",
        "summary": "Submit audio or video",
        "description": "Queues one Arabic audio or video recording for transcription. The request is asynchronous: save the returned job ID and poll it until it is completed or failed. The same Idempotency-Key may be retried for the same file and speaker settings. Requires the `transcriptions:write` key permission.",
        "tags": [
          "Transcriptions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/TranscriptionUpload"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Transcription accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptionJob"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "402": {
            "description": "Not enough available credit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Add minutes before submitting this recording."
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key was already used for different upload data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Upload exceeds the 20 MiB limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "The request is not multipart form data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/transcriptions/{id}": {
      "get": {
        "operationId": "getTranscription",
        "summary": "Get transcription status",
        "description": "Returns the current state and billing details for a transcription created by the authenticated account. Requires the `transcriptions:read` key permission.",
        "tags": [
          "Transcriptions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TranscriptionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Transcription status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptionJob"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/transcriptions/{id}/result": {
      "get": {
        "operationId": "getTranscriptionResult",
        "summary": "Get a JSON transcription result",
        "description": "Returns the completed transcription with full text, word-level timestamps, and speaker IDs. Results expire 24 hours after submission. Requires the `transcriptions:read` key permission.",
        "tags": [
          "Transcriptions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TranscriptionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Completed transcription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptionResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The transcription is not completed yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The result is not ready."
                }
              }
            }
          },
          "410": {
            "description": "The result has expired or is no longer available",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/transcriptions/{id}/srt": {
      "get": {
        "operationId": "getTranscriptionSrt",
        "summary": "Download SRT subtitles",
        "description": "Returns the completed transcription as an SRT subtitle file. The result expires 24 hours after submission. Requires the `transcriptions:read` key permission.",
        "tags": [
          "Transcriptions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TranscriptionId"
          }
        ],
        "responses": {
          "200": {
            "description": "SRT subtitle file",
            "headers": {
              "Content-Disposition": {
                "description": "Suggested filename for the downloaded subtitle file",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/x-subrip": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The transcription is not completed yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "The result has expired or is no longer available",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "kal_live_<64 hex characters>",
        "description": "Create a scoped key in your Kalemio account, then send it as `Authorization: Bearer kal_live_...`. Keep keys on your server. Rotate lost or exposed keys; the prior key is revoked immediately."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "A stable identifier for this upload attempt. Use 8–128 letters, digits, dots, colons, underscores, or hyphens. Reuse it only for the same file and speaker settings.",
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_.:-]+$"
        },
        "example": "recording-2026-001"
      },
      "TranscriptionId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The transcription job ID returned by the upload request.",
        "schema": {
          "type": "string",
          "pattern": "^tr_[a-f0-9]{32}$"
        },
        "example": "tr_0123456789abcdef0123456789abcdef"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is invalid",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The API key is missing, invalid, or revoked",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "A valid API key is required."
            }
          }
        }
      },
      "Forbidden": {
        "description": "The API key does not have the permission required by this operation",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "The transcription does not exist for this account",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests. Retry after a short delay.",
        "headers": {
          "Retry-After": {
            "description": "Number of seconds to wait before retrying, when supplied",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "The API is temporarily unavailable or transcription has not launched",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "The media or form fields are invalid",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Language must be ar; speakers must be auto or 1–20."
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message."
          }
        },
        "additionalProperties": false
      },
      "Account": {
        "type": "object",
        "required": [
          "mode",
          "available_seconds",
          "monthly_seconds",
          "topup_seconds",
          "billing_method",
          "metered_price_usd_per_minute",
          "transcription_enabled"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "available_seconds": {
            "type": "integer",
            "description": "Total credit currently available, in seconds."
          },
          "monthly_seconds": {
            "type": "integer",
            "description": "Available seconds from the current monthly allowance."
          },
          "topup_seconds": {
            "type": "integer",
            "description": "Available seconds from non-expiring top-ups."
          },
          "billing_method": {
            "type": "string",
            "enum": [
              "credits",
              "metered_api_v1"
            ],
            "description": "The billing path used by new transcription jobs."
          },
          "metered_price_usd_per_minute": {
            "anyOf": [
              {
                "type": "number",
                "const": 0.09
              },
              {
                "type": "null"
              }
            ],
            "description": "The active usage-based price, when billing_method is metered_api_v1."
          },
          "transcription_enabled": {
            "type": "boolean",
            "description": "Whether the transcription backend is currently enabled."
          }
        },
        "additionalProperties": false
      },
      "TranscriptionUpload": {
        "type": "object",
        "required": [
          "file"
        ],
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "description": "One MP3, M4A, PCM WAV, MP4, or MOV file, up to 20 MiB and 10 minutes."
          },
          "language": {
            "type": "string",
            "enum": [
              "ar"
            ],
            "default": "ar",
            "description": "The recording language. Arabic is currently supported."
          },
          "speakers": {
            "type": "string",
            "pattern": "^(auto|[1-9]|1[0-9]|20)$",
            "default": "auto",
            "description": "Use automatic speaker detection, or provide a known speaker count from 1 to 20."
          }
        },
        "additionalProperties": false
      },
      "TranscriptionJob": {
        "type": "object",
        "required": [
          "id",
          "status",
          "duration_seconds",
          "billed_seconds",
          "billed_minutes",
          "reserved_seconds",
          "billing_method",
          "expires_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^tr_[a-f0-9]{32}$"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "completed",
              "failed"
            ]
          },
          "duration_seconds": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Measured media duration before billing rounding."
          },
          "billed_seconds": {
            "type": "integer",
            "minimum": 0,
            "description": "Seconds charged after a successful transcription for prepaid credit accounts; zero for metered accounts."
          },
          "billed_minutes": {
            "type": "integer",
            "minimum": 0,
            "description": "Whole minutes charged after a successful transcription for usage-based accounts; the minimum is one minute."
          },
          "reserved_seconds": {
            "type": "integer",
            "minimum": 0,
            "description": "Prepaid seconds reserved while the job is active; zero for metered accounts."
          },
          "billing_method": {
            "type": "string",
            "enum": [
              "credits",
              "metered_api_v1"
            ],
            "description": "The billing path used for this job."
          },
          "error": {
            "type": "string",
            "description": "Present when status is failed. Failed jobs return reserved credit."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Result retention deadline."
          }
        },
        "additionalProperties": false
      },
      "Word": {
        "type": "object",
        "required": [
          "text",
          "start",
          "end",
          "speaker_id"
        ],
        "properties": {
          "text": {
            "type": "string"
          },
          "start": {
            "type": "number",
            "minimum": 0,
            "description": "Word start time in seconds."
          },
          "end": {
            "type": "number",
            "minimum": 0,
            "description": "Word end time in seconds."
          },
          "speaker_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Speaker label within this recording, or null when unavailable."
          }
        },
        "additionalProperties": false
      },
      "TranscriptionResult": {
        "type": "object",
        "required": [
          "text",
          "words",
          "duration_seconds"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "The complete transcript assembled from the word results."
          },
          "words": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Word"
            }
          },
          "duration_seconds": {
            "type": "number",
            "minimum": 0
          }
        },
        "additionalProperties": false
      }
    }
  }
}
