# Nyxzie DaemonX Documentation

Bundle scope: bridge

## Table of Contents

- [Bridge API Overview](#overview)
- [Health Check](#01-get-api-health)
- [List Printers](#02-get-api-printers)
- [Print Thermal Receipt](#03-post-api-print-thermal)
- [Print Invoice](#04-post-api-print-invoice)
- [Open Cash Drawer](#05-post-api-drawer-open)
- [Unpair Counter](#06-post-api-pair-unpair)

---

## Bridge API Overview

Source: bridge/README.md

# DaemonX Bridge Service - Developer API Documentation

> **Version:** 1.0.2  
> **Service Name:** DaemonX Local POS Hardware Bridge  
> **Base URL:** `http://127.0.0.1:9123` (or `http://localhost:9123`)  
> **Transport:** HTTP / 1.1 with JSON payloads  
> **Target Platform:** Windows 10 / Windows 11 (x64)  

---

## 1. Architectural Overview

DaemonX is an ultra-low latency, zero-configuration local desktop daemon that connects modern web-based Point of Sale (POS) applications directly to physical retail hardware (ESC/POS thermal printers, A4 invoice spoolers, and cash drawers) without browser print prompts, Java runtimes, or OS dialogs.

```
┌─────────────────────────────────┐
│     Cloud Web POS Application   │  (e.g., https://pos.yourdomain.com)
│   (Next.js / React / Angular)   │
└────────────────┬────────────────┘
                 │
                 │ 1. Local HTTP Dispatch (Sub-15ms)
                 │    Headers: X-Bridge-Counter-Id, X-Bridge-Profile-Secret
                 ▼
┌────────────────────────────────────────────────────────┐
│     DaemonX Local Bridge Daemon (127.0.0.1:9123)       │
│                                                        │
│  ├── Loopback WAF & Origin Filter                      │
│  ├── Secure Encrypted Profile Storage                  │
│  ├── Offline Resilience Engine                         │
│  └── Raw ESC/POS & Headless Edge PDF Spooler Engine    │
└────────────────┬───────────────────┬───────────────────┘
                 │                   │
                 │ 2. Spooler Bytes  │ 3. Background Sync & Leases (5m)
                 ▼                   ▼
┌────────────────────────┐  ┌─────────────────────────────────┐
│ Windows Hardware Layer │  │     Nyxzie Cloud Core API       │
│  - USB/LAN Thermal     │  │  - Billing Quota & Heartbeat    │
│  - RJ11 Cash Drawers   │  │  - 24h Cryptographic JWT Lease  │
│  - Laser / Inkjet A4   │  └─────────────────────────────────┘
└────────────────────────┘
```

---

## 2. Security Model & Global Headers

All communication between the browser and DaemonX takes place over the host's loopback interface.

### 2.1. Network Isolation (Loopback Only)
DaemonX enforces strict loopback binding. Incoming connections from any remote IP address (non-`127.0.0.1`, `::1`) are immediately rejected with `403 Forbidden` (`"Loopback only"`). External network access to the port is blocked by design.

### 2.2. CORS Policy & Private Network Access
DaemonX fully supports **W3C Private Network Access (PNA)** preflight requests:
- Responds with `Access-Control-Allow-Private-Network: true`
- Validates the `Origin` header against the counter's `allowed_origins` whitelist configured in the secure profile.
- Unlisted browser origins receive `403 Forbidden` (`"CORS origin not allowed"`).

### 2.3. Counter Scoping & Authentication Headers
Protected operational endpoints require two authentication headers representing the paired counter:

| Header Name | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `Content-Type` | `string` | **Yes** | Payload format (for `POST` requests). | `application/json` |
| `X-Bridge-Counter-Id` | `string` | **Yes\*** | Target counter UUID registered in local profile storage. | `d402ddec-ecf7-4b39-b57a-86ce6008df05` |
| `X-Bridge-Profile-Secret` | `string` | **Yes\*** | Cryptographic secret issued during counter pairing (`dxp_...`). | `dxp_202b541b2a72ecc32867e0f62b26b...` |

*\*Note: Public diagnostics (`/api/health`) has relaxed loopback discovery scope as noted in its documentation.*

`X-Bridge-Profile-Secret` is a developer/operator credential. It may be revealed in a trusted business/developer dashboard or copied into a local `.env`, but it must not be bundled into public client code, `NEXT_PUBLIC_*` environment variables, or generic long-lived browser storage.

---

## 3. Standard Response & Error Envelope

All responses return standard HTTP status codes. 

### Success Envelope
Successful requests return JSON with `"success": true` or the relevant resource representation:
```json
{
  "success": true,
  "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05"
}
```

### Error Envelope
When an error occurs, DaemonX returns a structured JSON envelope under the `detail` property:
```json
{
  "detail": {
    "error": "DAEMONX_OFFLINE_ALLOWANCE_EXHAUSTED",
    "message": "Emergency offline allowance exhausted. Reconnect this counter to continue.",
    "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
    "offline_remaining": 0,
    "reconnect_required": true,
    "gate_reason": "OFFLINE_ALLOWANCE_EXHAUSTED",
    "operation": "print_thermal"
  }
}
```

### Standard Error Code Registry

| HTTP Status | Error Code (`detail.error`) | Description | Client Action |
| :--- | :--- | :--- | :--- |
| `401 Unauthorized` | `UNAUTHORIZED` | Missing or invalid `X-Bridge-Profile-Secret` or inactive counter profile. | Re-authenticate counter or verify stored secret. |
| `403 Forbidden` | `CORS_ORIGIN_BLOCKED` | Browser origin is not in the counter's `allowed_origins` whitelist. | Add domain in business counter settings on Core. |
| `403 Forbidden` | `DAEMONX_OFFLINE_ALLOWANCE_EXHAUSTED` | Workstation is offline and has consumed all emergency prints (0 remaining). | Reconnect internet to restore connectivity. Background worker auto-recovers. |
| `403 Forbidden` | `DAEMONX_LEASE_EXPIRED` | 24-hour cryptographic offline lease timestamp has expired while disconnected. | Connect to internet and renew lease. |
| `400 Bad Request` | `INVALID_PAYLOAD` | Empty or malformed print payload, invalid paper format, or missing parameters. | Review request schema and JSON fields. |
| `500 Server Error` | `HARDWARE_ERROR` | Windows print spooler error, disconnected USB cable, or out of paper. | Check Windows Printer queue and printer power. |

---

## 4. Endpoint Sitemap & Documentation Index

### Local Bridge Endpoints (`http://127.0.0.1:9123`)

| Category | Method | Endpoint | Document Link | Description |
| :--- | :--- | :--- | :--- | :--- |
| **Diagnostics** | `GET` | `/api/health` | [01_GET_api_health.md](./01_GET_api_health.md) | Daemon liveness, online state & counter pairing status. |
| **Hardware** | `GET` | `/api/printers` | [02_GET_api_printers.md](./02_GET_api_printers.md) | Discovers all installed Windows printers and drivers. |
| **Hardware** | `POST` | `/api/print/thermal` | [03_POST_api_print_thermal.md](./03_POST_api_print_thermal.md) | Silent raw ESC/POS thermal receipt printing with drawer kick & cutter. |
| **Hardware** | `POST` | `/api/print/invoice` | [04_POST_api_print_invoice.md](./04_POST_api_print_invoice.md) | Silent HTML-to-spooler full invoice printing (A4 / Letter / 80mm). |
| **Hardware** | `POST` | `/api/drawer/open` | [05_POST_api_drawer_open.md](./05_POST_api_drawer_open.md) | Independent cash drawer pulse kick via thermal printer. |
| **De-registration** | `POST` | `/api/unpair` | [06_POST_api_pair_unpair.md](./06_POST_api_pair_unpair.md) | Secure counter de-registration and profile reset. |

---

## 5. Offline Tolerance & High-Availability Resilience

DaemonX is engineered for uninterrupted checkout uptime during physical store network outages:

1. **24-Hour Offline Lease**: Active workstations operate under a rolling 24-hour cryptographic lease, ensuring registers continue printing even if the store loses WAN internet connectivity.
2. **Emergency Print Allowance**: If connectivity is temporarily interrupted, counters seamlessly utilize a dynamic local allowance (30 to 200 emergency prints) to keep physical checkout lines moving.
3. **Automatic Cloud Recovery**: When network access is restored, the daemon automatically resyncs in the background, renews the lease, and resets allowances without requiring cashiers to restart the application or re-authenticate.

---

## Health Check

Source: bridge/01_GET_api_health.md

# `GET /api/health`
> **Endpoint:** `GET /api/health`  
> **Auth Scope:** Public Loopback (No secret required)  
> **Target Service:** Local DaemonX Bridge Daemon (`127.0.0.1:9123`)  
> **Introduced In:** v1.0.0  
> **Last Updated:** v1.0.0  

---

## 1. Description & Purpose

`GET /api/health` is an ultra-fast, unauthenticated liveness and readiness probe for the DaemonX Windows desktop bridge.

Web POS applications call this endpoint to verify:
1. Is the DaemonX background service running on the workstation?
2. Is the current counter paired and ready to print?

Wiring this endpoint into any frontend takes less than 5 minutes.

---

## 2. Request Contract

| Header | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `Accept` | `string` | No | `application/json` |

### Query Parameters

| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `counter_id` | `string` (UUID) | Optional | The specific counter profile to query. If omitted, DaemonX returns the currently active profile on the machine. |

---

## 3. TypeScript Contract

```typescript
export interface DaemonXHealthResponse {
  status: 'ok';
  online: boolean;
  app: string;
  version: string;
  port: number;
  counter_id: string | null;
  counter_name: string | null;
  counter_status: 'active' | 'not_paired' | 'disabled';
  is_paired: boolean;
  business_verified: boolean;
}
```

---

## 4. Response Field Matrix

| Field | Type | Description |
| :--- | :--- | :--- |
| `status` | `string` | Always `"ok"` if daemon is running. |
| `online` | `boolean` | `true` when the local DaemonX daemon handled the loopback request. This is a daemon liveness flag, not the Core heartbeat/offline allowance state. |
| `app` | `string` | Always `"DaemonX"`. |
| `version` | `string` | Installed desktop version (e.g. `"1.0.2"`). |
| `port` | `number` | Active HTTP port (defaults to `9123`). |
| `counter_id` | `string \| null` | UUID of the resolved counter profile. |
| `counter_name` | `string \| null` | Human-readable counter name (e.g. `"Counter 1"`). |
| `counter_status`| `string` | `"active"` when paired and usable, `"not_paired"`, or `"disabled"`. |
| `is_paired` | `boolean` | `true` if local workstation is paired and ready. |
| `business_verified` | `boolean` | `true` if the merchant's business entity is verified. |

---

## 5. Real-World Response Examples

### Example A: Paired Counter (200 OK)
```json
{
  "status": "ok",
  "online": true,
  "app": "DaemonX",
  "version": "1.0.2",
  "port": 9123,
  "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
  "counter_name": "Counter 1",
  "counter_status": "active",
  "is_paired": true,
  "business_verified": true
}
```

### Example B: Workstation Installed, Not Yet Paired (200 OK)
```json
{
  "status": "ok",
  "online": true,
  "app": "DaemonX",
  "version": "1.0.2",
  "port": 9123,
  "counter_id": null,
  "counter_name": null,
  "counter_status": "not_paired",
  "is_paired": false,
  "business_verified": false
}
```

---

## 6. Integration Snippets

### cURL
```bash
curl -X GET "http://127.0.0.1:9123/api/health" \
  -H "Accept: application/json"
```

### TypeScript Client Function
```typescript
export async function checkDaemonXHealth(
  counterId?: string
): Promise<DaemonXHealthResponse | null> {
  const url = counterId 
    ? `http://127.0.0.1:9123/api/health?counter_id=${encodeURIComponent(counterId)}`
    : 'http://127.0.0.1:9123/api/health';

  try {
    const res = await fetch(url, {
      method: 'GET',
      headers: { 'Accept': 'application/json' },
      signal: AbortSignal.timeout(2000), // 2s timeout
    });

    if (!res.ok) return null;
    return (await res.json()) as DaemonXHealthResponse;
  } catch {
    // DaemonX is not running on this host
    return null;
  }
}
```

### POS UI Indicator Example
```typescript
function getHardwareBridgeBadge(health: DaemonXHealthResponse | null) {
  if (!health) {
    return { status: 'DISCONNECTED', label: 'DaemonX Not Running', color: 'red' };
  }
  if (!health.is_paired) {
    return { status: 'UNPAIRED', label: 'Counter Not Paired', color: 'gray' };
  }
  return { status: 'READY', label: `Connected (${health.counter_name})`, color: 'emerald' };
}
```

---

## List Printers

Source: bridge/02_GET_api_printers.md

# `GET /api/printers`

> **Endpoint:** `GET /api/printers`  
> **Auth Scope:** Profile Protected (`verify_operational_profile_access`)  
> **Target Service:** Local DaemonX Bridge Daemon (`127.0.0.1:9123`)  
> **Introduced In:** v1.0.0  
> **Last Updated:** v1.0.0  

---

## 1. Description & Purpose

`GET /api/printers` queries the host machine's Windows Print Spooler (`Win32_Printer`) and returns an inventory of all currently installed printer devices, their associated driver names, assigned hardware ports (e.g. `USB001`, `COM1`, or IP socket ports), online readiness status, and default printer flag.

Web POS applications use this endpoint in **Hardware / Register Settings** to populate printer selection dropdowns for:
- Thermal Receipt Printers (e.g., Epson TM-T82, Star TSP100, Rongta, Xprinter)
- Kitchen Order Printers (KOT)
- Full-page A4 / Letter Invoice Printers (Epson L-series, HP LaserJet, Canon)

---

## 2. Authentication & Required Headers

This endpoint requires counter credentials and verifies caller origin. It also enforces the **operational offline gate** (blocked with HTTP 403 if the workstation is offline and its emergency print allowance is exhausted).

| Header Name | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `Accept` | `string` | No | `application/json` | `application/json` |
| `Origin` | `string` | **Yes** | Web POS browser origin. Must match counter's `allowed_origins`. | `https://pos.yourdomain.com` |
| `X-Bridge-Counter-Id` | `string` (UUID) | **Yes** | Counter ID registered in local DaemonX profile store. | `d402ddec-ecf7-4b39-b57a-86ce6008df05` |
| `X-Bridge-Profile-Secret` | `string` | **Yes** | Counter profile secret issued during pairing (`dxp_...`). | `dxp_202b541b2a72ecc32867e0f62b26b...` |

---

## 3. TypeScript Contracts

```typescript
export interface PrinterInfo {
  name: string;
  driver_name: string;
  port_name: string;
  status: 'Ready' | string;
  is_default: boolean;
}

export interface DaemonXPrintersResponse {
  success: boolean;
  counter_id: string;
  printers: PrinterInfo[];
}

export interface DaemonXErrorResponse {
  detail: {
    error: string;
    message: string;
    counter_id?: string;
    offline_remaining?: number;
    reconnect_required?: boolean;
    gate_reason?: string;
    operation?: string;
  };
}
```

---

## 4. Response Field Matrix

| Field | Type | Description |
| :--- | :--- | :--- |
| `success` | `boolean` | `true` when query succeeds. |
| `counter_id` | `string` | UUID of the authenticated counter profile. |
| `printers` | `PrinterInfo[]`| Array of installed Windows printer devices. |
| `printers[].name` | `string` | **Exact Windows printer name**. Pass this string to `/api/print/*` endpoints. |
| `printers[].driver_name` | `string` | Windows driver model (e.g. `"EPSON Advanced Printer Driver 6"`). |
| `printers[].port_name` | `string` | Windows hardware port (e.g. `"USB001"`, `"192.168.1.100"`, `"COM3"`). |
| `printers[].status` | `string` | `"Ready"` if online and ready to accept print spooler payloads. |
| `printers[].is_default` | `boolean` | `true` if this printer is configured as the Windows default device. |

---

## 5. Real-World Response Examples

### Example A: Successful Query (200 OK)
```json
{
  "success": true,
  "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
  "printers": [
    {
      "name": "EPSON TM-T82III Receipt",
      "driver_name": "EPSON Advanced Printer Driver 6",
      "port_name": "USB001",
      "status": "Ready",
      "is_default": true
    },
    {
      "name": "EPSON L130 Series",
      "driver_name": "EPSON L130 Series",
      "port_name": "USB002",
      "status": "Ready",
      "is_default": false
    },
    {
      "name": "Microsoft Print to PDF",
      "driver_name": "Microsoft Print To PDF",
      "port_name": "PORTPROMPT:",
      "status": "Ready",
      "is_default": false
    }
  ]
}
```

### Example B: Operational Lockout (403 Forbidden)
Returned when the workstation has been disconnected from Core and has exhausted all 30 emergency offline prints:
```json
{
  "detail": {
    "error": "DAEMONX_OFFLINE_ALLOWANCE_EXHAUSTED",
    "message": "Emergency offline allowance exhausted. Reconnect this counter to continue.",
    "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
    "offline_remaining": 0,
    "reconnect_required": true,
    "gate_reason": "OFFLINE_ALLOWANCE_EXHAUSTED",
    "operation": "profile_operation"
  }
}
```

---

## 6. Integration Snippets

### cURL
```bash
curl -X GET "http://127.0.0.1:9123/api/printers" \
  -H "X-Bridge-Counter-Id: d402ddec-ecf7-4b39-b57a-86ce6008df05" \
  -H "X-Bridge-Profile-Secret: dxp_202b541b2a72ecc32867e0f62b26b07be38024210e30af90" \
  -H "Origin: https://pos.yourdomain.com"
```

### TypeScript Client Function
```typescript
export async function getInstalledPrinters(
  counterId: string,
  profileSecret: string
): Promise<PrinterInfo[]> {
  const res = await fetch('http://127.0.0.1:9123/api/printers', {
    method: 'GET',
    headers: {
      'Accept': 'application/json',
      'X-Bridge-Counter-Id': counterId,
      'X-Bridge-Profile-Secret': profileSecret,
    },
  });

  if (!res.ok) {
    const errorData = await res.json().catch(() => null);
    if (res.status === 403 && errorData?.detail?.reconnect_required) {
      throw new Error('OFFLINE_LOCKED: Please reconnect terminal to internet.');
    }
    throw new Error(errorData?.detail?.message || `Failed to fetch printers (HTTP ${res.status})`);
  }

  const data: DaemonXPrintersResponse = await res.json();
  return data.printers;
}
```

---

## Print Thermal Receipt

Source: bridge/03_POST_api_print_thermal.md

# `POST /api/print/thermal`
> **Endpoint:** `POST /api/print/thermal`  
> **Auth Scope:** Profile Protected (`verify_operational_profile_access`)  
> **Target Service:** Local DaemonX Bridge Daemon (`127.0.0.1:9123`)  
> **Introduced In:** v1.0.0  
> **Last Updated:** v1.0.0  
> **Average Latency:** < 15ms  

---

## 1. Description & Purpose

`POST /api/print/thermal` sends binary ESC/POS command sequences directly to physical receipt printers over USB, LAN, or COM ports. It operates with zero browser print dialogs, zero rasterization lag, and sub-15ms execution time.

### Core Capabilities:
- **Instant Cut**: Sends standard full/partial knife cut commands (`GS V B \x00`).
- **Simultaneous Cash Drawer Kick**: Fires an ESC/POS pulse pin (`ESC p \x00 \x19 \xFA`) immediately before the receipt prints.
- **Auto-Formatting (58mm vs 80mm)**: Automatically calculates column widths (32 columns for 58mm, 42 columns for 80mm) and aligns item prices.
- **Sandbox Watermark Injection**: In Sandbox environments, DaemonX automatically prepends and appends physical test receipt banners to prevent unauthorized live commercial use.
- **Three Input Formats**: Supports **Structured Lines** (recommended for AI and web apps), **Raw Base64** (for logos, barcodes, and custom ESC/POS bytes), and **Plain Text**.

---

## 2. Authentication & Required Headers

| Header Name | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `Content-Type` | `string` | **Yes** | `application/json` | `application/json` |
| `Origin` | `string` | **Yes** | Web POS browser origin. Must be whitelisted in profile. | `https://pos.yourdomain.com` |
| `X-Bridge-Counter-Id` | `string` (UUID) | **Yes** | Target counter UUID registered on this workstation. | `d402ddec-ecf7-4b39-b57a-86ce6008df05` |
| `X-Bridge-Profile-Secret` | `string` | **Yes** | Secret issued during counter pairing (`dxp_...`). | `dxp_202b541b2a72ecc32867e0f62b26b...` |

---

## 3. TypeScript Contracts

```typescript
export type ThermalLineType = 
  | 'header'   // Centered, double-height, double-width bold text
  | 'divider'  // Automatic dashed separator line (32 or 42 dashes)
  | 'item'     // Line item with bold product name, quantity, and right-aligned price
  | 'total'    // Summary row (Subtotal, Tax, Total) with bold aligned label and value
  | 'text';    // Freeform text with custom alignment

export interface ThermalLine {
  type: ThermalLineType;
  text?: string;
  label?: string;
  value?: string | number;
  name?: string;
  qty?: string | number;
  price?: string | number;
  align?: 'left' | 'center' | 'right';
}

export interface ThermalPrintRequest {
  /** Exact Windows printer name (from GET /api/printers) */
  printer_name: string;

  /** Paper width: '80mm' (42 cols) or '58mm' (32 cols). Defaults to '80mm' */
  format?: '80mm' | '58mm';

  /** High-level structured receipt lines (Recommended) */
  lines?: ThermalLine[];

  /** Pre-encoded raw ESC/POS binary string in Base64 (for graphics/bitmaps) */
  raw_base64?: string;

  /** Simple plain text fallback (encoded automatically as CP437) */
  text?: string;

  /** Automatically trigger printer paper cutter after payload. Default: true */
  cut?: boolean;

  /** Pulse pin 2 to open connected RJ11 cash drawer before print. Default: false */
  kick_drawer?: boolean;
}

export interface ThermalPrintResponse {
  success: boolean;
  counter_id: string;
  printer: string;
  bytes: number;
}
```

---

## 4. Parameter Matrix

| Parameter | Type | Required | Default | Description |
| :--- | :--- | :--- | :--- | :--- |
| `printer_name` | `string` | **Yes** | - | Name of target Windows printer as reported by `GET /api/printers`. |
| `format` | `string` | No | `"80mm"` | `"80mm"` (42 columns) or `"58mm"` (32 columns). |
| `lines` | `ThermalLine[]` | Conditional | `null` | Structured line objects. **Required if `raw_base64` or `text` is not provided**. |
| `lines[].type` | `string` | **Yes** | - | `"header"`, `"divider"`, `"item"`, `"total"`, or `"text"`. |
| `lines[].name` | `string` | Conditional | `null` | Item title (used with `type: "item"`). |
| `lines[].qty` | `number \| string`| No | `null` | Item quantity (e.g. `2` or `"1.5 kg"`). |
| `lines[].price`| `number \| string`| No | `null` | Item line total (e.g. `1200.00` or `"$12.00"`). |
| `lines[].label`| `string` | Conditional | `null` | Summary label (e.g. `"SUBTOTAL"`, `"DISCOUNT"`). |
| `lines[].value`| `number \| string`| No | `null` | Summary amount (e.g. `2400.00`). |
| `lines[].align`| `string` | No | `"left"` | `"left"`, `"center"`, or `"right"`. |
| `raw_base64` | `string` | Conditional | `null` | Base64-encoded ESC/POS bytes. Bypasses structured formatting. |
| `text` | `string` | Conditional | `null` | Plain text with newlines. |
| `cut` | `boolean` | No | `true` | Appends paper feed and partial cut command. |
| `kick_drawer` | `boolean` | No | `false` | Fires 50ms cash drawer kick pulse before receipt prints. |

---

## 5. Real-World Request & Response Examples

### Example A: Complete Retail Receipt with Drawer Kick & Cut
**Endpoint:** `POST http://127.0.0.1:9123/api/print/thermal`

```json
{
  "printer_name": "EPSON TM-T82III Receipt",
  "format": "80mm",
  "kick_drawer": true,
  "cut": true,
  "lines": [
    { "type": "header", "text": "STUDIO MILAN" },
    { "type": "text", "text": "No. 45 Galle Road, Colombo 03", "align": "center" },
    { "type": "text", "text": "Tel: +94 11 234 5678", "align": "center" },
    { "type": "divider" },
    { "type": "text", "text": "Invoice: #INV-2026-0042", "align": "left" },
    { "type": "text", "text": "Cashier: Kasun Perera", "align": "left" },
    { "type": "text", "text": "Date: 2026-10-05 03:00 PM", "align": "left" },
    { "type": "divider" },
    { "type": "item", "name": "Linen Casual Shirt - Navy (M)", "qty": 1, "price": 4500.00 },
    { "type": "item", "name": "Slim Fit Chino Pants - Khaki", "qty": 2, "price": 7800.00 },
    { "type": "item", "name": "Leather Dress Belt", "qty": 1, "price": 2200.00 },
    { "type": "divider" },
    { "type": "total", "label": "Subtotal", "value": 14500.00 },
    { "type": "total", "label": "Loyalty Discount (10%)", "value": -1450.00 },
    { "type": "total", "label": "VAT (0%)", "value": 0.00 },
    { "type": "divider" },
    { "type": "total", "label": "GRAND TOTAL", "value": "LKR 13,050.00" },
    { "type": "total", "label": "Cash Tendered", "value": "LKR 15,000.00" },
    { "type": "total", "label": "Change Due", "value": "LKR 1,950.00" },
    { "type": "divider" },
    { "type": "text", "text": "Thank you for shopping with us!", "align": "center" },
    { "type": "text", "text": "Exchange possible within 7 days with bill.", "align": "center" },
    { "type": "text", "text": "Powered by Nyxzie Commerce", "align": "center" }
  ]
}
```

#### Successful Response (200 OK)
```json
{
  "success": true,
  "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
  "printer": "EPSON TM-T82III Receipt",
  "bytes": 542
}
```

---

### Example B: Offline Allowance Exhausted (403 Forbidden)
```json
{
  "detail": {
    "error": "DAEMONX_OFFLINE_ALLOWANCE_EXHAUSTED",
    "message": "Emergency offline allowance exhausted. Reconnect this counter to continue.",
    "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
    "offline_remaining": 0,
    "reconnect_required": true,
    "gate_reason": "OFFLINE_ALLOWANCE_EXHAUSTED",
    "operation": "print_thermal"
  }
}
```

---

## 6. Sandbox Watermark Behavior

If the counter was activated using a **Sandbox API Key** (`nyx_daemonx_sandbox_*`), DaemonX enforces physical watermark injection. The printer will physically print:

```text
==========================================
       *** DAEMONX TEST RECEIPT ***
       *** SANDBOX ENVIRONMENT ***
          NOT A VALID TAX INVOICE
==========================================

   [ ... Cashier receipt contents ... ]

==========================================
           *** TEST RECEIPT ***
     UNAUTHORIZED FOR COMMERCIAL SALE
==========================================
```
In `live` environments (`nyx_daemonx_live_*`), `watermark_enforced` is `false` and zero watermark banners are printed.

---

## 7. Integration Snippets

### cURL
```bash
curl -X POST "http://127.0.0.1:9123/api/print/thermal" \
  -H "Content-Type: application/json" \
  -H "X-Bridge-Counter-Id: d402ddec-ecf7-4b39-b57a-86ce6008df05" \
  -H "X-Bridge-Profile-Secret: dxp_202b541b2a72ecc32867e0f62b26b07be38024210e30af90" \
  -H "Origin: https://pos.yourdomain.com" \
  -d '{
    "printer_name": "EPSON TM-T82III Receipt",
    "format": "80mm",
    "cut": true,
    "lines": [
      { "type": "header", "text": "QUICK TEST" },
      { "type": "text", "text": "Hello from DaemonX!", "align": "center" }
    ]
  }'
```

### TypeScript Client Function
```typescript
export async function printThermalReceipt(
  request: ThermalPrintRequest,
  counterId: string,
  profileSecret: string
): Promise<ThermalPrintResponse> {
  const res = await fetch('http://127.0.0.1:9123/api/print/thermal', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Bridge-Counter-Id': counterId,
      'X-Bridge-Profile-Secret': profileSecret,
    },
    body: JSON.stringify(request),
  });

  if (!res.ok) {
    const errorPayload = await res.json().catch(() => null);
    const detail = errorPayload?.detail;

    if (res.status === 403 && detail?.reconnect_required) {
      throw new Error(`OFFLINE_LOCKED: ${detail.message}`);
    }
    throw new Error(detail?.message || `Print failed with HTTP ${res.status}`);
  }

  return (await res.json()) as ThermalPrintResponse;
}
```

---

## Print Invoice

Source: bridge/04_POST_api_print_invoice.md

# `POST /api/print/invoice`

> **Endpoint:** `POST /api/print/invoice`  
> **Auth Scope:** Profile Protected (`verify_operational_profile_access`)  
> **Target Service:** Local DaemonX Bridge Daemon (`127.0.0.1:9123`)  
> **Introduced In:** v1.0.0  
> **Last Updated:** v1.0.0  
> **Rendering Engine:** Headless Microsoft Edge / Google Chrome (300 DPI)  

---

## 1. Description & Purpose

`POST /api/print/invoice` enables web applications to print rich, fully styled HTML documents directly to any physical Windows printer (inkjet, laser, or high-resolution continuous thermal rolls) **without displaying the browser print dialog (`Ctrl+P`)**.

### How It Works Under the Hood:
1. **HTML Ingestion**: Accepts a complete HTML string with embedded CSS (fonts, Flexbox/Grid, SVG logos, barcodes, tables).
2. **Sandbox Watermark Injection**: If the counter is running in a Sandbox environment, DaemonX injects top/bottom security banners and a 45° diagonal watermark before rendering.
3. **Headless Vector Rasterization**: Microsoft Edge or Google Chrome renders the HTML headlessly into an exact vector PDF at 300 DPI.
4. **Direct Spooler Dispatch**: The generated vector document is dispatched directly to the target Windows printer queue via `System.Drawing.Printing.PrintDocument`.
5. **Instant Cleanup**: All temporary PDF and image artifacts are immediately unlinked from `%TEMP%`.

---

## 2. Authentication & Required Headers

| Header Name | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `Content-Type` | `string` | **Yes** | `application/json` | `application/json` |
| `Origin` | `string` | **Yes** | Web POS browser origin. Must be whitelisted in profile. | `https://pos.yourdomain.com` |
| `X-Bridge-Counter-Id` | `string` (UUID) | **Yes** | Target counter UUID registered on this workstation. | `d402ddec-ecf7-4b39-b57a-86ce6008df05` |
| `X-Bridge-Profile-Secret` | `string` | **Yes** | Counter secret issued during pairing (`dxp_...`). | `dxp_202b541b2a72ecc32867e0f62b26b...` |

---

## 3. TypeScript Contracts

```typescript
export interface InvoicePrintRequest {
  /** Target Windows printer name (from GET /api/printers) */
  printer_name: string;

  /** Full standalone HTML document string with <style> */
  html: string;

  /** Target paper format: 'A4' | 'Letter' | 'receipt_80mm'. Default: 'A4' */
  format?: 'A4' | 'Letter' | 'receipt_80mm' | string;

  /** Number of physical copies to print (1 to 10). Default: 1 */
  copies?: number;

  /** Document title displayed in Windows Print Queue. Default: 'Nyxzie POS Receipt' */
  title?: string;
}

export interface InvoicePrintResponse {
  success: boolean;
  counter_id: string;
  printer: string;
  format: string;
  pages: number;
  copies: number;
}
```

---

## 4. Parameter Matrix

| Parameter | Type | Required | Default | Validation & Description |
| :--- | :--- | :--- | :--- | :--- |
| `printer_name` | `string` | **Yes** | - | Name of target Windows printer as reported by `GET /api/printers`. |
| `html` | `string` | **Yes** | - | Valid HTML markup. Should include self-contained CSS styles. |
| `format` | `string` | No | `"A4"` | Document paper sizing: `"A4"`, `"Letter"`, or custom width. |
| `copies` | `integer`| No | `1` | Range: `1` to `10`. Each copy decrements 1 offline allowance unit when disconnected. |
| `title` | `string` | No | `"POS Receipt"` | Job title visible in Windows Spooler queue. |

---

## 5. Real-World Request & Response Examples

### Example A: Full A4 Invoice Print
**Endpoint:** `POST http://127.0.0.1:9123/api/print/invoice`

```json
{
  "printer_name": "EPSON L130 Series",
  "format": "A4",
  "copies": 1,
  "title": "Tax Invoice #INV-8891",
  "html": "<!DOCTYPE html><html><head><meta charset='utf-8'><style>body { font-family: -apple-system, sans-serif; margin: 20px; color: #111; } .header { border-bottom: 2px solid #7c3aed; padding-bottom: 12px; } .title { font-size: 24px; font-weight: 900; color: #7c3aed; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th, td { padding: 10px; border-bottom: 1px solid #e5e7eb; text-align: left; } th { background: #f9fafb; font-size: 12px; text-transform: uppercase; } .total-row { font-weight: bold; font-size: 16px; }</style></head><body><div class='header'><div class='title'>STUDIO MILAN</div><div>Commercial Tax Invoice #INV-8891</div></div><table><thead><tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Total</th></tr></thead><tbody><tr><td>Italian Linen Blazer (Navy - 40R)</td><td>1</td><td>LKR 28,500.00</td><td>LKR 28,500.00</td></tr><tr><td>Custom Tailoring & Alteration</td><td>1</td><td>LKR 3,500.00</td><td>LKR 3,500.00</td></tr><tr class='total-row'><td colspan='3'>GRAND TOTAL</td><td>LKR 32,000.00</td></tr></tbody></table></body></html>"
}
```

#### Successful Response (200 OK)
```json
{
  "success": true,
  "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
  "printer": "EPSON L130 Series",
  "format": "A4",
  "pages": 1,
  "copies": 1
}
```

---

### Example B: Offline Allowance Lockout (403 Forbidden)
```json
{
  "detail": {
    "error": "DAEMONX_OFFLINE_ALLOWANCE_EXHAUSTED",
    "message": "Emergency offline allowance exhausted. Reconnect this counter to continue.",
    "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
    "offline_remaining": 0,
    "reconnect_required": true,
    "gate_reason": "OFFLINE_ALLOWANCE_EXHAUSTED",
    "operation": "print_invoice"
  }
}
```

---

## 6. HTML Rendering Best Practices for Silent Spooling

To ensure pixel-perfect printing across all printer brands:

1. **Use Self-Contained Styles**:
   Do not rely on external CSS stylesheets (`<link rel="stylesheet">`) that require internet requests during rendering. Inline your CSS rules in a `<style>` block.
2. **Explicit Page Dimensions**:
   For A4 documents, set `@page { size: A4; margin: 10mm; }` in CSS.
3. **Use Web-Safe Fonts or Embedded SVGs**:
   Standard system fonts (Arial, Segoe UI, Roboto, sans-serif) render instantly without network latency. For logos and barcodes, embed SVG code or inline Base64 data URLs (`data:image/png;base64,...`).

---

## 7. Sandbox Watermarking

When connected to a **Sandbox API Key** (`nyx_daemonx_sandbox_*`), DaemonX automatically injects:
- **Red Top Banner**: `[ DAEMONX SANDBOX TEST INVOICE - NOT A VALID TAX RECEIPT ]`
- **Red Bottom Banner**: `[ TEST RECEIPT ONLY - UNAUTHORIZED FOR COMMERCIAL SALE ]`
- **Center Diagonal Watermark**: A semi-transparent 45-degree rotated box with `"DAEMONX SANDBOX - TEST RECEIPT ONLY"`.

In live environments, zero watermarks are injected.

---

## 8. Integration Snippets

### TypeScript Client Function
```typescript
export async function printInvoiceHtml(
  request: InvoicePrintRequest,
  counterId: string,
  profileSecret: string
): Promise<InvoicePrintResponse> {
  const res = await fetch('http://127.0.0.1:9123/api/print/invoice', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Bridge-Counter-Id': counterId,
      'X-Bridge-Profile-Secret': profileSecret,
    },
    body: JSON.stringify({
      format: 'A4',
      copies: 1,
      title: 'POS Receipt',
      ...request,
    }),
  });

  if (!res.ok) {
    const errorPayload = await res.json().catch(() => null);
    const detail = errorPayload?.detail;

    if (res.status === 403 && detail?.reconnect_required) {
      throw new Error(`OFFLINE_LOCKED: ${detail.message}`);
    }
    throw new Error(detail?.message || `Invoice print failed with HTTP ${res.status}`);
  }

  return (await res.json()) as InvoicePrintResponse;
}
```

---

## Open Cash Drawer

Source: bridge/05_POST_api_drawer_open.md

# `POST /api/drawer/open`
> **Endpoint:** `POST /api/drawer/open`  
> **Auth Scope:** Profile Protected (`verify_operational_profile_access`)  
> **Target Service:** Local DaemonX Bridge Daemon (`127.0.0.1:9123`)  
> **Introduced In:** v1.0.0  
> **Last Updated:** v1.0.0  
> **Average Latency:** < 10ms  

---

## 1. Description & Purpose

`POST /api/drawer/open` triggers a direct hardware solenoid pulse to kick open an RJ11/RJ12 connected cash drawer **without printing a paper receipt**.

### When to Use This Endpoint:
- **"No-Sale" Button**: A cashier opens the drawer to make change or count opening float without a sale.
- **Cash Deposit / Payout**: Petty cash withdrawals, float adjustments, or cash drop operations.
- **Manual Drawer Kick**: Drawer override action in POS settings or register screens.

*(Note: If you are printing a receipt and want the drawer to open automatically when the receipt prints, you do not need this endpoint. Instead, use [`POST /api/print/thermal`](./03_POST_api_print_thermal.md) with `"kick_drawer": true`).*

---

## 2. Hardware Mechanism & Offline Policy

1. **ESC/POS Solenoid Pulse**:
   DaemonX dispatches raw bytes `\x1B\x70\x00\x19\xFA` (`ESC p m t1 t2`) through the Windows spooler directly to the printer:
   - `m = 0x00`: Pin 2 (standard RJ11 cash drawer solenoid).
   - `t1 = 0x19`: Pulse ON time (50ms).
   - `t2 = 0xFA`: Pulse OFF time (500ms).
2. **Offline Allowance Rule**:
   - **Gated**: If the counter is locked out (`offline_locked: true` due to exhausted allowance or expired 24h lease), this endpoint returns `403 Forbidden` (`DAEMONX_OFFLINE_ALLOWANCE_EXHAUSTED`).
   - **Zero Deduction**: While in offline emergency mode, opening the drawer **does NOT deduct** from the 30–200 emergency print allowance. Only print operations consume print units.

---

## 3. Authentication & Required Headers

| Header Name | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `Content-Type` | `string` | **Yes** | `application/json` | `application/json` |
| `Origin` | `string` | **Yes** | Web POS browser origin. Must match counter's `allowed_origins`. | `https://pos.yourdomain.com` |
| `X-Bridge-Counter-Id` | `string` (UUID) | **Yes** | Target counter UUID registered on this workstation. | `d402ddec-ecf7-4b39-b57a-86ce6008df05` |
| `X-Bridge-Profile-Secret` | `string` | **Yes** | Counter secret issued during pairing (`dxp_...`). | `dxp_202b541b2a72ecc32867e0f62b26b...` |

---

## 4. TypeScript Contracts

```typescript
export interface DrawerOpenRequest {
  /** 
   * Name of the thermal printer with the RJ11 cash drawer cable attached 
   * (as returned by GET /api/printers) 
   */
  printer_name: string;
}

export interface DrawerOpenResponse {
  success: boolean;
  counter_id: string;
  printer: string;
}

export interface DaemonXErrorResponse {
  detail: {
    error: string;
    message: string;
    counter_id?: string;
    offline_remaining?: number;
    reconnect_required?: boolean;
    gate_reason?: string;
    operation?: string;
  };
}
```

---

## 5. Parameter Matrix

| Parameter | Type | Required | Default | Description |
| :--- | :--- | :--- | :--- | :--- |
| `printer_name` | `string` | **Yes** | - | Exact Windows printer name (e.g., `"EPSON TM-T82III Receipt"`). The cash drawer cable must be plugged into this printer's DK/RJ11 port. |

---

## 6. Real-World Request & Response Examples

### Example A: Successful Cash Drawer Kick (200 OK)
**Endpoint:** `POST http://127.0.0.1:9123/api/drawer/open`

#### Request Payload
```json
{
  "printer_name": "EPSON TM-T82III Receipt"
}
```

#### Successful Response
```json
{
  "success": true,
  "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
  "printer": "EPSON TM-T82III Receipt"
}
```

---

### Example B: Lockout on Exhausted Allowance (403 Forbidden)
```json
{
  "detail": {
    "error": "DAEMONX_OFFLINE_ALLOWANCE_EXHAUSTED",
    "message": "Emergency offline allowance exhausted. Reconnect this counter to continue.",
    "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
    "offline_remaining": 0,
    "reconnect_required": true,
    "gate_reason": "OFFLINE_ALLOWANCE_EXHAUSTED",
    "operation": "drawer_open"
  }
}
```

---

## 7. Integration Snippets

### cURL
```bash
curl -X POST "http://127.0.0.1:9123/api/drawer/open" \
  -H "Content-Type: application/json" \
  -H "X-Bridge-Counter-Id: d402ddec-ecf7-4b39-b57a-86ce6008df05" \
  -H "X-Bridge-Profile-Secret: dxp_202b541b2a72ecc32867e0f62b26b07be38024210e30af90" \
  -H "Origin: https://pos.yourdomain.com" \
  -d '{
    "printer_name": "EPSON TM-T82III Receipt"
  }'
```

### TypeScript Client Function
```typescript
export async function kickCashDrawer(
  printerName: string,
  counterId: string,
  profileSecret: string
): Promise<DrawerOpenResponse> {
  const res = await fetch('http://127.0.0.1:9123/api/drawer/open', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Bridge-Counter-Id': counterId,
      'X-Bridge-Profile-Secret': profileSecret,
    },
    body: JSON.stringify({ printer_name: printerName }),
  });

  if (!res.ok) {
    const errorPayload = await res.json().catch(() => null);
    const detail = errorPayload?.detail;

    if (res.status === 403 && detail?.reconnect_required) {
      throw new Error(`OFFLINE_LOCKED: ${detail.message}`);
    }
    throw new Error(detail?.message || `Failed to open cash drawer (HTTP ${res.status})`);
  }

  return (await res.json()) as DrawerOpenResponse;
}
```

---

## Unpair Counter

Source: bridge/06_POST_api_pair_unpair.md

# `POST /api/unpair`

> **Endpoint:** `POST /api/unpair`  
> **Auth Scope:** Profile Protected (`verify_profile_access`)  
> **Target Service:** Local DaemonX Bridge Daemon (`127.0.0.1:9123`)  
> **Introduced In:** v1.0.0  
> **Last Updated:** v1.0.0  

---

## 1. Description & Purpose

`POST /api/unpair` securely de-registers an active counter from the workstation.

> **Note on Pairing:**  
> Workstation pairing is performed directly inside the **DaemonX Windows Desktop App** using a 6-character pairing code generated by the Web POS. The desktop app communicates directly with Nyxzie Core over HTTPS. There is no unauthenticated `/api/pair` loopback endpoint on the bridge.

`POST /api/unpair` is available to trusted local callers that already possess the counter profile secret. This includes a separated business/developer dashboard where an authorized operator can view or copy the profile secret for that counter. Do not bake the secret into public POS bundles.

Use it when:
1. Decommissioning or replacing a register workstation.
2. Re-allocating a counter to a different store or site.
3. Clearing local hardware credentials.

---

## 2. Authentication & Request Headers

`POST /api/unpair` requires valid counter credentials to prevent unauthorized local profile deletion:

| Header Name | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `Content-Type` | `string` | **Yes** | `application/json` |
| `Origin` | `string` | Conditional | Browser origin. Required for browser calls and validated against the counter's allowed origins; direct trusted local tooling may omit it. |
| `X-Bridge-Counter-Id` | `string` (UUID) | **Yes** | Counter ID being unpaired. |
| `X-Bridge-Profile-Secret` | `string` | **Yes** | Counter profile secret (`dxp_...`). |

---

## 3. TypeScript Contracts

```typescript
export interface LocalUnpairResponse {
  success: boolean;
  counter_id: string;
  core_unpair_status: 'confirmed' | 'already_missing';
  message: string;
}

export interface DaemonXErrorResponse {
  detail: {
    error: string;
    message: string;
    counter_id?: string;
  };
}
```

---

## 4. Request Payload

*Empty JSON body `{}` or no body required. The target counter is identified by the `X-Bridge-Counter-Id` header.*

---

## 5. Successful Response (200 OK)

```json
{
  "success": true,
  "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
  "core_unpair_status": "confirmed",
  "message": "Counter d402ddec-ecf7-4b39-b57a-86ce6008df05 unpaired successfully."
}
```

### Post-Unpair State Machine Transitions:
1. **Local Storage Cleanup**: The counter profile is purged from local encrypted storage.
2. **Tray Menu Update**: The Windows System Tray immediately switches status to `"○ Not Paired"`.
3. **Core Quota Release**: Nyxzie Core frees the counter slot under the merchant's plan quota.
4. **Subsequent Calls**: Future hardware commands using the old credentials return `401 Unauthorized`.

---

## 6. Integration Snippets

### Trusted Local Unpairing Function

This is safe for trusted admin/developer tooling where the authorized user supplies or reveals the secret. Do not place `profileSecret` in public browser bundles, `NEXT_PUBLIC_*` variables, or generic long-lived browser storage.

```typescript
export async function unpairDaemonXTerminal(
  counterId: string,
  profileSecret: string
): Promise<LocalUnpairResponse> {
  const res = await fetch('http://127.0.0.1:9123/api/unpair', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Bridge-Counter-Id': counterId,
      'X-Bridge-Profile-Secret': profileSecret,
    },
  });

  if (!res.ok) {
    const err = await res.json().catch(() => null);
    throw new Error(err?.detail?.message || err?.detail || `Unpairing failed (HTTP ${res.status})`);
  }

  return (await res.json()) as LocalUnpairResponse;
}
```

### cURL
```bash
curl -X POST "http://127.0.0.1:9123/api/unpair" \
  -H "Content-Type: application/json" \
  -H "X-Bridge-Counter-Id: d402ddec-ecf7-4b39-b57a-86ce6008df05" \
  -H "X-Bridge-Profile-Secret: dxp_202b541b2a72ecc32867e0f62b26b07be38024210e30af90" \
  -H "Origin: https://pos.yourdomain.com"
```
