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