Skip to main content

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

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:
// 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.
GET /scanner
Authorization: Bearer <token>
Response:
{
  "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.
GET /scanner/inventory/lookup?barcode=012345678901&location_id=loc-uuid
Authorization: Bearer <token>
Response:
{
  "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"
      }
    ]
  }
}
Inventory items are returned in FEFO/FIFO order - items expiring soonest first, then oldest received.

Inventory Scan

Process an inventory scan operation.
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

ActionDescription
lookupJust look up product and inventory
adjustAdd or subtract from current quantity
countSet exact count (inventory audit)
receiveReceive new inventory (e.g., from sweep)
Response:
{
  "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.
GET /scanner/orders?status=pending&limit=20
Authorization: Bearer <token>
Response:
{
  "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.
GET /scanner/orders/:id
Authorization: Bearer <token>
Response:
{
  "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

EndpointPermission
GET /scannerscanner.use
GET /scanner/inventory/lookupinventory.read
POST /scanner/inventory/scaninventory.scan
POST /scanner/inventory/scan (receive)inventory.receive
GET /scanner/ordersorders.read
GET /scanner/orders/:idorders.read

Error Responses

401 Unauthorized

No valid authentication token.
{
  "error": "Unauthorized"
}

403 Forbidden

Authenticated but missing required permission.
{
  "error": "Forbidden: Missing required permission",
  "required": "inventory.scan"
}

404 Not Found

Barcode not found in sellable catalog.
{
  "error": "Product not found",
  "barcode": "012345678901"
}
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.

Mobile SDK Example

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