Open Cash Drawer

Kick a connected cash drawer through the configured printer.

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 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 NameTypeRequiredDescriptionExample
Content-TypestringYesapplication/jsonapplication/json
OriginstringYesWeb POS browser origin. Must match counter's allowed_origins.https://pos.yourdomain.com
X-Bridge-Counter-Idstring (UUID)YesTarget counter UUID registered on this workstation.d402ddec-ecf7-4b39-b57a-86ce6008df05
X-Bridge-Profile-SecretstringYesCounter secret issued during pairing (dxp_...).dxp_202b541b2a72ecc32867e0f62b26b...

4. TypeScript Contracts

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

ParameterTypeRequiredDefaultDescription
printer_namestringYes-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

{
  "printer_name": "EPSON TM-T82III Receipt"
}

Successful Response

{
  "success": true,
  "counter_id": "d402ddec-ecf7-4b39-b57a-86ce6008df05",
  "printer": "EPSON TM-T82III Receipt"
}

Example B: Lockout on Exhausted Allowance (403 Forbidden)

{
  "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

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

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;
}