{
  "openapi": "3.1.0",
  "info": {
    "title": "PiiBlur API",
    "version": "1.0.0",
    "description": "Public API for asynchronous PII redaction of images and videos."
  },
  "servers": [
    {
      "url": "https://piiblur.com/api/v1"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "paths": {
    "/media/redact": {
      "post": {
        "summary": "Upload media and queue redaction",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Reuse the same key when retrying the same upload after a network failure. Keys expire after 24 hours.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file", "categories"],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "categories": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "$ref": "#/components/schemas/RedactionCategory"
                    }
                  },
                  "redaction_method": {
                    "$ref": "#/components/schemas/RedactionMethod"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Queued media",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Media"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/media": {
      "get": {
        "summary": "List media",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/MediaStatus"
            }
          },
          {
            "name": "media_type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/MediaType"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Media collection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaCollection"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/media/{mediaId}": {
      "get": {
        "summary": "Get media",
        "parameters": [
          {
            "$ref": "#/components/parameters/MediaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Media",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Media"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "summary": "Delete media",
        "parameters": [
          {
            "$ref": "#/components/parameters/MediaId"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/media/{mediaId}/download": {
      "get": {
        "summary": "Download redacted media",
        "parameters": [
          {
            "$ref": "#/components/parameters/MediaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Redacted file",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/webp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "video/quicktime": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "video/webm": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/usage": {
      "get": {
        "summary": "Get usage",
        "responses": {
          "200": {
            "description": "Usage",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Usage"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    },
    "parameters": {
      "MediaId": {
        "name": "mediaId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "Conflict": {
        "description": "Conflict",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Quota or rate limit exceeded",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying after a rate-limit response.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Limit": {
            "description": "Maximum requests per minute for the current plan.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "description": "Requests remaining in the current rate-limit window.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "description": "Unix timestamp when the current rate-limit window resets.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "ValidationError": {
        "description": "Validation failed",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorEnvelope": {
        "type": "object",
        "required": ["error", "request_id"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message", "details"],
            "properties": {
              "code": {
                "type": "string",
                "examples": ["validation_failed"]
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "additionalProperties": true
              }
            }
          },
          "request_id": {
            "type": "string",
            "examples": ["req_abc123"]
          }
        }
      },
      "Media": {
        "type": "object",
        "required": [
          "id",
          "status",
          "filename",
          "media_type",
          "categories",
          "redaction_method",
          "file_size_bytes",
          "duration_seconds",
          "created_at",
          "processed_at",
          "failed_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/MediaStatus"
          },
          "filename": {
            "type": "string"
          },
          "media_type": {
            "$ref": "#/components/schemas/MediaType"
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RedactionCategory"
            }
          },
          "redaction_method": {
            "$ref": "#/components/schemas/RedactionMethod"
          },
          "file_size_bytes": {
            "type": "integer",
            "minimum": 0
          },
          "duration_seconds": {
            "type": ["number", "null"]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "processed_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "failed_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "download_url": {
            "type": "string",
            "format": "uri"
          }
        },
        "additionalProperties": false
      },
      "MediaCollection": {
        "type": "object",
        "required": ["data", "pagination", "links"],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Media"
            }
          },
          "pagination": {
            "type": "object",
            "required": ["page", "per_page", "total", "total_pages"],
            "properties": {
              "page": {
                "type": "integer"
              },
              "per_page": {
                "type": "integer"
              },
              "total": {
                "type": "integer"
              },
              "total_pages": {
                "type": "integer"
              }
            },
            "additionalProperties": false
          },
          "links": {
            "type": "object",
            "required": ["next", "previous"],
            "properties": {
              "next": {
                "type": ["string", "null"],
                "format": "uri"
              },
              "previous": {
                "type": ["string", "null"],
                "format": "uri"
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "MediaStatus": {
        "type": "string",
        "enum": ["pending", "queued", "processing", "completed", "failed", "quota_exceeded"]
      },
      "MediaType": {
        "type": "string",
        "enum": ["image", "video"]
      },
      "RedactionMethod": {
        "type": "string",
        "enum": ["blur", "pixelation"],
        "default": "blur"
      },
      "RedactionCategory": {
        "type": "string",
        "enum": [
          "heads",
          "license_plates",
          "screens",
          "writing",
          "street_signs",
          "id_cards",
          "passports",
          "credit_cards",
          "name_badges",
          "qr_codes",
          "barcodes",
          "documents",
          "tattoos"
        ]
      },
      "Usage": {
        "type": "object",
        "required": ["plan", "period_start", "period_end", "images", "video_minutes"],
        "properties": {
          "plan": {
            "type": "string"
          },
          "period_start": {
            "type": "string",
            "format": "date"
          },
          "period_end": {
            "type": "string",
            "format": "date"
          },
          "images": {
            "type": "object",
            "required": ["used", "limit"],
            "properties": {
              "used": {
                "type": "integer"
              },
              "limit": {
                "type": ["integer", "null"]
              }
            },
            "additionalProperties": false
          },
          "video_minutes": {
            "type": "object",
            "required": ["used", "limit"],
            "properties": {
              "used": {
                "type": "number"
              },
              "limit": {
                "type": ["number", "null"]
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      }
    }
  }
}
