> ## Documentation Index
> Fetch the complete documentation index at: https://docs.switchyard.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Scanner API

> Mobile scanning and inventory operations

# Scanner API

The Scanner API is designed for mobile devices used by warehouse pickers, drivers, and intake staff.

## Overview

The Scanner API provides:

* **Barcode Lookup**: Find sellable products by UPC/EAN barcode
* **Inventory Scanning**: Update quantities with FEFO/FIFO tracking
* **Inventory Receiving**: Receive new inventory from sweeps
* **Order Picking**: Access orders assigned for fulfillment
* **Bag Scanning**: Scan items into bags for order assembly

## Authentication

Scanner devices authenticate using Supabase JWT tokens:

```javascript theme={null}
// Login with Supabase
const { data } = await supabase.auth.signInWithPassword({
  email: 'picker@example.com',
  password: 'password123'
})

// Use access token for API calls
const response = await fetch('/scanner/inventory/lookup?barcode=012345678901', {
  headers: {
    'Authorization': `Bearer ${data.session.access_token}`
  }
})
```

## Endpoints

### Scanner Status

Check scanner API availability and user permissions.

```bash theme={null}
GET /scanner
Authorization: Bearer <token>
```

Response:

```json theme={null}
{
  "status": "ok",
  "version": "2.0.0",
  "features": {
    "inventory_scan": true,
    "barcode_lookup": true,
    "location_tracking": true,
    "fefo_fifo_picking": true,
    "inventory_receiving": true
  }
}
```

### Product Lookup

Look up a sellable product by barcode. Returns inventory in FEFO/FIFO order.

```bash theme={null}
GET /scanner/inventory/lookup?barcode=012345678901&location_id=loc-uuid
Authorization: Bearer <token>
```

Response:

```json theme={null}
{
  "success": true,
  "barcode": "012345678901",
  "product": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Organic Bananas",
    "brand": "Dole",
    "image_url": "https://cdn.example.com/bananas.jpg",
    "selling_price": 1.99,
    "is_perishable": true,
    "warehouse_zone": "C"
  },
  "inventory": {
    "total_quantity": 48,
    "total_reserved": 12,
    "available_quantity": 36,
    "items": [
      {
        "id": "inv-item-uuid",
        "location_id": "loc-uuid",
        "quantity": 24,
        "reserved_quantity": 6,
        "available": 18,
        "expiration_date": "2025-01-15",
        "received_at": "2024-12-15T10:00:00Z",
        "lot_number": "LOT-2024-001"
      }
    ]
  }
}
```

<Note>
  Inventory items are returned in FEFO/FIFO order - items expiring soonest first, then oldest received.
</Note>

### Inventory Scan

Process an inventory scan operation.

```bash theme={null}
POST /scanner/inventory/scan
Authorization: Bearer <token>
Content-Type: application/json

{
  "barcode": "012345678901",
  "location_id": "loc-uuid",
  "quantity": 24,
  "action": "receive",
  "expiration_date": "2025-01-15",
  "lot_number": "LOT-2024-001"
}
```

#### Actions

| Action    | Description                              |
| --------- | ---------------------------------------- |
| `lookup`  | Just look up product and inventory       |
| `adjust`  | Add or subtract from current quantity    |
| `count`   | Set exact count (inventory audit)        |
| `receive` | Receive new inventory (e.g., from sweep) |

Response:

```json theme={null}
{
  "success": true,
  "barcode": "012345678901",
  "action": "receive",
  "product": {
    "id": "prod-uuid",
    "name": "Organic Bananas",
    "is_perishable": true
  },
  "inventory": {
    "item_id": "new-inv-item-uuid",
    "quantity": 24,
    "reserved_quantity": 0,
    "available": 24,
    "location_id": "loc-uuid",
    "expiration_date": "2025-01-15"
  },
  "totals": {
    "total_quantity": 48,
    "total_reserved": 12,
    "available": 36
  },
  "scanned_at": "2024-12-18T15:30:00Z",
  "scanned_by": "staff-uuid"
}
```

### List Orders

Get orders assigned for picking.

```bash theme={null}
GET /scanner/orders?status=pending&limit=20
Authorization: Bearer <token>
```

Response:

```json theme={null}
{
  "orders": [
    {
      "id": "order-uuid",
      "display_id": 1234,
      "status": "picking",
      "source": "app",
      "created_at": "2024-12-18T10:00:00Z",
      "total": 45.99,
      "location_id": "loc-uuid"
    }
  ],
  "count": 15,
  "limit": 20,
  "offset": 0
}
```

### Order Details

Get full order details for picking, including totes and bags.

```bash theme={null}
GET /scanner/orders/:id
Authorization: Bearer <token>
```

Response:

```json theme={null}
{
  "order": {
    "id": "order-uuid",
    "display_id": 1234,
    "status": "picking",
    "source": "app",
    "created_at": "2024-12-18T10:00:00Z",
    "location_id": "loc-uuid",
    "items": [
      {
        "id": "item-uuid",
        "sellable_product_id": "prod-uuid",
        "title": "Organic Bananas",
        "quantity": 2,
        "unit_price": 1.99,
        "image_url": "https://cdn.example.com/bananas.jpg",
        "fulfillment_source": "inventory",
        "allocated_at": "2024-12-18T10:05:00Z"
      }
    ],
    "totes": [
      {
        "id": "tote-uuid",
        "tote_code": "TOTE-001",
        "status": "packing",
        "bags": [
          {
            "id": "bag-uuid",
            "bag_code": "BAG-001",
            "bag_type": "chilled"
          }
        ]
      }
    ]
  }
}
```

## Bag and Tote Workflow

### Picking Flow

1. Picker receives pick\_list assignment
2. For each pick\_list\_item:
   * Scan product barcode
   * System shows inventory location (FEFO/FIFO)
   * Picker retrieves item
   * Scan into bag (creates bag\_item)
3. Seal bag and assign to tote
4. Stage tote for robot delivery

### Intake Flow (Sweep Returns)

1. Driver returns from sweep
2. For each sweep\_item:
   * Scan product barcode
   * Scan into bag (creates bag\_item)
   * Links to order\_item for fulfillment
3. Bags assigned to totes
4. Stage for delivery

## Permissions

| Endpoint                                 | Permission          |
| ---------------------------------------- | ------------------- |
| `GET /scanner`                           | `scanner.use`       |
| `GET /scanner/inventory/lookup`          | `inventory.read`    |
| `POST /scanner/inventory/scan`           | `inventory.scan`    |
| `POST /scanner/inventory/scan` (receive) | `inventory.receive` |
| `GET /scanner/orders`                    | `orders.read`       |
| `GET /scanner/orders/:id`                | `orders.read`       |

## Error Responses

### 401 Unauthorized

No valid authentication token.

```json theme={null}
{
  "error": "Unauthorized"
}
```

### 403 Forbidden

Authenticated but missing required permission.

```json theme={null}
{
  "error": "Forbidden: Missing required permission",
  "required": "inventory.scan"
}
```

### 404 Not Found

Barcode not found in sellable catalog.

```json theme={null}
{
  "error": "Product not found",
  "barcode": "012345678901"
}
```

<Warning>
  The barcode must exist in both scraped\_products AND sellable\_products to be found. If a product exists in scraped\_products but hasn't been curated into sellable\_products, it will return 404.
</Warning>

## Mobile SDK Example

```typescript theme={null}
class ScannerClient {
  private token: string;
  private baseUrl = 'https://api.switchyard.run';

  async login(email: string, password: string) {
    const { data } = await supabase.auth.signInWithPassword({ email, password });
    this.token = data.session.access_token;
  }

  async lookupBarcode(barcode: string, locationId?: string) {
    let url = `${this.baseUrl}/scanner/inventory/lookup?barcode=${barcode}`;
    if (locationId) url += `&location_id=${locationId}`;
    
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${this.token}` }
    });
    return response.json();
  }

  async receiveInventory(
    barcode: string, 
    locationId: string, 
    quantity: number,
    expirationDate?: string,
    lotNumber?: string
  ) {
    const response = await fetch(`${this.baseUrl}/scanner/inventory/scan`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        barcode,
        location_id: locationId,
        quantity,
        action: 'receive',
        expiration_date: expirationDate,
        lot_number: lotNumber
      })
    });
    return response.json();
  }

  async adjustInventory(barcode: string, locationId: string, adjustment: number) {
    const response = await fetch(`${this.baseUrl}/scanner/inventory/scan`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        barcode,
        location_id: locationId,
        quantity: adjustment,
        action: 'adjust'
      })
    });
    return response.json();
  }
}
```
