{
  "openapi": "3.1.0",
  "info": {
    "title": "Nightfolio AI Generation API",
    "version": "1.0.0-preview",
    "description": "Nightfolio AI text, image and motion API. A valid Nightfolio AI Bearer key and sufficient available credit are required. Displayed Nightfolio prices are used for reservation and successful-request charges. Credit is reserved before submission and charged only after successful completion. Image and motion generation use custom asynchronous jobs. Claude /v1/messages and OpenAI /v1/responses are not implemented. Reference assets use the same Bearer key via /v1/assets. Asset submission returns 202; poll the returned ID until active before referencing it in generation."
  },
  "servers": [
    {
      "url": "https://developer.nightfolioai.com"
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/v1/chat/completions": {
      "post": {
        "operationId": "createChatCompletion",
        "summary": "Create a text completion",
        "tags": ["text"],
        "description": "OpenAI-compatible text-only Chat Completions for published models including deepseek-v4.1-flash-1017 and deepseek-v4-pro. DeepSeek is billed at the connected MR account catalog price with zero markup. Uncached input, cached input and output are priced separately using a UTC rule snapshot fixed at request start. Reasoning tokens are included in output. Charges are summed exactly then rounded up once to micro-USD. A hold is reserved before submission; missing or inconsistent usage retains it for review. Supplier cost is a catalog estimate; invoice reconciliation is manual. Chat retries are not deduplicated.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "required": ["model", "messages"],
                "properties": {
                  "model": {
                    "type": "string",
                    "example": "deepseek-v4.1-flash-1017",
                    "description": "Published model ID from GET /v1/models; DeepSeek: deepseek-v4.1-flash-1017 or deepseek-v4-pro."
                  },
                  "messages": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 200,
                    "items": {
                      "type": "object",
                      "required": ["role", "content"],
                      "properties": {
                        "role": {
                          "enum": [
                            "system",
                            "developer",
                            "user",
                            "assistant",
                            "tool",
                            "function"
                          ]
                        },
                        "content": {
                          "type": ["string", "null"]
                        }
                      }
                    }
                  },
                  "max_tokens": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Default 1024. DeepSeek platform maximum 65536, including reasoning. Cannot be combined with max_completion_tokens."
                  },
                  "temperature": {
                    "type": "number"
                  },
                  "stream": {
                    "type": "boolean",
                    "default": false,
                    "description": "Returns text/event-stream. Usage is requested automatically and can accompany a final choices chunk. Process content and reasoning_content independently, then [DONE]. An error event can follow partial text. DeepSeek timeout: 10 minutes."
                  },
                  "n": {
                    "type": "integer",
                    "const": 1
                  },
                  "thinking": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": ["type"],
                    "properties": {
                      "type": {
                        "enum": ["enabled", "disabled"]
                      }
                    },
                    "description": "DeepSeek only. Omit to use upstream defaults. Workspace default is disabled."
                  },
                  "reasoning_effort": {
                    "enum": ["none", "low", "high", "max"],
                    "description": "DeepSeek only. For thinking enabled, use low, high or max."
                  },
                  "max_completion_tokens": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Alternative output limit; mutually exclusive with max_tokens."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chat completion or SSE stream. X-Request-ID identifies its billing record. Nonstream responses can include X-Billing-Status: pending-review. Stream billing uncertainty is signaled by an error event before [DONE]. Usage.prompt_tokens includes cached input; usage.prompt_tokens_details.cached_tokens (or native prompt_cache_hit_tokens) identifies the cached portion. usage.completion_tokens includes reasoning; completion_tokens_details.reasoning_tokens is a subset, not an additional charge.",
            "headers": {
              "X-Request-ID": {
                "schema": {
                  "type": "string"
                },
                "description": "Request ID for usage and billing support."
              },
              "X-Billing-Status": {
                "schema": {
                  "type": "string"
                },
                "description": "pending-review when valid usage was not received."
              }
            }
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Invalid key"
          },
          "402": {
            "description": "Insufficient credit"
          },
          "404": {
            "description": "Model not found"
          },
          "503": {
            "description": "Catalog pricing or upstream connection temporarily unavailable; request not submitted."
          }
        }
      }
    },
    "/v1/images/generations": {
      "post": {
        "operationId": "createImages",
        "summary": "Queue one image generation",
        "tags": ["images"],
        "description": "Nightfolio AI asynchronous job API. Prices and generation specifications must be published first. No supplier key is required. This is not a synchronous OpenAI Images response.",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": ["model", "prompt"],
                "properties": {
                  "model": {
                    "type": "string",
                    "example": "nightfolio-image-4.5"
                  },
                  "prompt": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 8000
                  },
                  "size": {
                    "type": "string",
                    "description": "Must exactly match an explicit dimension published for the selected model, for example 2848x1600."
                  },
                  "watermark": {
                    "type": "boolean",
                    "default": false
                  },
                  "image": {
                    "description": "One reference image URL or an array within the selected model limit. Values may be owned active asset:// image IDs or public HTTPS URLs. Nightfolio Image 5.0 Pro accepts up to 10; other published Seedream profiles accept up to 14.",
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 14,
                        "items": {
                          "type": "string"
                        }
                      }
                    ]
                  },
                  "quality": {
                    "type": "string",
                    "description": "GPT Image 2 only; medium."
                  },
                  "n": {
                    "type": "integer",
                    "const": 1
                  },
                  "response_format": {
                    "const": "b64_json"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted or replay of the same idempotent operation. Poll using the returned ID.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-ID": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "400": {
            "description": "Invalid fields or profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Model not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body exceeds 1 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate or queue limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Unpublished model or unavailable service",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/images/{id}": {
      "get": {
        "operationId": "getImages",
        "summary": "Read an owned job and its result",
        "tags": ["images"],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job; result data retained for 24 hours. Download links may expire earlier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "description": "Invalid key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Job not found or not owned by this account"
          },
          "429": {
            "description": "Rate or queue limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/videos/generations": {
      "post": {
        "operationId": "createVideos",
        "summary": "Queue one video generation",
        "tags": ["videos"],
        "description": "Nightfolio AI asynchronous job API. Prices and generation specifications must be published first. No supplier key is required. This is not a synchronous OpenAI Images response.",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": ["model", "prompt"],
                "properties": {
                  "model": {
                    "type": "string",
                    "example": "nightfolio-motion-2.0-fast"
                  },
                  "prompt": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 8000
                  },
                  "seconds": {
                    "oneOf": [
                      {
                        "type": "integer",
                        "minimum": 4,
                        "maximum": 30
                      },
                      {
                        "const": -1
                      }
                    ],
                    "default": 5,
                    "description": "Nightfolio Motion 2.0/Fast/Mini: 4-15 seconds. Nightfolio Motion 2.5: 4-30 seconds. A video reference requires -1 for automatic duration. Published Nightfolio Motion 1.x profiles remain fixed at 5 seconds."
                  },
                  "resolution": {
                    "type": "string",
                    "enum": ["480p", "720p", "1080p", "4k"],
                    "default": "720p",
                    "description": "Nightfolio Motion 2.0 supports all listed values for text/video-reference requests, but reference-image and frame-control requests do not support 1080p. Fast, Mini and 2.5 support 480p/720p. Published Nightfolio Motion 1.x profiles remain fixed at 720p."
                  },
                  "aspect_ratio": {
                    "type": "string",
                    "enum": [
                      "16:9",
                      "9:16",
                      "1:1",
                      "4:3",
                      "3:4",
                      "21:9",
                      "adaptive"
                    ],
                    "default": "16:9"
                  },
                  "generate_audio": {
                    "type": "boolean",
                    "default": false,
                    "description": "Supported by exposed Nightfolio Motion 2.x profiles. Published Nightfolio Motion 1.x profiles remain fixed at false."
                  },
                  "output_format": {
                    "type": "string",
                    "enum": ["mp4", "mov"],
                    "default": "mp4",
                    "description": "Supported only by Nightfolio Motion 2.5."
                  },
                  "input_references": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 50,
                    "description": "Nightfolio Motion 2.0 family: up to 9 images, 3 videos and 3 audio clips; audio cannot be used alone. Nightfolio Motion 2.5: up to 30 images, 10 videos and 10 audio clips; audio-only is supported. Use an owned active asset:// URL or a public HTTPS URL. Video references require seconds=-1. Mutually exclusive with frame_images.",
                    "items": {
                      "oneOf": [
                        {
                          "type": "object",
                          "additionalProperties": false,
                          "required": ["type", "url", "role"],
                          "properties": {
                            "type": {
                              "const": "image"
                            },
                            "url": {
                              "type": "string"
                            },
                            "role": {
                              "const": "reference_image"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "additionalProperties": false,
                          "required": ["type", "url", "role"],
                          "properties": {
                            "type": {
                              "const": "video"
                            },
                            "url": {
                              "type": "string"
                            },
                            "role": {
                              "const": "reference_video"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "additionalProperties": false,
                          "required": ["type", "url", "role"],
                          "properties": {
                            "type": {
                              "const": "audio"
                            },
                            "url": {
                              "type": "string"
                            },
                            "role": {
                              "const": "reference_audio"
                            }
                          }
                        }
                      ]
                    }
                  },
                  "frame_images": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 2,
                    "description": "Exact first-frame or first-and-last-frame control. The first_frame item is required; last_frame is optional. Use an owned active image asset:// URL or public HTTPS URL. Mutually exclusive with input_references. Nightfolio Motion 2.5 requires aspect_ratio=adaptive.",
                    "items": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": ["frame_type", "image_url"],
                      "properties": {
                        "frame_type": {
                          "enum": ["first_frame", "last_frame"]
                        },
                        "image_url": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted or replay of the same idempotent operation. Poll using the returned ID.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-ID": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "400": {
            "description": "Invalid fields or profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Model not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body exceeds 1 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "JSON required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate or queue limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Unpublished model or unavailable service",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/videos/{id}": {
      "get": {
        "operationId": "getVideos",
        "summary": "Read an owned job and its result",
        "tags": ["videos"],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job; result data retained for 24 hours. Download links may expire earlier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "description": "Invalid key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Job not found or not owned by this account"
          },
          "429": {
            "description": "Rate or queue limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/models": {
      "get": {
        "operationId": "listModels",
        "summary": "List published models",
        "responses": {
          "200": {
            "description": "Only models available for calls",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "object": {
                            "const": "model"
                          },
                          "owned_by": {
                            "const": "nightfolio"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/media/catalog": {
      "get": {
        "operationId": "getCatalog",
        "security": [],
        "summary": "List fixed image/video profiles, availability and USD prices",
        "responses": {
          "200": {
            "description": "Includes unpublished profiles. price=null means unpriced; integer prices use micro-USD. Underlying model family is factual attribution; supplier routing IDs, provider identity and costs are not exposed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["models"],
                  "properties": {
                    "models": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MediaCatalogModel"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/assets": {
      "post": {
        "operationId": "createAsset",
        "tags": ["assets"],
        "summary": "Upload a file or import a public HTTPS URL",
        "description": "Uses the customer API key without Console login. Assets are owned by the account and shared with its Console library. File uploads infer type; a supplied type must match. Limits: image 30 MB, video 200 MB, audio 15 MB; 8 upload attempts/minute/account shared with Console. URL imports are validated during processing. No group ID or provider key is accepted. Idempotency-Key is not supported: list existing assets before resubmitting an uncertain upload.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": ["file"],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Exactly one JPEG/PNG/WebP/BMP/TIFF/GIF/HEIC/HEIF image, MP4/MOV video or WAV/MP3 audio file."
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 180,
                    "description": "Optional display name."
                  },
                  "type": {
                    "type": "string",
                    "enum": ["image", "video", "audio"]
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": ["url", "type"],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 4096,
                    "description": "Public HTTPS URL, port 443, without credentials, fragment or a private/internal destination."
                  },
                  "type": {
                    "type": "string",
                    "enum": ["image", "video", "audio"]
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 180,
                    "description": "Optional display name."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted. Save the ID and poll GET /v1/assets/{id} until active or failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Asset"
                }
              }
            },
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "Relative status-query URL."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Suggested polling interval in seconds when processing."
              }
            }
          },
          "400": {
            "description": "Invalid import or upload fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Asset exceeds its media-type size limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content type or media format.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid/revoked API key or inactive account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Account rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Asset provider failed; retry status queries. After an uncertain upload response, list assets before resubmitting.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Asset service is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listAssets",
        "tags": ["assets"],
        "summary": "List assets owned by the authenticated account",
        "description": "Includes API and Console uploads in all local groups. Lists show saved status; GET an individual processing asset to refresh its state. No temporary provider download URLs are exposed.",
        "parameters": [
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            }
          },
          {
            "in": "query",
            "name": "page_size",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "in": "query",
            "name": "type",
            "schema": {
              "type": "string",
              "enum": ["image", "video", "audio"]
            }
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "enum": ["processing", "active", "failed"]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Saved assets, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["object", "data", "total", "page", "page_size"],
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Asset"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "page_size": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid pagination or filter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid/revoked API key or inactive account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Account rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/assets/{id}": {
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getAsset",
        "tags": ["assets"],
        "summary": "Query an asset and refresh its processing status",
        "description": "A processing record is persisted before upload returns. Transient polling errors preserve it; retry GET instead of uploading again. Active/failed are terminal.",
        "responses": {
          "200": {
            "description": "Asset status. Poll processing assets about every 2 seconds; only active assets are usable in generation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Asset"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "description": "Asset does not exist or belongs to another account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid/revoked API key or inactive account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Account rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Asset provider failed; retry status queries. After an uncertain upload response, list assets before resubmitting.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Asset service is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteAsset",
        "tags": ["assets"],
        "summary": "Delete an owned asset from API and Console",
        "description": "Wait for generation tasks referencing this asset to finish before deleting it. A provider deletion failure preserves the local record.",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["id", "object", "deleted"],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "object": {
                      "const": "asset"
                    },
                    "deleted": {
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Asset does not exist or belongs to another account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid/revoked API key or inactive account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Account rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Asset provider failed; retry status queries. After an uncertain upload response, list assets before resubmitting.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Asset service is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Nightfolio AI API key"
      }
    },
    "schemas": {
      "MediaCatalogModel": {
        "type": "object",
        "required": [
          "id",
          "name",
          "kind",
          "family",
          "supplier",
          "source_model",
          "parameters",
          "enabled",
          "availability",
          "billing_mode"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable Nightfolio model ID used in API requests",
            "example": "nightfolio-motion-2.5"
          },
          "name": {
            "type": "string",
            "example": "Nightfolio Motion 2.5"
          },
          "kind": {
            "enum": ["image", "video"]
          },
          "family": {
            "type": "string",
            "example": "Nightfolio Motion"
          },
          "supplier": {
            "const": "Nightfolio"
          },
          "source_model": {
            "type": "string",
            "description": "Underlying model family currently used by this endpoint",
            "example": "Seedance 2.5"
          },
          "parameters": {
            "type": "object",
            "additionalProperties": true
          },
          "enabled": {
            "type": "integer",
            "enum": [0, 1]
          },
          "availability": {
            "type": "string"
          },
          "billing_mode": {
            "type": "string"
          },
          "price": {
            "type": ["integer", "null"],
            "description": "Quoted generation price in micro-USD when available"
          },
          "catalog_pricing": {
            "type": ["object", "null"],
            "additionalProperties": true
          }
        }
      },
      "Job": {
        "type": "object",
        "required": [
          "id",
          "object",
          "model",
          "status",
          "created_at",
          "charge",
          "reserved",
          "billing_status"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "enum": ["image", "video"]
          },
          "model": {
            "type": "string"
          },
          "status": {
            "enum": [
              "queued",
              "submitting",
              "processing",
              "completed",
              "failed",
              "needs_review"
            ]
          },
          "created_at": {
            "type": "integer",
            "description": "Unix seconds"
          },
          "charge": {
            "type": "integer",
            "description": "micro-USD; 1 USD = 1,000,000"
          },
          "reserved": {
            "type": "integer",
            "description": "micro-USD"
          },
          "billing_status": {
            "type": "string"
          },
          "billing_mode": {
            "type": "string",
            "enum": ["metered", "unmetered"]
          },
          "result_expires_at": {
            "type": ["integer", "null"]
          },
          "result": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "b64_json": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "error": {
            "$ref": "#/components/schemas/ErrorDetail"
          }
        }
      },
      "ErrorDetail": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorDetail"
          }
        }
      },
      "Asset": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "object",
          "name",
          "type",
          "status",
          "reference_url",
          "size_bytes",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "YOUR_ASSET_ID"
          },
          "object": {
            "const": "asset"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "enum": ["image", "video", "audio"]
          },
          "status": {
            "enum": ["processing", "active", "failed"]
          },
          "reference_url": {
            "type": "string",
            "description": "Use only after status=active, as input_references[].url or frame_images[].image_url in video generation.",
            "example": "asset://YOUR_ASSET_ID"
          },
          "size_bytes": {
            "type": ["integer", "null"],
            "minimum": 0,
            "description": "Null for URL imports whose size is unknown."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}
