{
  "openapi": "3.1.0",
  "info": {
    "title": "PrintSocket API",
    "summary": "Cloud print API: send print jobs to printers attached to machines running the PrintSocket agent.",
    "description": "A lightweight agent runs on your machine, connects outbound to PrintSocket, and exposes that machine's printers (and scales) to this REST API. There are no inbound ports to open and no print server to run.\n\nConventions that hold for every endpoint:\n\n- **Authentication.** `Authorization: Bearer <key>` on every endpoint except `GET /ping` and `GET /openapi.json`. Keys look like `sk_live_...` or `sk_test_...`, and the prefix identifies the mode. Each key carries a scope: `read` (GET only), `print` (read plus create and cancel jobs), or `manage` (everything, including key and webhook management).\n- **Test mode.** `sk_test_` keys get a virtual device and printer that runs the full job lifecycle, so an integration can be built end to end before any hardware exists. Test data and live data are completely separate, and jobs to the virtual printer never count toward plan limits.\n- **Requests and responses.** JSON only, `snake_case` fields, timestamps in RFC 3339 UTC. IDs are opaque and prefixed by type (`dev_`, `prn_`, `scl_`, `job_`, `doc_`, `whk_`, `key_`); treat them as strings.\n- **Every response** carries a `Request-Id` header and the `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` headers. Quote the request id in support requests.\n- **Pagination.** List endpoints are cursor based and newest first by default: `?limit=50&cursor=<opaque>&order=asc|desc`, limit default 25 and max 100. Pass `next_cursor` back as `cursor`, and stop when `has_more` is false.\n- **Idempotency.** Send `Idempotency-Key: <string>` on any POST. A replay of the same key within 24 hours returns the stored original response, the same status and the same body, so a network retry cannot create a duplicate job.\n- **Metadata.** Every mutable resource accepts up to 20 metadata keys with string values of 500 characters or less, and every list endpoint filters on them with `?metadata[order_id]=12345`.\n- **Rate limiting.** 20 requests per second per account by default, burstable. Sustained excess returns 429 with `Retry-After`.\n\nThis document describes the customer-facing API. The endpoints under `/agent` are the protocol between PrintSocket and its own agent, are authenticated by a device token rather than an API key, and are deliberately not published here.",
    "version": "1.0.0",
    "termsOfService": "https://www.printsocket.com/terms",
    "contact": {
      "name": "PrintSocket support",
      "email": "support@printsocket.com",
      "url": "https://www.printsocket.com/docs"
    }
  },
  "externalDocs": {
    "description": "PrintSocket API reference",
    "url": "https://www.printsocket.com/docs"
  },
  "servers": [
    {
      "url": "https://api.printsocket.com/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Utility",
      "description": "Liveness and key introspection."
    },
    {
      "name": "Devices",
      "description": "Machines running the PrintSocket agent, and the enrollment tokens that add them."
    },
    {
      "name": "Printers",
      "description": "Print queues an enrolled device reports. Discovered by the agent, never created through the API."
    },
    {
      "name": "Scales",
      "description": "Read-only weight telemetry from scales attached to a device."
    },
    {
      "name": "Jobs",
      "description": "Things to print, and their state transitions."
    },
    {
      "name": "Documents",
      "description": "Content uploaded once and printed many times."
    },
    {
      "name": "Webhooks",
      "description": "Signed event delivery, and the endpoints that receive it."
    },
    {
      "name": "API keys",
      "description": "Key management. Requires the manage scope, including for reads."
    }
  ],
  "paths": {
    "/ping": {
      "get": {
        "operationId": "ping",
        "tags": [
          "Utility"
        ],
        "summary": "Liveness check",
        "description": "Unauthenticated liveness check. Returns `{\"ok\": true}` when the API is serving.",
        "security": [],
        "responses": {
          "200": {
            "description": "The API is serving.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenAPIDocument",
        "tags": [
          "Utility"
        ],
        "summary": "This document",
        "description": "Returns this OpenAPI document. Unauthenticated, and served from https://www.printsocket.com/openapi.json as well.",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI document describing this API.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/me": {
      "get": {
        "operationId": "getMe",
        "x-required-scope": "read",
        "tags": [
          "Utility"
        ],
        "summary": "Introspect the calling key",
        "description": "Reports the account, key id, name, mode and scopes behind the key making the request.",
        "responses": {
          "200": {
            "description": "The calling key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/enrollment_tokens": {
      "post": {
        "operationId": "createEnrollmentToken",
        "x-required-scope": "manage",
        "tags": [
          "Devices"
        ],
        "summary": "Create an enrollment token",
        "description": "Returns a short-lived (1 hour), single-use token for the agent installer. The token exchanges for device-scoped credentials, so account API keys never have to be embedded in an installer or stored on an end customer's machine. Any name and metadata given here are applied to the device when it enrolls. The token inherits the calling key's mode, so a test-mode key enrolls a machine into the test pool.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateEnrollmentTokenRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The token, shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentToken"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/devices": {
      "get": {
        "operationId": "listDevices",
        "x-required-scope": "read",
        "tags": [
          "Devices"
        ],
        "summary": "List devices",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Order"
          },
          {
            "name": "state",
            "in": "query",
            "description": "Only devices in this state.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "online",
                "offline",
                "revoked"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/MetadataFilter"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of devices.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/devices/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DeviceId"
        }
      ],
      "get": {
        "operationId": "getDevice",
        "x-required-scope": "read",
        "tags": [
          "Devices"
        ],
        "summary": "Retrieve a device",
        "responses": {
          "200": {
            "description": "The device.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Device"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "patch": {
        "operationId": "updateDevice",
        "x-required-scope": "manage",
        "tags": [
          "Devices"
        ],
        "summary": "Update a device",
        "description": "Only the fields present in the body are changed. Unknown fields are rejected with 400 rather than ignored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateDeviceRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated device.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Device"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/devices/{id}/revoke": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DeviceId"
        }
      ],
      "post": {
        "operationId": "revokeDevice",
        "x-required-scope": "manage",
        "tags": [
          "Devices"
        ],
        "summary": "Revoke a device",
        "description": "Invalidates the agent's credentials and cuts any live connection. The device goes to `state: revoked` and cannot reconnect until it is enrolled again. Reconnecting the machine will not bring it back. The virtual test device refuses this with 409 `device_virtual`, because the credential clear is one way and nothing would provision a new fixture.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The revoked device.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Device"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The device is already revoked (`device_already_revoked`), or it is the virtual test device (`device_virtual`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/printers": {
      "get": {
        "operationId": "listPrinters",
        "x-required-scope": "read",
        "tags": [
          "Printers"
        ],
        "summary": "List printers",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Order"
          },
          {
            "name": "device_id",
            "in": "query",
            "description": "Only printers on this device.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "description": "Only printers in this state. A printer is offline whenever its device is offline.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "online",
                "offline"
              ]
            }
          },
          {
            "name": "enabled",
            "in": "query",
            "description": "Only printers with this enabled flag.",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "$ref": "#/components/parameters/MetadataFilter"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of printers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrinterList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/printers/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PrinterId"
        }
      ],
      "get": {
        "operationId": "getPrinter",
        "x-required-scope": "read",
        "tags": [
          "Printers"
        ],
        "summary": "Retrieve a printer",
        "description": "Includes the printer's full reported capabilities.",
        "responses": {
          "200": {
            "description": "The printer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Printer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "patch": {
        "operationId": "updatePrinter",
        "x-required-scope": "manage",
        "tags": [
          "Printers"
        ],
        "summary": "Update a printer",
        "description": "Sets your own display alias, soft-disables the printer, or configures options that merge underneath every job's own options. Only the fields present in the body are changed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePrinterRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated printer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Printer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/scales": {
      "get": {
        "operationId": "listScales",
        "x-required-scope": "read",
        "tags": [
          "Scales"
        ],
        "summary": "List scales",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Order"
          },
          {
            "name": "device_id",
            "in": "query",
            "description": "Only scales on this device.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of scales.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScaleList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/scales/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ScaleId"
        }
      ],
      "get": {
        "operationId": "getScale",
        "x-required-scope": "read",
        "tags": [
          "Scales"
        ],
        "summary": "Retrieve a scale",
        "description": "Includes the latest reading the agent reported. Read the weight on demand at the moment you need it, and check `captured_at` to see how stale it is.",
        "responses": {
          "200": {
            "description": "The scale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Scale"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/jobs": {
      "get": {
        "operationId": "listJobs",
        "x-required-scope": "read",
        "tags": [
          "Jobs"
        ],
        "summary": "List jobs",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Order"
          },
          {
            "name": "printer_id",
            "in": "query",
            "description": "Only jobs sent to this printer.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "device_id",
            "in": "query",
            "description": "Only jobs on this device.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only jobs in this status.",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/JobStatus"
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "description": "Only jobs created strictly after this RFC 3339 timestamp.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "created_before",
            "in": "query",
            "description": "Only jobs created strictly before this RFC 3339 timestamp.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/MetadataFilter"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "post": {
        "operationId": "createJob",
        "x-required-scope": "print",
        "tags": [
          "Jobs"
        ],
        "summary": "Create a print job",
        "description": "Sends one thing to one printer. Content arrives inline as a URL or as base64, or as a document uploaded earlier.\n\nCapability mismatches never reject a job. Asking for an option the printer does not report support for returns 201 with a `warnings` entry attached, because capability reporting is best effort and refusing to print would be worse than printing wrong.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateJobRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "The printer (`printer_not_found`) or the document (`document_not_found`) does not exist in this mode.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "The printer is disabled and rejects new jobs (`printer_disabled`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "description": "The printer is offline and the request set `queue_if_offline` to false (`printer_offline`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/jobs/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobId"
        }
      ],
      "get": {
        "operationId": "getJob",
        "x-required-scope": "read",
        "tags": [
          "Jobs"
        ],
        "summary": "Retrieve a job",
        "responses": {
          "200": {
            "description": "The job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/jobs/{id}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobId"
        }
      ],
      "post": {
        "operationId": "cancelJob",
        "x-required-scope": "print",
        "tags": [
          "Jobs"
        ],
        "summary": "Cancel a job",
        "description": "Cancels a job that has not yet reached a terminal state.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The canceled job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The job has already been sent to the printer or reached a terminal state (`job_not_cancelable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/jobs/{id}/events": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobId"
        }
      ],
      "get": {
        "operationId": "listJobEvents",
        "x-required-scope": "read",
        "tags": [
          "Jobs"
        ],
        "summary": "List a job's state transitions",
        "description": "The ordered timeline for one job. The list is bounded by the job lifecycle, so it is not paginated and carries no cursor.",
        "responses": {
          "200": {
            "description": "The job's transitions, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobEventList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/documents": {
      "post": {
        "operationId": "createDocument",
        "x-required-scope": "print",
        "tags": [
          "Documents"
        ],
        "summary": "Upload a document",
        "description": "Two ways to send content. Either PUT the bytes directly with the document's own content type (any type other than `application/json`), passing `expire_after_seconds` and `metadata[...]` as query parameters, or send `application/json` with a `source_url` for PrintSocket to fetch server side.\n\nSource fetches are limited to publicly routable addresses: loopback, private and link-local destinations are refused at dial time and on every redirect hop.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "expire_after_seconds",
            "in": "query",
            "description": "Direct uploads only. Lifetime of the document, 60 to 604800 seconds. JSON bodies carry this as a body field instead.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 60,
              "maximum": 604800,
              "default": 86400
            }
          },
          {
            "name": "metadata",
            "in": "query",
            "description": "Direct uploads only. Metadata to attach, as `metadata[order_id]=12345`.",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDocumentFromSourceRequest"
              }
            },
            "application/pdf": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The stored document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "description": "The source could not be fetched (`source_fetch_failed`), or what came back was over 50 MB (`source_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "description": "Document storage is not configured on this server (`documents_unavailable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "get": {
        "operationId": "getDocument",
        "x-required-scope": "read",
        "tags": [
          "Documents"
        ],
        "summary": "Retrieve a document",
        "description": "Returns the document's metadata. The stored bytes are not served back.",
        "responses": {
          "200": {
            "description": "The document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "delete": {
        "operationId": "deleteDocument",
        "x-required-scope": "print",
        "tags": [
          "Documents"
        ],
        "summary": "Delete a document",
        "description": "Jobs that already reference the document keep printing: a job snapshots the content when it is created.",
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "x-required-scope": "read",
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook endpoints",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Order"
          },
          {
            "$ref": "#/components/parameters/MetadataFilter"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of webhook endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "x-required-scope": "manage",
        "tags": [
          "Webhooks"
        ],
        "summary": "Create a webhook endpoint",
        "description": "The response is the only place the signing secret is shown. Store it before you discard the response.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created endpoint, including its secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/webhooks/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookId"
        }
      ],
      "get": {
        "operationId": "getWebhook",
        "x-required-scope": "read",
        "tags": [
          "Webhooks"
        ],
        "summary": "Retrieve a webhook endpoint",
        "responses": {
          "200": {
            "description": "The endpoint, without its secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "patch": {
        "operationId": "updateWebhook",
        "x-required-scope": "manage",
        "tags": [
          "Webhooks"
        ],
        "summary": "Update a webhook endpoint",
        "description": "Only the fields present in the body are changed. Set `disabled` to false to re-enable an endpoint PrintSocket disabled after sustained delivery failures.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "x-required-scope": "manage",
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook endpoint",
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/webhooks/{id}/test": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookId"
        }
      ],
      "post": {
        "operationId": "testWebhook",
        "x-required-scope": "manage",
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a test event",
        "description": "Delivers a synthetic `ping` event to the endpoint, signed the same way a real delivery is, and reports what the receiver said. Works on disabled endpoints too, which is how a fixed receiver is verified before it is re-enabled. The 200 describes the delivery attempt: check `success`, not the HTTP status.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The delivery attempt and its outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookTestResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api_keys": {
      "get": {
        "operationId": "listAPIKeys",
        "x-required-scope": "manage",
        "tags": [
          "API keys"
        ],
        "summary": "List API keys",
        "description": "Keys of both modes are listed. Secrets are never re-shown: a listing carries the prefix and the last four characters only. Requires the manage scope, because a listing exposes key prefixes and scopes.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Order"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of API keys.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIKeyList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "post": {
        "operationId": "createAPIKey",
        "x-required-scope": "manage",
        "tags": [
          "API keys"
        ],
        "summary": "Create an API key",
        "description": "The response is the only place the secret is shown. A test-mode key cannot create a live-mode key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAPIKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created key, including its secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIKeyWithSecret"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The key lacks the manage scope (`insufficient_scope`), or a test-mode key tried to create a live-mode key (`live_mode_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api_keys/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIKeyId"
        }
      ],
      "delete": {
        "operationId": "deleteAPIKey",
        "x-required-scope": "manage",
        "tags": [
          "API keys"
        ],
        "summary": "Revoke an API key",
        "description": "Revocation is immediate. Revoking the key making the request is allowed, which is how a leaked key kills itself. A test-mode key cannot revoke a live-mode key.",
        "responses": {
          "204": {
            "description": "Revoked."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The key lacks the manage scope (`insufficient_scope`), or a test-mode key tried to revoke a live-mode key (`live_mode_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    }
  },
  "webhooks": {
    "event": {
      "post": {
        "operationId": "receiveEvent",
        "tags": [
          "Webhooks"
        ],
        "summary": "Event delivered to your endpoint",
        "description": "PrintSocket POSTs this body to every webhook endpoint subscribed to the event's type.\n\nEvery delivery carries `PrintSocket-Signature: t=<unix>,v1=<hmac-sha256>`, computed with the endpoint's secret over `t.payload`. Verify it, and reject timestamps outside a 5 minute tolerance. Delivery is at least once, so dedupe on the event id. Failed deliveries retry with exponential backoff for 24 hours, and an endpoint that keeps failing is disabled automatically with a `webhook.disabled` event.\n\nRespond 2xx to acknowledge.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx acknowledges the delivery."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your API key, as `Authorization: Bearer sk_live_...` or `Authorization: Bearer sk_test_...`. The prefix identifies the mode, and the key's scope (`read`, `print` or `manage`) decides what it may do. Each operation carries `x-required-scope` naming the scope it needs; `/ping` and `/openapi.json` carry none because they take no key."
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "How many records to return.",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "description": "An opaque cursor from a previous page's `next_cursor`.",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "Order": {
        "name": "order",
        "in": "query",
        "description": "Sort direction by creation time.",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "asc",
            "desc"
          ],
          "default": "desc"
        }
      },
      "MetadataFilter": {
        "name": "metadata",
        "in": "query",
        "description": "Filter on your own metadata, as `metadata[order_id]=12345`. Several keys may be given, and all of them must match.",
        "required": false,
        "style": "deepObject",
        "explode": true,
        "schema": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "description": "A key of your choosing. A replay within 24 hours returns the stored original response rather than acting twice.",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "DeviceId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The device id, like `dev_7hk2m`.",
        "schema": {
          "type": "string"
        }
      },
      "PrinterId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The printer id, like `prn_8f2k1`.",
        "schema": {
          "type": "string"
        }
      },
      "ScaleId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The scale id, like `scl_9d3f2`.",
        "schema": {
          "type": "string"
        }
      },
      "JobId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The job id, like `job_5tq8p`.",
        "schema": {
          "type": "string"
        }
      },
      "DocumentId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The document id, like `doc_2ka9x`.",
        "schema": {
          "type": "string"
        }
      },
      "WebhookId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The webhook endpoint id, like `whk_4bd21`.",
        "schema": {
          "type": "string"
        }
      },
      "APIKeyId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The API key id, like `key_1a2b3`.",
        "schema": {
          "type": "string"
        }
      }
    },
    "headers": {
      "RequestId": {
        "description": "The id of this request, worth quoting in a support request.",
        "schema": {
          "type": "string"
        }
      },
      "RetryAfter": {
        "description": "Whole seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "A parameter or body field is invalid. `param` names the field.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The Authorization header is missing, malformed, or the key is unknown or revoked.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "A plan limit was reached (`plan_limit_reached` or `device_limit_reached`). The message names the plan, and the job message names the reset date.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key does not carry the scope this endpoint requires (`insufficient_scope`).",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource in this mode. Test-mode keys cannot see live-mode resources, or the reverse.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "Content is over the 50 MB limit (`payload_too_large`). Upload a document or pass a URL instead.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests (`rate_limited`). Retry after the Retry-After delay.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "ServerError": {
        "description": "Something failed on our side (`internal_error`). Quote the request id.",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorEnvelope": {
        "type": "object",
        "description": "The body of every non-2xx response.",
        "properties": {
          "error": {
            "$ref": "#/components/schemas/Error"
          }
        },
        "required": [
          "error"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "The coarse category. Branch on this for broad handling.",
            "enum": [
              "invalid_request",
              "authentication",
              "permission",
              "not_found",
              "conflict",
              "rate_limited",
              "billing",
              "server"
            ]
          },
          "code": {
            "type": "string",
            "description": "The specific case, like `printer_offline`. Branch on this when you handle one case."
          },
          "message": {
            "type": "string",
            "description": "Human readable, and subject to change. Do not parse it."
          },
          "param": {
            "type": "string",
            "description": "The offending field, on validation errors."
          },
          "request_id": {
            "type": "string",
            "description": "The same value as the Request-Id header."
          }
        },
        "required": [
          "type",
          "message"
        ]
      },
      "Metadata": {
        "type": [
          "object",
          "null"
        ],
        "description": "Your own identifiers: up to 20 keys, string values of 500 characters or less.",
        "additionalProperties": {
          "type": "string"
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "me"
          },
          "account_id": {
            "type": "string"
          },
          "key_id": {
            "type": "string"
          },
          "key_name": {
            "type": "string"
          },
          "mode": {
            "$ref": "#/components/schemas/Mode"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          }
        },
        "required": [
          "object",
          "account_id",
          "key_id",
          "key_name",
          "mode",
          "scopes"
        ]
      },
      "Mode": {
        "type": "string",
        "description": "Which pool a resource or key belongs to. The two are completely separate.",
        "enum": [
          "live",
          "test"
        ]
      },
      "Scope": {
        "type": "string",
        "description": "`read` is GET only, `print` adds creating and canceling jobs, `manage` is everything.",
        "enum": [
          "read",
          "print",
          "manage"
        ]
      },
      "DeviceState": {
        "type": "string",
        "enum": [
          "online",
          "offline",
          "revoked"
        ]
      },
      "HardwareState": {
        "type": "string",
        "description": "Printers and scales are offline whenever their device is offline.",
        "enum": [
          "online",
          "offline"
        ]
      },
      "DeviceOS": {
        "type": [
          "object",
          "null"
        ],
        "description": "Recorded at enrollment time only.",
        "properties": {
          "family": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "family",
          "version"
        ]
      },
      "Device": {
        "type": "object",
        "description": "A machine running the PrintSocket agent.",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "device"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own display name."
          },
          "state": {
            "$ref": "#/components/schemas/DeviceState"
          },
          "hostname": {
            "type": [
              "string",
              "null"
            ],
            "description": "As reported by the machine at enrollment."
          },
          "os": {
            "$ref": "#/components/schemas/DeviceOS"
          },
          "agent_version": {
            "type": [
              "string",
              "null"
            ],
            "description": "The build the machine is running right now, not the one it enrolled with."
          },
          "last_seen_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "virtual": {
            "type": "boolean",
            "description": "True only for the test-mode fixture device, and the only field that identifies it."
          }
        },
        "required": [
          "id",
          "object",
          "name",
          "state",
          "hostname",
          "os",
          "agent_version",
          "last_seen_at",
          "created_at",
          "metadata",
          "virtual"
        ]
      },
      "DeviceList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Device"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "UpdateDeviceRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "Your own display name, or null to clear it."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        },
        "additionalProperties": false
      },
      "CreateEnrollmentTokenRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "Applied to the device when it enrolls."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "EnrollmentToken": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "enrollment_token"
          },
          "token": {
            "type": "string",
            "description": "The secret, shown once. Single use, and it expires an hour after it is created."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "object",
          "token",
          "name",
          "metadata",
          "created_at",
          "expires_at"
        ]
      },
      "PrinterCapabilities": {
        "type": [
          "object",
          "null"
        ],
        "description": "What the printer reports it can do. Best effort: an agent cannot always know, so a job is never rejected for disagreeing with this.",
        "properties": {
          "color": {
            "type": "boolean"
          },
          "duplex": {
            "type": "boolean"
          },
          "copies_max": {
            "type": "integer"
          },
          "dpis": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Like `203x203`."
          },
          "papers": {
            "type": "object",
            "description": "Paper name to [width, height] in tenths of a millimetre.",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            }
          },
          "trays": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "media": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "collate": {
            "type": "boolean"
          },
          "custom_paper_size": {
            "type": "boolean"
          }
        },
        "additionalProperties": true
      },
      "Printer": {
        "type": "object",
        "description": "A print queue on a device. Discovered by the agent, never created through the API.",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "printer"
          },
          "device_id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own alias for it."
          },
          "system_name": {
            "type": "string",
            "description": "The queue name as reported by the operating system."
          },
          "state": {
            "$ref": "#/components/schemas/HardwareState"
          },
          "enabled": {
            "type": "boolean",
            "description": "False soft-disables the printer: new jobs are rejected with 409 `printer_disabled`."
          },
          "default_options": {
            "type": [
              "object",
              "null"
            ],
            "description": "Options that merge underneath every job's own options.",
            "additionalProperties": true
          },
          "capabilities": {
            "$ref": "#/components/schemas/PrinterCapabilities"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        },
        "required": [
          "id",
          "object",
          "device_id",
          "name",
          "system_name",
          "state",
          "enabled",
          "default_options",
          "capabilities",
          "created_at",
          "metadata"
        ]
      },
      "PrinterList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Printer"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "UpdatePrinterRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "enabled": {
            "type": "boolean"
          },
          "default_options": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        },
        "additionalProperties": false
      },
      "ScaleReading": {
        "type": [
          "object",
          "null"
        ],
        "description": "Null until the agent reports one. A report without a fresh reading keeps the previous one, so check `captured_at`.",
        "properties": {
          "weight_grams": {
            "type": "integer"
          },
          "stable": {
            "type": "boolean",
            "description": "Whether the scale had settled when the weight was taken."
          },
          "captured_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "weight_grams",
          "stable",
          "captured_at"
        ]
      },
      "Scale": {
        "type": "object",
        "description": "Read-only weight telemetry. Scales carry no metadata and emit no webhook events.",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "scale"
          },
          "device_id": {
            "type": "string"
          },
          "system_name": {
            "type": "string"
          },
          "state": {
            "$ref": "#/components/schemas/HardwareState"
          },
          "reading": {
            "$ref": "#/components/schemas/ScaleReading"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "device_id",
          "system_name",
          "state",
          "reading",
          "created_at"
        ]
      },
      "ScaleList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scale"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "JobStatus": {
        "type": "string",
        "description": "created to queued to sent to printing to succeeded, with failed, canceled and expired as terminal exits.",
        "enum": [
          "created",
          "queued",
          "sent",
          "printing",
          "succeeded",
          "failed",
          "canceled",
          "expired"
        ]
      },
      "JobWarning": {
        "type": "object",
        "description": "A non-fatal notice. The job still prints.",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "option_unsupported",
              "copies_exceeds_max"
            ]
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "code",
          "message"
        ]
      },
      "JobError": {
        "type": [
          "object",
          "null"
        ],
        "description": "Set when the job failed.",
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "code",
          "message"
        ]
      },
      "JobOptions": {
        "type": [
          "object",
          "null"
        ],
        "description": "Print options. Anything the printer does not report support for is passed to the driver anyway, with a warning attached to the job.",
        "properties": {
          "duplex": {
            "type": "string",
            "enum": [
              "long_edge",
              "short_edge",
              "none"
            ]
          },
          "color": {
            "type": "boolean"
          },
          "paper": {
            "type": "string",
            "description": "A paper name from the printer's capabilities, like `A4`."
          },
          "dpi": {
            "type": "string",
            "description": "Like `300x300`."
          },
          "page_ranges": {
            "type": "string",
            "description": "Like `1,3-5`."
          },
          "tray": {
            "type": "string"
          },
          "media": {
            "type": [
              "string",
              "null"
            ]
          },
          "collate": {
            "type": "boolean"
          },
          "fit_to_page": {
            "type": "boolean"
          },
          "rotate": {
            "type": "integer"
          },
          "pages_per_sheet": {
            "type": "integer"
          }
        },
        "additionalProperties": true
      },
      "JobContent": {
        "type": "object",
        "description": "Exactly one of url, base64 or document_id.",
        "properties": {
          "format": {
            "type": "string",
            "description": "`raw` is passed to the printer unvalidated, which is how ZPL and ESC/POS are sent.",
            "enum": [
              "pdf",
              "raw"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "An http(s) URL the agent fetches directly."
          },
          "base64": {
            "type": "string",
            "description": "The content inline, base64 encoded. Up to 50 MB decoded."
          },
          "document_id": {
            "type": "string",
            "description": "A document uploaded earlier. Its bytes are snapshotted onto the job, so the job survives the document expiring."
          },
          "fetch_auth": {
            "type": "object",
            "description": "Credentials for fetching url. Only valid alongside url.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "basic"
                ]
              },
              "username": {
                "type": "string"
              },
              "password": {
                "type": "string"
              }
            },
            "required": [
              "type"
            ]
          }
        },
        "required": [
          "format"
        ]
      },
      "CreateJobRequest": {
        "type": "object",
        "properties": {
          "printer_id": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "content": {
            "$ref": "#/components/schemas/JobContent"
          },
          "copies": {
            "type": "integer",
            "minimum": 1,
            "maximum": 999,
            "default": 1,
            "description": "Handed to the platform's own copy mechanism rather than queuing the job several times."
          },
          "options": {
            "$ref": "#/components/schemas/JobOptions"
          },
          "queue_if_offline": {
            "type": "boolean",
            "default": true,
            "description": "False rejects the job immediately with 422 `printer_offline` instead of holding it until the device is back."
          },
          "expire_after_seconds": {
            "type": "integer",
            "minimum": 60,
            "maximum": 604800,
            "default": 86400,
            "description": "How long the job may wait before it expires unprinted."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        },
        "required": [
          "printer_id",
          "content"
        ]
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "job"
          },
          "printer_id": {
            "type": "string"
          },
          "device_id": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus"
          },
          "copies": {
            "type": "integer"
          },
          "options": {
            "$ref": "#/components/schemas/JobOptions"
          },
          "warnings": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/JobWarning"
            }
          },
          "error": {
            "$ref": "#/components/schemas/JobError"
          },
          "completion_confidence": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "printer",
              "spooler",
              null
            ],
            "description": "Present once a job succeeds. `printer` means the hardware confirmed the page came out; `spooler` means only the spooler did, because the driver cannot confirm more. If a print must be confirmed at the hardware, check this rather than status alone."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "queued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        },
        "required": [
          "id",
          "object",
          "printer_id",
          "device_id",
          "title",
          "status",
          "copies",
          "options",
          "warnings",
          "error",
          "created_at",
          "queued_at",
          "sent_at",
          "completed_at",
          "expires_at",
          "metadata"
        ]
      },
      "JobList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Job"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "JobEvent": {
        "type": "object",
        "description": "One state transition of one job.",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "job_event"
          },
          "job_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus"
          },
          "error": {
            "$ref": "#/components/schemas/JobError"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "job_id",
          "status",
          "error",
          "created_at"
        ]
      },
      "JobEventList": {
        "type": "object",
        "description": "Not paginated: the timeline is bounded by the job lifecycle, so has_more is always false and there is no cursor.",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JobEvent"
            }
          },
          "has_more": {
            "type": "boolean"
          }
        },
        "required": [
          "data",
          "has_more"
        ]
      },
      "CreateDocumentFromSourceRequest": {
        "type": "object",
        "description": "The application/json form of a document upload: PrintSocket fetches the URL server side.",
        "properties": {
          "source_url": {
            "type": "string",
            "format": "uri"
          },
          "fetch_auth": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "basic"
                ]
              },
              "username": {
                "type": "string"
              },
              "password": {
                "type": "string"
              }
            },
            "required": [
              "type"
            ]
          },
          "expire_after_seconds": {
            "type": "integer",
            "minimum": 60,
            "maximum": 604800,
            "default": 86400
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        },
        "required": [
          "source_url"
        ]
      },
      "Document": {
        "type": "object",
        "description": "Content uploaded once and printable many times. Documents expire, 24 hours by default.",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "document"
          },
          "content_type": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        },
        "required": [
          "id",
          "object",
          "content_type",
          "size_bytes",
          "created_at",
          "expires_at",
          "metadata"
        ]
      },
      "EventType": {
        "type": "string",
        "description": "Subscribable event types. Wildcards are accepted in a subscription: `*`, `job.*`, `printer.*`, `device.*` and `webhook.*`.",
        "enum": [
          "job.created",
          "job.queued",
          "job.sent",
          "job.printing",
          "job.succeeded",
          "job.failed",
          "job.canceled",
          "job.expired",
          "printer.discovered",
          "printer.state_changed",
          "printer.removed",
          "device.enrolled",
          "device.state_changed",
          "device.revoked",
          "webhook.disabled"
        ]
      },
      "Event": {
        "type": "object",
        "description": "One delivery to a webhook endpoint.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Like `evt_3mm2z`. Delivery is at least once, so dedupe on this."
          },
          "type": {
            "description": "The event type, or `ping` for a test delivery.",
            "anyOf": [
              {
                "$ref": "#/components/schemas/EventType"
              },
              {
                "type": "string",
                "const": "ping"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "description": "The full resource the event is about: a Job, Printer, Device or Webhook, matching the event's family.",
            "additionalProperties": true
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ]
      },
      "CreateWebhookRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "An http(s) URL. Plain http is allowed for local receivers; the signature protects the payload either way."
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "type": "string"
            },
            "description": "Event types or wildcards, like `[\"job.*\", \"printer.state_changed\"]`."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        },
        "required": [
          "url",
          "events"
        ]
      },
      "UpdateWebhookRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "type": "string"
            }
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "disabled": {
            "type": "boolean"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        },
        "additionalProperties": false
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "webhook"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "disabled": {
            "type": "boolean",
            "description": "Set automatically after sustained delivery failures, alongside a `webhook.disabled` event."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        },
        "required": [
          "id",
          "object",
          "url",
          "events",
          "description",
          "disabled",
          "created_at",
          "metadata"
        ]
      },
      "WebhookWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "The signing secret, shown once here and never again."
              }
            },
            "required": [
              "secret"
            ]
          }
        ]
      },
      "WebhookList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Webhook"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "WebhookTestResult": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "webhook_test_result"
          },
          "event_id": {
            "type": "string"
          },
          "success": {
            "type": "boolean",
            "description": "True when the receiver answered 2xx."
          },
          "status_code": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Null when the endpoint could not be reached at all."
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "object",
          "event_id",
          "success",
          "status_code",
          "error"
        ]
      },
      "CreateAPIKeyRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "scopes": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          },
          "mode": {
            "$ref": "#/components/schemas/Mode"
          }
        },
        "required": [
          "name",
          "scopes",
          "mode"
        ]
      },
      "APIKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "api_key"
          },
          "name": {
            "type": "string"
          },
          "mode": {
            "$ref": "#/components/schemas/Mode"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          },
          "secret_prefix": {
            "type": "string",
            "description": "Like `sk_test_`."
          },
          "secret_last4": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "name",
          "mode",
          "scopes",
          "secret_prefix",
          "secret_last4",
          "created_at"
        ]
      },
      "APIKeyWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/APIKey"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "The full key, shown once here and never again."
              }
            },
            "required": [
              "secret"
            ]
          }
        ]
      },
      "APIKeyList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIKey"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      }
    }
  }
}
