{
  "openapi": "3.0.3",
  "info": {
    "title": "File Convert Online — Public Agent API",
    "version": "1.0",
    "description": "Programmatic interface for file conversion at file-convert.online. Covers the full agent workflow: discover formats, upload a source file, start a conversion, poll for completion, and download the result. All endpoints are fronted by the Traefik gateway; clients talk only to https://file-convert.online — never to individual services.\n**File retention:** converted files are retained for up to 14 days. The default platform retention window is 1 hour; callers may extend a specific file's window up to the 14-day ceiling via the retention API. Download results promptly or set a longer retention immediately after the job completes.\n**Rate limits:** limits are per 60-second window and vary by plan: - Anonymous (no key / no account): 120 requests / 60 s (public endpoints; seed default is 30, updated in production) - Free plan: 120 requests / 60 s (public), 60 / 60 s (authenticated) - Plus plan: 600 / 60 s (public), 300 / 60 s (authenticated) - Pro plan: 3000 / 60 s (public), 1500 / 60 s (authenticated)\nThe gateway returns `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` (Unix seconds) on every response. On HTTP 429 it additionally returns `Retry-After` (seconds); back off and retry after that interval.\n",
    "contact": {
      "name": "File Convert Online Support",
      "url": "https://file-convert.online/contact",
      "email": "support@file-convert.online"
    },
    "license": {
      "name": "Apache 2.0",
      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
    }
  },
  "servers": [
    {
      "url": "https://file-convert.online",
      "description": "Production gateway"
    }
  ],
  "components": {
    "securitySchemes": {
      "OAuth2": {
        "type": "openIdConnect",
        "openIdConnectUrl": "https://auth.file-convert.online/realms/fileconvert/.well-known/openid-configuration",
        "description": "OAuth 2.1 / OpenID Connect against the file-convert.online Keycloak realm. Send the realm access token as `Authorization: Bearer <token>`. Interactive agents (MCP clients) discover the authorization server from `GET /api/mcp/.well-known/oauth-protected-resource`, register themselves dynamically (RFC 7591, no initial access token) and run the authorization-code flow with PKCE; the user signs in with their normal file-convert.online account. Access tokens are short-lived; use the refresh token to renew. There are no API keys.\n"
      }
    },
    "schemas": {
      "Envelope": {
        "type": "object",
        "description": "Standard JSON envelope used by all services. Branch on the HTTP status code, not just the `status` field.\n",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "example": "success"
          },
          "message": {
            "type": "string",
            "description": "Human-readable description of the result or error.",
            "example": "Conversion created successfully"
          },
          "data": {
            "description": "Payload on success; absent or null on error."
          }
        },
        "required": [
          "status",
          "message"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ],
            "example": "error"
          },
          "message": {
            "type": "string",
            "description": "Human-readable error description.",
            "example": "file not found"
          }
        },
        "required": [
          "status",
          "message"
        ]
      },
      "FileDTO": {
        "type": "object",
        "description": "File metadata returned by the file-service.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Internal numeric ID."
          },
          "uid": {
            "type": "string",
            "description": "Public UUID used in all subsequent API calls.",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "name": {
            "type": "string",
            "description": "Original filename.",
            "example": "report.pdf"
          },
          "status": {
            "type": "string",
            "description": "File lifecycle status. Key values: `uploaded` (ready to use), `expired` (retention window elapsed, no longer downloadable).\n",
            "example": "uploaded"
          },
          "upload_time": {
            "type": "string",
            "format": "date-time",
            "description": "UTC timestamp of upload."
          },
          "last_access_time": {
            "type": "string",
            "format": "date-time",
            "description": "UTC timestamp of last download."
          },
          "original": {
            "type": "boolean",
            "description": "True for source files uploaded by the caller; false for converted outputs."
          },
          "metadata": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FileMetadata"
            }
          }
        },
        "required": [
          "id",
          "uid",
          "name",
          "status",
          "upload_time",
          "original"
        ]
      },
      "FileMetadata": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "key": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        }
      },
      "FileFormat": {
        "type": "object",
        "description": "A single supported file format (e.g. PDF, JPEG, MP4).",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Format ID — use this in conversion requests.",
            "example": 12
          },
          "format_type_id": {
            "type": "integer",
            "description": "Parent format-type group (e.g. 1=document, 2=image, 3=video).",
            "example": 1
          },
          "extension": {
            "type": "string",
            "description": "File extension without leading dot.",
            "example": "pdf"
          },
          "name": {
            "type": "string",
            "description": "Human-readable format name.",
            "example": "PDF"
          },
          "description": {
            "type": "string",
            "example": "Portable Document Format"
          }
        }
      },
      "FileFormatType": {
        "type": "object",
        "description": "A category of file formats (e.g. document, image, video, audio).",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Format-type ID — used as `format_type_id` in conversion requests.",
            "example": 1
          },
          "name": {
            "type": "string",
            "example": "document"
          },
          "description": {
            "type": "string",
            "example": "Text and presentation documents"
          }
        }
      },
      "ConversionRequest": {
        "type": "object",
        "description": "Body for POST /api/conversion/v1/conversion. The four ID fields are required. Obtain the ID values from the formats catalogue (GET /api/conversion/v1/file_formats and GET /api/conversion/v1/file_formats/types).\n**`additional_arguments` MUST be sent as an explicit empty object `{}` when not used.** Do not omit the field and do not send `null` — the underlying JSONB column rejects absent/null values at the database layer. This is a known service-side constraint, not a schema requirement; the service does not return a validation error on omission in all cases, but passing `{}` is the only safe behaviour for conformant clients.\n",
        "properties": {
          "file_uid": {
            "type": "string",
            "minLength": 36,
            "maxLength": 36,
            "description": "UID of the source file returned by POST /api/file/v1/file.",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "format_type_id": {
            "type": "integer",
            "minimum": 1,
            "description": "Format-type category ID (from GET /api/conversion/v1/file_formats/types).",
            "example": 1
          },
          "source_format_id": {
            "type": "integer",
            "minimum": 1,
            "description": "Source file format ID (from GET /api/conversion/v1/file_formats).",
            "example": 12
          },
          "target_format_id": {
            "type": "integer",
            "minimum": 1,
            "description": "Target file format ID (from GET /api/conversion/v1/file_formats).",
            "example": 34
          },
          "additional_arguments": {
            "type": "object",
            "additionalProperties": true,
            "description": "Free-form converter arguments. Send `{}` when unused — do not omit or send null.\n",
            "example": {}
          },
          "effects": {
            "type": "array",
            "description": "Optional post-processing effects from the effects catalogue.",
            "items": {
              "$ref": "#/components/schemas/ConversionEffectRequest"
            }
          }
        },
        "required": [
          "file_uid",
          "format_type_id",
          "source_format_id",
          "target_format_id"
        ]
      },
      "ConversionEffectRequest": {
        "type": "object",
        "properties": {
          "effect_id": {
            "type": "integer",
            "minimum": 1,
            "description": "Effect ID from GET /api/conversion/v1/effects.",
            "example": 3
          },
          "params": {
            "type": "object",
            "additionalProperties": true,
            "description": "Parameter values matching the effect's schema.",
            "example": {
              "quality": 80
            }
          }
        },
        "required": [
          "effect_id"
        ]
      },
      "NewConversionDTO": {
        "type": "object",
        "description": "Data field of the POST /api/conversion/v1/conversion 200 response.",
        "properties": {
          "uid": {
            "type": "string",
            "description": "Conversion UID. Use this to poll GET /api/conversion/v1/conversion/uid/{uid} and to subscribe on the WebSocket.\n",
            "example": "7b0a1e60-0c3e-4c59-9f2e-1234567890ab"
          }
        }
      },
      "ConversionDTO": {
        "type": "object",
        "description": "Full conversion record.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "uid": {
            "type": "string",
            "description": "Conversion UID.",
            "example": "7b0a1e60-0c3e-4c59-9f2e-1234567890ab"
          },
          "job_uid": {
            "type": "string",
            "description": "Underlying job UID (for job-level status if needed)."
          },
          "source_file_uid": {
            "type": "string",
            "description": "UID of the uploaded source file."
          },
          "converted_file_uid": {
            "type": "string",
            "description": "UID of the converted output file. Populated once status is `completed`. Pass this to GET /api/file/v1/file/download/{uid} to retrieve the result.\n",
            "example": "a1b2c3d4-0000-4000-8000-111111111111"
          },
          "status": {
            "type": "string",
            "description": "Current status name. Canonical values (fetch the list from GET /api/conversion/v1/status rather than hardcoding): Received, Waiting, Processing, Completed, Failed.\n",
            "example": "Completed"
          },
          "conversion_type": {
            "type": "string",
            "nullable": true,
            "description": "Format-type category name (e.g. \"image\", \"document\"). Null until the conversion record is fully committed. Wire field name is `conversion_type` — there is no `format_type_id` field on this DTO.\n"
          },
          "source_format_id": {
            "type": "string",
            "nullable": true,
            "description": "Source format identifier as a string value. May be null until conversion is fully committed."
          },
          "target_format_id": {
            "type": "string",
            "nullable": true,
            "description": "Target format identifier as a string value. May be null until conversion is fully committed."
          },
          "job_template_id": {
            "type": "integer"
          },
          "user_id": {
            "type": "integer"
          },
          "additional_arguments": {
            "type": "object",
            "additionalProperties": true
          },
          "effects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConversionEffectDTO"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ConversionEffectDTO": {
        "type": "object",
        "properties": {
          "effect": {
            "$ref": "#/components/schemas/EffectDTO"
          },
          "parameters": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "EffectDTO": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "Effect": {
        "type": "object",
        "description": "A post-processing effect from the catalogue.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Effect ID — use in ConversionEffectRequest.effect_id."
          },
          "format_type_id": {
            "type": "integer",
            "description": "The format-type group this effect applies to."
          },
          "name": {
            "type": "string",
            "description": "Internal name."
          },
          "display_name": {
            "type": "string",
            "description": "Human-readable name."
          },
          "description": {
            "type": "string"
          },
          "parameters": {
            "type": "object",
            "description": "Schema for each parameter. Each key is a parameter name; the value is a descriptor object. The `_meta` key is internal engine wiring — not a user parameter.\n",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "ConversionStatus": {
        "type": "object",
        "description": "A canonical conversion status entry.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Status ID. Fetch this list rather than hardcoding IDs."
          },
          "name": {
            "type": "string",
            "description": "Status name (e.g. Received, Waiting, Processing, Completed, Failed)."
          },
          "description": {
            "type": "string"
          }
        }
      },
      "SetRetentionRequest": {
        "type": "object",
        "description": "Body for PUT /api/file/v1/file/uid/{uid}/retention.",
        "properties": {
          "retention_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 14,
            "description": "Number of days from upload time before the file expires. Capped at 14.",
            "example": 7
          }
        },
        "required": [
          "retention_days"
        ]
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, expired or invalid bearer token. Refresh the token (or sign in again) and retry; tokens are bound to the environment that issued them.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Key is valid but not authorized for this operation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found, or belongs to a different user/app (the API returns 404 rather than 403 for cross-tenant reads).\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded. Respect the `Retry-After` header (seconds) and the `X-RateLimit-Reset` header (Unix epoch) before retrying.\n",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait before retrying."
          },
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "integer"
            },
            "description": "Unix epoch seconds when the window resets."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "InternalError": {
        "description": "Server error. Retry with exponential backoff.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "schema": {
          "type": "integer"
        },
        "description": "Maximum requests allowed in the current window."
      },
      "X-RateLimit-Remaining": {
        "schema": {
          "type": "integer"
        },
        "description": "Requests remaining in the current window."
      },
      "X-RateLimit-Reset": {
        "schema": {
          "type": "integer"
        },
        "description": "Unix epoch seconds when the current window resets."
      }
    }
  },
  "security": [
    {
      "OAuth2": []
    }
  ],
  "paths": {
    "/api/conversion/v1/file_formats/types": {
      "get": {
        "operationId": "listFormatTypes",
        "summary": "List all format-type categories (document, image, video, audio, …).",
        "description": "Returns the top-level format-type categories. Each type groups related formats (e.g. all image formats share the same `format_type_id`). Fetch this list once and cache it; use the `id` as `format_type_id` in conversion requests. Response is stable and changes rarely.\n",
        "tags": [
          "Formats"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "List of format types.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/FileFormatType"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversion/v1/file_formats": {
      "get": {
        "operationId": "listFormats",
        "summary": "List all supported file formats.",
        "description": "Returns every supported source and target format. Each entry carries an `id` (use as `source_format_id` or `target_format_id`) and a `format_type_id` (use as `format_type_id`). Filter by type using the `/file_formats/{type}` variant. Cache this list — it changes infrequently.\n",
        "tags": [
          "Formats"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "List of all file formats.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/FileFormat"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversion/v1/file_formats/{type}": {
      "get": {
        "operationId": "listFormatsByType",
        "summary": "List supported file formats filtered by format-type ID.",
        "description": "Returns formats that belong to the given format-type category. Useful for building a filtered picker (e.g. show only image formats).\n",
        "tags": [
          "Formats"
        ],
        "security": [],
        "parameters": [
          {
            "name": "type",
            "in": "path",
            "required": true,
            "description": "Format-type ID from GET /api/conversion/v1/file_formats/types.",
            "schema": {
              "type": "integer",
              "example": 2
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Formats for the given type.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/FileFormat"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversion/v1/effects": {
      "get": {
        "operationId": "listEffects",
        "summary": "List all available post-processing effects.",
        "description": "Returns the full effects catalogue. Each effect belongs to a format-type group (`format_type_id`) and declares a `parameters` schema describing accepted parameter names and their constraints. Pass selected effects as the `effects` array when creating a conversion.\n",
        "tags": [
          "Formats"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "All available effects.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Effect"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversion/v1/status": {
      "get": {
        "operationId": "listConversionStatuses",
        "summary": "List canonical conversion status definitions.",
        "description": "Returns the authoritative list of conversion status names and their IDs. Fetch this at startup and use the names rather than hardcoding integer IDs; the canonical status names are Received, Waiting, Processing, Completed, and Failed.\n",
        "tags": [
          "Formats"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "All conversion statuses.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ConversionStatus"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/file/v1/file": {
      "post": {
        "operationId": "uploadFile",
        "summary": "Upload a source file for conversion.",
        "description": "Accepts a multipart/form-data upload. Returns file metadata including the `uid` — pass this as `file_uid` when creating a conversion. Maximum upload size: 50 MB (server-enforced). The gateway derives the app context from the request hostname; do not send `X-App-Id`.\n**Retention:** the default retention window is 1 hour. To keep the file longer (up to 14 days), call PUT /api/file/v1/file/uid/{uid}/retention immediately after upload.\n",
        "tags": [
          "Files"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The file to upload."
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File uploaded successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/FileDTO"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/file/v1/file/uid/{uid}": {
      "get": {
        "operationId": "getFile",
        "summary": "Retrieve file metadata by UID.",
        "description": "Returns metadata for a file owned by the calling user. Use `converted_file_uid` (from the conversion record) to look up the output file after conversion completes. Returns 404 for files that belong to a different user.\n",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "description": "File UID returned by POST /api/file/v1/file or from the conversion record.",
            "schema": {
              "type": "string",
              "example": "550e8400-e29b-41d4-a716-446655440000"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/FileDTO"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/file/v1/file/download/{uid}": {
      "get": {
        "operationId": "downloadFile",
        "summary": "Download file bytes by UID.",
        "description": "Returns the raw file bytes. Use `converted_file_uid` from the conversion record to download the converted output. The response Content-Type reflects the file type. Returns 404 if the file does not exist, belongs to a different user, or has expired (status `expired`).\n**Retention note:** files are retained for up to 14 days from upload/conversion. The default platform window is 1 hour; extend it via PUT /api/file/v1/file/uid/{uid}/retention (1–14 days) immediately after the conversion completes if you need longer access.\n",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "description": "File UID to download. For converted output use `converted_file_uid`.",
            "schema": {
              "type": "string",
              "example": "a1b2c3d4-0000-4000-8000-111111111111"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File bytes.",
            "headers": {
              "Content-Disposition": {
                "schema": {
                  "type": "string"
                },
                "description": "Attachment filename."
              }
            },
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/file/v1/file/uid/{uid}/retention": {
      "put": {
        "operationId": "setFileRetention",
        "summary": "Extend a file's retention window (1–14 days).",
        "description": "Sets how long the file is kept, measured from its upload time. The hard ceiling is 14 days (`MaxRetentionDays`). Call this immediately after uploading a source file or after a conversion completes if you need the file to remain accessible beyond the 1-hour default. Only the owning user may update a file's retention.\n",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "description": "File UID.",
            "schema": {
              "type": "string",
              "example": "550e8400-e29b-41d4-a716-446655440000"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetRetentionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Retention updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "retention_days is outside the allowed 1–14 range, or body is malformed.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversion/v1/conversion": {
      "post": {
        "operationId": "createConversion",
        "summary": "Start a new file conversion job.",
        "description": "Creates a conversion from a previously uploaded file. The service reserves credits immediately; if insufficient credits are available the request fails with a 4xx error — present an upsell rather than a generic error message. Capture the `data.uid` (conversion UID) from the response to track progress via polling or WebSocket.\n**Typical flow:** 1. POST /api/file/v1/file — upload source file, get `file_uid`. 2. POST /api/conversion/v1/conversion — create conversion, get `uid`. 3. Poll GET /api/conversion/v1/conversion/uid/{uid} or subscribe on\n   wss://file-convert.online/api/conversion-status/ws.\n4. When status is `Completed`, use `converted_file_uid` with\n   GET /api/file/v1/file/download/{uid} to download the result.\n",
        "tags": [
          "Conversions"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConversionRequest"
              },
              "example": {
                "file_uid": "550e8400-e29b-41d4-a716-446655440000",
                "format_type_id": 1,
                "source_format_id": 12,
                "target_format_id": 34,
                "additional_arguments": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Conversion created. Capture `data.uid` to track progress.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/NewConversionDTO"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversion/v1/conversion/uid/{uid}": {
      "get": {
        "operationId": "getConversion",
        "summary": "Get the full conversion record by conversion UID.",
        "description": "Returns the complete conversion object including current `status` and, once the job is done, `converted_file_uid`. Use this to poll for completion if not using the WebSocket. Also call this after a WebSocket reconnect to resync state. Returns 404 for conversions that belong to a different user.\n",
        "tags": [
          "Conversions"
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "description": "Conversion UID returned by POST /api/conversion/v1/conversion.",
            "schema": {
              "type": "string",
              "example": "7b0a1e60-0c3e-4c59-9f2e-1234567890ab"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Full conversion record. When `status` is `Completed`, `converted_file_uid` is populated and ready for download.\n",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/ConversionDTO"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversion/v1/conversion/uid/{uid}/status": {
      "get": {
        "operationId": "getConversionStatus",
        "summary": "Get just the status of a conversion by UID.",
        "description": "Lightweight endpoint that returns only the current status string. Prefer this over GET /api/conversion/v1/conversion/uid/{uid} when polling — less payload. Fetch the full record to get `converted_file_uid` once `Completed` is observed.\n",
        "tags": [
          "Conversions"
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "description": "Conversion UID.",
            "schema": {
              "type": "string",
              "example": "7b0a1e60-0c3e-4c59-9f2e-1234567890ab"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current status.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "description": "Status data (shape is a string map — fetch the full record for typed access)."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversion/v1/conversion/search": {
      "get": {
        "operationId": "listConversions",
        "summary": "List and filter the caller's past conversions.",
        "description": "Returns the authenticated caller's conversion history. All parameters are optional filters. Useful for building a conversion history view or for finding a previous conversion's output file UID.\n",
        "tags": [
          "Conversions"
        ],
        "parameters": [
          {
            "name": "status_id",
            "in": "query",
            "description": "Filter by conversion status ID (from GET /api/conversion/v1/status).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "source_format_id",
            "in": "query",
            "description": "Filter by source format ID.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "target_format_id",
            "in": "query",
            "description": "Filter by target format ID.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "format_type_id",
            "in": "query",
            "description": "Filter by format-type category ID.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "created",
            "in": "query",
            "description": "Filter by creation timestamp (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "completed",
            "in": "query",
            "description": "Filter by completion timestamp (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "sort_by",
            "in": "query",
            "description": "Field to sort by (e.g. created_at, completed_at).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort_order",
            "in": "query",
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "page_number",
            "in": "query",
            "description": "Page number (1-based). Sent as a string query parameter (service swagger type is string).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "description": "Results per page. Sent as a string query parameter (service swagger type is string).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of conversions.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ConversionDTO"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversion-status/ws": {
      "get": {
        "operationId": "conversionStatusWebSocket",
        "summary": "WebSocket stream for real-time conversion status updates.",
        "description": "Upgrade to WebSocket to receive push notifications as conversions progress. One connection can track multiple conversions.\n**Connect:** `wss://file-convert.online/api/conversion-status/ws`\n**Subscribe** (send after open): ```json {\"action\": \"subscribe\", \"conversion_uid\": \"<uid>\"} ```\n**Messages from server:**\nSubscribe acknowledgement: ```json {\"type\": \"subscribed\", \"conversion_uid\": \"<uid>\"} ```\nStatus update (fires on each transition): ```json {\n  \"message_type\": \"conversion_status\",\n  \"conversion_uid\": \"<uid>\",\n  \"job_uid\": \"<uid>\",\n  \"status\": \"Processing\",\n  \"status_id\": 3,\n  \"step\": \"...\",\n  \"progress\": {\"current_step\": 1, \"total_steps\": 3, \"percentage\": 40}\n} ```\nCompletion event (enables download): ```json {\n  \"message_type\": \"conversion_job_client_update\",\n  \"conversion_uid\": \"<uid>\",\n  \"job_uid\": \"<uid>\",\n  \"converted_file_uid\": \"<file uid>\",\n  \"status\": \"completed\",\n  \"job_status_id\": 3\n} ```\nError: ```json {\"type\": \"error\", \"message\": \"...\"} ```\nOn `conversion_job_client_update` with `converted_file_uid` (or `status: completed`), call GET /api/file/v1/file/download/{converted_file_uid} to download the result. Always maintain a REST fallback: on socket drop/reconnect, call GET /api/conversion/v1/conversion/uid/{uid} to resync.\n",
        "tags": [
          "Conversions"
        ],
        "responses": {
          "101": {
            "description": "Switching protocols — WebSocket connection established."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Formats",
      "description": "Discover supported formats, format-type categories, effects, and status definitions. These endpoints are public (no key required) and should be fetched once at startup and cached.\n"
    },
    {
      "name": "Files",
      "description": "Upload source files and download converted results."
    },
    {
      "name": "Conversions",
      "description": "Create conversion jobs, poll for status, and manage conversion history. Real-time updates are available via the WebSocket endpoint.\n"
    }
  ]
}
