> ## 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.

# Taxonomy IDs

> Comprehensive guide to the 18-digit taxonomy ID system used across all Switchyard entities

# Taxonomy ID System

Switchyard uses an 18-digit **taxonomy ID** system as a scannable, human-readable alternative to UUIDs. Every entity in the system has both a UUID (for internal database operations) and a taxonomy ID (for scanning, display, and external references).

## Overview

Taxonomy IDs are designed to be:

* **Scannable** - Can be printed as Code 128 barcodes
* **Checksum-protected** - MOD-97 validation prevents typos
* **Type-aware** - First digits identify the entity type
* **Globally unique** - No collisions across entity types

<Note>
  All V1 API endpoints accept both UUIDs and taxonomy IDs as the `:id` parameter. The system automatically detects and resolves the correct format.
</Note>

## ID Format (Memory Map)

An 18-digit taxonomy ID follows this structure:

| Offset | Width | Field    | Allowed             | Description                                          |
| ------ | ----- | -------- | ------------------- | ---------------------------------------------------- |
| 0      | 2     | **VER**  | `01`                | Schema version. Reserve 90-99 for future versions    |
| 2      | 2     | **TYPE** | See table below     | Entity type. Partitions the ID space                 |
| 4      | 3     | **FAC**  | `000`–`999`         | Facility/site code. Single site uses `000`           |
| 7      | 7     | **SEQ**  | `0000000`–`9999999` | Per-TYPE per-FAC counter                             |
| 14     | 2     | **RSV**  | `00`–`99`           | Reserved for future use (flags, subtypes). Keep `00` |
| 16     | 2     | **CS**   | `00`–`96`           | MOD-97 checksum over digits 0–15                     |

**Total: 18 digits** (even number, ideal for Code 128 Set C barcodes)

## Type Allocations

Each entity type has a unique 2-digit TYPE code:

| TYPE      | Entity                | Database Table       | Description                        |
| --------- | --------------------- | -------------------- | ---------------------------------- |
| `10`      | **Tote**              | `totes`              | Container for order items          |
| `11`      | **Robot**             | `robots`             | Delivery robot                     |
| `13`      | **Product**           | `sellable_products`  | Sellable product in the catalog    |
| `14`      | **Sweep**             | `sweeps`             | Warehouse sweep operations         |
| `20`      | **Cart**              | `carts`              | Picking cart (holds up to 3 totes) |
| `21`      | **Bag**               | `bags`               | Order bag (ambient/cold/frozen)    |
| `22`      | **Inventory Group**   | `inventory_groups`   | Warehouse location/slot            |
| `23`      | **Staff**             | `staff`              | Employee                           |
| `24`      | **Retailer Location** | `retailer_locations` | Store/retailer location            |
| `25`      | **Portal**            | `pickup_portals`     | Pickup portal                      |
| `27`      | **Manifest**          | `partner_manifests`  | Partner brand shipment manifest    |
| `50`–`89` | *Reserved*            | —                    | Lot tracking, vendor/batch use     |
| `90`–`99` | *Future Schema*       | —                    | Reserved for schema evolution      |

<Note>
  Type codes `12`, `15`–`19`, `26`, and `28`–`49` are currently unassigned and available for future entity types.
</Note>

### Entity Type Details

<AccordionGroup>
  <Accordion title="TYPE 10 - Totes">
    Physical containers used to hold items during fulfillment. Uses `tote_code` column in the `totes` table.

    **Use cases:**

    * Order picking containers
    * Receiving containers
    * Inter-zone transfer bins

    **Seeded:** 200 totes (SEQ 100001–100200)

    **Example:** `01 10 000 0100001 00 CS` → Tote #100001
  </Accordion>

  <Accordion title="TYPE 11 - Robots">
    Autonomous delivery robots for warehouse operations. Primary key is the taxonomy ID in the `robots` table.

    **Use cases:**

    * Delivery robots
    * Picking assistants

    **Seeded:** 12 robots (SEQ 100001–100012)

    **Example:** `01 11 000 0100001 00 CS` → Robot #100001
  </Accordion>

  <Accordion title="TYPE 13 - Products">
    Sellable products in the catalog. Has a `taxonomy_id` column on the `sellable_products` table.

    **Use cases:**

    * Product lookup
    * Inventory tracking
    * Order line items

    **Example:** `01 13 000 0100001 00 CS` → Product #100001
  </Accordion>

  <Accordion title="TYPE 14 - Sweeps">
    Warehouse sweep operations for inventory management. Has a `taxonomy_id` column on the `sweeps` table.

    **Use cases:**

    * Inventory sweep tracking
    * Cycle counts

    **Example:** `01 14 000 0100001 00 CS` → Sweep #100001
  </Accordion>

  <Accordion title="TYPE 20 - Carts">
    Picking carts that hold up to 3 totes. Primary key is the taxonomy ID in the `carts` table. Each cart has a color+animal identification name.

    **Use cases:**

    * Order picking workflows
    * Tote assignment

    **Seeded:** 10 carts (SEQ 100001–100010)

    **Example:** `01 20 000 0100001 00 CS` → Cart #100001
  </Accordion>

  <Accordion title="TYPE 21 - Bags">
    Order bags with temperature zone support (ambient, cold, frozen). Uses `bag_code` column in the `bags` table.

    **Use cases:**

    * Order packaging
    * Temperature zone segregation

    **Seeded:** 1,000 test bags (SEQ 1–1000) + 5,000 production bags (SEQ 100001–105000)

    **Example:** `01 21 000 0100001 00 CS` → Bag #100001
  </Accordion>

  <Accordion title="TYPE 22 - Inventory Groups">
    Warehouse locations and inventory slots. Has a `taxonomy_id` column on the `inventory_groups` table.

    **Use cases:**

    * Warehouse location tracking
    * Slot/bin identification
    * Zone management

    **Example:** `01 22 000 0100001 00 CS` → Inventory Group #100001
  </Accordion>

  <Accordion title="TYPE 23 - Staff">
    Employees and warehouse staff. Has a `taxonomy_id` column on the `staff` table.

    **Use cases:**

    * Staff identification
    * Task assignment
    * Access control

    **Example:** `01 23 000 0100001 00 CS` → Staff #100001
  </Accordion>

  <Accordion title="TYPE 24 - Retailer Locations">
    Store and retailer locations. Has a `taxonomy_id` column on the `retailer_locations` table.

    **Use cases:**

    * Location identification
    * Multi-store operations

    **Example:** `01 24 000 0100001 00 CS` → Retailer Location #100001
  </Accordion>

  <Accordion title="TYPE 25 - Portals">
    Pickup portals for order collection. Has a `taxonomy_id` column on the `pickup_portals` table.

    **Use cases:**

    * Customer pickup points
    * Portal scanning

    **Example:** `01 25 000 0100001 00 CS` → Portal #100001
  </Accordion>

  <Accordion title="TYPE 27 - Manifests">
    Partner brand shipment manifests. Has a `taxonomy_id` column on the `partner_manifests` table. Used for QR code scanning of incoming partner shipments.

    **Use cases:**

    * Partner shipment receiving
    * QR code scanning
    * Manifest tracking

    **Example:** `01 27 000 0100001 00 CS` → Manifest #100001
  </Accordion>
</AccordionGroup>

## SEQ Reservation Bands

Within each TYPE×FAC combination, the 7-digit SEQ space is divided:

| SEQ Range           | Purpose                                         |
| ------------------- | ----------------------------------------------- |
| `0000000`–`0099999` | **Test/Temporary** - Development and testing    |
| `0100000`–`8999999` | **Production** - Live working pool (\~8.9M IDs) |
| `9000000`–`9999999` | **Future/Expansion** - Reserved for growth      |

This gives each TYPE×FAC up to **8.9 million production IDs**, which is more than sufficient for operational scale.

## Checksum Calculation

The 2-digit checksum (CS) at positions 16-17 is calculated using **MOD-97**:

```
CS = (numeric value of first 16 digits) mod 97
```

Zero-pad to 2 digits if needed.

### Examples (FAC=000, RSV=00)

| Entity           | Base (first 16 digits) | CS                        | Complete Taxonomy ID |
| ---------------- | ---------------------- | ------------------------- | -------------------- |
| Tote #100001     | `0110000010000100`     | `0110000010000100 mod 97` | 18-digit result      |
| Robot #100001    | `0111000010000100`     | `0111000010000100 mod 97` | 18-digit result      |
| Product #100001  | `0113000010000100`     | `0113000010000100 mod 97` | 18-digit result      |
| Cart #100001     | `0120000010000100`     | `0120000010000100 mod 97` | 18-digit result      |
| Bag #100001      | `0121000010000100`     | `0121000010000100 mod 97` | 18-digit result      |
| Staff #100001    | `0123000010000100`     | `0123000010000100 mod 97` | 18-digit result      |
| Manifest #100001 | `0127000010000100`     | `0127000010000100 mod 97` | 18-digit result      |

<Note>
  The checksum provides typo detection. If a user manually enters an ID incorrectly, the checksum validation will fail.
</Note>

## Database Implementation

Taxonomy IDs are automatically generated by database triggers when new entities are created.

### Table Structure

Each table with taxonomy IDs has:

```sql theme={null}
-- Primary key (UUID)
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

-- Taxonomy ID (auto-generated, unique)
taxonomy_id TEXT UNIQUE NOT NULL,
```

### Generation Trigger

The `set_taxonomy_id()` trigger function runs on INSERT for supported tables and auto-generates a taxonomy ID if one is not already provided:

```sql theme={null}
CREATE OR REPLACE FUNCTION set_taxonomy_id()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
  v_type_code TEXT;
BEGIN
  -- Determine type code based on table name
  CASE TG_TABLE_NAME
    WHEN 'sellable_products' THEN v_type_code := '13';
    WHEN 'sweeps' THEN v_type_code := '14';
    WHEN 'inventory_groups' THEN v_type_code := '22';
    WHEN 'staff' THEN v_type_code := '23';
    WHEN 'retailer_locations' THEN v_type_code := '24';
    WHEN 'pickup_portals' THEN v_type_code := '25';
    WHEN 'partner_manifests' THEN v_type_code := '27';
    ELSE
      RAISE EXCEPTION 'Unknown table for taxonomy ID trigger: %', TG_TABLE_NAME;
  END CASE;

  -- Only set if not already provided
  IF NEW.taxonomy_id IS NULL THEN
    NEW.taxonomy_id := get_next_taxonomy_id(v_type_code);
  END IF;

  RETURN NEW;
END;
$$;
```

<Note>
  Totes, robots, carts, and bags use their taxonomy ID as a primary key or dedicated code column (`tote_code`, `bag_code`) rather than the trigger-based approach. Their IDs are generated during table seeding.
</Note>

## API Usage

### Dual ID Support

All V1 API endpoints accept both UUIDs and taxonomy IDs:

```bash theme={null}
# Using UUID
GET /v1/products/550e8400-e29b-41d4-a716-446655440000

# Using Taxonomy ID
GET /v1/products/011300000012345042
```

### ID Resolution

The API automatically detects the ID format and resolves taxonomy IDs to UUIDs for database queries:

```typescript theme={null}
// In route handlers
const id = await resolveId('sellable_products', idParam)
// Returns UUID regardless of input format
```

Tables currently supported by the `resolveId()` utility:

* `sellable_products`
* `staff`
* `inventory_groups`
* `sweeps`
* `retailer_locations`

### Detection Logic

```typescript theme={null}
function isTaxonomyId(id: string): boolean {
  // Taxonomy IDs are exactly 18 digits
  return /^\d{18}$/.test(id)
}

function isUUID(id: string): boolean {
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)
}
```

## Barcode Implementation

### Code 128 Set C

Taxonomy IDs are optimized for **Code 128 Set C** encoding:

* 18 digits = 9 symbol pairs
* Highly compact barcode
* Fast scanning

### Label Format

```
┌─────────────────────────────────┐
│  [BARCODE: 011300000012345042]  │
│                                 │
│     ITEM #12345                 │
│     SKU: EXAMPLE-PRODUCT        │
└─────────────────────────────────┘
```

## Validation

### Client-Side Validation

```typescript theme={null}
function validateTaxonomyId(id: string): boolean {
  if (!/^\d{18}$/.test(id)) return false

  const base = id.substring(0, 16)
  const actualChecksum = id.substring(16, 18)

  // Use BigInt for accurate calculation with large numbers
  const expectedChecksum = (BigInt(base) % 97n).toString().padStart(2, '0')

  return actualChecksum === expectedChecksum
}
```

### Server-Side Validation

The API validates taxonomy IDs using Zod schemas:

```typescript theme={null}
const taxonomyIdSchema = z.string().refine(
  (val) => /^\d{18}$/.test(val) && validateChecksum(val),
  { message: 'Invalid taxonomy ID' }
)
```

## TypeScript Types

The taxonomy ID system exposes the following types and utilities from `apps/api/src/lib/taxonomy-id.ts`:

### EntityType

```typescript theme={null}
type EntityType =
  | 'tote'
  | 'robot'
  | 'cart'
  | 'bag'
  | 'product'
  | 'sweep'
  | 'inventory_group'
  | 'staff'
  | 'retailer_location'
  | 'portal'
  | 'manifest'
```

### TYPE\_CODES

```typescript theme={null}
const TYPE_CODES: Record<EntityType, string> = {
  tote: '10',
  robot: '11',
  product: '13',
  sweep: '14',
  cart: '20',
  bag: '21',
  inventory_group: '22',
  staff: '23',
  retailer_location: '24',
  portal: '25',
  manifest: '27',
}
```

### Utility Functions

| Function                                          | Description                                                                            |
| ------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `generateTaxonomyId(type, seq, fac?, ver?, rsv?)` | Generate a taxonomy ID for a given entity type and sequence                            |
| `validateTaxonomyId(id)`                          | Validate format and MOD-97 checksum                                                    |
| `parseEntityType(id)`                             | Extract the `EntityType` from a taxonomy ID                                            |
| `parseTaxonomyId(id)`                             | Parse into all component parts (version, type, facility, sequence, reserved, checksum) |
| `formatDisplayId(id)`                             | Extract the 5-digit display sequence                                                   |
| `formatEntityLabel(id)`                           | Format as human-readable label (e.g., "Cart 00001")                                    |
| `calculateChecksum(digits)`                       | Calculate MOD-97 checksum for a 16-digit base string                                   |

## Best Practices

<CardGroup cols={2}>
  <Card title="Use Taxonomy IDs for Display" icon="eye">
    Show taxonomy IDs in user interfaces and printed materials. They're easier to read and verify than UUIDs.
  </Card>

  <Card title="Store UUIDs Internally" icon="database">
    Use UUIDs for database foreign keys and internal references. Taxonomy IDs are for external interfaces.
  </Card>

  <Card title="Validate on Input" icon="shield-check">
    Always validate the checksum when accepting taxonomy ID input to catch typos early.
  </Card>

  <Card title="Include Both in Exports" icon="file-export">
    When exporting data, include both the UUID and taxonomy ID for maximum compatibility.
  </Card>
</CardGroup>

## Related Documentation

* [Data Model](/architecture/data-model) - Database schema and relationships
* [V1 API Overview](/api-reference/v1/overview) - API endpoint documentation
* [Scanner API Guide](/guides/scanner-api) - Mobile scanning integration
