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
| 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:
{
"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
- Picker receives pick_list assignment
- For each pick_list_item:
- Scan product barcode
- System shows inventory location (FEFO/FIFO)
- Picker retrieves item
- Scan into bag (creates bag_item)
- Seal bag and assign to tote
- Stage tote for robot delivery
Intake Flow (Sweep Returns)
- Driver returns from sweep
- For each sweep_item:
- Scan product barcode
- Scan into bag (creates bag_item)
- Links to order_item for fulfillment
- Bags assigned to totes
- 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.
{
"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();
}
}