{
  "openapi": "3.1.0",
  "info": {
    "title": "Proxybox API",
    "description": "Proxybox API Documentation.",
    "license": {
      "name": "MIT"
    },
    "version": "2.0.0"
  },
  "servers": [
    {
      "url": "https://pbx-{proxyboxId}.pbxz.io/api/v1",
      "description": "Version 1 of Proxybox API",
      "variables": {
        "proxyboxId": {
          "description": "The 4 character ID of your proxybox. You MUST input a value. The default will not work.",
          "default": "ABCD"
        }
      }
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/status": {
      "get": {
        "summary": "Get Status",
        "tags": ["Status"],
        "description": "Returns the status of your proxybox device.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/GetStatusResponse"
          },
          "400": {
            "$ref": "#/components/responses/ErrorResponse"
          }
        }
      }
    },
    "/printers": {
      "get": {
        "summary": "Get Printers",
        "tags": ["Printers"],
        "description": "Returns the list of printers configured on your proxybox, including real-time status from CUPS.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/GetPrintersResponse"
          },
          "400": {
            "$ref": "#/components/responses/ErrorResponse"
          }
        }
      }
    },
    "/printers/{path}": {
      "parameters": [
        {
          "name": "path",
          "in": "path",
          "required": true,
          "schema": { "type": "string", "examples": ["tcp_192-168-86-250_9100", "usb_sn_D8N225200819"] },
          "description": "The printer path (URL-encoded). This is the `path` field from the printer object returned by `GET /printers`."
        }
      ],
      "get": {
        "summary": "Get Single Printer",
        "tags": ["Printers"],
        "description": "Returns a single printer by its path, including real-time status. The response shape matches a single element from the `GET /printers` array.",
        "responses": {
          "200": {
            "description": "Printer found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse",
                  "properties": {
                    "result": {
                      "properties": {
                        "content": { "$ref": "#/components/schemas/Printer" }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/ErrorResponse"
          }
        }
      }
    },
    "/print/{target}": {
      "parameters": [
        {
          "name": "target",
          "in": "path",
          "required": true,
          "schema": { "type": "string", "examples": ["tcp_192-168-86-250_9100", "tag_LABELS", "usb_sn_D8N225200819"] },
          "description": "The printer target path. This is the `path` field from `GET /printers`, or a tag alias prefixed with `tag_`."
        }
      ],
      "post": {
        "summary": "Send Print Job",
        "tags": ["Printing"],
        "description": "Send a print job to a printer. Returns a job ID for tracking.\n\n**Three request formats:**\n- Raw binary data via `application/octet-stream` (always synchronous)\n- JSON with a URL to fetch: `{ \"content\": \"https://...\", \"contentType\": \"pdf_url\" }`\n- JSON with base64 data: `{ \"content\": \"JVBERi...\", \"contentType\": \"pdf_base64\" }`\n\nSet `wait: false` for non-blocking submission (returns immediately with `state: \"queued\"`, poll `GET /jobs/{jobId}` for status).",
        "responses": {
          "200": {
            "description": "Job completed (synchronous mode, `wait: true`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse",
                  "properties": {
                    "result": {
                      "properties": {
                        "content": { "$ref": "#/components/schemas/PrintJobResponse" }
                      }
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Job accepted (non-waiting mode, `wait: false`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse",
                  "properties": {
                    "result": {
                      "properties": {
                        "content": { "$ref": "#/components/schemas/PrintJobResponse" }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/ErrorResponse"
          },
          "503": {
            "$ref": "#/components/responses/PrinterNotReadyResponse"
          }
        },
        "requestBody": {
          "description": "The print data. Send raw binary as `application/octet-stream`, or a JSON object with content, options, and metadata.",
          "required": true,
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PrintRequest"
              }
            }
          }
        }
      }
    },
    "/jobs/{jobId}": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "schema": { "type": "string", "examples": ["pj_a8f3e1b2"] },
          "description": "The job ID returned by `POST /print/{target}`."
        }
      ],
      "get": {
        "summary": "Get Job Status",
        "tags": ["Jobs"],
        "description": "Returns the current state of a print job. The state is queried live from CUPS on each request — no background polling. Job metadata is kept in memory for 1 hour after creation.\n\nFor jobs submitted with `wait: false`, poll this endpoint to track completion.",
        "responses": {
          "200": {
            "description": "Job found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse",
                  "properties": {
                    "result": {
                      "properties": {
                        "content": { "$ref": "#/components/schemas/PrintJob" }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/ErrorResponse"
          }
        }
      }
    },
    "/discover": {
      "get": {
        "summary": "Discover Printers",
        "tags": ["Printers"],
        "description": "Triggers a scan for USB and network printers. The discovered printers are not returned in this response — call `GET /printers` afterward to see the updated list.",
        "responses": {
          "200": {
            "description": "Discovery completed successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["status", "statusCode", "result"],
                  "properties": {
                    "status": { "type": "string", "examples": ["success"] },
                    "statusCode": { "type": "integer", "examples": [200] },
                    "result": {
                      "type": "object",
                      "required": ["module", "message", "content"],
                      "properties": {
                        "module": { "type": "string", "examples": ["/discover"] },
                        "message": { "type": "string", "examples": ["Successfully discovered printers."] },
                        "content": { "type": "null" }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErrorResponse"
          }
        }
      }
    },
    "/print/{target}/test": {
      "parameters": [
        {
          "name": "target",
          "in": "path",
          "required": true,
          "schema": { "type": "string", "examples": ["tcp_192-168-86-250_9100", "usb_sn_D8N225200819"] },
          "description": "The printer target path or tag alias."
        }
      ],
      "post": {
        "summary": "Send Test Print",
        "tags": ["Printing"],
        "description": "Send a test print to a printer. For ZPL printers, prints a branded test label. For all other printers, prints a PDF test page.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/PostPrintResponse"
          },
          "404": {
            "$ref": "#/components/responses/ErrorResponse"
          },
          "503": {
            "$ref": "#/components/responses/PrinterNotReadyResponse"
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "GetStatusResponse": {
        "description": "Status Response",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": ["status", "statusCode", "result"],
              "properties": {
                "status": { "type": "string", "examples": ["success"] },
                "statusCode": { "type": "integer", "examples": [200] },
                "result": {
                  "type": "object",
                  "required": ["module", "message", "content"],
                  "properties": {
                    "module": { "type": "string", "examples": ["/status"] },
                    "message": { "type": "string", "examples": ["Successfully obtained device status."] },
                    "content": { "$ref": "#/components/schemas/StatusContent" }
                  }
                }
              }
            }
          }
        }
      },
      "GetPrintersResponse": {
        "description": "Printers Response",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": ["status", "statusCode", "result"],
              "properties": {
                "status": { "type": "string", "examples": ["success"] },
                "statusCode": { "type": "integer", "examples": [200] },
                "result": {
                  "type": "object",
                  "required": ["module", "message", "content"],
                  "properties": {
                    "module": { "type": "string", "examples": ["/printers"] },
                    "message": { "type": "string", "examples": ["Successfully obtained printers."] },
                    "content": { "$ref": "#/components/schemas/PrintersContent" }
                  }
                }
              }
            }
          }
        }
      },
      "PostPrintResponse": {
        "description": "Print job response (legacy format for test print).",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": ["status", "statusCode", "result"],
              "properties": {
                "status": { "type": "string", "examples": ["success"] },
                "statusCode": { "type": "integer", "examples": [200] },
                "result": {
                  "type": "object",
                  "required": ["module", "message", "content"],
                  "properties": {
                    "module": { "type": "string", "examples": ["/print/tcp_192-168-86-250_9100/test"] },
                    "message": { "type": "string", "examples": ["Test print sent successfully."] },
                    "content": { "type": "null" }
                  }
                }
              }
            }
          }
        }
      },
      "PrinterNotReadyResponse": {
        "description": "Printer is not ready to accept print jobs.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": ["status", "statusCode", "result"],
              "properties": {
                "status": { "type": "string", "examples": ["error"] },
                "statusCode": { "type": "integer", "examples": [503] },
                "result": {
                  "type": "object",
                  "required": ["module", "message", "content"],
                  "properties": {
                    "module": { "type": "string", "examples": ["/print/tcp_192-168-86-250_9100"] },
                    "message": { "type": "string", "examples": ["Printer is not ready"] },
                    "content": { "$ref": "#/components/schemas/PrintJobResponse" }
                  }
                }
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "description": "Error Response",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": ["status", "statusCode", "result"],
              "properties": {
                "status": { "type": "string", "examples": ["error"] },
                "statusCode": { "type": "integer", "examples": [400] },
                "result": {
                  "type": "object",
                  "required": ["module", "message", "content"],
                  "properties": {
                    "module": { "type": "string", "examples": ["/status"] },
                    "message": { "type": "string", "examples": ["An error occurred."] },
                    "content": {
                      "type": "object",
                      "properties": {
                        "message": { "type": "string", "examples": ["A specific error occurred."] }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "ApiResponse": {
        "type": "object",
        "description": "Standard API response envelope used by all endpoints.",
        "required": ["status", "statusCode", "result"],
        "properties": {
          "status": { "type": "string", "enum": ["success", "error"] },
          "statusCode": { "type": "integer" },
          "result": {
            "type": "object",
            "required": ["module", "message", "content"],
            "properties": {
              "module": { "type": "string", "description": "The API path that was called." },
              "message": { "type": "string", "description": "Human-readable status message." },
              "content": { "description": "Endpoint-specific payload." }
            }
          }
        }
      },
      "PrintRequest": {
        "type": "object",
        "required": ["content"],
        "properties": {
          "content": {
            "type": "string",
            "description": "The document to print. A URL (`http://...`), base64-encoded data, a local file path (`/path/to/file`), or literal ZPL commands.",
            "examples": ["https://example.com/label.zpl", "JVBERi0xLjQK..."]
          },
          "contentType": {
            "type": "string",
            "enum": ["pdf_url", "pdf_base64", "raw_url", "raw_base64", "zpl", "zpl_base64"],
            "description": "Explicit content type. Strongly recommended. When omitted, inferred from content (URLs detected by `http` prefix, local files by `/` prefix, otherwise base64)."
          },
          "title": {
            "type": "string",
            "description": "Human-readable job name. Passed to CUPS. Defaults to \"Untitled\".",
            "examples": ["Invoice #1234"]
          },
          "source": {
            "type": "string",
            "description": "Identifier for the submitting application or user.",
            "examples": ["acme-erp", "barbrogers"]
          },
          "wait": {
            "type": "boolean",
            "default": true,
            "description": "`true` (default): block until job completes or fails. `false`: return immediately with `state: \"queued\"`, poll `GET /jobs/{jobId}` for status."
          },
          "options": {
            "$ref": "#/components/schemas/PrintOptions"
          },
          "idempotencyKey": {
            "type": "string",
            "description": "Unique key to prevent duplicate submissions. If a job with this key already exists (within 1-hour TTL), the existing job is returned (HTTP 200). Keys do not survive device restart.",
            "examples": ["ord_98765_invoice"]
          }
        }
      },
      "PrintOptions": {
        "type": "object",
        "description": "Print options mapped to CUPS `lp` flags. Unsupported options for a given printer are silently ignored.",
        "properties": {
          "copies": { "type": "integer", "minimum": 1, "description": "Number of copies.", "examples": [2] },
          "duplex": { "type": "string", "enum": ["one-sided", "long-edge", "short-edge"], "description": "Duplex mode." },
          "colorMode": { "type": "string", "enum": ["color", "grayscale", "auto"], "description": "Color mode. `grayscale` maps to CUPS `monochrome`." },
          "paperSize": { "type": "string", "description": "CUPS media name. Must be in the printer's `mediaSupported`.", "examples": ["letter", "a4"] },
          "pageRange": { "type": "string", "description": "Page range string. PDF only.", "examples": ["1-5", "1,3,7-9"] },
          "bin": { "type": "string", "description": "Paper tray / input slot name.", "examples": ["Tray2"] },
          "fitToPage": { "type": "boolean", "description": "Scale content to fit the page. PDF only." },
          "orientation": { "type": "string", "enum": ["portrait", "landscape", "reverse-landscape", "reverse-portrait"] },
          "quality": { "type": "string", "enum": ["draft", "normal", "high"] }
        }
      },
      "PrintJobResponse": {
        "type": "object",
        "description": "Job summary returned by `POST /print/{target}` and error responses.",
        "required": ["jobId", "printer", "target", "title", "state", "createdAt"],
        "properties": {
          "jobId": { "type": "string", "description": "ProxyBox job ID.", "examples": ["pj_a8f3e1b2"] },
          "printer": { "type": "string", "description": "Printer path that received the job.", "examples": ["tcp_192-168-86-250_9100"] },
          "target": { "type": "string", "description": "Original target expression from the request.", "examples": ["tag_LABELS"] },
          "title": { "type": "string", "examples": ["Invoice #1234"] },
          "source": { "type": ["string", "null"], "examples": ["acme-erp"] },
          "state": { "$ref": "#/components/schemas/JobState" },
          "error": { "type": ["string", "null"], "description": "Error message if state is `failed`." },
          "createdAt": { "type": "string", "format": "date-time" },
          "completedAt": { "type": ["string", "null"], "format": "date-time" }
        }
      },
      "PrintJob": {
        "type": "object",
        "description": "Full job object returned by `GET /jobs/{jobId}`.",
        "required": ["jobId", "printer", "target", "title", "state", "createdAt"],
        "properties": {
          "jobId": { "type": "string", "examples": ["pj_a8f3e1b2"] },
          "printer": { "type": "string", "examples": ["tcp_192-168-86-250_9100"] },
          "target": { "type": "string", "examples": ["tag_LABELS"] },
          "title": { "type": "string", "examples": ["Invoice #1234"] },
          "source": { "type": ["string", "null"], "examples": ["acme-erp"] },
          "contentType": { "type": "string", "description": "Content type used for submission.", "examples": ["pdf_base64"] },
          "size": { "type": "integer", "description": "Document size in bytes.", "examples": [45230] },
          "state": { "$ref": "#/components/schemas/JobState" },
          "error": { "type": ["string", "null"] },
          "createdAt": { "type": "string", "format": "date-time" },
          "completedAt": { "type": ["string", "null"], "format": "date-time" },
          "cupsJobId": { "type": ["string", "null"], "description": "Numeric CUPS job ID as a string. For debugging only.", "examples": ["42"] }
        }
      },
      "JobState": {
        "type": "string",
        "enum": ["queued", "printing", "completed", "failed"],
        "description": "`queued`: submitted to CUPS, waiting. `printing`: CUPS is actively sending to printer. `completed`: finished successfully. `failed`: CUPS error or pre-flight check failed."
      },
      "StatusContent": {
        "type": "object",
        "properties": {
          "uptime": { "type": "number", "examples": [109975.16] },
          "version": { "type": "string", "examples": ["3.2.1"] },
          "load": { "type": "number", "examples": [0] },
          "totalMemory": { "type": "integer", "examples": [1936011264] },
          "freeMemory": { "type": "integer", "examples": [1559552000] },
          "ipAddress": { "type": "string", "examples": ["192.168.86.31"] },
          "buildVersion": { "type": "string", "examples": ["3.2.1"] },
          "builtAt": { "type": "string", "examples": ["2025-01-15"] },
          "hostname": { "type": "string", "examples": ["pbx-JRDN"] },
          "domain": { "type": "string", "examples": ["pbxz.io"] },
          "extensions": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ExtensionStatus" }
          }
        }
      },
      "PrintersContent": {
        "type": "array",
        "items": { "$ref": "#/components/schemas/Printer" }
      },
      "Printer": {
        "type": "object",
        "required": ["id", "type", "path", "connection", "content", "discovered", "status"],
        "properties": {
          "id": { "type": "string", "examples": ["MYS9QT8D"] },
          "type": { "type": "string", "examples": ["printer"] },
          "path": {
            "type": "string",
            "description": "The addressable path for this printer. Use this as the `target` parameter when printing.",
            "examples": ["tcp_192-168-86-250_9100", "usb_sn_D8N225200819", "ipp_HP8469932355FF-local_631_ipp-print"]
          },
          "connection": {
            "oneOf": [{ "$ref": "#/components/schemas/NetworkConnection" }, { "$ref": "#/components/schemas/USBConnection" }]
          },
          "model": { "type": "string", "examples": ["Zebra ZD421"] },
          "content": {
            "type": "string",
            "enum": ["raw", "pdf", "zpl"],
            "description": "The content type this printer accepts."
          },
          "supportedFormats": {
            "type": "array",
            "items": { "type": "string" },
            "description": "MIME types the printer supports (discovered via IPP).",
            "examples": [["application/pdf", "image/jpeg"]]
          },
          "discovered": {
            "type": "boolean",
            "description": "Whether this printer was auto-discovered (true) or manually configured (false)."
          },
          "localAddress": {
            "type": "string",
            "description": "Resolved IPv4 address for network printers.",
            "examples": ["192.168.86.250"]
          },
          "status": {
            "type": "string",
            "enum": ["online", "offline", "error", "unknown"],
            "description": "Printer status derived from CUPS queue state."
          }
        }
      },
      "NetworkConnection": {
        "type": "object",
        "required": ["type", "host", "protocol"],
        "properties": {
          "type": { "type": "string", "const": "network" },
          "host": { "type": "string", "examples": ["192.168.86.250", "HP8469932355FF.local"] },
          "port": { "type": "number", "examples": [9100, 631] },
          "path": { "type": "string", "description": "URL path component for IPP/HTTP printers.", "examples": ["/ipp/print"] },
          "protocol": { "type": "string", "enum": ["tcp", "ipp", "http", "https"] }
        }
      },
      "USBConnection": {
        "type": "object",
        "required": ["type"],
        "properties": {
          "type": { "type": "string", "const": "usb" },
          "busNumber": { "type": "number", "examples": [1] },
          "portNumbers": { "type": "array", "items": { "type": "number" }, "examples": [[1, 1, 4, 1]] },
          "serialNumber": { "type": "string", "examples": ["D8N225200819"] }
        }
      },
      "ExtensionStatus": {
        "type": "object",
        "required": ["name", "instances"],
        "properties": {
          "name": { "type": "string", "examples": ["reverseproxy", "manifest", "ifsprintagent"] },
          "instances": {
            "type": "array",
            "items": {
              "oneOf": [
                { "$ref": "#/components/schemas/ReverseProxyStatus", "title": "ReverseProxy" },
                { "$ref": "#/components/schemas/ManifestExtensionStatus", "title": "ExternalApp" },
                { "$ref": "#/components/schemas/IFSPrintAgentStatus", "title": "IFSPrintAgent" },
                { "$ref": "#/components/schemas/NetbirdStatus", "title": "Netbird" }
              ]
            }
          }
        }
      },
      "NetbirdStatus": {
        "type": "object",
        "required": ["id", "active"],
        "properties": {
          "id": { "type": "number", "examples": [0] },
          "active": { "type": "boolean", "examples": [true] },
          "os": { "type": "string", "examples": ["linux/arm64"] },
          "daemon_version": { "type": "string", "examples": ["0.49.0"] },
          "cli_version": { "type": "string", "examples": ["0.49.0"] },
          "management": { "type": "string", "examples": ["Connected"] },
          "signal": { "type": "string", "examples": ["Connected"] },
          "relays": { "type": "string", "examples": ["4/4 Available"] },
          "nameservers": { "type": "string", "examples": ["0/0 Available"] },
          "fqdn": { "type": "string" },
          "netbird_ip": { "type": "string" },
          "interface_type": { "type": "string", "examples": ["Kernel"] },
          "quantum_resistance": { "type": "string", "examples": ["false"] },
          "lazy_connection": { "type": "string", "examples": ["false"] },
          "networks": { "type": "string", "examples": ["-"] },
          "forwarding_rules": { "type": "string", "examples": ["0"] },
          "peers_count": { "type": "string", "examples": ["3/32 Connected"] }
        }
      },
      "IFSPrintAgentStatus": {
        "type": "object",
        "required": ["id", "active", "pollTime"],
        "properties": {
          "id": { "type": "number", "examples": [0] },
          "active": { "type": "boolean", "examples": [true] },
          "pollTime": { "type": "number", "examples": [60] }
        }
      },
      "ReverseProxyStatus": {
        "type": "object",
        "required": ["id", "active", "pathPrefix", "targetHost", "targetPort", "proxyUrl"],
        "properties": {
          "id": { "type": "number", "examples": [0] },
          "active": { "type": "boolean", "examples": [true] },
          "pathPrefix": { "type": "string", "examples": ["/wiki"] },
          "targetHost": { "type": "string", "examples": ["localhost"] },
          "targetPort": { "type": "string", "examples": ["8080"] },
          "proxyUrl": { "type": "string", "examples": ["/wiki/"] }
        }
      },
      "ManifestExtensionStatus": {
        "type": "object",
        "required": ["id", "active", "displayName", "manifestFound", "serviceName"],
        "properties": {
          "id": { "type": "number", "examples": [0] },
          "active": { "type": "boolean", "examples": [false] },
          "displayName": { "type": "string", "examples": ["My App"] },
          "manifestFound": { "type": "boolean", "examples": [true] },
          "serviceName": { "type": "string", "examples": ["my-app.service"] }
        }
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    }
  }
}
