{
  "openapi": "3.1.0",
  "info": {
    "title": "Filtrate API",
    "description": "Media compliance API for e-commerce marketplaces. A single `POST /v1/process` call strips all EXIF metadata, compresses to WebP, and returns an AI content moderation verdict — 1–5 second response, $0.002 per image.\n\n**Resources:**\n- [Pricing](/pricing) — pay-as-you-go, no monthly minimums\n- [Trust & Security](/trust) — GDPR/CCPA posture, image lifecycle, sub-processors\n- [Dashboard](/dashboard) — manage API keys and view usage",
    "version": "1.0.0",
    "contact": {
      "name": "Filtrate Support",
      "email": "support@filtrate.polsia.app",
      "url": "https://filtrate.polsia.app/docs"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://filtrate.polsia.app/trust"
    }
  },
  "servers": [
    {
      "url": "https://filtrate.polsia.app",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/process": {
      "post": {
        "operationId": "processImage",
        "summary": "Process an image",
        "description": "The primary Filtrate endpoint. Accepts either an image URL (JSON body) or a direct file upload (multipart). Strips all EXIF/IPTC/XMP metadata, converts to WebP, uploads to CDN, and runs AI content moderation — all in one call.\n\nProvide **either** `image_url` (JSON) **or** a multipart `image` field. Do not send both.",
        "tags": ["Images"],
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Publicly accessible URL of the image to process.",
                    "example": "https://example.com/product-photo.jpg"
                  },
                  "compress": {
                    "type": "object",
                    "description": "WebP compression options.",
                    "properties": {
                      "quality": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 100,
                        "default": 85,
                        "description": "WebP quality 1–100. Lower = smaller file, more compression artifacts.",
                        "example": 80
                      }
                    }
                  },
                  "strip_exif": {
                    "type": "boolean",
                    "default": true,
                    "description": "When false, original orientation hint is preserved. EXIF is always stripped regardless."
                  }
                },
                "required": ["image_url"]
              },
              "example": {
                "image_url": "https://example.com/product-photo.jpg",
                "compress": { "quality": 80 }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary",
                    "description": "Image file to process. Accepted formats: JPEG, PNG, WebP, GIF, AVIF. Max 20 MB."
                  },
                  "compress[quality]": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 85,
                    "description": "WebP quality 1–100."
                  },
                  "strip_exif": {
                    "type": "string",
                    "enum": ["true", "false"],
                    "default": "true",
                    "description": "Pass 'false' to preserve orientation hint."
                  }
                },
                "required": ["image"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Image processed successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProcessResponse"
                },
                "example": {
                  "status": "safe",
                  "optimized_url": "https://cdn.polsia.app/filtrate/1234567890.webp",
                  "size_reduction": "67%",
                  "original_size_bytes": 2048576,
                  "output_size_bytes": 675840,
                  "format": "webp",
                  "quality": 85,
                  "exif_stripped": true,
                  "processing_ms": 1842,
                  "from_cache": false,
                  "pipeline_timings": {
                    "parseAndReceive": 45,
                    "sharpProcessing": 128,
                    "r2Upload": 312,
                    "moderation": 1342,
                    "moderation_cache_hit": 0,
                    "response": 15
                  },
                  "compliance": {
                    "verdict": "safe",
                    "categories": {
                      "explicit": false,
                      "fraud": false,
                      "banned_logo": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — missing image input, unsupported format, or failed to fetch remote URL.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": "Provide either image_url in JSON body or an image file as multipart field 'image'"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — API key is missing, invalid, or revoked.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Invalid or missing API key" }
              }
            }
          },
          "402": {
            "description": "Payment required — account has an overdue balance.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Account has an overdue invoice. Please settle your balance at /billing." }
              }
            }
          },
          "413": {
            "description": "Payload too large — file exceeds 20 MB.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "File too large. Maximum size is 20 MB." }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Check the `Retry-After` header.",
            "headers": {
              "Retry-After": {
                "schema": { "type": "integer" },
                "description": "Seconds to wait before retrying."
              },
              "X-RateLimit-Limit": {
                "schema": { "type": "integer" },
                "description": "Max requests per minute for this API key."
              },
              "X-RateLimit-Remaining": {
                "schema": { "type": "integer" },
                "description": "Requests remaining in the current window."
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Rate limit exceeded. Try again in 12 seconds." }
              }
            }
          },
          "500": {
            "description": "Internal server error — unexpected processing failure.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Processing failed: sharp conversion error" }
              }
            }
          }
        }
      }
    },
    "/v1/batch": {
      "post": {
        "operationId": "processBatch",
        "summary": "Bulk process multiple images",
        "description": "Process up to 50 images in a single batch call. Each image is fetched, EXIF-stripped, compressed to WebP, and run through AI moderation. Errors are included per-item — one failing image does not fail the entire batch.\n\nEach image in the batch is billed as a separate call ($0.002/image). Returns a per-item result array plus a summary.",
        "tags": ["Images"],
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "image_urls": {
                    "type": "array",
                    "items": { "type": "string", "format": "uri" },
                    "maxItems": 50,
                    "description": "Array of publicly accessible image URLs. Max 50.",
                    "example": [
                      "https://example.com/photo1.jpg",
                      "https://example.com/photo2.jpg",
                      "https://example.com/photo3.jpg"
                    ]
                  }
                },
                "required": ["image_urls"]
              },
              "example": {
                "image_urls": [
                  "https://example.com/photo1.jpg",
                  "https://example.com/photo2.jpg"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch processed. Errors are included per-item.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BatchResponse" },
                "example": {
                  "results": [
                    {
                      "url": "https://example.com/photo1.jpg",
                      "status": "safe",
                      "optimized_url": "https://cdn.polsia.app/filtrate/1234567890.webp",
                      "original_size_bytes": 2048576,
                      "output_size_bytes": 675840,
                      "size_reduction_pct": 67,
                      "processing_ms": 1850,
                      "compliance": {
                        "verdict": "safe",
                        "categories": { "explicit": false, "fraud": false, "banned_logo": false }
                      }
                    },
                    {
                      "url": "https://example.com/photo2.jpg",
                      "status": "error",
                      "error": "Remote fetch failed: 404 Not Found",
                      "processing_ms": 120
                    }
                  ],
                  "summary": {
                    "total": 2,
                    "completed": 1,
                    "errors": 1,
                    "total_ms": 2103
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — missing or oversized image_urls array.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Maximum 50 images per batch. Received 55." }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Invalid or missing API key" }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Rate limit exceeded. Try again in 12 seconds." }
              }
            }
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Current-period usage summary",
        "description": "Returns total image count and estimated cost for the current billing period.",
        "tags": ["Account"],
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": {
            "description": "Usage summary for the current billing period.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageResponse"
                },
                "example": {
                  "period": "2026-05",
                  "image_count": 4821,
                  "estimated_cost_usd": 9.642,
                  "price_per_image_usd": 0.002
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/v1/keys": {
      "post": {
        "operationId": "createApiKey",
        "summary": "Create a new API key",
        "description": "Creates an API key. Keys are shown **once** — store the returned value securely. This open endpoint creates anonymous keys; to associate a key with your account, use the dashboard or log in first.",
        "tags": ["Account"],
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "description": "Human-readable label for this key.",
                    "example": "production"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key created. Shown once — not retrievable again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyResponse"
                },
                "example": {
                  "api_key": "flt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                  "id": "key_abc123",
                  "message": "Store this key securely — it cannot be retrieved again."
                }
              }
            }
          },
          "500": {
            "description": "Failed to create key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key starting with flt_live_",
        "description": "API key from your [Dashboard](/dashboard). Pass as `Authorization: Bearer <api_key>`."
      }
    },
    "schemas": {
      "ProcessResponse": {
        "type": "object",
        "description": "Successful image processing result.",
        "required": ["status", "optimized_url", "size_reduction", "original_size_bytes", "output_size_bytes", "format", "quality", "exif_stripped", "processing_ms", "from_cache", "pipeline_timings", "compliance"],
        "properties": {
          "status": {
            "$ref": "#/components/schemas/Verdict"
          },
          "optimized_url": {
            "type": "string",
            "format": "uri",
            "description": "CDN URL of the processed WebP file.",
            "example": "https://cdn.polsia.app/filtrate/1234567890.webp"
          },
          "size_reduction": {
            "type": "string",
            "description": "Size reduction percentage string.",
            "example": "67%"
          },
          "original_size_bytes": {
            "type": "integer",
            "description": "Input image size in bytes.",
            "example": 2048576
          },
          "output_size_bytes": {
            "type": "integer",
            "description": "Output WebP size in bytes.",
            "example": 675840
          },
          "format": {
            "type": "string",
            "description": "Output format. Always 'webp'.",
            "example": "webp"
          },
          "quality": {
            "type": "integer",
            "description": "WebP quality setting used.",
            "example": 85
          },
          "exif_stripped": {
            "type": "boolean",
            "description": "Always true — all EXIF/IPTC/XMP metadata removed.",
            "example": true
          },
          "processing_ms": {
            "type": "integer",
            "description": "Total request processing time in milliseconds.",
            "example": 1842
          },
          "from_cache": {
            "type": "boolean",
            "description": "True if AI moderation verdict came from cache (identical image already processed).",
            "example": false
          },
          "pipeline_timings": {
            "$ref": "#/components/schemas/PipelineTimings"
          },
          "compliance": {
            "$ref": "#/components/schemas/ComplianceResult"
          }
        }
      },
      "Verdict": {
        "type": "string",
        "enum": ["safe", "explicit", "fraud", "banned_logo"],
        "description": "AI content moderation verdict.\n- `safe` — image passes all compliance checks\n- `explicit` — NSFW or adult content detected\n- `fraud` — counterfeit signals or prohibited item\n- `banned_logo` — trademark/brand logo that violates marketplace policy"
      },
      "ComplianceResult": {
        "type": "object",
        "description": "AI moderation result with per-category breakdown.",
        "required": ["verdict", "categories"],
        "properties": {
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "categories": {
            "$ref": "#/components/schemas/Categories"
          },
          "error": {
            "type": "string",
            "description": "Present only when AI moderation fails — the image is still processed and returned.",
            "example": "AI moderation unavailable; defaulting to safe"
          }
        }
      },
      "Categories": {
        "type": "object",
        "description": "Boolean flags for each moderation category.",
        "required": ["explicit", "fraud", "banned_logo"],
        "properties": {
          "explicit": {
            "type": "boolean",
            "description": "NSFW or adult content detected.",
            "example": false
          },
          "fraud": {
            "type": "boolean",
            "description": "Counterfeit signals or prohibited item detected.",
            "example": false
          },
          "banned_logo": {
            "type": "boolean",
            "description": "Trademark or brand logo violation detected.",
            "example": false
          }
        }
      },
      "PipelineTimings": {
        "type": "object",
        "description": "Time spent (ms) in each processing stage.",
        "properties": {
          "parseAndReceive": { "type": "integer", "example": 45 },
          "sharpProcessing": { "type": "integer", "example": 128 },
          "r2Upload": { "type": "integer", "example": 312 },
          "moderation": { "type": "integer", "example": 1342 },
          "moderation_cache_hit": { "type": "integer", "example": 0 },
          "response": { "type": "integer", "example": 15 }
        }
      },
      "UsageResponse": {
        "type": "object",
        "description": "Current billing period usage summary.",
        "properties": {
          "period": {
            "type": "string",
            "description": "Billing period in YYYY-MM format.",
            "example": "2026-05"
          },
          "image_count": {
            "type": "integer",
            "description": "Total images processed this period.",
            "example": 4821
          },
          "estimated_cost_usd": {
            "type": "number",
            "description": "Estimated cost in USD at $0.002/image.",
            "example": 9.642
          },
          "price_per_image_usd": {
            "type": "number",
            "description": "Per-image rate.",
            "example": 0.002
          }
        }
      },
      "ApiKeyResponse": {
        "type": "object",
        "description": "Newly created API key. The key value is shown once.",
        "properties": {
          "api_key": {
            "type": "string",
            "description": "Full API key value. Shown once — store securely.",
            "example": "flt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
          },
          "id": {
            "type": "string",
            "description": "Key ID for management operations.",
            "example": "key_abc123"
          },
          "message": {
            "type": "string",
            "example": "Store this key securely — it cannot be retrieved again."
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "API error response.",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message.",
            "example": "Invalid or missing API key"
          }
        }
      },
      "BatchResponse": {
        "type": "object",
        "description": "Bulk processing result with per-item outcomes and a summary.",
        "required": ["results", "summary"],
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "description": "Result for one image in the batch."
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "total":    { "type": "integer", "example": 10 },
              "completed": { "type": "integer", "example": 9 },
              "errors":   { "type": "integer", "example": 1 },
              "total_ms": { "type": "integer", "example": 12400 }
            }
          }
        }
      }
    }
  },
  "tags": [
    { "name": "Images", "description": "Image processing and compliance pipeline." },
    { "name": "Account", "description": "API key management and usage reporting." }
  ]
}
