{"openapi":"3.1.0","info":{"title":"ext.systems API","version":"1.0.0","description":"Public API for ext.systems image generation. Authenticate with an API key from /settings/keys."},"servers":[{"url":"https://www.ext.systems","description":"Production"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"API key from /settings/keys. Format: ext_<key>. Pass as Bearer token."}},"schemas":{"WebhookPayload":{"type":"object","properties":{"id":{"type":"number","description":"Generation ID"},"status":{"type":"string","enum":["COMPLETED","FAILED","CANCELLED"],"description":"Terminal status"},"percentage":{"type":["number","null"]},"image_url":{"type":["string","null"],"description":"Presigned URL (when COMPLETED)"},"error":{"type":["string","null"],"description":"Error (when FAILED)"},"created_at":{"type":["string","null"]},"started_at":{"type":["string","null"]},"finished_at":{"type":["string","null"]}},"required":["id","status","percentage","image_url","error","created_at","started_at","finished_at"]},"FileUploadResponse":{"type":"object","properties":{"file_id":{"type":"string","description":"File identifier to reference in generation requests","example":"a1b2c3d4e5f6.png"}},"required":["file_id"]},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[false],"example":false},"message":{"type":"string","example":"Invalid request payload"}},"required":["success","message"]},"CreateImageResponse":{"type":"object","properties":{"id":{"type":"number","description":"Generation ID for polling status","example":123},"status":{"type":"string","enum":["PENDING"],"description":"Initial status","example":"PENDING"},"model":{"type":"string","description":"Model used","example":"wan22-t2i"},"created_at":{"type":"string","description":"ISO 8601 timestamp","example":"2026-03-25T12:00:00Z"}},"required":["id","status","model","created_at"]},"CreateImageRequest":{"type":"object","properties":{"model":{"type":"string","enum":["wan22-t2i","illustrious-hentai-rx","qwen-edit-advanced"],"description":"The image generation model to use. qwen-edit-advanced requires a file_id from POST /api/v1/files.","example":"wan22-t2i"},"prompt":{"type":"string","minLength":1,"maxLength":1000,"description":"Text prompt describing the image to generate or the edit to apply","example":"A cyberpunk cityscape at sunset with neon lights"},"negative_prompt":{"type":"string","maxLength":1000,"description":"Text describing what to exclude from the image","example":"blurry, low quality"},"seed":{"type":"integer","minimum":0,"maximum":4294967295,"description":"Seed for reproducible generation. Random if omitted.","example":42},"width":{"type":"integer","minimum":256,"maximum":2048,"description":"Image width in pixels (for T2I models: wan22-t2i, illustrious-hentai-rx)","example":1536},"height":{"type":"integer","minimum":256,"maximum":2048,"description":"Image height in pixels (for T2I models: wan22-t2i, illustrious-hentai-rx)","example":1536},"file_id":{"type":"string","description":"Input image file ID from POST /api/v1/files. Required for qwen-edit-advanced.","example":"a1b2c3d4e5f6.png"},"instruction":{"type":"string","description":"Custom editing instruction for qwen-edit-advanced"},"target_size":{"type":"integer","minimum":256,"maximum":2048,"description":"Output image size in pixels for qwen-edit-advanced (default 1024)","example":1024},"target_vl_size":{"type":"integer","minimum":128,"maximum":1024,"description":"Vision-language processing size for qwen-edit-advanced (default 384)","example":384},"upscale_method":{"type":"string","description":"Upscale method for qwen-edit-advanced: lanczos, nearest, bilinear, bicubic"},"crop_method":{"type":"string","description":"Crop method for qwen-edit-advanced: pad, center, or random"},"loras":{"type":"array","items":{"$ref":"#/components/schemas/Lora"},"description":"Optional LoRA adapters to apply"},"callback_url":{"type":"string","description":"Webhook URL to receive status updates (COMPLETED, FAILED, CANCELLED)","example":"https://example.com/webhook/generation"},"callback_api_key":{"type":"string","description":"Bearer token sent with webhook requests for authentication"}},"required":["model","prompt"]},"Lora":{"type":"object","properties":{"id":{"type":"string","description":"LoRA adapter identifier","example":"illustrious-hentai-studio-quality"},"strength":{"type":"number","minimum":0,"maximum":2,"description":"LoRA strength multiplier","example":0.8}},"required":["id","strength"]},"ImageStatusResponse":{"type":"object","properties":{"id":{"type":"number","description":"Generation ID","example":123},"status":{"type":"string","enum":["PENDING","PROCESSING","COMPLETED","FAILED","CANCELLED"],"description":"Current generation status","example":"COMPLETED"},"percentage":{"type":["number","null"],"description":"Progress 0-100","example":100},"model":{"type":["string","null"],"description":"Workflow/model ID","example":"wan22-t2i"},"image_url":{"type":"string","description":"Presigned URL to download the image (only when COMPLETED)","example":"https://r2.example.com/output/abc123.png?signature=..."},"error":{"type":"string","description":"Error details (only when FAILED)","example":"Workflow execution failed"},"created_at":{"type":["string","null"],"description":"ISO 8601"},"started_at":{"type":["string","null"],"description":"ISO 8601"},"finished_at":{"type":["string","null"],"description":"ISO 8601"}},"required":["id","status","percentage","model","created_at","started_at","finished_at"]},"CreateVideoResponse":{"type":"object","properties":{"id":{"type":"number","description":"Generation ID","example":456},"status":{"type":"string","enum":["PENDING"],"example":"PENDING"},"model":{"type":"string","example":"wan22-t2v"},"duration":{"type":"number","description":"Duration in seconds","example":3},"created_at":{"type":"string","example":"2026-03-25T12:00:00Z"}},"required":["id","status","model","duration","created_at"]},"CreateVideoRequest":{"type":"object","properties":{"model":{"type":"string","enum":["wan22-t2v","wan22-i2v"],"description":"Video generation model. wan22-t2v for text-to-video, wan22-i2v for image-to-video.","example":"wan22-t2v"},"prompt":{"type":"string","minLength":1,"maxLength":1000,"description":"Text prompt describing the video to generate","example":"A cat walking through a neon-lit alley at night"},"negative_prompt":{"type":"string","maxLength":1000,"description":"Text describing what to exclude","example":"blurry, low quality"},"seed":{"type":"integer","minimum":0,"maximum":4294967295,"description":"Seed for reproducible generation. Random if omitted.","example":42},"duration":{"type":"integer","minimum":1,"maximum":6,"description":"Video duration in seconds (1-6)","example":3},"width":{"type":"integer","minimum":256,"maximum":2048,"description":"Video width in pixels","example":1536},"height":{"type":"integer","minimum":256,"maximum":2048,"description":"Video height in pixels","example":1536},"file_id":{"type":"string","description":"Input image file ID from POST /api/v1/files. Required for wan22-i2v.","example":"a1b2c3d4e5f6.png"},"end_frame_file_id":{"type":"string","description":"End frame image for loop transition (optional)"},"loop":{"type":"boolean","description":"Generate looping video","example":false},"loras":{"type":"array","items":{"$ref":"#/components/schemas/Lora"},"description":"Optional LoRA adapters"},"callback_url":{"type":"string","description":"Webhook URL for status notifications","example":"https://example.com/webhook/video"},"callback_api_key":{"type":"string","description":"Bearer token sent with webhook requests"}},"required":["model","prompt","duration"]},"VideoStatusResponse":{"type":"object","properties":{"id":{"type":"number","description":"Generation ID","example":456},"status":{"type":"string","enum":["PENDING","PROCESSING","COMPLETED","FAILED","CANCELLED"],"example":"COMPLETED"},"percentage":{"type":["number","null"],"example":100},"model":{"type":["string","null"],"example":"wan22-t2v"},"video_url":{"type":"string","description":"Presigned URL to download the video (only when COMPLETED)"},"error":{"type":"string","description":"Error details (only when FAILED)"},"created_at":{"type":["string","null"],"description":"ISO 8601"},"started_at":{"type":["string","null"],"description":"ISO 8601"},"finished_at":{"type":["string","null"],"description":"ISO 8601"}},"required":["id","status","percentage","model","created_at","started_at","finished_at"]}},"parameters":{}},"paths":{"/api/v1/files":{"post":{"operationId":"uploadFile","summary":"Upload a file for use in generation requests","description":"Upload an image file (JPG/PNG, max 10MB) via multipart/form-data. Returns a file_id to reference in image editing (qwen-edit-advanced) or video generation requests.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"Image file (JPG or PNG, max 10MB)"}},"required":["file"]}}}},"responses":{"201":{"description":"File uploaded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileUploadResponse"}}}},"400":{"description":"Invalid file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/img":{"post":{"operationId":"createImageGeneration","summary":"Create an image generation job","description":"Submit an image generation or editing request. For text-to-image (wan22-t2i, illustrious-hentai-rx), provide a prompt and optional width/height. For image editing (qwen-edit-advanced), upload an image first via POST /api/v1/files and provide the file_id. Returns a generation ID that can be polled via GET /api/v1/img/{generationId}.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateImageRequest"}}}},"responses":{"201":{"description":"Generation job created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateImageResponse"}}}},"400":{"description":"Invalid request payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Queue limit reached (max 6 concurrent generations)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Image generation service error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Image generation service temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/img/{generationId}":{"get":{"operationId":"getImageGenerationStatus","summary":"Check the status of an image generation job","description":"Poll this endpoint to check progress and retrieve the image URL when generation is complete. The image_url field is only present when status is COMPLETED.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Generation ID from the create response","example":"123"},"required":true,"description":"Generation ID from the create response","name":"generationId","in":"path"}],"responses":{"200":{"description":"Generation status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageStatusResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Generation not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/video":{"post":{"operationId":"createVideoGeneration","summary":"Create a video generation job","description":"Submit a text-to-video or image-to-video generation request. For I2V, upload an image first via POST /api/v1/files and pass the file_id. Returns a generation ID that can be polled via GET /api/v1/video/{generationId}.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVideoRequest"}}}},"responses":{"201":{"description":"Generation job created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVideoResponse"}}}},"400":{"description":"Invalid request payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Queue limit reached (max 6 concurrent generations)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Video generation service error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Video generation service temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/video/{generationId}":{"get":{"operationId":"getVideoGenerationStatus","summary":"Check the status of a video generation job","description":"Poll this endpoint to check progress and retrieve the video URL when generation is complete. The video_url field is only present when status is COMPLETED.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Generation ID from the create response","example":"456"},"required":true,"description":"Generation ID from the create response","name":"generationId","in":"path"}],"responses":{"200":{"description":"Generation status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoStatusResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Generation not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"webhooks":{}}