{
  "openapi": "3.1.0",
  "info": {
    "title": "TmpFiles Bridge API",
    "version": "2.0.0",
    "description": "An anonymizing Cloudflare Worker proxy for the tmpfiles.org upload API. The multipart body is streamed upstream so curl -F works as usual, but all client-identifying request metadata is removed before the request leaves the Worker, and all upstream response headers are discarded.",
    "x-privacy": [
      {
        "title": "Fixed anonymous User-Agent",
        "detail": "Every upstream subrequest carries exactly one UA: Anonymous-Upload-Client. No version, no OS, no browser, no client hint. Your real UA is never read or forwarded."
      },
      {
        "title": "Upstream never sees your IP",
        "detail": "The request is re-issued by the Worker, so tmpfiles.org sees a Cloudflare egress IP. CF-Connecting-IP, X-Forwarded-For, True-Client-IP and similar inbound hints are dropped."
      },
      {
        "title": "Outbound header allowlist",
        "detail": "Only Content-Type (boundary only), Accept: application/json, and the fixed UA are sent. Cookie, Referer, Origin, Accept-Language, Authorization, Via and all other inbound headers are dropped."
      },
      {
        "title": "Filename sanitization",
        "detail": "Multipart part headers are rewritten in-stream: directory paths in filename= are stripped. Set ANONYMIZE_FILENAME=random to replace the whole name with upload-<hex>.<ext>. The Web UI anonymizes the filename before the request even leaves the browser."
      },
      {
        "title": "No token in URLs",
        "detail": "If UPLOAD_TOKEN is configured it is accepted only via Authorization: Bearer. Query-string tokens are rejected so nothing sensitive lands in logs, history or Referer."
      },
      {
        "title": "Clean responses, no fingerprint",
        "detail": "No upstream response header is copied: Set-Cookie, CF-Ray, Server, ETag and tracing headers are gone. Upstream JSON is re-emitted through a field allowlist and URLs are validated against tmpfiles.org over HTTPS."
      },
      {
        "title": "No logs, no storage, no analytics",
        "detail": "The Worker keeps nothing in memory beyond one request, writes no KV/R2/DO, emits no console output, and never logs filenames, sizes, tokens, timestamps or IPs. Every response is no-store, private."
      },
      {
        "title": "Browser-side hardening",
        "detail": "The UI is served with a fresh per-response CSP nonce, no unsafe-inline scripts, no-referrer, COOP, CORP, Permissions-Policy and frame-ancestors none. CORS can be locked to a single origin."
      },
      {
        "title": "Anonymity is not encryption",
        "detail": "The file itself is public on tmpfiles.org until it expires. Anyone with the link can download it. Metadata anonymization does not make the content private. Never upload confidential data."
      }
    ],
    "x-config": [
      {
        "key": "UPLOAD_TOKEN",
        "default": "unset",
        "detail": "Require Authorization: Bearer <token>."
      },
      {
        "key": "CORS_ORIGIN",
        "default": "*",
        "detail": "Pin Access-Control-Allow-Origin and add Vary: Origin."
      },
      {
        "key": "ANONYMIZE_FILENAME",
        "default": "strip",
        "detail": "strip | random | off."
      },
      {
        "key": "KEEP_UPSTREAM_BODY",
        "default": "0",
        "detail": "1 = forward raw upstream JSON (headers still cleaned)."
      },
      {
        "key": "EXPOSE_BRIDGE_HEADERS",
        "default": "0",
        "detail": "1 = add x-bridge-* debug headers."
      }
    ]
  },
  "servers": [
    {
      "url": "https://upload.cogpu.com",
      "description": "This Worker"
    }
  ],
  "tags": [
    {
      "name": "Upload",
      "description": "Anonymizing file upload proxy"
    }
  ],
  "paths": {
    "/api/v1/upload": {
      "post": {
        "tags": [
          "Upload"
        ],
        "summary": "Upload a temporary file anonymously",
        "description": "Accepts multipart/form-data and forwards it to https://tmpfiles.org/api/v1/upload. Alias: POST /api/upload. Client headers are replaced by a fixed allowlist; the response is rebuilt with clean headers and a sanitized JSON body.",
        "operationId": "uploadFile",
        "parameters": [
          {
            "in": "header",
            "name": "Authorization",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Bearer <UPLOAD_TOKEN>, only when the secret is configured. Never send the token in the query string."
          },
          {
            "in": "header",
            "name": "User-Agent",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "IGNORED. Overwritten with the fixed anonymous UA before the upstream request."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The file to upload. Max 100 MB. Directory paths in the filename are stripped. Use curl -F \"file=@/path/to/test.jpg;filename=photo.jpg\" to control the published name."
                  },
                  "expire": {
                    "type": "integer",
                    "minimum": 60,
                    "maximum": 172800,
                    "default": 3600,
                    "description": "Seconds until deletion. Optional; defaults to 3600 (1 hour)."
                  }
                }
              },
              "example": {
                "file": "@/path/to/test.jpg",
                "expire": 21600
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload succeeded. data.url is the landing page URL.",
            "content": {
              "application/json": {
                "example": {
                  "status": "success",
                  "data": {
                    "url": "https://tmpfiles.org/6136281/test.jpg"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing request body."
          },
          "401": {
            "description": "UPLOAD_TOKEN configured and the bearer token is missing or wrong."
          },
          "405": {
            "description": "Method not allowed. Only POST is accepted on this path."
          },
          "415": {
            "description": "Content-Type is not multipart/form-data with a valid boundary."
          },
          "502": {
            "description": "Upstream unreachable, timed out, or returned non-JSON."
          }
        }
      }
    },
    "/api/docs": {
      "get": {
        "tags": [
          "Upload"
        ],
        "summary": "This OpenAPI document",
        "operationId": "getApiDocs",
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 JSON."
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Upload"
        ],
        "summary": "Health check (no client data)",
        "operationId": "health",
        "responses": {
          "200": {
            "description": "Service is alive."
          }
        }
      }
    },
    "/": {
      "get": {
        "tags": [
          "Upload"
        ],
        "summary": "Web UI (upload + rendered API docs + privacy notes)",
        "operationId": "getWebUi",
        "responses": {
          "200": {
            "description": "text/html; charset=utf-8, CSP nonce per response."
          }
        }
      }
    }
  }
}