{
  "openapi": "3.1.0",
  "info": {
    "title": "Veil browser image-processing API",
    "version": "1.0.0-candidate",
    "description": "Proposed stateless signing/processing contract for the Pages and Lambda migration. Not currently deployed."
  },
  "servers": [
    {
      "url": "https://bztv3km292.execute-api.ca-central-1.amazonaws.com",
      "description": "Placeholder; replace with staging/production HTTPS origin"
    }
  ],
  "paths": {
    "/v1/uploads": {
      "post": {
        "summary": "Issue bounded private S3 upload authorization",
        "description": "Backend validates content type and declared bytes, enforces rate/concurrency/cost budget, issues a short-lived signed S3 POST policy scoped to one private input key. The Lambda processing endpoint must independently validate actual object type, size, and decoded pixels.",
        "operationId": "authorizeUpload",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Opaque upload reference and signed S3 POST form",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadAuthorization"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedType"
          },
          "429": {
            "$ref": "#/components/responses/Throttled"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/v1/obfuscations": {
      "post": {
        "summary": "Process a previously authorized private upload",
        "description": "The server validates signed uploadId authorization, key ownership and TTL; retrieves the private S3 input, bounds image pixels and compute usage, and returns a private short-lived result URL.",
        "operationId": "createObfuscation",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ObfuscationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed private result URL and access expiration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ObfuscationResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedType"
          },
          "429": {
            "$ref": "#/components/responses/Throttled"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "UploadRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "contentType",
          "size"
        ],
        "properties": {
          "contentType": {
            "type": "string",
            "enum": [
              "image/jpeg",
              "image/png",
              "image/gif",
              "image/bmp"
            ]
          },
          "size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10485760,
            "description": "Input size in bytes, validated against actual object size after upload"
          }
        }
      },
      "UploadAuthorization": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "uploadId",
          "upload"
        ],
        "properties": {
          "uploadId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512,
            "description": "Opaque, short-lived, integrity-protected reference to authorized input key"
          },
          "upload": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "url",
              "fields"
            ],
            "properties": {
              "url": {
                "type": "string",
                "format": "uri",
                "description": "Exact HTTPS origin of private upload bucket, signed S3 POST destination"
              },
              "fields": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "description": "Opaque signed POST policy fields, sent in the multipart form before file"
              }
            }
          }
        }
      },
      "ObfuscationRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "uploadId"
        ],
        "properties": {
          "uploadId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512
          },
          "cellCount": {
            "type": "integer",
            "minimum": 2,
            "maximum": 20000
          }
        }
      },
      "ObfuscationResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "resultUrl",
          "expiresAt"
        ],
        "properties": {
          "resultUrl": {
            "type": "string",
            "format": "uri",
            "description": "Private short-lived HTTPS presigned GET URL for generated output"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Actual maximum valid access time of result URL; not merely object storage cleanup time"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request or image limits",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Upload missing or no longer valid",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "UnsupportedType": {
        "description": "Unsupported or invalid actual image",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Throttled": {
        "description": "Capacity/rate limit enforced",
        "headers": {
          "Retry-After": {
            "description": "Retry delay seconds",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unavailable": {
        "description": "Service admission controls or execution unavailable",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
