{
  "openapi": "3.1.0",
  "info": {
    "title": "Undraft API",
    "version": "v1",
    "description": "Public API for Undraft — host and share SPAs with shareable URLs, version history, and comments.\n\n**Hosted**: `https://undraft.page/api/v1`\n**Self-hosted**: `<your-undraft-instance>/api/v1`\n\nOn self-hosted deployments the plan-limit error codes (`PLAN_LIMIT_*`) do not apply — quotas are unlimited.\n",
    "license": {
      "name": "Apache 2.0",
      "url": "https://www.apache.org/licenses/LICENSE-2.0"
    }
  },
  "servers": [
    {
      "url": "https://undraft.page/api/v1",
      "description": "Undraft hosted cloud"
    },
    {
      "url": "{baseUrl}/api/v1",
      "description": "Self-hosted instance",
      "variables": {
        "baseUrl": {
          "default": "http://localhost:3001",
          "description": "Your self-hosted Undraft base URL."
        }
      }
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Projects",
      "description": "Create, update, list, and delete projects."
    },
    {
      "name": "Account",
      "description": "Authenticated user information."
    },
    {
      "name": "Device Auth",
      "description": "OAuth 2.0 device authorization flow used by the `undraft` CLI."
    }
  ],
  "paths": {
    "/status": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getApiStatus",
        "summary": "Discover the public Undraft API",
        "description": "Returns API reachability, documentation links, supported input types, authentication requirements, and CLI discovery data without requiring credentials.",
        "security": [],
        "responses": {
          "200": {
            "description": "Public API capability information.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiStatus"
                }
              }
            }
          }
        }
      }
    },
    "/projects": {
      "post": {
        "tags": [
          "Projects"
        ],
        "operationId": "publishProject",
        "summary": "Create a project (or push a new version if slug is owned by caller)",
        "description": "One-shot endpoint. If `slug` matches a project owned by the caller, a new version is pushed.\nIf it matches a project owned by someone else, returns 409. Otherwise a new project is created.\n",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "A `.zip` bundle, or single HTML/asset file. Use either `file` or `html`."
                  },
                  "html": {
                    "type": "string",
                    "description": "Raw HTML shortcut — becomes `index.html`. Mutually exclusive with `file`."
                  },
                  "name": {
                    "type": "string",
                    "description": "Display name; defaults to `Untitled`."
                  },
                  "slug": {
                    "type": "string",
                    "description": "Desired slug. If taken by another user returns 409."
                  },
                  "title": {
                    "type": "string",
                    "description": "Version title; defaults to `Version N`."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            },
            "required": false,
            "description": "Opaque key for safe retries. Repeats with the same key return the original response."
          }
        ],
        "responses": {
          "201": {
            "description": "Project created or version pushed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PushResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/QuotaExceeded"
          },
          "409": {
            "$ref": "#/components/responses/SlugConflict"
          }
        }
      },
      "get": {
        "tags": [
          "Projects"
        ],
        "operationId": "listProjects",
        "summary": "List caller's projects",
        "description": "Lists projects owned by the authenticated API-key user.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "projects": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/projects/{slug}": {
      "parameters": [
        {
          "name": "slug",
          "in": "path",
          "required": true,
          "description": "Stable project slug owned by the authenticated user.",
          "schema": {
            "type": "string",
            "minLength": 1
          }
        }
      ],
      "get": {
        "tags": [
          "Projects"
        ],
        "operationId": "getProject",
        "summary": "Get project metadata",
        "description": "Returns metadata for one project owned by the authenticated API-key user.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Projects"
        ],
        "operationId": "deleteProject",
        "summary": "Delete a project (owner only)",
        "description": "Permanently deletes a project owned by the authenticated API-key user.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/projects/{slug}/versions": {
      "parameters": [
        {
          "name": "slug",
          "in": "path",
          "required": true,
          "description": "Stable project slug owned by the authenticated user.",
          "schema": {
            "type": "string",
            "minLength": 1
          }
        }
      ],
      "post": {
        "tags": [
          "Projects"
        ],
        "operationId": "publishProjectVersion",
        "summary": "Push a new version to an existing project",
        "description": "Publishes a new file or HTML version to an existing project owned by the authenticated API-key user.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "A ZIP archive or supported single file. Use either file or html."
                  },
                  "html": {
                    "type": "string",
                    "description": "Raw HTML that becomes index.html. Use either html or file."
                  },
                  "title": {
                    "type": "string",
                    "description": "Optional human-readable title for the new version."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            },
            "required": false,
            "description": "Opaque key for safe retries. Repeats with the same key return the original response."
          }
        ],
        "responses": {
          "201": {
            "description": "Version created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PushResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/whoami": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCurrentUser",
        "summary": "Echo authenticated user",
        "description": "Verifies an API key and returns the authenticated user's identity and plan.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/auth/device/code": {
      "post": {
        "tags": [
          "Device Auth"
        ],
        "operationId": "createDeviceAuthorization",
        "summary": "Start a device authorization flow (CLI login)",
        "description": "Issues a short-lived device code and browser verification URL for CLI authentication.",
        "security": [],
        "responses": {
          "200": {
            "description": "Device code issued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "device_code",
                    "user_code",
                    "verification_uri",
                    "verification_uri_complete",
                    "expires_in",
                    "interval"
                  ],
                  "properties": {
                    "device_code": {
                      "type": "string"
                    },
                    "user_code": {
                      "type": "string",
                      "example": "ABCD-2345"
                    },
                    "verification_uri": {
                      "type": "string",
                      "format": "uri"
                    },
                    "verification_uri_complete": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expires_in": {
                      "type": "integer",
                      "example": 600
                    },
                    "interval": {
                      "type": "integer",
                      "example": 2
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/device/token": {
      "post": {
        "tags": [
          "Device Auth"
        ],
        "operationId": "exchangeDeviceAuthorization",
        "summary": "Poll for an approved device code to receive an API key",
        "description": "Exchanges an approved device code for an Undraft bearer API key; clients should respect the returned polling interval.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "device_code"
                ],
                "properties": {
                  "device_code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved. The `access_token` is an `undraft_*` API key usable for all other endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "access_token",
                    "token_type"
                  ],
                  "properties": {
                    "access_token": {
                      "type": "string",
                      "example": "undraft_..."
                    },
                    "token_type": {
                      "type": "string",
                      "example": "Bearer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Still pending, slow down, denied, or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "authorization_pending",
                        "slow_down",
                        "access_denied",
                        "expired_token",
                        "invalid_grant"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "undraft_*",
        "description": "API key as `Authorization: Bearer undraft_<key>`. Create and revoke keys in the\ndashboard at **Developer Settings**, or via the CLI: `undraft login`.\n"
      }
    },
    "schemas": {
      "ApiStatus": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "service",
          "version",
          "status",
          "documentation",
          "openapi",
          "authentication",
          "acceptedInputs",
          "cli"
        ],
        "properties": {
          "service": {
            "type": "string",
            "const": "Undraft API"
          },
          "version": {
            "type": "string",
            "const": "v1"
          },
          "status": {
            "type": "string",
            "const": "ok"
          },
          "documentation": {
            "type": "string",
            "format": "uri"
          },
          "openapi": {
            "type": "string",
            "format": "uri"
          },
          "authentication": {
            "type": "string"
          },
          "acceptedInputs": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "html",
                "zip",
                "file"
              ]
            }
          },
          "cli": {
            "type": "string",
            "example": "npx undraft"
          }
        }
      },
      "Project": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "slug",
          "name",
          "url",
          "visibility",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "liveVersionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "visibility": {
            "type": "string",
            "enum": [
              "public",
              "restricted"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PushResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url",
          "slug",
          "projectId",
          "versionId",
          "versionNumber",
          "entryPoint",
          "created"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "slug": {
            "type": "string"
          },
          "projectId": {
            "type": "string",
            "format": "uuid"
          },
          "versionId": {
            "type": "string",
            "format": "uuid"
          },
          "versionNumber": {
            "type": "integer"
          },
          "entryPoint": {
            "type": "string"
          },
          "created": {
            "type": "boolean",
            "description": "True when a new project was created, false when a version was pushed."
          }
        }
      },
      "User": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "email",
          "name",
          "plan"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string"
          },
          "plan": {
            "type": "string",
            "description": "On cloud deployments, one of `free`/`pro`/`team`. On self-hosted, typically `free` (unused)."
          }
        }
      },
      "Error": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "Machine-readable code for plan limits and slug conflicts.",
            "example": "PLAN_LIMIT_PROJECTS"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Bad request.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, malformed, unknown, or revoked API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Not the owner of this project.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "SlugConflict": {
        "description": "Slug is owned by another user.",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "enum": [
                        "SLUG_CONFLICT"
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "QuotaExceeded": {
        "description": "Plan limit reached. On cloud deployments only. On self-hosted the core has no\nplan awareness, so this response is never returned.\n",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "enum": [
                        "PLAN_LIMIT_PROJECTS",
                        "PLAN_LIMIT_VERSIONS",
                        "PLAN_LIMIT_STORAGE",
                        "PLAN_LIMIT_AI_EDITS"
                      ]
                    },
                    "currentCount": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "currentBytes": {
                      "type": "integer"
                    },
                    "uploadBytes": {
                      "type": "integer"
                    },
                    "limitBytes": {
                      "type": "integer"
                    }
                  }
                }
              ]
            }
          }
        }
      }
    }
  }
}
