{
  "openapi": "3.0.3",
  "info": {
    "title": "ki-kennzeichnen API",
    "version": "1.0.0",
    "description": "REST API for labeling images and videos with the official EU icons for AI-generated content (EU AI Act Article 50).\n\n## Authentication\nEvery request carries an organization API key as a Bearer header: `Authorization: Bearer kk_live_...`. Keys are created in the account area, are scoped to one organization, and can be revoked at any time. The key value is shown exactly once at creation.\n\n## Quick start\n1. `POST /jobs` with your files as multipart form data. The job returns already running.\n2. Poll `GET /jobs/{id}` until the items succeed.\n3. Fetch the signed download URL per item via `GET /jobs/{id}/items/{itemId}/download`.\n\n## MCP\nThe hosted MCP endpoint is `https://api.ki-kennzeichnen.de/mcp` (Streamable HTTP, stateless). It authenticates with the same API key as a Bearer header.\n\nErrors use `{\"error\": {\"code\", \"message\", \"request_id\"}}`; responses carry `x-api-version` and `x-request-id`."
  },
  "servers": [
    {
      "url": "https://api.ki-kennzeichnen.de/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Jobs",
      "description": "Create labeling jobs, poll their status, and download results."
    },
    {
      "name": "Templates",
      "description": "Reusable render templates per organization."
    },
    {
      "name": "Tokens",
      "description": "Organization API tokens (signed-in session only)."
    },
    {
      "name": "Account",
      "description": "Session binding, usage, and team management."
    }
  ],
  "paths": {
    "/jobs": {
      "post": {
        "tags": [
          "Jobs"
        ],
        "summary": "Create a labeling job",
        "description": "Two variants share this endpoint. Direct ingestion (multipart/form-data): send 1 to 20 images (png, jpeg, webp; up to 25 MB each, 32 MB per request) in the files field plus an optional payload JSON field; the API detects type and size, uploads, renders, and responds with the running job. The Idempotency-Key header is optional here. Presigned flow (application/json): for video and browser direct uploads; the Idempotency-Key header is required (8 to 160 printable ASCII characters) and the response returns one signed PUT upload instruction per item (exact byte length, short TTL); finish with POST /jobs/{id}/upload-complete. With an API token, organization_id is inferred from the token.",
        "operationId": "createJob",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Required for the JSON presigned flow, optional for multipart direct ingestion. 8 to 160 printable ASCII characters.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 160
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "files"
                ],
                "properties": {
                  "files": {
                    "type": "array",
                    "description": "1 to 20 image files (image/png, image/jpeg, image/webp).",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  },
                  "payload": {
                    "type": "string",
                    "description": "Optional JSON string: {settings?, template?, template_id?, items?, auto_placement?}. settings, template, or template_id apply to the whole job; items is an array aligned with the file order where each entry may set {template, settings} per file. Inline settings override the template per top-level key. An unknown template name fails with template_not_found. auto_placement (boolean, beta) lets a vision model decide a free position (x and y in percent, held within 18 percent of a border), size and light or dark icon variant per image before rendering; it needs a paid plan and an OpenAI key configured under Settings, otherwise the request fails with auto_placement_unavailable. The decision is returned in the item settings under auto_placement."
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobCreateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay: a job for this Idempotency-Key already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobCreateResponse"
                }
              }
            }
          },
          "201": {
            "description": "Job created. Direct ingestion responds with the already running job; the presigned flow includes one upload instruction per item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobCreateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "description": "Request body is too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "List newest jobs",
        "operationId": "listJobs",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Number of jobs to return (1 to 100, default 50).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Newest jobs first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "jobs"
                  ],
                  "properties": {
                    "jobs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Job"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/jobs/{id}": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Get a job",
        "description": "Returns the job with items, statuses, and error codes. Job status flow: awaiting_upload, queued, running, then succeeded, partial, or failed (plus cancelled and expired).",
        "operationId": "getJob",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "The job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Jobs"
        ],
        "summary": "Cancel a job",
        "description": "Cancels the job and releases quota. Requires If-Match with the current job state version.",
        "operationId": "cancelJob",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "description": "If-Match must contain the job state version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{id}/upload-complete": {
      "post": {
        "tags": [
          "Jobs"
        ],
        "summary": "Finalize uploads and start the job",
        "description": "Presigned flow only. Verifies every upload against storage metadata, pins input generations, queues, dispatches render tasks, and starts the job. Safe to retry; named render tasks make dispatch idempotent. Requires If-Match with the current job state version.",
        "operationId": "completeUploads",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "200": {
            "description": "Retry of an already finalized job (queued or running).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResponse"
                }
              }
            }
          },
          "202": {
            "description": "Uploads verified, render tasks dispatched, job started.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Job cannot be finalized from its current state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "410": {
            "description": "Job upload window has expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "428": {
            "description": "If-Match must contain the job state version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{id}/items/{itemId}/download": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Get a signed download URL",
        "description": "Returns a short-lived signed download URL for a succeeded item output. Media never flows through the API gateway.",
        "operationId": "getDownload",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "description": "Job item ID (UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The signed download URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "download"
                  ],
                  "properties": {
                    "download": {
                      "$ref": "#/components/schemas/DownloadInstruction"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Job item output is not available yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/templates": {
      "get": {
        "tags": [
          "Templates"
        ],
        "summary": "List render templates",
        "operationId": "listTemplates",
        "responses": {
          "200": {
            "description": "The organization's render templates.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "templates"
                  ],
                  "properties": {
                    "templates": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Template"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Templates"
        ],
        "summary": "Create or update a template",
        "description": "Upserts by name: an existing template with the same name in the organization is replaced. Settings are validated as image render settings.",
        "operationId": "upsertTemplate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "organization_id",
                  "name",
                  "settings"
                ],
                "properties": {
                  "organization_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "settings": {
                    "$ref": "#/components/schemas/RenderSettings"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The stored template.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "template"
                  ],
                  "properties": {
                    "template": {
                      "$ref": "#/components/schemas/Template"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/templates/{id}": {
      "delete": {
        "tags": [
          "Templates"
        ],
        "summary": "Delete a template",
        "operationId": "deleteTemplate",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Template ID (UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Template deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deleted"
                  ],
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/tokens": {
      "get": {
        "tags": [
          "Tokens"
        ],
        "summary": "List API tokens",
        "description": "Signed-in session only; an API token cannot manage tokens. Token hashes never leave the API; only the prefix is returned.",
        "operationId": "listTokens",
        "responses": {
          "200": {
            "description": "The organization's API tokens.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tokens"
                  ],
                  "properties": {
                    "tokens": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiToken"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SessionRequired"
          }
        }
      },
      "post": {
        "tags": [
          "Tokens"
        ],
        "summary": "Create an API token",
        "description": "Signed-in session only. The full token value (kk_live_...) appears only in this response; the API stores only a hash. Default lifetime is 30 days; an explicit null expires_in_days means unlimited.",
        "operationId": "createToken",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "organization_id",
                  "name"
                ],
                "properties": {
                  "organization_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "expires_in_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 365,
                    "nullable": true,
                    "default": 30,
                    "description": "Lifetime in days (1 to 365). Omitted: 30 days. Explicit null: the token never expires."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created token. The token value is shown exactly once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "token",
                    "record"
                  ],
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "The full token value (kk_live_...). Store it now; it is never shown again.",
                      "example": "kk_live_70SI2c9GfW4mZo8rQe1TnB5xL0aVdKjP"
                    },
                    "record": {
                      "$ref": "#/components/schemas/ApiToken"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SessionRequired"
          }
        }
      }
    },
    "/tokens/{id}": {
      "delete": {
        "tags": [
          "Tokens"
        ],
        "summary": "Revoke or delete an API token",
        "description": "Signed-in session only. Two-step retirement: deleting an active token revokes it ({\"revoked\": true, \"outcome\": \"revoked\"}); deleting an already revoked token removes it permanently ({\"deleted\": true, \"outcome\": \"deleted\"}).",
        "operationId": "retireToken",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Token ID (UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Outcome of the retirement step.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "outcome"
                  ],
                  "properties": {
                    "revoked": {
                      "type": "boolean",
                      "description": "Present and true when an active token was revoked."
                    },
                    "deleted": {
                      "type": "boolean",
                      "description": "Present and true when a revoked token was removed permanently."
                    },
                    "outcome": {
                      "type": "string",
                      "enum": [
                        "revoked",
                        "deleted"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SessionRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/me": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Inspect the current credential",
        "description": "Reports what the current bearer credential is bound to: a user session or an organization API token.",
        "operationId": "getMe",
        "responses": {
          "200": {
            "description": "The credential binding.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "session"
                  ],
                  "properties": {
                    "session": {
                      "type": "string",
                      "enum": [
                        "user",
                        "token"
                      ]
                    },
                    "user_id": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    },
                    "organization_id": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/usage": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Get usage and quota",
        "description": "Daily usage plus plan and quota for a date range. Defaults to the last 30 days; the range may span at most 400 days.",
        "operationId": "getUsage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationIdQuery"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Start day (YYYY-MM-DD). Default: 29 days before to.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "End day (YYYY-MM-DD). Default: today.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The usage report.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "usage"
                  ],
                  "properties": {
                    "usage": {
                      "$ref": "#/components/schemas/UsageReport"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/team": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "List team members",
        "description": "Signed-in session only; requires organization administration.",
        "operationId": "listTeam",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationIdQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The organization's members.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "members"
                  ],
                  "properties": {
                    "members": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TeamMember"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SessionRequired"
          }
        }
      },
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Invite a team member",
        "description": "Signed-in session only; requires organization administration. Creates the member with a temporary password that is returned exactly once.",
        "operationId": "createTeamMember",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationIdQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "display_name",
                  "role"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "display_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "admin",
                      "member",
                      "viewer"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created member and their temporary password.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "member",
                    "temporary_password"
                  ],
                  "properties": {
                    "member": {
                      "$ref": "#/components/schemas/TeamMember"
                    },
                    "temporary_password": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SessionRequired"
          }
        }
      }
    },
    "/team/{userId}": {
      "patch": {
        "tags": [
          "Account"
        ],
        "summary": "Update a team member",
        "description": "Signed-in session only; requires organization administration. At least one of role or status must be present.",
        "operationId": "updateTeamMember",
        "parameters": [
          {
            "$ref": "#/components/parameters/TeamUserId"
          },
          {
            "$ref": "#/components/parameters/OrganizationIdQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "minProperties": 1,
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "admin",
                      "member",
                      "viewer"
                    ]
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "blocked"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Member updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "updated"
                  ],
                  "properties": {
                    "updated": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SessionRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Account"
        ],
        "summary": "Remove a team member",
        "description": "Signed-in session only; requires organization administration.",
        "operationId": "removeTeamMember",
        "parameters": [
          {
            "$ref": "#/components/parameters/TeamUserId"
          },
          {
            "$ref": "#/components/parameters/OrganizationIdQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Member removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "removed"
                  ],
                  "properties": {
                    "removed": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SessionRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Organization API token (kk_live_...) or a signed-in user session JWT. Token and team management routes accept only sessions."
      }
    },
    "parameters": {
      "JobId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Job ID (UUID).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "TeamUserId": {
        "name": "userId",
        "in": "path",
        "required": true,
        "description": "Member user ID (UUID).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "IfMatch": {
        "name": "If-Match",
        "in": "header",
        "required": true,
        "description": "Current job state_version, optionally quoted.",
        "schema": {
          "type": "string",
          "example": "\"3\""
        }
      },
      "OrganizationIdQuery": {
        "name": "organization_id",
        "in": "query",
        "required": false,
        "description": "Organization to act on. Optional when the credential is bound to exactly one organization.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is invalid.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Bearer authentication is required.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Access to the resource is denied.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "SessionRequired": {
        "description": "This route requires a signed-in session; an API token is not accepted.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "The resource was not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "invalid_request"
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              }
            }
          }
        }
      },
      "RenderLabel": {
        "type": "object",
        "required": [
          "icon"
        ],
        "description": "One placed EU icon. Up to 10 labels per image, exactly 1 for video.",
        "properties": {
          "icon": {
            "type": "string",
            "description": "One of the 12 official EU icon IDs.",
            "enum": [
              "ai-basic-black",
              "ai-basic-white",
              "ai-basic-black-transparent",
              "ai-basic-white-transparent",
              "ai-generated-black",
              "ai-generated-white",
              "ai-generated-black-transparent",
              "ai-generated-white-transparent",
              "ai-modified-black",
              "ai-modified-white",
              "ai-modified-black-transparent",
              "ai-modified-white-transparent"
            ]
          },
          "position": {
            "type": "string",
            "description": "9-cell grid placement. x and y override the grid per axis.",
            "enum": [
              "top-left",
              "top-center",
              "top-right",
              "center-left",
              "center",
              "center-right",
              "bottom-left",
              "bottom-center",
              "bottom-right"
            ],
            "default": "bottom-right"
          },
          "x": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Icon center on the horizontal axis in percent. Overrides the grid column."
          },
          "y": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Icon center on the vertical axis in percent. Overrides the grid row."
          },
          "size": {
            "type": "number",
            "minimum": 4,
            "maximum": 40,
            "default": 12,
            "description": "Icon width in percent of the media width."
          },
          "margin": {
            "type": "number",
            "minimum": 0,
            "maximum": 20,
            "default": 3,
            "description": "Edge distance in percent (horizontal from width, vertical from height)."
          },
          "opacity": {
            "type": "number",
            "minimum": 0.1,
            "maximum": 1
          },
          "label_text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60,
            "description": "Optional text lockup rendered next to the icon."
          },
          "theme": {
            "type": "string",
            "enum": [
              "light",
              "dark"
            ]
          }
        }
      },
      "RenderSettings": {
        "type": "object",
        "description": "Validated render specification. Defaults when omitted: one ai-basic-black icon bottom-right plus metadata.",
        "properties": {
          "labels": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "description": "Placed EU icons: up to 10 per image, exactly 1 for video.",
            "items": {
              "$ref": "#/components/schemas/RenderLabel"
            }
          },
          "format": {
            "type": "string",
            "enum": [
              "png",
              "jpeg",
              "webp"
            ],
            "description": "Converts image output. Images only."
          },
          "embed_metadata": {
            "type": "boolean",
            "default": true,
            "description": "Embeds the machine-readable IPTC/XMP DigitalSourceType marking."
          },
          "source_type": {
            "type": "string",
            "enum": [
              "generated",
              "modified"
            ],
            "default": "generated",
            "description": "generated selects trainedAlgorithmicMedia, modified selects compositeWithTrainedAlgorithmicMedia."
          },
          "watermark": {
            "type": "boolean",
            "default": false,
            "description": "Adds the invisible DCT/QIM watermark. Images only."
          }
        }
      },
      "JobCreateRequest": {
        "type": "object",
        "required": [
          "kind",
          "items"
        ],
        "description": "Presigned flow request for video and browser direct uploads.",
        "properties": {
          "organization_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional with an API token (inferred from the token)."
          },
          "kind": {
            "type": "string",
            "enum": [
              "image",
              "batch",
              "video"
            ]
          },
          "settings": {
            "$ref": "#/components/schemas/RenderSettings"
          },
          "template": {
            "type": "string",
            "description": "Organization-unique template name seeding the job settings. Inline settings override the template per top-level key."
          },
          "template_id": {
            "type": "string",
            "format": "uuid"
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "kind",
                "content_type",
                "size_bytes"
              ],
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "image",
                    "video"
                  ]
                },
                "content_type": {
                  "type": "string",
                  "enum": [
                    "image/png",
                    "image/jpeg",
                    "image/webp",
                    "video/mp4",
                    "video/quicktime"
                  ]
                },
                "size_bytes": {
                  "type": "integer",
                  "minimum": 1
                },
                "duration_seconds": {
                  "type": "number",
                  "description": "Video items only."
                },
                "settings": {
                  "$ref": "#/components/schemas/RenderSettings"
                },
                "template": {
                  "type": "string",
                  "description": "Per-item template name. An item's inline settings override its template."
                }
              }
            }
          }
        }
      },
      "JobItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "ordinal": {
            "type": "integer"
          },
          "kind": {
            "type": "string",
            "enum": [
              "image",
              "video"
            ]
          },
          "status": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "declared_bytes": {
            "type": "integer"
          },
          "input_object_path": {
            "type": "string"
          },
          "input_generation": {
            "type": "integer",
            "nullable": true
          },
          "render_settings": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/RenderSettings"
              }
            ],
            "description": "Per-item settings, or null when the item uses the job settings."
          },
          "output_object_path": {
            "type": "string",
            "nullable": true
          },
          "error_code": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "subject_id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "image",
              "batch",
              "video"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_upload",
              "queued",
              "running",
              "succeeded",
              "partial",
              "failed",
              "cancelled",
              "expired"
            ]
          },
          "queue_class": {
            "type": "string"
          },
          "state_version": {
            "type": "integer",
            "description": "Optimistic concurrency version for If-Match."
          },
          "item_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "End of the upload window."
          },
          "render_settings": {
            "$ref": "#/components/schemas/RenderSettings"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JobItem"
            }
          }
        }
      },
      "JobResponse": {
        "type": "object",
        "required": [
          "job"
        ],
        "properties": {
          "job": {
            "$ref": "#/components/schemas/Job"
          }
        }
      },
      "JobCreateResponse": {
        "type": "object",
        "required": [
          "created",
          "job",
          "uploads"
        ],
        "properties": {
          "created": {
            "type": "boolean",
            "description": "false on an idempotent replay."
          },
          "job": {
            "$ref": "#/components/schemas/Job"
          },
          "uploads": {
            "type": "array",
            "description": "One signed upload instruction per awaiting item (presigned flow). Empty for direct multipart ingestion.",
            "items": {
              "$ref": "#/components/schemas/UploadInstruction"
            }
          }
        }
      },
      "UploadInstruction": {
        "type": "object",
        "properties": {
          "item_id": {
            "type": "string",
            "format": "uuid"
          },
          "upload_url": {
            "type": "string",
            "format": "uri",
            "description": "Signed PUT URL pinned to the exact byte length."
          },
          "method": {
            "type": "string",
            "enum": [
              "PUT"
            ]
          },
          "required_headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Headers that must accompany the PUT exactly as given."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DownloadInstruction": {
        "type": "object",
        "properties": {
          "item_id": {
            "type": "string",
            "format": "uuid"
          },
          "download_url": {
            "type": "string",
            "format": "uri",
            "description": "Short-lived signed download URL."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Template": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "mein-template"
          },
          "settings": {
            "$ref": "#/components/schemas/RenderSettings"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiToken": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "token_prefix": {
            "type": "string",
            "example": "kk_live_70SI"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "revoked"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "null means the token never expires."
          }
        }
      },
      "TeamMember": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "display_name": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "member",
              "viewer"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "blocked"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UsageReport": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          },
          "daily": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "day": {
                  "type": "string",
                  "format": "date"
                },
                "images": {
                  "type": "integer"
                },
                "videos": {
                  "type": "integer"
                },
                "video_seconds": {
                  "type": "number"
                },
                "output_bytes": {
                  "type": "integer"
                }
              }
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "images": {
                "type": "integer"
              },
              "videos": {
                "type": "integer"
              },
              "video_seconds": {
                "type": "number"
              },
              "output_bytes": {
                "type": "integer"
              }
            }
          },
          "plan": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "nullable": true
              },
              "status": {
                "type": "string",
                "nullable": true
              },
              "monthly_quota": {
                "type": "integer",
                "nullable": true
              },
              "used_this_month": {
                "type": "integer"
              },
              "remaining": {
                "type": "integer",
                "nullable": true
              },
              "renews_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          }
        }
      }
    }
  }
}
