# Nyxzie DaemonX Documentation

Bundle scope: all

## Table of Contents

- [Platform Guide](#overview)
- [Core API Overview](#overview)
- [List Counters](#01-get-api-v1-daemonx-counters)
- [Create Pairing Session](#02a-post-api-v1-daemonx-onboarding-sessions)
- [Check Pairing Session](#02b-get-api-v1-daemonx-onboarding-sessions-id)
- [Cancel Pairing Session](#02c-post-api-v1-daemonx-onboarding-sessions-cancel)
- [Replay Pairing Webhook](#02d-post-api-v1-daemonx-onboarding-sessions-webhook-replay)
- [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)

---

## Platform Guide

Source: README.md

# DaemonX - Hardware Integration Platform Guide

> **System:** Nyxzie DaemonX Hardware & POS Infrastructure  
> **Architecture:** Two-Tier Hybrid (Cloud Orchestration + Local Hardware Bridge)  
> **Current Version:** v1.0.2  
> **Status:** Production Active  

---

## 1. Executive Summary & The Two-Tier Architecture

**DaemonX** provides sub-15ms, zero-dialog physical hardware spooling (ESC/POS thermal receipt printers, laser/inkjet A4 invoice printers, cash drawers, scales, and customer displays) for modern web-based Point of Sale (POS) applications.

To deliver hardware speed without compromising security, DaemonX separates operations into **two distinct tiers**:

```
┌────────────────────────────────────────────────────────────────────────────────────────┐
│                                 DAEMONX ARCHITECTURE                                   │
└───────────────────────────────────────────┬────────────────────────────────────────────┘
                                            │
                ┌───────────────────────────┴───────────────────────────┐
                ▼                                                       ▼
 ┌─────────────────────────────────────────┐             ┌─────────────────────────────────────────┐
 │       TIER 1: NYXZIE CLOUD CORE         │             │      TIER 2: LOCAL DESKTOP DAEMON       │
 │            (core/ directory)            │             │           (bridge/ directory)           │
 ├─────────────────────────────────────────┤             ├─────────────────────────────────────────┤
 │ • Base URL: https://api.nyxzie.com      │             │ • Base URL: http://127.0.0.1:9123       │
 │ • Network: Public Cloud (HTTPS)         │             │ • Network: OS Loopback Only             │
 │ • Caller: Your Backend Server           │             │ • Caller: Cashier Web Browser (POS)     │
 │ • Auth: Developer API Bearer Token      │             │ • Auth: X-Bridge-Counter-Id + Secret    │
 │ • Role: Provisioning, Fleet, Pairing,   │             │ • Role: Raw USB/LAN Byte Spooling,      │
 │   Telemetry & Webhook Outbox            │             │   RJ11 Solenoid Kick, Printer Discovery │
 └─────────────────────────────────────────┘             └─────────────────────────────────────────┘
```

---

## 2. Global Data Flow Diagram

```
┌───────────────────────────────┐                  ┌──────────────────────────────────────────┐
│   Your Cloud Backend Server   │                  │          Nyxzie Cloud Core API           │
│   (SaaS / Retail Management)  │                  │     (https://api.nyxzie.com/api/v1)      │
└───────────────┬───────────────┘                  └────────────────────┬─────────────────────┘
                │                                                       │
                │ 1. POST /onboarding/sessions (Bearer nyx_daemonx_...) │
                ├──────────────────────────────────────────────────────>│
                │    Returns: 6-digit code ("842915") & session_id      │
                │<──────────────────────────────────────────────────────┤
                │                                                       │
                │                                                       │
┌───────────────┴───────────────┐                  ┌────────────────────┴─────────────────────┐
│    Cashier Browser (Web POS)  │                  │      DaemonX Local Desktop Bridge        │
│  (https://pos.yourdomain.com) │                  │         (http://127.0.0.1:9123)          │
└───────────────┬───────────────┘                  └────────────────────┬─────────────────────┘
                │                                                       │
                │ 2. Cashier enters 6-digit code into Desktop App GUI   │
                │    Desktop app connects to Core & securely saves creds│
                │    Core triggers webhook: daemonx.onboarding.completed│
                │                                                       │
                │ 3. Probe: GET http://127.0.0.1:9123/api/health        │
                ├──────────────────────────────────────────────────────>│
                │    Returns: { status: "ready", paired: true }         │
                │<──────────────────────────────────────────────────────┤
                │                                                       │
                │ 4. Print: POST http://127.0.0.1:9123/api/print/thermal│
                ├──────────────────────────────────────────────────────>│──┐ 5. USB/LAN
                │    Headers: X-Bridge-Counter-Id, Profile-Secret       │  │    Raw Bytes
                │    Response: { success: true, latency_ms: 11 }        │  ▼
                │<──────────────────────────────────────────────────────┤ ┌─────────────────┐
                │                                                       │ │ Physical ESC/POS│
                │                                                       │ │ Thermal Printer │
                │                                                       │ └─────────────────┘
```

---

## 3. The 5-Step Integration Lifecycle Pattern

When integrating DaemonX into your retail SaaS or web POS, implement the lifecycle across your backend and frontend in these 5 sequential stages:

### Step 1: Session Creation (Backend Server → Core API)
Your backend initiates an onboarding session when a merchant clicks "Add Cash Register / Counter" in your portal:
* **Request:** `POST https://api.nyxzie.com/api/v1/daemonx/onboarding/sessions`
* **Headers:** `Authorization: Bearer nyx_daemonx_live_<your_token>`
* **Payload:** `{ "counter_name": "Front Desk Lane 1", "external_customer_ref": "cust_99" }`
* **Result:** Receives an ephemeral 6-digit pairing code (e.g. `842915`) valid for **900 seconds (15 minutes)**.
* **Doc:** [`core/02a_POST_api_v1_daemonx_onboarding_sessions.md`](./core/02a_POST_api_v1_daemonx_onboarding_sessions.md)

### Step 2: Workstation Activation (Cashier → Desktop App)
The cashier downloads and launches the **DaemonX Windows Desktop App** (`.exe`) on the physical register:
1. The app displays an activation prompt: *"Enter 6-Digit Pairing Code"*.
2. Cashier types `842915`.
3. The desktop app contacts Nyxzie Core, generates cryptographic machine credentials, and completes identity binding.
4. Core writes a completed counter row and locks the credentials locally inside secure encrypted storage.

### Step 3: Completion Confirmation (Core API → Backend Server)
Your backend confirms pairing using one of two patterns:
* **Webhook (Recommended):** Core dispatches `daemonx.onboarding.completed` to your webhook URL containing the newly provisioned `counter_id`.
* **Polling:** Your frontend/backend polls `GET https://api.nyxzie.com/api/v1/daemonx/onboarding/sessions/:id` until `status === "completed"`.
* **Doc:** [`core/02b_GET_api_v1_daemonx_onboarding_sessions_id.md`](./core/02b_GET_api_v1_daemonx_onboarding_sessions_id.md)

### Step 4: Local Bridge Probing (Browser Web POS → Localhost)
When the cashier opens the web POS interface in Chrome, Edge, or Firefox:
1. The web application issues an HTTP request to `GET http://127.0.0.1:9123/api/health`.
2. DaemonX responds immediately with loopback status, connected printers, and paired counter info.
3. If the daemon is not running, the POS displays a friendly banner: *"DaemonX bridge not detected. Please launch the desktop application."*
4. **Doc:** [`bridge/01_GET_api_health.md`](./bridge/01_GET_api_health.md)

### Step 5: Silent Printing & Drawer Control (Browser Web POS → Localhost)
Upon completing a sale:
1. Web POS constructs the receipt payload (raw text, ESC/POS formatting, barcode, cutter commands).
2. Sends `POST http://127.0.0.1:9123/api/print/thermal` directly to loopback.
3. DaemonX streams the raw bytes directly to the Windows print spooler in under 15ms without showing any print dialogue or prompt.
4. **Doc:** [`bridge/03_POST_api_print_thermal.md`](./bridge/03_POST_api_print_thermal.md)

---

## 4. Security & Credential Isolation Matrix

To prevent compromised browser tabs from tampering with fleet configurations or stealing developer tokens, DaemonX enforces strict credential separation:

| Credential Type | Format / Example | Where Is It Stored? | Who Uses It? | Exposed to Browser? |
| :--- | :--- | :--- | :--- | :--- |
| **Developer API Token** | `nyx_daemonx_live_6b431e...` | Backend Server `.env` | Cloud Core API requests | **NEVER** |
| **Counter Profile Secret** | `dxc_a1b2c3d4e5...` | Secure Encrypted Local Vault | Local bridge authentication | Authorized POS origin only |
| **Machine Secret** | `dxm_99f8e7d6c5...` | Secure Encrypted Local Vault | Desktop background heartbeats | **NEVER** |
| **Offline Lease Token** | RS256 JWT Signed by Nyxzie | Local Memory & Disk Cache | Allows 24h printing if internet drops | **NEVER** |

### Network & WAF Safeguards:
* **Loopback-Only Binding:** DaemonX rejects all non-loopback IPs (`127.0.0.1`, `::1`) with `403 Forbidden`. External LAN attacks cannot hit port `9123`.
* **CORS & Private Network Access (PNA):** DaemonX honors Chrome/W3C PNA preflight standards and strictly validates the `Origin` header against the developer's registered POS domain.
* **Token Scope Gating:** Core developer tokens are gated by endpoint scope (`/api/v1/daemonx/counters` and `/api/v1/daemonx/onboarding/sessions`).

---

## 5. Master API Directory & Documentation Index

### 5.1. Tier 1: Cloud Core API Reference (`./core/`)
Full specification guide: [`core/README.md`](./core/README.md)

| Endpoint | Method | Scope | Documentation |
| :--- | :--- | :--- | :--- |
| `/api/v1/daemonx/counters` | `GET` | Developer | [01_GET_api_v1_daemonx_counters.md](./core/01_GET_api_v1_daemonx_counters.md) |
| `/api/v1/daemonx/onboarding/sessions` | `POST` | Developer | [02a_POST_api_v1_daemonx_onboarding_sessions.md](./core/02a_POST_api_v1_daemonx_onboarding_sessions.md) |
| `/api/v1/daemonx/onboarding/sessions/:id` | `GET` | Developer | [02b_GET_api_v1_daemonx_onboarding_sessions_id.md](./core/02b_GET_api_v1_daemonx_onboarding_sessions_id.md) |
| `/api/v1/daemonx/onboarding/sessions/:id/cancel` | `POST` | Developer | [02c_POST_api_v1_daemonx_onboarding_sessions_cancel.md](./core/02c_POST_api_v1_daemonx_onboarding_sessions_cancel.md) |
| `/api/v1/daemonx/onboarding/sessions/:id/webhook/replay` | `POST` | Developer | [02d_POST_api_v1_daemonx_onboarding_sessions_webhook_replay.md](./core/02d_POST_api_v1_daemonx_onboarding_sessions_webhook_replay.md) |

### 5.2. Tier 2: Local Hardware Bridge API Reference (`./bridge/`)
Full specification guide: [`bridge/README.md`](./bridge/README.md)

| Endpoint | Method | Scope | Documentation |
| :--- | :--- | :--- | :--- |
| `/api/health` | `GET` | Public Loopback | [01_GET_api_health.md](./bridge/01_GET_api_health.md) |
| `/api/printers` | `GET` | Authenticated POS | [02_GET_api_printers.md](./bridge/02_GET_api_printers.md) |
| `/api/print/thermal` | `POST` | Authenticated POS | [03_POST_api_print_thermal.md](./bridge/03_POST_api_print_thermal.md) |
| `/api/print/invoice` | `POST` | Authenticated POS | [04_POST_api_print_invoice.md](./bridge/04_POST_api_print_invoice.md) |
| `/api/drawer/open` | `POST` | Authenticated POS | [05_POST_api_drawer_open.md](./bridge/05_POST_api_drawer_open.md) |
| `/api/pair/unpair` | `POST` | Authenticated POS | [06_POST_api_pair_unpair.md](./bridge/06_POST_api_pair_unpair.md) |

---

## 6. Implementation Checklist for Developers

- [ ] **Store Developer Token Securely:** Ensure `nyx_daemonx_live_*` is stored on your server and never bundled in client-side code.
- [ ] **Listen for Webhooks:** Register an HTTPS webhook endpoint for `daemonx.onboarding.completed` events and verify HMAC signatures.
- [ ] **Handle Bridge Availability:** Implement fallback UI in your POS frontend when `GET http://127.0.0.1:9123/api/health` returns offline.
- [ ] **Configure Allowed Origins:** Ensure your POS web domain (e.g. `https://pos.yourdomain.com`) is configured in your Developer Portal profile so CORS passes.
- [ ] **Use Standard ESC/POS Commands:** For thermal printing, pass standard byte sequences or formatted text strings as documented in `bridge/03_POST_api_print_thermal.md`.

---

## Core API Overview

Source: core/README.md

# Nyxzie Core - DaemonX B2B Developer API

> **Service Name:** Nyxzie Cloud Core - DaemonX Management & Telemetry API  
> **Production Base URL:** `https://api.nyxzie.com/api/v1/daemonx`  
> **Transport:** HTTPS with JSON payloads  

---

## 1. Architectural Overview

While the **DaemonX Local Bridge** (`http://127.0.0.1:9123`) runs on individual Windows workstations to handle silent ESC/POS hardware spooling, the **Nyxzie Core Cloud API** serves as the central orchestration and management platform:

```
┌──────────────────────────────────────┐
│  B2B Merchant SaaS / Developer App   │
└──────────────────┬───────────────────┘
                   │
                   │ HTTPS REST (Bearer nyx_daemonx_live_...)
                   ▼
┌────────────────────────────────────────────────────────┐
│              Nyxzie Cloud Core API                     │
│               (/api/v1/daemonx/*)                      │
│                                                        │
│  ├── Token Shield & Developer Tier Metering            │
│  ├── Counter Registry & Device Configuration           │
│  ├── Ephemeral Pairing Handshake Engine (Redis)        │
│  └── Offline Lease Signing & Cryptographic Validation  │
└──────────────────┬─────────────────────────────────────┘
                   │
                   │ Background Heartbeat & Lease Sync (60s/300s)
                   ▼
┌────────────────────────────────────────────────────────┐
│     DaemonX Windows Workstation (127.0.0.1:9123)       │
└────────────────────────────────────────────────────────┘
```

---

## 2. Authentication & Authorization

All B2B developer management endpoints are protected by **DaemonX API Tokens** issued from the Nyxzie Developer Portal:

### 2.1. Bearer Token Header
Requests must include the HTTP `Authorization` header:
```http
Authorization: Bearer nyx_daemonx_live_6b431eda073e6cb462bd0a9971687376ef5481887c46dfa688bcb618982fc905
```

| Token Type | Prefix | Description |
| :--- | :--- | :--- |
| **Live** | `nyx_daemonx_live_` | Production token bound to live counters and billing meters. |
| **Sandbox / Test** | `nyx_daemonx_sandbox_` | Development token isolated from production quotas and live transactions. |

### 2.2. Endpoint Scope
DaemonX developer tokens are also restricted by endpoint group. A token must include the relevant group in its `allowed_endpoints` list:

| Endpoint Group | Allows |
| :--- | :--- |
| `/api/v1/daemonx/counters` | `GET /api/v1/daemonx/counters` |
| `/api/v1/daemonx/onboarding/sessions` | Session creation, status lookup, cancellation, and webhook replay. |

If the token is valid but does not include the required endpoint group, Core returns `403 DAEMONX_ENDPOINT_NOT_ALLOWED`.

### 2.3. Error Responses & Envelopes
Responses follow a standard JSON envelope:

#### Success Envelope
```json
{
  "success": true,
  "counters": [ ... ]
}
```

#### Error Envelope
```json
{
  "success": false,
  "error": "Human-readable error description",
  "code": "ERROR_CODE"
}
```

---

## 3. Developer Endpoints Index

### Counters Management
| Method | Endpoint | Document Link | Description |
| :--- | :--- | :--- | :--- |
| `GET` | `/api/v1/daemonx/counters` | [01_GET_api_v1_daemonx_counters.md](./01_GET_api_v1_daemonx_counters.md) | Query, list, and filter registered counters by location or external reference. |

### Onboarding Sessions & Pairing
| Method | Endpoint | Document Link | Description |
| :--- | :--- | :--- | :--- |
| `POST` | `/api/v1/daemonx/onboarding/sessions` | [02a_POST_api_v1_daemonx_onboarding_sessions.md](./02a_POST_api_v1_daemonx_onboarding_sessions.md) | Initiate an onboarding session & generate a 6-digit desktop pairing code. |
| `GET` | `/api/v1/daemonx/onboarding/sessions/:id` | [02b_GET_api_v1_daemonx_onboarding_sessions_id.md](./02b_GET_api_v1_daemonx_onboarding_sessions_id.md) | Query onboarding session status and resolve paired counter UUID. |
| `POST` | `/api/v1/daemonx/onboarding/sessions/:id/cancel` | [02c_POST_api_v1_daemonx_onboarding_sessions_cancel.md](./02c_POST_api_v1_daemonx_onboarding_sessions_cancel.md) | Cancel an active or pending onboarding pairing session. |
| `POST` | `/api/v1/daemonx/onboarding/sessions/:id/webhook/replay` | [02d_POST_api_v1_daemonx_onboarding_sessions_webhook_replay.md](./02d_POST_api_v1_daemonx_onboarding_sessions_webhook_replay.md) | Request outbox redelivery of completion webhook for completed session. |

---

## List Counters

Source: core/01_GET_api_v1_daemonx_counters.md

# `GET /api/v1/daemonx/counters`
> **Endpoint:** `GET /api/v1/daemonx/counters`  
> **Auth Scope:** Developer Token Protected (`Authorization: Bearer nyx_daemonx_live_...`)  
> **Target Service:** Nyxzie Cloud Core API  
> **Introduced In:** v1.0.0  
> **Last Updated:** v1.0.0  

---

## 1. Description & Purpose

`GET /api/v1/daemonx/counters` is a B2B developer management endpoint that enables external platforms, POS frontends, and SaaS backends to query and inspect all retail counters associated with their DaemonX developer profile.

Key features:
1. **Multi-Store & Tenant Filtering:** Query by your platform's own external references (`external_customer_ref`, `external_location_ref`, `external_counter_ref`).
2. **Liveness & State Peeking:** Inspect whether the counter is currently registered (`is_active`), connected to the cloud (`is_online`), and its cumulative lifetime print jobs (`total_jobs_completed`).
3. **Privacy & Sanitization:** Internal infrastructure timestamps (such as low-level worker heartbeats) are stripped from this public B2B DTO.

---

## 2. Request Headers

| Header Name | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `Authorization` | `string` | **Yes** | Developer API Bearer Token | `Bearer nyx_daemonx_live_6b431eda073e...` |
| `Accept` | `string` | No | Response format | `application/json` |

---

## 3. Query Parameters

All query parameters are optional, trimmed strings (1–255 characters):

| Parameter | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `external_customer_ref` | `string` | Optional | Filter by merchant/customer identifier in your external platform. | `cust_milan_01` |
| `external_location_ref` | `string` | Optional | Filter by store branch or physical venue identifier. | `loc_colombo_flagship` |
| `external_counter_ref` | `string` | Optional | Filter by specific register / workstation identifier. | `pos_lane_01` |

---

## 4. TypeScript Contracts

```typescript
export interface DaemonxCounterListItem {
  counter_id: string;
  counter_name: string;
  environment: 'live' | 'sandbox';
  external_customer_ref?: string;
  external_location_ref?: string;
  external_counter_ref?: string;
  is_active: boolean;
  is_online: boolean;
  total_jobs_completed: number;
  created_at: string;
}

export interface DaemonxCounterListResponse {
  success: true;
  counters: DaemonxCounterListItem[];
}

export interface DaemonxErrorResponse {
  success: false;
  error: string;
  code?: string;
}
```

---

## 5. Successful Response (200 OK)

```json
{
  "success": true,
  "counters": [
    {
      "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
      "counter_name": "Cashier Counter 1",
      "environment": "live",
      "external_customer_ref": "cust_milan_01",
      "external_location_ref": "loc_colombo_flagship",
      "external_counter_ref": "pos_lane_01",
      "is_active": true,
      "is_online": true,
      "total_jobs_completed": 1420,
      "created_at": "2026-09-24T18:42:10.000Z"
    },
    {
      "counter_id": "8f3b610c-99d1-4cb5-8271-9f26038101ee",
      "counter_name": "Bar Station Register",
      "environment": "live",
      "external_customer_ref": "cust_milan_01",
      "external_location_ref": "loc_colombo_flagship",
      "external_counter_ref": "pos_bar_02",
      "is_active": true,
      "is_online": false,
      "total_jobs_completed": 350,
      "created_at": "2026-09-28T11:15:33.000Z"
    }
  ]
}
```

---

## 6. Error Responses

### 401 Unauthorized
Returned when `Authorization` header is missing, malformed, or the API token is revoked.
```json
{
  "success": false,
  "error": "Missing or invalid DaemonX API token.",
  "code": "UNAUTHORIZED"
}
```

### 429 Too Many Requests
Returned if request limits exceed the rate shield threshold.
```json
{
  "success": false,
  "error": "Rate limit exceeded. Please throttle your requests.",
  "code": "RATE_LIMIT_EXCEEDED"
}
```

---

## 7. Integration Snippets

### cURL
```bash
curl -X GET "https://api.nyxzie.com/api/v1/daemonx/counters?external_location_ref=loc_colombo_flagship" \
  -H "Authorization: Bearer nyx_daemonx_live_6b431eda073e6cb462bd0a9971687376ef5481887c46dfa688bcb618982fc905" \
  -H "Accept: application/json"
```

### TypeScript Client Function
```typescript
import type { DaemonxCounterListResponse, DaemonxCounterListItem } from './types';

export async function fetchDaemonxCounters(
  apiToken: string,
  filters?: {
    externalCustomerRef?: string;
    externalLocationRef?: string;
    externalCounterRef?: string;
  }
): Promise<DaemonxCounterListItem[]> {
  const url = new URL('https://api.nyxzie.com/api/v1/daemonx/counters');

  if (filters?.externalCustomerRef) {
    url.searchParams.set('external_customer_ref', filters.externalCustomerRef.trim());
  }
  if (filters?.externalLocationRef) {
    url.searchParams.set('external_location_ref', filters.externalLocationRef.trim());
  }
  if (filters?.externalCounterRef) {
    url.searchParams.set('external_counter_ref', filters.externalCounterRef.trim());
  }

  const res = await fetch(url.toString(), {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${apiToken.trim()}`,
      'Accept': 'application/json',
    },
  });

  if (!res.ok) {
    const errorBody = await res.json().catch(() => null);
    throw new Error(errorBody?.error || `Failed to fetch counters (HTTP ${res.status})`);
  }

  const data = (await res.json()) as DaemonxCounterListResponse;
  return data.counters;
}
```

---

## Create Pairing Session

Source: core/02a_POST_api_v1_daemonx_onboarding_sessions.md

# `POST /api/v1/daemonx/onboarding/sessions`

> **Endpoint:** `POST /api/v1/daemonx/onboarding/sessions`  
> **Auth Scope:** Developer Token Protected (`Authorization: Bearer nyx_daemonx_live_...`)  
> **Target Service:** Nyxzie Cloud Core API  
> **Introduced In:** v1.0.0  
> **Last Updated:** v1.0.0  

---

## 1. Description & Purpose

`POST /api/v1/daemonx/onboarding/sessions` initiates a secure counter onboarding session for retail workstations. It generates an ephemeral 6-digit pairing code (valid for 900 seconds / 15 minutes) that a cashier or technician enters into the **DaemonX Windows Desktop App** to pair the workstation with Nyxzie Core.

Key characteristics:
1. **Server-to-Server B2B Security:** Called by your backend with a Developer API token (`nyx_daemonx_live_*` or `nyx_daemonx_sandbox_*`). The token is **never** shared with the cashier browser or desktop machine.
2. **Deterministic Entity Correlation:** Binds the session to your platform's customer/merchant reference (`external_customer_ref`), store branch (`external_location_ref`), and register lane (`external_counter_ref`).

---

## 2. Request Headers

| Header Name | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `Authorization` | `string` | **Yes** | Developer API Bearer Token | `Bearer nyx_daemonx_live_6b431eda073e...` |
| `Content-Type` | `string` | **Yes** | Payload format | `application/json` |
| `Accept` | `string` | No | Response format | `application/json` |

---

## 3. Request Payload

```typescript
export interface CreateOnboardingSessionRequest {
  external_customer_ref: string;
  external_location_ref?: string;
  external_counter_ref?: string;
  counter_name: string;
}
```

| Field | Type | Required | Constraints | Description |
| :--- | :--- | :--- | :--- | :--- |
| `external_customer_ref` | `string` | **Yes** | 1–255 chars | Merchant / site identifier in your system. |
| `external_location_ref` | `string` | Optional | 1–255 chars | Store branch or venue identifier. |
| `external_counter_ref` | `string` | Optional | 1–255 chars | POS register lane or counter identifier. |
| `counter_name` | `string` | **Yes** | 2–100 chars | Friendly display name for the register. |

---

## 4. Successful Response (201 Created)

```json
{
  "success": true,
  "onboarding_session_id": "4b68ff5e-f001-4475-8025-a741544256eb",
  "pairing_code": "842915",
  "expires_in": 900,
  "onboarding_session": {
    "id": "4b68ff5e-f001-4475-8025-a741544256eb",
    "status": "pending",
    "external_customer_ref": "cust_milan_01",
    "external_location_ref": "loc_colombo_flagship",
    "external_counter_ref": "pos_lane_01",
    "counter_name": "Cashier Counter 1",
    "environment": "live",
    "is_sandbox": false,
    "counter_id": null,
    "completed_at": null,
    "expires_at": "2026-10-07T02:38:24.000Z",
    "created_at": "2026-10-07T02:33:24.000Z"
  }
}
```

---

## 5. TypeScript Contracts

```typescript
export interface DaemonxOnboardingSessionDTO {
  id: string;
  status: 'pending' | 'completed' | 'expired' | 'cancelled' | 'failed';
  external_customer_ref: string | null;
  external_location_ref?: string | null;
  external_counter_ref?: string | null;
  counter_name: string | null;
  environment: 'live' | 'sandbox';
  is_sandbox: boolean;
  counter_id?: string | null;
  completed_at?: string | null;
  expires_at: string;
  created_at: string;
}

export interface CreateOnboardingSessionResponse {
  success: true;
  onboarding_session_id: string;
  pairing_code: string;
  expires_in: number;
  onboarding_session: DaemonxOnboardingSessionDTO;
}

export interface DaemonxErrorResponse {
  success: false;
  error: string;
  code?: string;
}
```

---

## 6. Integration Snippets

### cURL
```bash
curl -X POST "https://api.nyxzie.com/api/v1/daemonx/onboarding/sessions" \
  -H "Authorization: Bearer nyx_daemonx_live_6b431eda073e6cb462bd0a9971687376ef5481887c46dfa688bcb618982fc905" \
  -H "Content-Type: application/json" \
  -d '{
    "external_customer_ref": "cust_milan_01",
    "external_location_ref": "loc_colombo_flagship",
    "external_counter_ref": "pos_lane_01",
    "counter_name": "Cashier Counter 1"
  }'
```

### TypeScript Backend Example
```typescript
import type { CreateOnboardingSessionResponse } from './types';

export async function createDaemonxPairingSession(
  apiToken: string,
  input: {
    customerRef: string;
    locationRef?: string;
    counterRef?: string;
    counterName: string;
  }
): Promise<CreateOnboardingSessionResponse> {
  const res = await fetch('https://api.nyxzie.com/api/v1/daemonx/onboarding/sessions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiToken.trim()}`,
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
    body: JSON.stringify({
      external_customer_ref: input.customerRef.trim(),
      external_location_ref: input.locationRef?.trim(),
      external_counter_ref: input.counterRef?.trim(),
      counter_name: input.counterName.trim(),
    }),
  });

  if (!res.ok) {
    const errorBody = await res.json().catch(() => null);
    throw new Error(errorBody?.error || `Failed to create onboarding session (HTTP ${res.status})`);
  }

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

---

## Check Pairing Session

Source: core/02b_GET_api_v1_daemonx_onboarding_sessions_id.md

# `GET /api/v1/daemonx/onboarding/sessions/:id`

> **Endpoint:** `GET /api/v1/daemonx/onboarding/sessions/:id`  
> **Auth Scope:** Developer Token Protected (`Authorization: Bearer nyx_daemonx_live_...`)  
> **Target Service:** Nyxzie Cloud Core API  
> **Introduced In:** v1.0.0  
> **Last Updated:** v1.0.0  

---

## 1. Description & Purpose

`GET /api/v1/daemonx/onboarding/sessions/:id` resolves the canonical lifecycle status of an existing onboarding session.

Use cases:
1. **Pairing Status Polling / Verification:** Check if the cashier has entered the 6-digit code in their desktop app and if workstation pairing has completed.
2. **Post-Pairing Metadata Resolution:** Once the session transitions to `"completed"`, this endpoint reveals the newly issued `counter_id` assigned to the physical workstation.
3. **Timeout / Expiration Inspection:** Detect if a pairing session expired (`"expired"`) or failed (`"failed"`).

---

## 2. Request Headers

| Header Name | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `Authorization` | `string` | **Yes** | Developer API Bearer Token | `Bearer nyx_daemonx_live_6b431eda073e...` |
| `Accept` | `string` | No | Response format | `application/json` |

---

## 3. Path Parameters

| Parameter | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `id` | `string` (UUID) | **Yes** | Onboarding session UUID returned when creating the session. | `4b68ff5e-f001-4475-8025-a741544256eb` |

---

## 4. Successful Response Examples (200 OK)

### State A: Session Completed (Workstation Paired)
```json
{
  "success": true,
  "onboarding_session": {
    "id": "4b68ff5e-f001-4475-8025-a741544256eb",
    "status": "completed",
    "external_customer_ref": "cust_milan_01",
    "external_location_ref": "loc_colombo_flagship",
    "external_counter_ref": "pos_lane_01",
    "counter_name": "Cashier Counter 1",
    "environment": "live",
    "is_sandbox": false,
    "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
    "completed_at": "2026-10-07T02:35:10.000Z",
    "expires_at": "2026-10-07T02:38:24.000Z",
    "created_at": "2026-10-07T02:33:24.000Z"
  }
}
```

### State B: Session Pending (Waiting for Code Entry on Desktop)
```json
{
  "success": true,
  "onboarding_session": {
    "id": "4b68ff5e-f001-4475-8025-a741544256eb",
    "status": "pending",
    "external_customer_ref": "cust_milan_01",
    "external_location_ref": "loc_colombo_flagship",
    "external_counter_ref": "pos_lane_01",
    "counter_name": "Cashier Counter 1",
    "environment": "live",
    "is_sandbox": false,
    "counter_id": null,
    "completed_at": null,
    "expires_at": "2026-10-07T02:38:24.000Z",
    "created_at": "2026-10-07T02:33:24.000Z"
  }
}
```

---

## 5. Lifecycle Status Values

| Status | Meaning | Next Action |
| :--- | :--- | :--- |
| `pending` | Pairing code has been issued and is awaiting desktop activation. | Continue polling or listen to SSE/webhook events. |
| `completed` | The workstation was successfully registered and activated. | Safe to dispatch print jobs using the returned `counter_id`. |
| `expired` | The 900-second TTL elapsed without being entered. | Generate a fresh pairing session. |
| `cancelled` | The session was cancelled via the developer API before pairing. | Ignore this session. |
| `failed` | Machine binding failed during pairing (e.g. invalid machine profile). | Review error logs or request a new code. |

---

## 6. TypeScript Contracts

```typescript
export interface DaemonxOnboardingSessionDTO {
  id: string;
  status: 'pending' | 'completed' | 'expired' | 'cancelled' | 'failed';
  external_customer_ref: string | null;
  external_location_ref?: string | null;
  external_counter_ref?: string | null;
  counter_name: string | null;
  environment: 'live' | 'sandbox';
  is_sandbox: boolean;
  counter_id?: string | null;
  completed_at?: string | null;
  expires_at: string;
  created_at: string;
}

export interface GetOnboardingSessionResponse {
  success: true;
  onboarding_session: DaemonxOnboardingSessionDTO;
}

export interface DaemonxErrorResponse {
  success: false;
  error: string;
  code?: string;
}
```

---

## 7. Integration Snippets

### cURL
```bash
curl -X GET "https://api.nyxzie.com/api/v1/daemonx/onboarding/sessions/4b68ff5e-f001-4475-8025-a741544256eb" \
  -H "Authorization: Bearer nyx_daemonx_live_6b431eda073e6cb462bd0a9971687376ef5481887c46dfa688bcb618982fc905" \
  -H "Accept: application/json"
```

### TypeScript Client Function
```typescript
import type { GetOnboardingSessionResponse, DaemonxOnboardingSessionDTO } from './types';

export async function getDaemonxOnboardingSession(
  apiToken: string,
  sessionId: string
): Promise<DaemonxOnboardingSessionDTO> {
  const res = await fetch(`https://api.nyxzie.com/api/v1/daemonx/onboarding/sessions/${encodeURIComponent(sessionId)}`, {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${apiToken.trim()}`,
      'Accept': 'application/json',
    },
  });

  if (!res.ok) {
    const errorBody = await res.json().catch(() => null);
    throw new Error(errorBody?.error || `Failed to fetch session (HTTP ${res.status})`);
  }

  const data = (await res.json()) as GetOnboardingSessionResponse;
  return data.onboarding_session;
}
```

---

## Cancel Pairing Session

Source: core/02c_POST_api_v1_daemonx_onboarding_sessions_cancel.md

# `POST /api/v1/daemonx/onboarding/sessions/:id/cancel`

> **Endpoint:** `POST /api/v1/daemonx/onboarding/sessions/:id/cancel`  
> **Auth Scope:** Developer Token Protected (Writable Token Required)  
> **Target Service:** Nyxzie Cloud Core API  
> **Introduced In:** v1.0.0  
> **Last Updated:** v1.0.0  

---

## 1. Description & Purpose

`POST /api/v1/daemonx/onboarding/sessions/:id/cancel` cancels an active or pending onboarding pairing session before it is claimed or completed on a physical workstation.

Use cases:
1. **User Aborted Setup:** Cashier or administrator closes the pairing modal or clicks "Cancel" on the POS UI.
2. **Session Cleanup:** Invalidate an outstanding 6-digit pairing code before its 900-second TTL expires.
3. **Idempotent Invalidation:** Safe to call multiple times; already-cancelled sessions return the current cancelled session representation.

> [!NOTE]
> Sessions that have already transitioned to `"completed"` cannot be cancelled and will return a `409 Conflict` (`DAEMONX_ONBOARDING_COMPLETED`). To deactivate an already paired counter, use the counter unpair lifecycle.

---

## 2. Request Headers

| Header Name | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `Authorization` | `string` | **Yes** | Developer API Bearer Token (must have write permissions) | `Bearer nyx_daemonx_live_6b431eda073e...` |
| `Content-Type` | `string` | No | Body media type | `application/json` |
| `Accept` | `string` | No | Response format | `application/json` |

---

## 3. Path Parameters

| Parameter | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `id` | `string` (UUID) | **Yes** | The onboarding session UUID to cancel. | `4b68ff5e-f001-4475-8025-a741544256eb` |

---

## 4. Request Body

This endpoint is an empty-body command action. No parameters are required.

```json
{}
```

---

## 5. Successful Response Example (200 OK)

```json
{
  "success": true,
  "onboarding_session": {
    "id": "4b68ff5e-f001-4475-8025-a741544256eb",
    "status": "cancelled",
    "external_customer_ref": "cust_milan_01",
    "external_location_ref": "loc_colombo_flagship",
    "external_counter_ref": "pos_lane_01",
    "counter_name": "Cashier Counter 1",
    "environment": "live",
    "is_sandbox": false,
    "counter_id": null,
    "completed_at": null,
    "expires_at": "2026-10-07T02:38:24.000Z",
    "created_at": "2026-10-07T02:33:24.000Z"
  }
}
```

---

## 6. Error Responses

### 6.1. Cannot Cancel Completed Session (`409 Conflict`)
If the workstation has already completed pairing and bound to the session:
```json
{
  "success": false,
  "error": "Completed DaemonX onboarding sessions cannot be cancelled.",
  "code": "DAEMONX_ONBOARDING_COMPLETED"
}
```

### 6.2. Session Not Found (`404 Not Found`)
If the session UUID does not exist or belongs to another developer profile:
```json
{
  "success": false,
  "error": "DaemonX onboarding session not found.",
  "code": "DAEMONX_ONBOARDING_SESSION_NOT_FOUND"
}
```

### 6.3. Read-Only Token Used (`403 Forbidden`)
If a read-only developer token attempts to cancel a session:
```json
{
  "success": false,
  "error": "DaemonX token does not have write permissions.",
  "code": "DAEMONX_TOKEN_READ_ONLY"
}
```

---

## 7. TypeScript Contracts

```typescript
export interface DaemonxOnboardingSessionCancelResponse {
  success: true;
  onboarding_session: {
    id: string;
    status: 'cancelled';
    external_customer_ref: string | null;
    external_location_ref?: string | null;
    external_counter_ref?: string | null;
    counter_name: string;
    environment: 'live' | 'sandbox';
    is_sandbox: boolean;
    counter_id?: string | null;
    completed_at?: string | null;
    expires_at: string;
    created_at: string;
  };
}
```

---

## 8. cURL Example

```bash
curl -X POST https://api.nyxzie.com/api/v1/daemonx/onboarding/sessions/4b68ff5e-f001-4475-8025-a741544256eb/cancel \
  -H "Authorization: Bearer nyx_daemonx_live_6b431eda073e6cb462bd0a9971687376ef5481887c46dfa688bcb618982fc905" \
  -H "Content-Type: application/json" \
  -d '{}'
```

---

## Replay Pairing Webhook

Source: core/02d_POST_api_v1_daemonx_onboarding_sessions_webhook_replay.md

# `POST /api/v1/daemonx/onboarding/sessions/:id/webhook/replay`

> **Endpoint:** `POST /api/v1/daemonx/onboarding/sessions/:id/webhook/replay`  
> **Auth Scope:** Developer Token Protected (Writable Token Required)  
> **Target Service:** Nyxzie Cloud Core API  
> **Introduced In:** v1.0.0  
> **Last Updated:** v1.0.0  

---

## 1. Description & Purpose

`POST /api/v1/daemonx/onboarding/sessions/:id/webhook/replay` queues a redelivery of the `daemonx.onboarding.completed` webhook event for an already completed onboarding session.

Use cases:
1. **Developer Downtime Recovery:** If the developer's receiving webhook service was down, returned a 5xx error, or suffered a network timeout when the workstation originally completed pairing.
2. **Ops Resync:** Enables administrative tooling and background reconciliation workers to trigger a fresh webhook dispatch without requiring counter re-pairing or database intervention.
3. **Outbox Guarantee:** Generates a new pending outbox entry with a fresh `event_id` in `daemonx_webhook_events` that is immediately picked up by the outbox dispatcher.

> [!NOTE]
> This endpoint only accepts sessions that have successfully reached `"completed"` status. It cannot replay pending, expired, or cancelled sessions.

---

## 2. Request Headers

| Header Name | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `Authorization` | `string` | **Yes** | Developer API Bearer Token (must have write permissions) | `Bearer nyx_daemonx_live_6b431eda073e...` |
| `Accept` | `string` | No | Response format | `application/json` |

---

## 3. Path Parameters

| Parameter | Type | Required | Description | Example |
| :--- | :--- | :--- | :--- | :--- |
| `id` | `string` (UUID) | **Yes** | Completed onboarding session UUID. | `4b68ff5e-f001-4475-8025-a741544256eb` |

---

## 4. Request Body

No request body is needed for this endpoint.

---

## 5. Successful Response Example (201 Created)

When the event is successfully queued in the outbox:

```json
{
  "success": true,
  "webhook": {
    "event_id": "dxe_9c2409f87c9343df8995a557",
    "event_type": "daemonx.onboarding.completed",
    "status": "pending"
  }
}
```

> [!TIP]
> The API returns the newly allocated outbox handle (`event_id` with `status: "pending"`). The actual webhook dispatch occurs asynchronously through the outbox dispatcher to the developer's configured webhook endpoint.

---

## 6. Dispatched Webhook Payload Structure

When the outbox dispatcher delivers the replayed webhook to your registered HTTPS endpoint, the HTTP `POST` body contains:

```json
{
  "event": "daemonx.onboarding.completed",
  "event_id": "dxe_9c2409f87c9343df8995a557",
  "onboarding_session_id": "4b68ff5e-f001-4475-8025-a741544256eb",
  "environment": "live",
  "is_sandbox": false,
  "external_customer_ref": "cust_milan_01",
  "external_location_ref": "loc_colombo_flagship",
  "external_counter_ref": "pos_lane_01",
  "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
  "counter_name": "Cashier Counter 1",
  "occurred_at": "2026-10-07T02:35:10.000Z"
}
```

Webhooks include the standard Nyxzie signature header:
- `X-DaemonX-Signature`: HMAC SHA-256 signature calculated over the raw request body using your developer webhook signing secret.

---

## 7. Error Responses

### 7.1. Session Not Completed (`404 Not Found`)
Returned if the session UUID does not exist under your developer profile, or if the session is not in `"completed"` status (e.g. still pending, expired, or cancelled):
```json
{
  "success": false,
  "error": "Completed DaemonX onboarding session not found.",
  "code": "DAEMONX_ONBOARDING_SESSION_NOT_COMPLETED"
}
```

### 7.2. Read-Only Token Used (`403 Forbidden`)
If a read-only developer token attempts to replay a webhook:
```json
{
  "success": false,
  "error": "DaemonX token does not have write permissions.",
  "code": "DAEMONX_TOKEN_READ_ONLY"
}
```

---

## 8. TypeScript Contracts

```typescript
export interface DaemonxOnboardingWebhookReplayResponse {
  success: true;
  webhook: {
    event_id: string;
    event_type: 'daemonx.onboarding.completed';
    status: 'pending' | string;
  };
}

export interface DaemonxOnboardingCompletedWebhookPayload {
  event: 'daemonx.onboarding.completed';
  event_id: string;
  onboarding_session_id: string;
  environment: 'live' | 'sandbox';
  is_sandbox: boolean;
  external_customer_ref: string | null;
  external_location_ref: string | null;
  external_counter_ref: string | null;
  counter_id: string;
  counter_name: string;
  occurred_at: string;
}
```

---

## 9. cURL Example

```bash
curl -X POST https://api.nyxzie.com/api/v1/daemonx/onboarding/sessions/4b68ff5e-f001-4475-8025-a741544256eb/webhook/replay \
  -H "Authorization: Bearer nyx_daemonx_live_6b431eda073e6cb462bd0a9971687376ef5481887c46dfa688bcb618982fc905"
```

---

## 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"
```
