{
    "openapi": "3.1.0",
    "info": {
        "title": "Free AI Wiper customer API",
        "version": "1.0.0",
        "description": "Authenticated server-side cleaning API. Upload one file, poll its job, and download its verified result. Requires a compatible external worker; the standalone Rust worker executable is delivered separately. Enabled per deployment. Unlike the local-only browser cleaner, this API uploads files to our servers. Billing, plans and commercial quotas are not implemented."
    },
    "servers": [
        {
            "url": "/",
            "description": "Same origin as the portal; production requires HTTPS."
        }
    ],
    "security": [
        {
            "CustomerApiKey": []
        }
    ],
    "tags": [
        {
            "name": "Cleaning jobs",
            "description": "Upload one file, poll its job and download the verified result."
        }
    ],
    "paths": {
        "/api/clean": {
            "post": {
                "tags": [
                    "Cleaning jobs"
                ],
                "operationId": "createCleaningJob",
                "summary": "Upload one file for cleaning",
                "description": "Accept exactly one file. Laravel detects its format and validates the upload and optional personal_metadata field;\nRust validates the internal structure during processing. Filename and declared\nMIME alone are insufficient. Remote URL imports and batch archives are unsupported.\nLimits: 25 MiB image/PDF/Office, 50 MiB audio/video, 1 MiB text/markup;\nstricter processor limits still apply. Text inputs must be valid UTF-8.\nAmbiguous text/markup families use a supported extension plus content validation.\nRemove selected supported signals while preserving protected content;\nthis is not universal watermark removal. Valid indirect-length PDFs currently\nfail safely as unsupported_format. Some failures are discovered asynchronously.\nDelete the original after success or terminal failure. Keep results for one hour\nafter readiness. Matching retries return the same job, including terminal jobs.\n",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "description": "Stable client-generated key for the file and normalized options, scoped to\nthe customer account across its keys. Identical retries return the existing\njob; different content/options return 409. Retained for seven days after\nterminal transition; reuse after metadata purge can create a new job.\n",
                        "schema": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128,
                            "pattern": "^[A-Za-z0-9._:-]+$"
                        },
                        "example": "file-clean-001"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "multipart/form-data": {
                            "schema": {
                                "type": "object",
                                "additionalProperties": false,
                                "required": [
                                    "file"
                                ],
                                "properties": {
                                    "file": {
                                        "type": "string",
                                        "format": "binary"
                                    },
                                    "personal_metadata": {
                                        "type": "integer",
                                        "enum": [
                                            0,
                                            1
                                        ],
                                        "default": 0,
                                        "description": "Optional multipart form field. Send 1 to also remove supported personal metadata, or omit/0 to retain it. Not applicable to plain text; omit for text files.",
                                        "example": 1
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Durably accepted job, or matching retry in any state. Acceptance is not\na guarantee of cleaning success. No duplicate processing for a matching retry.\n",
                        "headers": {
                            "Location": {
                                "description": "Authenticated job status URL.",
                                "schema": {
                                    "type": "string",
                                    "format": "uri-reference"
                                }
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/JobResponse"
                                },
                                "example": {
                                    "data": {
                                        "id": "80c69ef7-d674-4e8b-b793-c4153c11ce46",
                                        "status": "queued",
                                        "format": "png",
                                        "input_bytes": 4096,
                                        "options": {
                                            "ai_metadata": true,
                                            "personal_metadata": true
                                        },
                                        "created_at": "2026-10-20T12:00:00Z",
                                        "started_at": null,
                                        "completed_at": null,
                                        "expires_at": null,
                                        "status_url": "/api/jobs/80c69ef7-d674-4e8b-b793-c4153c11ce46",
                                        "download_url": "/api/jobs/80c69ef7-d674-4e8b-b793-c4153c11ce46/download",
                                        "output": null,
                                        "report": null,
                                        "error": null
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/AccessDisabled"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "413": {
                        "$ref": "#/components/responses/TooLarge"
                    },
                    "415": {
                        "$ref": "#/components/responses/Unsupported"
                    },
                    "422": {
                        "$ref": "#/components/responses/Invalid"
                    },
                    "429": {
                        "$ref": "#/components/responses/Throttled"
                    },
                    "503": {
                        "$ref": "#/components/responses/Unavailable"
                    }
                }
            }
        },
        "/api/jobs/{id}": {
            "get": {
                "tags": [
                    "Cleaning jobs"
                ],
                "operationId": "getCleaningJob",
                "summary": "Get job status and result details",
                "description": "Any active API key belonging to the job owner can access the job. Revoking\nthe submitting key does not cancel accepted work. Other-customer jobs return\nthe same 404 as nonexistent jobs. A processing failure is a 200 resource with\nstatus failed; it is not a failed HTTP status request. Start polling every two\nseconds, increasing to five while unchanged; respect Retry-After if present.\nMetadata is retained for seven days after readiness or terminal failure,\nthen purged; expired results can no longer be downloaded.\n",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/JobId"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Owned job state, safe report or safe processing failure.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/JobResponse"
                                },
                                "example": {
                                    "data": {
                                        "id": "80c69ef7-d674-4e8b-b793-c4153c11ce46",
                                        "status": "ready",
                                        "format": "png",
                                        "input_bytes": 4096,
                                        "options": {
                                            "ai_metadata": true,
                                            "personal_metadata": true
                                        },
                                        "created_at": "2026-10-20T12:00:00Z",
                                        "started_at": "2026-10-20T12:00:01Z",
                                        "completed_at": "2026-10-20T12:00:02Z",
                                        "expires_at": "2026-10-20T13:00:02Z",
                                        "status_url": "/api/jobs/80c69ef7-d674-4e8b-b793-c4153c11ce46",
                                        "download_url": "/api/jobs/80c69ef7-d674-4e8b-b793-c4153c11ce46/download",
                                        "output": {
                                            "bytes": 4000,
                                            "mime_type": "image/png",
                                            "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
                                        },
                                        "report": {
                                            "changed": true,
                                            "processor_version": "example-version",
                                            "verification": "passed",
                                            "removed": [
                                                {
                                                    "category": "ai_metadata",
                                                    "count": 1
                                                }
                                            ],
                                            "retained": [],
                                            "detection_status": "complete_for_supported_signals"
                                        },
                                        "error": null
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/AccessDisabled"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/Throttled"
                    },
                    "503": {
                        "$ref": "#/components/responses/Unavailable"
                    }
                }
            }
        },
        "/api/jobs/{id}/download": {
            "get": {
                "tags": [
                    "Cleaning jobs"
                ],
                "operationId": "downloadCleaningJob",
                "summary": "Download a verified result through a storage redirect",
                "description": "Laravel authorizes access and redirects; storage transfers file bytes.\nConfigure clients to follow redirects without forwarding the API Authorization\nheader to storage. The signed URL is reusable bearer access, valid for at most\nfive minutes and never beyond result expiry. API-key revocation prevents new\nredirects but does not revoke an issued URL. Expiry may not terminate a transfer\nalready in progress. queued/processing return 409 job_not_ready, failed returns\n409 job_failed, and expired returns 410 result_expired.\n",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/JobId"
                    }
                ],
                "responses": {
                    "302": {
                        "description": "Follow Location for an attachment with the detected MIME and sanitized filename.",
                        "headers": {
                            "Location": {
                                "description": "Short-lived signed storage GET URL.",
                                "schema": {
                                    "type": "string",
                                    "format": "uri"
                                }
                            },
                            "Cache-Control": {
                                "schema": {
                                    "type": "string",
                                    "const": "no-store, private"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/AccessDisabled"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "409": {
                        "$ref": "#/components/responses/Conflict"
                    },
                    "410": {
                        "$ref": "#/components/responses/Expired"
                    },
                    "429": {
                        "$ref": "#/components/responses/Throttled"
                    },
                    "503": {
                        "$ref": "#/components/responses/Unavailable"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "CustomerApiKey": {
                "type": "http",
                "scheme": "bearer",
                "description": "Create a named key in the portal. Copy the secret once; only its hash is stored.\nDo not put keys in URLs or forward them to the storage host on download redirects.\n"
            }
        },
        "parameters": {
            "JobId": {
                "name": "id",
                "in": "path",
                "required": true,
                "schema": {
                    "type": "string",
                    "format": "uuid"
                },
                "example": "80c69ef7-d674-4e8b-b793-c4153c11ce46"
            }
        },
        "schemas": {
            "CleaningOptions": {
                "type": "object",
                "additionalProperties": false,
                "description": "Read-only snapshot of server-selected processing settings returned with the job. Do not send this object in an upload. AI metadata removal is always enabled for non-text formats; personal metadata removal defaults to false. Plain text uses the eight conservative text trace defaults below, with no customer-selectable trace list.",
                "properties": {
                    "ai_metadata": {
                        "type": "boolean"
                    },
                    "personal_metadata": {
                        "type": "boolean"
                    },
                    "text_traces": {
                        "type": "array",
                        "minItems": 1,
                        "uniqueItems": true,
                        "items": {
                            "type": "string",
                            "enum": [
                                "zero_width_space",
                                "word_joiner",
                                "soft_hyphen",
                                "bidi_control",
                                "embedded_bom",
                                "unicode_tag",
                                "noncharacter",
                                "reserved_ignorable",
                                "deprecated_format",
                                "private_use",
                                "unusual_space"
                            ]
                        },
                        "default": [
                            "zero_width_space",
                            "word_joiner",
                            "soft_hyphen",
                            "embedded_bom",
                            "unicode_tag",
                            "noncharacter",
                            "reserved_ignorable",
                            "deprecated_format"
                        ]
                    }
                },
                "readOnly": true
            },
            "Format": {
                "type": "string",
                "description": "Stable catalogue ID; specific supported-content restrictions still apply.",
                "enum": [
                    "jpeg",
                    "png",
                    "webp",
                    "avif",
                    "heic",
                    "bmp",
                    "gif",
                    "tiff",
                    "pdf",
                    "docx",
                    "pptx",
                    "xlsx",
                    "html",
                    "svg",
                    "markdown",
                    "mp4",
                    "mov",
                    "wav",
                    "mp3",
                    "flac",
                    "m4a",
                    "text"
                ]
            },
            "Job": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "id",
                    "status",
                    "format",
                    "input_bytes",
                    "options",
                    "created_at",
                    "started_at",
                    "completed_at",
                    "expires_at",
                    "status_url",
                    "download_url",
                    "output",
                    "report",
                    "error"
                ],
                "description": "One accepted file, one job across retries. The overall waiting/processing lifetime is\n45 minutes, with at most three worker attempts; there is no separate queue timeout.\nready requires report/output and result\nexpiry; failed requires error. queued/processing have null report/output/error.\nexpires_at is null until ready; completed_at records success or terminal failure.\nStatus becomes expired when the successful result expires, with report and output\nfacts retained until metadata purge. No worker IDs, attempt tokens, private object\nkeys, full filenames, extracted content or signed URLs appear in this resource.\n",
                "properties": {
                    "id": {
                        "type": "string",
                        "format": "uuid"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "queued",
                            "processing",
                            "ready",
                            "failed",
                            "expired"
                        ]
                    },
                    "format": {
                        "$ref": "#/components/schemas/Format"
                    },
                    "input_bytes": {
                        "type": "integer",
                        "minimum": 1
                    },
                    "options": {
                        "$ref": "#/components/schemas/CleaningOptions"
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "started_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "completed_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "expires_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "status_url": {
                        "type": "string",
                        "format": "uri-reference"
                    },
                    "download_url": {
                        "type": "string",
                        "format": "uri-reference",
                        "description": "Authenticated API endpoint, never a signed storage URL; usable only when ready."
                    },
                    "output": {
                        "oneOf": [
                            {
                                "type": "null"
                            },
                            {
                                "type": "object",
                                "additionalProperties": false,
                                "required": [
                                    "bytes",
                                    "mime_type",
                                    "sha256"
                                ],
                                "properties": {
                                    "bytes": {
                                        "type": "integer",
                                        "minimum": 0
                                    },
                                    "mime_type": {
                                        "type": "string"
                                    },
                                    "sha256": {
                                        "type": "string",
                                        "pattern": "^[a-f0-9]{64}$"
                                    }
                                }
                            }
                        ]
                    },
                    "report": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/ProcessingReport"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "error": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/JobError"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    }
                }
            },
            "ProcessingReport": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "changed",
                    "processor_version",
                    "verification",
                    "removed",
                    "retained",
                    "detection_status"
                ],
                "description": "Only for verified success. Findings are aggregate counts without extracted values.\nUnknown/unavailable detection is not absence. Text uses its existing cleaning\nguarantees and an actual-output preservation check, not a new library operation.\n",
                "properties": {
                    "changed": {
                        "type": "boolean"
                    },
                    "processor_version": {
                        "type": "string"
                    },
                    "verification": {
                        "type": "string",
                        "enum": [
                            "passed"
                        ]
                    },
                    "removed": {
                        "type": "array",
                        "maxItems": 3,
                        "items": {
                            "$ref": "#/components/schemas/FindingSummary"
                        }
                    },
                    "retained": {
                        "type": "array",
                        "maxItems": 3,
                        "items": {
                            "$ref": "#/components/schemas/FindingSummary"
                        }
                    },
                    "detection_status": {
                        "type": "string",
                        "enum": [
                            "complete_for_supported_signals",
                            "partially_unavailable"
                        ]
                    }
                }
            },
            "FindingSummary": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "category",
                    "count"
                ],
                "properties": {
                    "category": {
                        "type": "string",
                        "enum": [
                            "ai_metadata",
                            "personal_metadata",
                            "unicode_traces"
                        ]
                    },
                    "count": {
                        "type": "integer",
                        "minimum": 0
                    }
                }
            },
            "JobError": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "code",
                    "message"
                ],
                "properties": {
                    "code": {
                        "type": "string",
                        "enum": [
                            "malformed_file",
                            "unsupported_format",
                            "verification_failed",
                            "resource_limit",
                            "processing_timeout",
                            "storage_unavailable",
                            "processing_failed"
                        ]
                    },
                    "message": {
                        "type": "string"
                    }
                }
            },
            "JobResponse": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "data"
                ],
                "properties": {
                    "data": {
                        "$ref": "#/components/schemas/Job"
                    }
                }
            },
            "ErrorResponse": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "error",
                    "request_id"
                ],
                "description": "Safe API errors. Proxy-generated rejections may not use this envelope.",
                "properties": {
                    "error": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                            "code",
                            "message"
                        ],
                        "properties": {
                            "code": {
                                "type": "string",
                                "enum": [
                                    "unauthenticated",
                                    "api_access_disabled",
                                    "job_not_found",
                                    "idempotency_conflict",
                                    "job_not_ready",
                                    "job_failed",
                                    "result_expired",
                                    "upload_too_large",
                                    "unsupported_format",
                                    "validation_failed",
                                    "rate_limited",
                                    "capacity_exhausted",
                                    "service_unavailable"
                                ]
                            },
                            "message": {
                                "type": "string"
                            }
                        }
                    },
                    "request_id": {
                        "type": "string"
                    }
                }
            }
        },
        "responses": {
            "Unauthorized": {
                "description": "Missing, invalid or revoked key; code unauthenticated.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResponse"
                        }
                    }
                }
            },
            "AccessDisabled": {
                "description": "Account API access disabled; code api_access_disabled. Not plan-based gating.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResponse"
                        }
                    }
                }
            },
            "NotFound": {
                "description": "Nonexistent, purged or other-owner job; code job_not_found.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResponse"
                        }
                    }
                }
            },
            "Conflict": {
                "description": "idempotency_conflict on upload; job_not_ready or job_failed on download.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResponse"
                        }
                    }
                }
            },
            "Expired": {
                "description": "Result expired; code result_expired.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResponse"
                        }
                    }
                }
            },
            "TooLarge": {
                "description": "Category/application size limit exceeded; code upload_too_large.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResponse"
                        }
                    }
                }
            },
            "Unsupported": {
                "description": "Unsupported or incompatible format; code unsupported_format.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResponse"
                        }
                    }
                }
            },
            "Invalid": {
                "description": "Invalid upload, encoding, options or idempotency key; code validation_failed.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResponse"
                        }
                    }
                }
            },
            "Throttled": {
                "description": "Operational request throttle, not a commercial quota; code rate_limited.",
                "headers": {
                    "Retry-After": {
                        "description": "Suggested delay in seconds.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    }
                },
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResponse"
                        }
                    }
                }
            },
            "Unavailable": {
                "description": "Temporary infrastructure or capacity failure; code service_unavailable or capacity_exhausted.",
                "headers": {
                    "Retry-After": {
                        "description": "Suggested delay in seconds when known.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    }
                },
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ErrorResponse"
                        }
                    }
                }
            }
        }
    }
}