{
  "openapi": "3.1.0",
  "info": {
    "title": "Metheus AI publishing API",
    "version": "v1",
    "description": "Upload files, create a draft, preview it, then publish or update a Metheus gallery post.\n\nFlow: POST /api/agent/v1/uploads for each file → POST /api/agent/v1/drafts with an Idempotency-Key header → open preview_url and check it → POST /api/agent/v1/drafts/{draftId}/publish. To change a published post, PATCH the draft with If-Match: <revision> and publish again; the same post id is updated.\n\nAuthentication: Authorization: Bearer mth_… personal API key. A key belongs to the account that issued it and carries scopes: posts:draft (upload files; create, edit and delete unpublished drafts) and posts:publish (make a draft public; update or delete its public post; always issued together with posts:draft). Get a key on the website (account menu → API 키) or with browser approval: POST /api/agent/v1/device/code, the user opens verification_uri_complete while signed in and approves, and the client polls POST /api/agent/v1/device/token every interval seconds. API keys work only on this API. /api/agent-keys and the approval endpoints need the signed-in website session. A Metheus account Bearer also works on this API with both scopes.\n\nRetries: an upload with the same bytes and file name returns the same upload (send X-Content-SHA256 to skip re-sending the body). Creating a draft with the same Idempotency-Key and body returns the same draft (200, reused=true); the same key with a different body is 409 idempotency_conflict. A replayed PATCH whose content is already applied returns 200. Publishing an unchanged draft returns the same post (reused=true).\n\nFiles (limit per file):\n\n| ext | kind | max |\n|---|---|---|\n| .png | image | 10 MiB |\n| .jpg | image | 10 MiB |\n| .jpeg | image | 10 MiB |\n| .webp | image | 10 MiB |\n| .glb | model | 50 MiB |\n| .fcstd | freecad | 64 MiB |\n| .step | step | 64 MiB |\n| .stp | step | 64 MiB |\n| .stl | stl | 64 MiB |\n| .pdf | drawing | 25 MiB |\n| .svg | drawing | 25 MiB |\n| .dxf | drawing | 25 MiB |\n| .dwg | drawing | 25 MiB |\n| .csv | bom | 5 MiB |\n| .tsv | bom | 5 MiB |\n| .xlsx | bom | 5 MiB |\n| .json | bom | 5 MiB |\n| .zip | archive | 64 MiB |\n\nContent is checked against the extension (GLB must be self-contained glTF 2.0, STEP must start with ISO-10303-21, ZIP/FCStd/XLSX must be ZIP, and so on). This is not design validation. Photos are re-encoded to WebP (at most 2400 px) and metadata such as GPS is removed. Uploads not attached to a draft are removed after 24 hours. One draft holds at most 256 MiB of files.\n\nBody Markdown: the gallery renders \"## heading\", \"- list\", blank-line paragraphs, http(s) links and [label](url), a YouTube URL alone on a line, and \"[사진 N]\" alone on a line (N=1 is the cover, 2 and up are images). #/### headings become ##, and * or + bullets become -. Tables, code blocks, bold/italic and ![img](url) stay as literal text; format_notes on the draft lists what was found. Do not present concept renders or learning examples as built or delivered results."
  },
  "servers": [
    {
      "url": "https://hub.metheus.ai",
      "description": "Production. Local development prefixes paths with the basePath from /api/config (for example /platform)."
    }
  ],
  "tags": [
    {
      "name": "Publishing"
    },
    {
      "name": "Uploads"
    },
    {
      "name": "Keys and sign-in"
    },
    {
      "name": "Public files"
    },
    {
      "name": "Problem reports"
    }
  ],
  "paths": {
    "/api/agent/v1/limits": {
      "get": {
        "operationId": "getLimits",
        "tags": [
          "Publishing"
        ],
        "summary": "Allowed extensions, byte limits, categories, roles, scopes and rendered Markdown",
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Limits"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/v1/openapi.json": {
      "get": {
        "operationId": "getOpenApi",
        "tags": [
          "Publishing"
        ],
        "summary": "This document",
        "security": [],
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 JSON."
          }
        }
      }
    },
    "/api/agent/v1/me": {
      "get": {
        "operationId": "getCaller",
        "tags": [
          "Keys and sign-in"
        ],
        "summary": "Check the credential and its scopes",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "kind": {
                      "enum": [
                        "api_key",
                        "account",
                        "automation"
                      ]
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "enum": [
                          "posts:draft",
                          "posts:publish"
                        ]
                      }
                    },
                    "key": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/ApiKey"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/v1/uploads": {
      "post": {
        "operationId": "uploadFile",
        "tags": [
          "Uploads"
        ],
        "summary": "Upload one file as raw bytes",
        "description": "Requires posts:draft. Send the file itself as the request body.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "X-Filename",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "URL-encoded file name with extension, no path, at most 160 characters."
          },
          {
            "name": "X-Content-SHA256",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{64}$"
            },
            "description": "SHA-256 of the bytes. If this owner already uploaded the same hash and name, 200 is returned without reading the body; otherwise the received bytes must match it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same file is already stored (reused=true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                }
              }
            }
          },
          "201": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                }
              }
            }
          },
          "400": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/v1/uploads/{uploadId}": {
      "get": {
        "operationId": "getUpload",
        "tags": [
          "Uploads"
        ],
        "summary": "Read upload metadata (owner only)",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "uploadId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Upload id."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                }
              }
            }
          },
          "404": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/v1/drafts": {
      "get": {
        "operationId": "listDrafts",
        "tags": [
          "Publishing"
        ],
        "summary": "List the latest 50 drafts of this owner",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Draft"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createDraft",
        "tags": [
          "Publishing"
        ],
        "summary": "Create a draft (not public)",
        "description": "Requires posts:draft. JSON body up to 64,000 bytes.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_.:-]{8,128}$"
            },
            "description": "Keep the same key when you retry the same request."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DraftInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Same key and body: the existing draft (reused=true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Draft"
                }
              }
            }
          },
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Draft"
                }
              }
            }
          },
          "400": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/v1/drafts/{draftId}": {
      "get": {
        "operationId": "getDraft",
        "tags": [
          "Publishing"
        ],
        "summary": "Read a draft",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "draftId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Draft id returned by createDraft."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Draft"
                }
              }
            }
          },
          "404": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateDraft",
        "tags": [
          "Publishing"
        ],
        "summary": "Change draft fields",
        "description": "Only the fields you send change; title.ko and title.en can change separately. Arrays (tags, images, attachments) replace the whole list. A published post changes only after publishDraft.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "draftId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Draft id returned by createDraft."
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The revision you read. Required unless expected_revision is in the body."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/DraftInput"
                  }
                ],
                "properties": {
                  "expected_revision": {
                    "type": "integer"
                  }
                },
                "required": []
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Draft"
                }
              }
            }
          },
          "400": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteDraft",
        "tags": [
          "Publishing"
        ],
        "summary": "Delete a draft; if it is published, also delete the public post",
        "description": "Deleting a published post requires posts:publish.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "draftId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Draft id returned by createDraft."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "const": true
                    },
                    "post_deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/v1/drafts/{draftId}/publish": {
      "post": {
        "operationId": "publishDraft",
        "tags": [
          "Publishing"
        ],
        "summary": "Publish the draft or update its post",
        "description": "Requires posts:publish. The first call creates a public gallery post; later calls update the same post. If the post was edited on the website after the last publish: 409 post_changed. If the post was deleted: 410 post_deleted.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "draftId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Draft id returned by createDraft."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "expected_revision": {
                    "type": "integer",
                    "description": "The revision you previewed. 409 revision_conflict if the draft changed since."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "status=published with post_id and post_url. reused=true when nothing changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Draft"
                }
              }
            }
          },
          "403": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/v1/drafts/{draftId}/preview": {
      "get": {
        "operationId": "previewDraft",
        "tags": [
          "Publishing"
        ],
        "summary": "Read a draft for the preview page",
        "description": "Authorized by X-Preview-Token (the preview value in preview_url) or by the owner credential. The browser preview page uses this.",
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "draftId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Draft id returned by createDraft."
          },
          {
            "name": "X-Preview-Token",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Draft"
                }
              }
            }
          },
          "404": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/v1/drafts/{draftId}/files/{uploadId}": {
      "get": {
        "operationId": "getDraftFile",
        "tags": [
          "Publishing"
        ],
        "summary": "Download a file attached to a draft",
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "draftId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Draft id returned by createDraft."
          },
          {
            "name": "uploadId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Upload id."
          },
          {
            "name": "X-Preview-Token",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "download",
            "in": "query",
            "required": false,
            "schema": {
              "const": "1"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File bytes."
          },
          "404": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/v1/device/code": {
      "post": {
        "operationId": "startDeviceSignIn",
        "tags": [
          "Keys and sign-in"
        ],
        "summary": "Start browser approval (RFC 8628 device authorization)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 60,
                    "description": "Shown to the user on the approval page."
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "enum": [
                        "posts:draft",
                        "posts:publish"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "device_code",
                    "user_code",
                    "verification_uri",
                    "verification_uri_complete",
                    "expires_in",
                    "interval"
                  ],
                  "properties": {
                    "device_code": {
                      "type": "string",
                      "description": "Secret. Keep it in the client."
                    },
                    "user_code": {
                      "type": "string",
                      "examples": [
                        "BCDF-GHJK"
                      ]
                    },
                    "verification_uri": {
                      "type": "string"
                    },
                    "verification_uri_complete": {
                      "type": "string"
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "interval": {
                      "type": "integer"
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/v1/device/token": {
      "post": {
        "operationId": "pollDeviceSignIn",
        "tags": [
          "Keys and sign-in"
        ],
        "summary": "Poll for the API key after the user approves",
        "description": "Poll every interval seconds. Errors carry code authorization_pending, slow_down (wait longer), access_denied or expired_token. On success the key is returned once; store it like a password.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "device_code"
                ],
                "properties": {
                  "device_code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "token_type": {
                      "const": "Bearer"
                    },
                    "scope": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "key": {
                      "$ref": "#/components/schemas/ApiKey"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/v1/device/requests/{userCode}": {
      "get": {
        "operationId": "getDeviceRequest",
        "tags": [
          "Keys and sign-in"
        ],
        "summary": "Approval page: read a pending request (signed-in account)",
        "security": [
          {
            "AccountBearer": []
          }
        ],
        "parameters": [
          {
            "name": "userCode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceRequest"
                }
              }
            }
          },
          "401": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "decideDeviceRequest",
        "tags": [
          "Keys and sign-in"
        ],
        "summary": "Approval page: approve or deny (signed-in account)",
        "security": [
          {
            "AccountBearer": []
          }
        ],
        "parameters": [
          {
            "name": "userCode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "approve"
                ],
                "properties": {
                  "approve": {
                    "type": "boolean"
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "enum": [
                        "posts:draft",
                        "posts:publish"
                      ]
                    },
                    "description": "A subset of the requested scopes."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceRequest"
                }
              }
            }
          },
          "400": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent-keys": {
      "get": {
        "operationId": "listApiKeys",
        "tags": [
          "Keys and sign-in"
        ],
        "summary": "List my API keys (no secrets)",
        "security": [
          {
            "AccountBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createApiKey",
        "tags": [
          "Keys and sign-in"
        ],
        "summary": "Issue an API key (the secret is shown once)",
        "security": [
          {
            "AccountBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 60
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "enum": [
                        "posts:draft",
                        "posts:publish"
                      ]
                    },
                    "default": [
                      "posts:draft",
                      "posts:publish"
                    ]
                  },
                  "expires_days": {
                    "enum": [
                      30,
                      90,
                      365
                    ],
                    "default": 90
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiKey"
                    }
                  ],
                  "properties": {
                    "token": {
                      "type": "string",
                      "pattern": "^mth_[A-Za-z0-9_-]{43}$"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent-keys/{keyId}": {
      "delete": {
        "operationId": "revokeApiKey",
        "tags": [
          "Keys and sign-in"
        ],
        "summary": "Revoke an API key",
        "security": [
          {
            "AccountBearer": []
          }
        ],
        "parameters": [
          {
            "name": "keyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key id."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/post-files/{postId}/{uploadId}": {
      "get": {
        "operationId": "getPostFile",
        "tags": [
          "Public files"
        ],
        "summary": "Public download of a file attached to a published post",
        "security": [],
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Post id."
          },
          {
            "name": "uploadId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Upload id."
          },
          {
            "name": "download",
            "in": "query",
            "required": false,
            "schema": {
              "const": "1"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File bytes. Images and GLB are inline; other files are attachments."
          },
          "404": {
            "description": "Error. Body: {error, code?, resource_id?}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/feedback": {
      "post": {
        "operationId": "reportProblem",
        "tags": [
          "Problem reports"
        ],
        "summary": "Report a site or API problem to the operators (private)",
        "description": "No sign-in needed; a valid credential is recorded, an invalid one is ignored. Report 5xx, responses that contradict this document, success responses whose effect did not happen, broken public data or links, and security issues. Do not report normal validation errors (400, 409, 413, 415), missing permission or empty results. Never include keys, tokens, personal data or whole conversations; values that look like keys are masked. An open report with the same kind, summary and page (query ignored) is counted instead of duplicated. Limits: 6 per minute per client address, 500 new reports per day.",
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "summary"
                ],
                "properties": {
                  "kind": {
                    "enum": [
                      "bug",
                      "content",
                      "api",
                      "security",
                      "idea",
                      "other"
                    ],
                    "default": "bug"
                  },
                  "summary": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 200
                  },
                  "details": {
                    "type": "string",
                    "maxLength": 4000,
                    "description": "Request, response status, steps, expected and actual result."
                  },
                  "page": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Page address or API path."
                  },
                  "source": {
                    "enum": [
                      "person",
                      "agent"
                    ],
                    "default": "person"
                  },
                  "client": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "contact": {
                    "type": "string",
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Counted on an existing open report (duplicate=true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedbackReceipt"
                }
              }
            }
          },
          "201": {
            "description": "New report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedbackReceipt"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "mth_ API key"
      },
      "AccountBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Metheus account access token of the signed-in website session."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "resource_id": {
            "type": "string"
          },
          "max_bytes": {
            "type": "integer",
            "description": "On 413: the byte limit that was exceeded."
          },
          "ext": {
            "type": "string",
            "description": "On 413 file_too_large from the app: the extension whose limit applies."
          },
          "limits": {
            "type": "string",
            "description": "On 413: where to read all limits."
          }
        }
      },
      "Limits": {
        "type": "object",
        "x-file-types": {
          ".png": 10485760,
          ".jpg": 10485760,
          ".jpeg": 10485760,
          ".webp": 10485760,
          ".glb": 52428800,
          ".fcstd": 67108864,
          ".step": 67108864,
          ".stp": 67108864,
          ".stl": 67108864,
          ".pdf": 26214400,
          ".svg": 26214400,
          ".dxf": 26214400,
          ".dwg": 26214400,
          ".csv": 5242880,
          ".tsv": 5242880,
          ".xlsx": 5242880,
          ".json": 5242880,
          ".zip": 67108864
        },
        "properties": {
          "version": {
            "const": "v1"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "roles": {
            "type": "array",
            "items": {
              "enum": [
                "freecad",
                "step",
                "stl",
                "drawing",
                "bom",
                "archive",
                "model",
                "image",
                "other"
              ]
            }
          },
          "file_types": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ext": {
                  "type": "string"
                },
                "kind": {
                  "type": "string"
                },
                "max_bytes": {
                  "type": "integer"
                }
              }
            }
          },
          "limits": {
            "type": "object"
          },
          "markdown": {
            "type": "object"
          },
          "docs": {
            "type": "object"
          }
        }
      },
      "Upload": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "ext": {
            "type": "string"
          },
          "kind": {
            "enum": [
              "image",
              "model",
              "freecad",
              "step",
              "stl",
              "drawing",
              "bom",
              "archive"
            ]
          },
          "content_type": {
            "type": "string"
          },
          "bytes": {
            "type": "integer"
          },
          "sha256": {
            "type": "string",
            "description": "Hash of the stored bytes (photos are re-encoded)."
          },
          "source_sha256": {
            "type": "string",
            "description": "Hash of the bytes you sent."
          },
          "width": {
            "type": "integer"
          },
          "height": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "reused": {
            "type": "boolean"
          }
        }
      },
      "Photo": {
        "oneOf": [
          {
            "type": "string",
            "format": "uuid",
            "description": "Upload id."
          },
          {
            "type": "object",
            "required": [
              "upload"
            ],
            "properties": {
              "upload": {
                "type": "string",
                "format": "uuid"
              },
              "alt": {
                "type": "string",
                "maxLength": 200
              }
            }
          }
        ]
      },
      "DraftInput": {
        "type": "object",
        "required": [
          "title",
          "body",
          "author_name"
        ],
        "properties": {
          "title": {
            "type": "object",
            "required": [
              "ko"
            ],
            "properties": {
              "ko": {
                "type": "string",
                "minLength": 2,
                "maxLength": 120
              },
              "en": {
                "type": "string",
                "maxLength": 120
              }
            }
          },
          "body": {
            "type": "string",
            "maxLength": 12000,
            "description": "Markdown. See the rendered subset in info.description."
          },
          "category": {
            "type": "string",
            "default": "만든 것",
            "description": "One of the /limits categories."
          },
          "author_name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 32,
            "description": "Public display name."
          },
          "tags": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "maxLength": 24
            },
            "description": "A leading # is removed; duplicates are ignored case-insensitively."
          },
          "cover": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Photo"
              },
              {
                "type": "null"
              }
            ],
            "description": "Image upload. Gallery thumbnail and [사진 1]."
          },
          "images": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/Photo"
            },
            "description": "More photos: [사진 2] and up."
          },
          "models": {
            "type": "object",
            "properties": {
              "assembled": {
                "type": "string",
                "format": "uuid",
                "description": "GLB upload."
              },
              "exploded": {
                "type": "string",
                "format": "uuid",
                "description": "GLB upload."
              }
            },
            "additionalProperties": false
          },
          "attachments": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "oneOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "object",
                  "required": [
                    "upload"
                  ],
                  "properties": {
                    "upload": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "role": {
                      "enum": [
                        "freecad",
                        "step",
                        "stl",
                        "drawing",
                        "bom",
                        "archive",
                        "model",
                        "image",
                        "other"
                      ],
                      "description": "Defaults to the upload kind."
                    },
                    "label": {
                      "type": "string",
                      "maxLength": 120
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "DraftFile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "bytes": {
            "type": "integer"
          },
          "sha256": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "alt": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "label": {
            "type": "string"
          }
        }
      },
      "Draft": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "enum": [
              "draft",
              "published",
              "changed",
              "deleted"
            ],
            "description": "changed = edited after the last publish; deleted = the public post was deleted."
          },
          "revision": {
            "type": "integer"
          },
          "published_revision": {
            "type": [
              "integer",
              "null"
            ]
          },
          "post_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "post_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "preview_url": {
            "type": "string",
            "description": "Browser page. Anyone with this link can view the draft; share it only with reviewers."
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "object",
            "properties": {
              "ko": {
                "type": "string"
              },
              "en": {
                "type": "string"
              }
            }
          },
          "body": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "author_name": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "cover": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DraftFile"
              },
              {
                "type": "null"
              }
            ]
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DraftFile"
            }
          },
          "models": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/DraftFile"
            }
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DraftFile"
            }
          },
          "format_notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "reused": {
            "type": "boolean"
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "source": {
            "enum": [
              "manual",
              "device"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "expires_at": {
            "type": "string"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "DeviceRequest": {
        "type": "object",
        "properties": {
          "user_code": {
            "type": "string"
          },
          "client_name": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "status": {
            "enum": [
              "pending",
              "approved",
              "denied",
              "consumed"
            ]
          },
          "expires_at": {
            "type": "string"
          }
        }
      },
      "FeedbackReceipt": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "const": "open"
          },
          "duplicate": {
            "type": "boolean"
          },
          "count": {
            "type": "integer"
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error. Body: {error, code?}.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
