{
  "openapi": "3.0.3",
  "info": {
    "title": "bytebin API",
    "version": "1.0.0",
    "description": "OpenAPI description for bytebin - a tiny content storage HTTP API.\n\nSee the project README for more details."
  },
  "servers": [
    { "url": "https://paste.ptrmc.net/api/", "description": "Production server" },
    { "url": "http://localhost:8080/", "description": "Local development server" }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      },
      "userApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key"
      }
    },
    "schemas": {
      "Credentials": {
        "type": "object",
        "required": ["username", "password"],
        "properties": {
          "username": { "type": "string" },
          "password": { "type": "string" }
        }
      },
      "TokenResponse": {
        "type": "object",
        "properties": { "token": { "type": "string" } }
      },
      "CreateUserApiKeyRequest": {
        "type": "object",
        "properties": { "label": { "type": "string" } }
      },
      "UserApiKey": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "label": { "type": "string" },
          "createdAt": { "type": "integer", "format": "int64" },
          "lastUsedAt": { "type": "integer", "format": "int64", "nullable": true }
        }
      },
      "CreateUserApiKeyResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/UserApiKey" },
          { "type": "object", "properties": { "apiKey": { "type": "string" } } }
        ]
      },
      "PasteItem": {
        "type": "object",
        "properties": {
          "key": { "type": "string" },
          "contentType": { "type": "string" },
          "contentLength": { "type": "integer" },
          "encoding": { "type": "string" },
          "backendId": { "type": "string" },
          "modifiable": { "type": "boolean" },
          "expiry": { "type": "integer", "format": "int64", "nullable": true },
          "lastModified": { "type": "integer", "format": "int64" }
        }
      },
      "UserItem": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "username": { "type": "string" },
          "createdAt": { "type": "integer", "format": "int64" }
        }
      },
      "PagedResponsePastes": {
        "type": "object",
        "properties": {
          "page": { "type": "integer" },
          "size": { "type": "integer" },
          "count": { "type": "integer" },
          "totalItems": { "type": "integer" },
          "totalPages": { "type": "integer" },
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/PasteItem" } }
        }
      },
      "PagedResponseUsers": {
        "type": "object",
        "properties": {
          "page": { "type": "integer" },
          "size": { "type": "integer" },
          "count": { "type": "integer" },
          "totalItems": { "type": "integer" },
          "totalPages": { "type": "integer" },
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/UserItem" } }
        }
      }
    }
  },
  "paths": {
    "/health": {
      "get": {
        "summary": "Health check",
        "responses": {
          "200": { "description": "ok", "content": { "application/json": { "example": { "status": "ok" } } } }
        }
      }
    },
    "/metrics": {
      "get": {
        "summary": "Prometheus metrics (if enabled)",
        "responses": { "200": { "description": "Prometheus text format", "content": { "text/plain": {} } } }
      }
    },
    "/post": {
      "post": {
        "summary": "Upload content (anonymous or authenticated)",
        "description": "Create content. Anonymous uploads are rate limited. Authenticated uploads may bypass limits by using a bearer JWT or X-Api-Key.",
        "requestBody": {
          "required": true,
          "content": {
            "application/octet-stream": { "schema": { "type": "string", "format": "binary" } },
            "*/*": { "schema": { "type": "string", "format": "binary" } }
          }
        },
        "security": [{ "userApiKey": [] }, { "bearerAuth": [] }, {}],
        "responses": {
          "201": {
            "description": "Created. Location header will contain the resource URL.",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "key": { "type": "string" } } } } }
          },
          "429": { "description": "Daily upload quota exceeded" }
        }
      },
      "put": {
        "summary": "Upload content and return full URL",
        "description": "Upload and return a full URL for the created content. Semantic is similar to POST but returns the full URL in the response body (or Location header).",
        "requestBody": {
          "required": true,
          "content": {
            "application/octet-stream": { "schema": { "type": "string", "format": "binary" } },
            "*/*": { "schema": { "type": "string", "format": "binary" } }
          }
        },
        "security": [{ "userApiKey": [] }, { "bearerAuth": [] }, {}],
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "url": { "type": "string" } } } } } },
          "429": { "description": "Daily upload quota exceeded" }
        }
      }
    },
    "/{id}": {
      "get": {
        "summary": "Retrieve content by key",
        "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ],
        "responses": {
          "200": {
            "description": "Returns the raw stored content. Content-Type will reflect the original content type.",
            "content": { "application/octet-stream": { }, "*/*": {} }
          },
          "404": { "description": "Not found" }
        }
      },
      "put": {
        "summary": "Update content at key (requires auth/modification key)",
        "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ],
        "requestBody": { "required": true, "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } } },
        "security": [{ "bearerAuth": [] }],
        "responses": { "200": { "description": "Updated" }, "403": { "description": "Incorrect modification key" }, "404": { "description": "Not found" } }
      }
    },
    "/auth/login": {
      "post": {
        "summary": "Login and issue JWT",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Credentials" } } } },
        "responses": { "200": { "description": "JWT issued", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TokenResponse" } } } }, "401": { "description": "Invalid credentials" } }
      }
    },
    "/auth/register": {
      "post": {
        "summary": "Register a new user (admin only)",
        "security": [{ "bearerAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Credentials" } } } },
        "responses": { "201": { "description": "Account created" }, "403": { "description": "Missing, invalid, or non-admin bearer token" }, "409": { "description": "Username already exists" } }
      }
    },
    "/auth/api-key": {
      "post": {
        "summary": "Issue a trusted admin API key (Authenticated)",
        "security": [{ "bearerAuth": [] }],
        "responses": { "200": { "description": "Returns a raw API key in JSON", "content": { "application/json": { "schema": { "type": "object", "properties": { "apiKey": { "type": "string" } } } } } }, "401": { "description": "Invalid token" } }
      }
    },
    "/users/me/apikeys": {
      "post": {
        "summary": "Create a user API key",
        "security": [{ "bearerAuth": [] }],
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateUserApiKeyRequest" } } } },
        "responses": { "201": { "description": "API key created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateUserApiKeyResponse" } } } }, "401": { "description": "Invalid bearer token" } }
      },
      "get": {
        "summary": "List user API keys metadata",
        "security": [{ "bearerAuth": [] }],
        "responses": { "200": { "description": "List of keys", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/UserApiKey" } } } } }, "401": { "description": "Invalid bearer token" } }
      }
    },
    "/users/me/apikeys/{id}": {
      "delete": {
        "summary": "Revoke user API key",
        "security": [{ "bearerAuth": [] }],
        "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ],
        "responses": { "204": { "description": "Key revoked" }, "401": { "description": "Invalid bearer token" }, "404": { "description": "API key not found" } }
      }
    },
    "/admin/bulkdelete": {
      "post": {
        "summary": "Bulk delete content (admin)",
        "security": [{ "bearerAuth": [] }],
        "parameters": [ { "name": "force", "in": "query", "schema": { "type": "boolean" }, "description": "Force delete even if marked not deletable" } ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string" }, "description": "List of keys to delete" } } } },
        "responses": { "200": { "description": "Number of items deleted", "content": { "application/json": { "schema": { "type": "integer" } } } }, "403": { "description": "Missing or invalid admin token" } }
      }
    },
    "/admin/pastes": {
      "get": {
        "summary": "List stored pastes metadata (admin)",
        "security": [{ "bearerAuth": [] }],
        "parameters": [ { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1 } }, { "name": "size", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100 } } ],
        "responses": { "200": { "description": "Paginated paste metadata", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PagedResponsePastes" } } } }, "403": { "description": "Missing or invalid admin token" } }
      }
    },
    "/admin/users": {
      "get": {
        "summary": "List registered users (admin)",
        "security": [{ "bearerAuth": [] }],
        "parameters": [ { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1 } }, { "name": "size", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100 } } ],
        "responses": { "200": { "description": "Paginated user metadata", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PagedResponseUsers" } } } }, "403": { "description": "Missing or invalid admin token" } }
      }
    }
  }
}

