{
  "openapi": "3.0.3",
  "info": {
    "title": "Bleepify API",
    "version": "1.0.0-beta.1",
    "description": "Public beta for standard profanity cleaning and Music Mode. Every Bleepify account can create a key without an invitation or subscription. Existing account minutes, storage, output entitlements and feature rules apply. Standard content and review-first workflow remain the defaults. Music Mode requires the account's existing Music Mode access and preserves the instrumental while cleaning vocals. Job acceptance is durable; a background worker dispatches queued jobs, usually within one minute. Uncertain processor acceptance is quarantined as dispatch_unknown: do not resubmit. R2 media is retained for 30 days from upload completion, with active/uncertain jobs held for safe reconciliation."
  },
  "servers": [
    {
      "url": "https://bleepify-server.fly.dev/v1",
      "description": "Live public beta"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/openapi.json": {
      "get": {
        "security": [],
        "operationId": "getOpenApi",
        "responses": {
          "200": {
            "description": "OpenAPI 3.0 document"
          }
        }
      }
    },
    "/uploads": {
      "post": {
        "operationId": "createUpload",
        "summary": "Create a direct upload session",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUploadRequest"
              },
              "example": {
                "filename": "episode.mp4",
                "content_type": "video/mp4",
                "size_bytes": 1048576,
                "checksum_md5": "00000000000000000000000000000000"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Upload session created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/uploads/{upload_id}/complete": {
      "post": {
        "operationId": "completeUpload",
        "summary": "Verify and seal uploaded media",
        "parameters": [
          {
            "$ref": "#/components/parameters/UploadId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Upload sealed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/jobs": {
      "post": {
        "operationId": "createJob",
        "summary": "Create an asynchronous cleaning job",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateJobRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job accepted",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/jobs/{job_id}": {
      "get": {
        "operationId": "getJob",
        "summary": "Get normalized job status",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Job status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/jobs/{job_id}/transcript": {
      "get": {
        "operationId": "getJobTranscript",
        "summary": "Get timed words and stable occurrence IDs",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Timed transcript",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transcript"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/jobs/{job_id}/renders": {
      "post": {
        "operationId": "renderJob",
        "summary": "Render selected transcript occurrences",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RenderRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Render accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/jobs/{job_id}/cancel": {
      "post": {
        "operationId": "cancelJob",
        "summary": "Cancel a job before processor dispatch",
        "description": "The API cannot safely cancel work after the processing server accepts it and returns job_not_cancelable in that case.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Job canceled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/jobs/{job_id}/output": {
      "get": {
        "operationId": "getJobOutput",
        "summary": "Get a short-lived output URL",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Output URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Output"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Get included, consumed, reserved, and overage minutes",
        "responses": {
          "200": {
            "description": "Current billing-period usage",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Usage"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "blp_live"
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      },
      "UploadId": {
        "name": "upload_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "JobId": {
        "name": "job_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "Problem": {
        "description": "Problem response",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "schemas": {
      "CreateUploadRequest": {
        "type": "object",
        "required": [
          "filename",
          "content_type",
          "size_bytes",
          "checksum_md5"
        ],
        "properties": {
          "filename": {
            "type": "string",
            "maxLength": 200
          },
          "content_type": {
            "type": "string",
            "pattern": "^(audio|video)/"
          },
          "size_bytes": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "maximum": 4294967296,
            "description": "Exact bytes, bound in the upload signature. Maximum 4 GiB video; 1 GiB Free/Fast audio or 3 GiB eligible Accurate audio. Available account storage and unfinished-upload reservations also apply. Single-part uploads only."
          },
          "checksum_md5": {
            "type": "string",
            "description": "32-character hex or base64 MD5 digest"
          }
        },
        "additionalProperties": false
      },
      "Upload": {
        "type": "object",
        "required": [
          "id",
          "object",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "object": {
            "type": "string",
            "enum": [
              "upload"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_upload",
              "uploaded",
              "expired"
            ]
          },
          "filename": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer",
            "format": "int64"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "upload": {
            "type": "object",
            "properties": {
              "method": {
                "type": "string",
                "enum": [
                  "PUT"
                ]
              },
              "url": {
                "type": "string",
                "format": "uri"
              },
              "headers": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          },
          "retain_until": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Input/output media deadline after completing the upload. Upload URL expiry is separate."
          }
        }
      },
      "CreateJobRequest": {
        "type": "object",
        "required": [
          "upload_id"
        ],
        "properties": {
          "content_mode": {
            "type": "string",
            "enum": [
              "standard",
              "music"
            ],
            "default": "standard",
            "description": "Use music to separate vocals, clean them, and remix with the instrumental. Works with review and auto workflows, bleep and mute. Requires Music Mode access; otherwise 403 music_mode_access_required. Fixed at creation; omit for unchanged standard behavior."
          },
          "upload_id": {
            "type": "string",
            "format": "uuid"
          },
          "workflow": {
            "type": "string",
            "enum": [
              "review",
              "auto"
            ],
            "default": "review"
          },
          "language": {
            "type": "string",
            "default": "en"
          },
          "word_list": {
            "type": "array",
            "maxItems": 250,
            "items": {
              "type": "string",
              "maxLength": 64
            }
          },
          "exact_match": {
            "type": "boolean",
            "default": true
          },
          "effect": {
            "type": "string",
            "enum": [
              "bleep",
              "mute"
            ],
            "default": "bleep"
          }
        },
        "additionalProperties": false
      },
      "Job": {
        "type": "object",
        "required": [
          "id",
          "object",
          "workflow",
          "status",
          "stage",
          "progress",
          "usage",
          "links"
        ],
        "properties": {
          "content_mode": {
            "type": "string",
            "enum": [
              "standard",
              "music"
            ],
            "default": "standard",
            "description": "The job's immutable content mode. Older idempotent response receipts may omit this field; treat omission as standard."
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "object": {
            "type": "string",
            "enum": [
              "job"
            ]
          },
          "workflow": {
            "type": "string",
            "enum": [
              "review",
              "auto"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_upload",
              "queued",
              "transcribing",
              "review_required",
              "rendering",
              "succeeded",
              "failed",
              "canceled",
              "expired",
              "dispatch_unknown"
            ]
          },
          "stage": {
            "type": "string",
            "description": "Processing detail. Music jobs may report separating_music or transcribing_lyrics while status is transcribing. Additional stages may be added; use status for workflow control."
          },
          "progress": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "error": {
            "type": "object",
            "nullable": true,
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "reserved_minutes": {
                "type": "number"
              },
              "consumed_seconds": {
                "type": "integer"
              }
            }
          },
          "links": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Media availability deadline, 30 days from upload completion."
          }
        }
      },
      "TranscriptWord": {
        "type": "object",
        "required": [
          "id",
          "word",
          "normalized_word",
          "detected"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "occ_a"
          },
          "word": {
            "type": "string"
          },
          "normalized_word": {
            "type": "string"
          },
          "start": {
            "type": "number",
            "nullable": true
          },
          "end": {
            "type": "number",
            "nullable": true
          },
          "confidence": {
            "type": "number",
            "nullable": true
          },
          "detected": {
            "type": "boolean"
          }
        }
      },
      "Transcript": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "language": {
            "type": "string"
          },
          "words": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TranscriptWord"
            }
          },
          "detected_occurrence_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "RenderRequest": {
        "type": "object",
        "required": [
          "occurrence_ids"
        ],
        "properties": {
          "occurrence_ids": {
            "type": "array",
            "minItems": 0,
            "maxItems": 10000,
            "uniqueItems": true,
            "items": {
              "type": "string"
            },
            "description": "Selected transcript occurrence IDs. An empty array returns the unchanged original file without a processor render."
          },
          "effect": {
            "type": "string",
            "enum": [
              "bleep",
              "mute"
            ]
          }
        },
        "additionalProperties": false
      },
      "Output": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "filename": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Usage": {
        "type": "object",
        "properties": {
          "period": {
            "type": "object",
            "properties": {
              "start": {
                "type": "string",
                "format": "date-time"
              },
              "end": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "plan": {
            "type": "string"
          },
          "included_minutes": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string",
                "enum": [
                  "unlimited"
                ]
              }
            ]
          },
          "consumed_minutes": {
            "type": "integer"
          },
          "reserved_minutes": {
            "type": "integer"
          },
          "prepaid_minutes_applied": {
            "type": "integer"
          },
          "overage_minutes": {
            "type": "integer"
          },
          "overage_rate_per_minute": {
            "type": "number",
            "nullable": true
          },
          "concurrent_job_limit": {
            "type": "integer"
          }
        }
      },
      "Problem": {
        "type": "object",
        "required": [
          "type",
          "code",
          "message",
          "request_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "request_id": {
            "type": "string"
          },
          "retry_after": {
            "type": "integer"
          }
        }
      }
    }
  }
}
