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
- 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).
- Offline Allowance Rule:
- Gated: If the counter is locked out (
offline_locked: truedue to exhausted allowance or expired 24h lease), this endpoint returns403 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.
- Gated: If the counter is locked out (
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
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
{
"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;
}