{"openapi":"3.1.0","info":{"title":"Receiptful API","description":"\n**Receiptful** lets you print receipts on thermal printers from anywhere via a simple REST API.\n\n# Getting Started\n\n## How It Works\n\n1. **You** send print jobs to our API\n2. **We** push them to your Android devices running our app\n3. **The app** prints to thermal printers connected via Bluetooth or USB\n\nNo printer drivers, no infrastructure headaches. Just an API call and your receipt prints.\n\n## Quick Start\n\n```bash\n# 1. Get your API key\ncurl -X POST /v1/users \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"your@email\"}'\n\n# 2. Register a printer (gives you a 6-character login code)\ncurl -X POST /v1/printers \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"label\": \"Kitchen\"}'\n\n# 3. Install the Android app and enter the login code (valid 7 days)\n#    Download: https://receiptful.io/files/android_v1.apk\n\n# 4. Print an HTML receipt\ncurl -X POST /v1/printers/1/jobs \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: text/html\" \\\n  -d '<h3>Hello World</h3><p>Your receipt here</p><cut/>'\n```\n\n## Authentication\n\nEvery request needs an `Authorization: Bearer YOUR_API_KEY` header. Get your key\nfrom `POST /v1/users` — it's returned once, so save it. Missing, malformed, or\nunknown keys return `401 Unauthorized`. Treat your key as a secret — it scopes\nall printers and print jobs to your account.\n","version":"1.1.0"},"paths":{"/v1/users":{"post":{"tags":["Users"],"summary":"Create User","description":"Create a new user account and get your API key.\n\nThe returned `api_key` is the plaintext value of the first project API\nkey. It is shown once; subsequent reads only expose `last4`.","operationId":"create_user_v1_users_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/printers":{"post":{"tags":["Printers"],"summary":"Create Printer","description":"Register a new printer.\n\nReturns a `login_code` that you enter in our Android app to log the printer in.\nOnce logged in, you can send print jobs to it.","operationId":"create_printer_v1_printers_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrinterCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrinterResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Printers"],"summary":"List Printers","description":"List all your printers.\n\nShows login status (`is_logged_in`) and pending login codes for printers that\naren't logged in yet.","operationId":"list_printers_v1_printers_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PrinterResponse"},"title":"Response List Printers V1 Printers Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/printers/{printer_id}":{"get":{"tags":["Printers"],"summary":"Get Printer","description":"Get a specific printer's details.","operationId":"get_printer_v1_printers__printer_id__get","parameters":[{"name":"printer_id","in":"path","required":true,"schema":{"type":"integer","title":"Printer Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrinterResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Printers"],"summary":"Update Printer","description":"Update a printer's label or profile.","operationId":"update_printer_v1_printers__printer_id__patch","parameters":[{"name":"printer_id","in":"path","required":true,"schema":{"type":"integer","title":"Printer Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrinterUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrinterResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Printers"],"summary":"Delete Printer","description":"Delete a printer.\n\nThe printer is soft-deleted and print job history is preserved.\nThe Android app will be logged out.","operationId":"delete_printer_v1_printers__printer_id__delete","parameters":[{"name":"printer_id","in":"path","required":true,"schema":{"type":"integer","title":"Printer Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/printers/{printer_id}/refresh-login-code":{"post":{"tags":["Printers"],"summary":"Refresh Login Code","description":"Generate a new login code.\n\nUse this if the previous code expired or you need to log in a different device.","operationId":"refresh_login_code_v1_printers__printer_id__refresh_login_code_post","parameters":[{"name":"printer_id","in":"path","required":true,"schema":{"type":"integer","title":"Printer Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrinterResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/printer-profiles":{"get":{"tags":["Printers"],"summary":"List Printer Profiles","description":"The known python-escpos printer profiles, for the console's profile\ndropdown. Project-scoped auth keeps the override path uniform with the\nrest of /v1; the catalog itself is project-independent.","operationId":"list_printer_profiles_v1_printer_profiles_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"title":"Response List Printer Profiles V1 Printer Profiles Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/printers/{printer_id}/jobs":{"post":{"tags":["Print Jobs"],"summary":"Create Print Job","description":"Send a print job to a printer.\n\nSet `Content-Type` to match the body:\n\n* `text/html` - HTML rendered server-side to ESC/POS using the printer's profile.\n* `application/vnd.escpos` - raw ESC/POS bytes stored verbatim.\n* `application/json` - render a stored receipt template: reference it via\n  `template_id` or `template_name` (id wins if both are sent) and supply the\n  template's variables in `data`. Rendering is strict — a variable the\n  template uses but `data` omits rejects the job with a 422.\n\nRaw bodies are capped at 1 MB. The job expires after the optional\n`X-Lifetime-Seconds` header or `lifetime_seconds` query parameter\n(default: 600 = 10 minutes).\nIf the printer doesn't pick it up in time, it won't print.","operationId":"create_print_job_v1_printers__printer_id__jobs_post","parameters":[{"name":"printer_id","in":"path","required":true,"schema":{"type":"integer","title":"Printer Id"}},{"name":"lifetime_seconds","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"title":"Lifetime Seconds"}},{"name":"X-Lifetime-Seconds","in":"header","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"title":"X-Lifetime-Seconds"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrintJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"text/html":{"schema":{"type":"string"}},"application/vnd.escpos":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"properties":{"template_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Template Id"},"template_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Name"},"data":{"additionalProperties":true,"default":{},"title":"Data","type":"object"},"lifetime_seconds":{"default":600,"minimum":1,"title":"Lifetime Seconds","type":"integer"}},"title":"PrintJobCreate","type":"object"}}}}},"get":{"tags":["Print Jobs"],"summary":"List Print Jobs","description":"List print jobs for a printer.\n\nReturns jobs in reverse chronological order (newest first).","operationId":"list_print_jobs_v1_printers__printer_id__jobs_get","parameters":[{"name":"printer_id","in":"path","required":true,"schema":{"type":"integer","title":"Printer Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PrintJobResponse"},"title":"Response List Print Jobs V1 Printers  Printer Id  Jobs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/printers/{printer_id}/jobs/{job_id}":{"get":{"tags":["Print Jobs"],"summary":"Get Print Job","description":"Get a print job's status.\n\n### Tracking Status\n\nA print job moves through this status sequence:\n\n`created` → `notification_sent` → `notification_received` → `printing` → `completed`\n\nPoll this endpoint to track its current status.\n\nThe response includes the full history in status_history (chronological), and the\nsubmitted html (null for raw ESC/POS jobs).","operationId":"get_print_job_v1_printers__printer_id__jobs__job_id__get","parameters":[{"name":"printer_id","in":"path","required":true,"schema":{"type":"integer","title":"Printer Id"}},{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","title":"Job Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrintJobDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/printers/{printer_id}/jobs/{job_id}/redact":{"post":{"tags":["Print Jobs"],"summary":"Redact Print Job","description":"Irreversibly erase a print job's content (GDPR erasure).\n\nEmpties the stored ESC/POS bytes and removes the source HTML and\ntemplate data, keeping the job itself and its status history as\nanonymous metadata. Sets `redacted_at`; a redacted job can no longer\nbe printed. Idempotent — redacting an already redacted job returns\n200 and keeps the original `redacted_at`.","operationId":"redact_print_job_v1_printers__printer_id__jobs__job_id__redact_post","parameters":[{"name":"printer_id","in":"path","required":true,"schema":{"type":"integer","title":"Printer Id"}},{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","title":"Job Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrintJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health Check","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"PrintJobDetailResponse":{"properties":{"id":{"type":"integer","title":"Id"},"printer_id":{"type":"integer","title":"Printer Id"},"status":{"$ref":"#/components/schemas/PrintJobStatusEnum"},"lifetime_seconds":{"type":"integer","title":"Lifetime Seconds"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"status_updated_at":{"type":"string","format":"date-time","title":"Status Updated At"},"redacted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Redacted At"},"html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Html"},"status_history":{"items":{"$ref":"#/components/schemas/PrintJobStatusEntryResponse"},"type":"array","title":"Status History"}},"type":"object","required":["id","printer_id","status","lifetime_seconds","created_at","expires_at","status_updated_at","redacted_at","html","status_history"],"title":"PrintJobDetailResponse"},"PrintJobResponse":{"properties":{"id":{"type":"integer","title":"Id"},"printer_id":{"type":"integer","title":"Printer Id"},"status":{"$ref":"#/components/schemas/PrintJobStatusEnum"},"lifetime_seconds":{"type":"integer","title":"Lifetime Seconds"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"status_updated_at":{"type":"string","format":"date-time","title":"Status Updated At"},"redacted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Redacted At"}},"type":"object","required":["id","printer_id","status","lifetime_seconds","created_at","expires_at","status_updated_at","redacted_at"],"title":"PrintJobResponse"},"PrintJobStatusEntryResponse":{"properties":{"status":{"$ref":"#/components/schemas/PrintJobStatusEnum"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["status","created_at"],"title":"PrintJobStatusEntryResponse"},"PrintJobStatusEnum":{"type":"string","enum":["created","notification_sent","notification_failed","notification_received","printing","completed","failed","expired"],"title":"PrintJobStatusEnum"},"PrinterConnectionType":{"type":"string","enum":["BLUETOOTH","USB","IP"],"title":"PrinterConnectionType"},"PrinterCreate":{"properties":{"label":{"type":"string","title":"Label"},"profile":{"type":"string","title":"Profile","default":"NT-5890K"}},"type":"object","required":["label"],"title":"PrinterCreate"},"PrinterResponse":{"properties":{"id":{"type":"integer","title":"Id"},"label":{"type":"string","title":"Label"},"profile":{"type":"string","title":"Profile"},"project_id":{"type":"string","format":"uuid","title":"Project Id"},"project_slug":{"type":"string","title":"Project Slug"},"login_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Login Code"},"login_code_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Login Code Expires At"},"is_logged_in":{"type":"boolean","title":"Is Logged In"},"is_paired":{"type":"boolean","title":"Is Paired","description":"Deprecated: use is_logged_in. Kept for backwards compatibility.","deprecated":true},"created_at":{"type":"string","format":"date-time","title":"Created At"},"logged_in_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Logged In At"},"paired_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paired At","description":"Deprecated: use logged_in_at. Kept for backwards compatibility.","deprecated":true},"logged_out_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Logged Out At"},"is_online":{"type":"boolean","title":"Is Online"},"bluetooth_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Bluetooth Enabled"},"printer_connected":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Printer Connected"},"last_heartbeat_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Heartbeat At"},"connection_type":{"anyOf":[{"$ref":"#/components/schemas/PrinterConnectionType"},{"type":"null"}]},"connection_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connection Name"},"connection_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connection Address"},"connection_usb_vendor_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Connection Usb Vendor Id"},"connection_usb_product_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Connection Usb Product Id"},"connection_created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Connection Created At"}},"type":"object","required":["id","label","profile","project_id","project_slug","login_code","login_code_expires_at","is_logged_in","is_paired","created_at","logged_in_at","logged_out_at","is_online","bluetooth_enabled","printer_connected","last_heartbeat_at","connection_type","connection_name","connection_address","connection_usb_vendor_id","connection_usb_product_id","connection_created_at"],"title":"PrinterResponse"},"PrinterUpdate":{"properties":{"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"profile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile"}},"type":"object","title":"PrinterUpdate"},"UserCreate":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"UserCreate"},"UserResponse":{"properties":{"email":{"type":"string","title":"Email"},"api_key":{"type":"string","title":"Api Key"}},"type":"object","required":["email","api_key"],"title":"UserResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}},"tags":[{"name":"Users","description":"Create a user account to get your API key. One user = one API key = isolated printers and jobs."},{"name":"Printers","description":"Register and manage your thermal printers. Each printer gets a one-time login code to log it in from our Android app."},{"name":"Print Jobs","description":"Send print jobs by POSTing HTML (`Content-Type: text/html`) or raw ESC/POS bytes (`Content-Type: application/vnd.escpos`) directly to your printer. Track job status via polling."},{"name":"HTML Reference","description":"Supported HTML elements for the `html` field in print jobs. The server converts\nHTML to ESC/POS bytes using the printer's profile.\n\n## Block Elements\n\n### `<p>` — Paragraph\n\nEstablishes a line boundary before and after its inline children. At either\nboundary, it emits a line feed only when the current line contains content.\nThis keeps adjacent or nested blocks on separate lines without adding blank\nlines; an empty block at the start of a line emits no line feed. Supports\n`align` attribute (`left`, `center`, `right`).\n\n```html\n<p>Hello world</p>\n<p align=\"center\">Centered text</p>\n```\n\n### `<div>` — Division\n\nSame conditional line-boundary behavior as `<p>`. Supports `align` attribute.\n\n```html\n<div align=\"right\">Right-aligned</div>\n```\n\n### `<hr/>` — Horizontal Rule\n\nRenders a thin horizontal line. Supports `width` (percentage) and `align` attributes.\n\n```html\n<hr/>\n<hr width=\"50%\" align=\"center\"/>\n```\n\n## Tables\n\n### `<table>` — Table\n\nRenders aligned plain-text columns. Supports `<thead>`, `<tbody>`,\n`<colgroup>`/`<col>`, and bare `<tr>` rows. A `<thead>` produces a dashed\nseparator line between header and body. Each rendered row ends with a line\nfeed; the table adds no extra blank line after its final row.\n\nColumn attributes (via `<col>` or first-row `<td>`/`<th>`):\n\n- `width` — absolute (chars) or percentage\n- `align` — `left`, `center`, `right`\n- `valign` — `top`, `middle`, `bottom`\n\n**Inline formatting inside cells** — `<b>`, `<strong>`, `<u>`, and `<inv>` can\nwrap any portion of a cell's text and may be nested. `<br>` inserts a hard line\nbreak within a cell (the resulting lines still wrap to column width if needed).\n`&nbsp;` is a non-breaking space — it prevents soft wrapping at that position\nand renders as a regular space. `<small>` is not supported inside cells (font B\nchanges character width and breaks column alignment). Wrapping the entire\n`<table>` in `<small>` is fine — all columns use font B uniformly.\n\n```html\n<!-- Percentage widths with inline cell formatting -->\n<table>\n  <colgroup>\n    <col width=\"60%\" align=\"left\"/>\n    <col width=\"40%\" align=\"right\"/>\n  </colgroup>\n  <thead>\n    <tr><th>Item</th><th>Price</th></tr>\n  </thead>\n  <tbody>\n    <tr><td>Espresso</td><td>3.50</td></tr>\n    <tr><td>Oat Milk<br><b>Flat White</b></td><td>4.50</td></tr>\n    <tr><td><b>Today's</b> Special</td><td>5.00</td></tr>\n    <tr><td>Croissant&nbsp;XL</td><td>3.00</td></tr>\n    <tr><td><b><u>TOTAL</u></b></td><td><b>16.00</b></td></tr>\n  </tbody>\n</table>\n\n<!-- Absolute width (chars) for fixed column, percentage for the rest -->\n<table>\n  <colgroup>\n    <col width=\"4\"/>\n    <col width=\"70%\"/>\n    <col width=\"30%\" align=\"right\"/>\n  </colgroup>\n  <tr><td>1x</td><td>Flat white</td><td>3.50</td></tr>\n  <tr><td>2x</td><td>Croissant</td><td>5.00</td></tr>\n</table>\n```\n\n## Images, QR & Barcodes\n\n### `<img>` — Image / QR Code / Barcode\n\nRenders an image, QR code, or barcode via `src` URI scheme.\n\nSupports `width` (percentage of paper width). Without `width`, the image prints\nat its natural pixel size, scaled down only if wider than the paper. Centering\nfrom parent `align=\"center\"`.\n\n**Base64 image** (`data:` URI):\n```html\n<img src=\"data:image/png;base64,iVBOR...\"/>\n<img src=\"data:image/png;base64,iVBOR...\" width=\"50%\"/>\n```\n\n**QR code** (`x-qrcode:` URI):\n```html\n<img src=\"x-qrcode:https://example.com\"/>\n<img src=\"x-qrcode:https://example.com\" width=\"50%\"/>\n<p align=\"center\"><img src=\"x-qrcode:https://example.com\"/></p>\n```\n\n**Barcode** (`x-barcode:` URI):\n```html\n<img src=\"x-barcode:12345678\"/>\n<img src=\"x-barcode:ean13/5901234123457\"/>\n<img src=\"x-barcode:code39/ABC123\" width=\"80%\"/>\n<p align=\"center\"><img src=\"x-barcode:12345678\"/></p>\n```\n\nBarcode formats: `code39`, `code128` (default), `ean13`, `ean8`, `upca`,\n`ean14`, `isbn13`, `isbn10`, `issn`, `pzn`, `jan`, `gs1_128`.\n\n## Text Formatting\n\n### `<font>` — Character Scaling\n\nPreferred inline API for scaling printer characters from 1× through 8×.\n`size` sets both dimensions; explicit `width` or `height` overrides that\ndimension. Size changes inherit and restore across nesting and do not add line\nbreaks.\n\n```html\n<font size=\"2\">2×2</font>\n<font width=\"3\" height=\"2\">3×2</font>\n<font size=\"2\" width=\"3\">3×2</font>\n```\n\n`size`, `width`, and `height` must each be an integer from 1 through 8;\nout-of-range or malformed values reject the print job with HTTP 422. This\nvalidation and scaling are intentionally ignored for `<font>` markup anywhere\nbeneath a `<table>` element. `<small>` is independent of character scaling, so\nit can be combined with `<font>`.\n\nTo scale a table, wrap the complete `<table>` in `<font>` so every column uses\nthe same dimensions. At every width factor from 1 through 8, the final allocated\ncolumn widths plus one-character separators must fit the available character\nbudget (`profile columns // width factor`); otherwise the print job is rejected\nwith HTTP 422.\n\n```html\n<font size=\"2\"><table>...</table></font>\n```\n\n### `<h1>` – `<h6>` — Font Size (Deprecated)\n\nChanges double-width/double-height for children. Does **not** emit a line break\n— wrap in `<p>` for block behavior.\n\nHeadings are compatibility syntax and will be removed in a future breaking\nrelease. Use `<font>` instead:\n\n```html\n<font size=\"2\">Replacement for h1</font>\n<font height=\"2\">Replacement for h2</font>\n<font width=\"2\">Replacement for h3</font>\n```\n\n| Tag | Double Width | Double Height |\n|-----|-------------|--------------|\n| `<h1>` | yes | yes |\n| `<h2>` | no | yes |\n| `<h3>` | yes | no |\n| `<h4>`–`<h6>` | no | no |\n\n```html\n<p><h1>Big Title</h1></p>\n```\n\n### `<b>`, `<strong>` — Bold\n\n```html\n<p><b>Bold text</b></p>\n```\n\n### `<u>` — Underline\n\n```html\n<p><u>Underlined text</u></p>\n```\n\n### `<small>` — Font B (smaller font)\n\n```html\n<p><small>Fine print</small></p>\n```\n\n### `<inv>` — Inverted\n\nWhite text on black background.\n\n```html\n<p><inv>Inverted</inv></p>\n```\n\n## Paper Control\n\n### `<br/>` — Line Break\n\n```html\nLine one<br/>Line two\n```\n\n### `<feed>` — Paper Advance\n\nAdvances paper. Two modes — use `height` for precise spacing, `lines` for coarse:\n\n**`height`** — advance by exact distance. Supported units: `mm`, `cm`,\n`in`/`inch`, `px` (unitless = pixels). Uses the printer's DPI for conversion.\nTakes precedence over `lines` if both are given.\n\n```html\n<feed height=\"10mm\"/>\n<feed height=\"0.5in\"/>\n<feed height=\"100px\"/>\n```\n\n**`lines`** — advance by N visible blank lines (default: `1`).\n\n```html\n<feed lines=\"3\"/>\n<feed/>\n```\n\nEdge cases: `height=\"0\"` or a negative value is a no-op. An unparseable\n`height` value falls back to 1 line.\n\n### `<cut>` — Paper Cut\n\nTriggers the printer's cutter. Full cut by default; add the `partial` attribute for a partial cut.\n\n```html\n<cut/>\n<cut partial/>\n```\n\n## Cash Drawer\n\n### `<cash-drawer>` — Open Cash Drawer\n\nSends a kick pulse to a cash drawer connected to the printer's drawer port. The\ndrawer opens when the tag's position in the document is reached — place it at\nthe top to open the drawer before printing. Defaults to pin 2 (nearly all\ndrawers); set `pin=\"5\"` for drawers wired to pin 5. Invalid `pin` values fall\nback to pin 2.\n\nA job may consist of only this tag to open the drawer without printing.\n\n```html\n<cash-drawer/>\n<cash-drawer pin=\"5\"/>\n```\n\n## Unrecognized Tags\n\nUnrecognized tags are not treated as errors — their text content is rendered as-is.\n"}]}