API Reference

All endpoints exposed by the Inventory Web (App 2) server. Base URL: http://<host>:4004
Two authentication schemes are used: API Key (upload endpoint, for App 1) and Session Cookie (all other endpoints, for the web UI and SAP item lookup).

Introduction

The Inventory server exposes three groups of endpoints:

GroupBase pathAuthUsed by
Batch upload/upload/batchAPI KeyApp 1 (Windows/scanner)
SAP item/vendor lookup/items/SessionWeb UI (item search page)
Batch management (OData V4)/odata/v4/batch/SessionWeb UI, direct OData clients
Authentication

API Key (upload endpoint)

App 1 must include the key in the x-api-key header. Keys are configured server-side via the UPLOAD_API_KEYS environment variable (comma-separated list). Key comparison is constant-time to prevent timing attacks.

# Example header
x-api-key: devkey123

Session Cookie (web & OData endpoints)

Human users authenticate via POST /auth/login (handled by the UI's Login view). On success, the server sets an inv_sid HttpOnly + SameSite=Strict cookie with an 8-hour sliding expiry. All subsequent requests must carry this cookie. Users are configured via the WEB_USERS env var (email:password,...).

💡 In development (mocked-auth mode), the session requirement is bypassed. OData endpoints are open. The upload endpoint still requires a valid API key.
Error format

All custom endpoints return JSON error bodies. OData endpoints follow the OData V4 error format.

{
  "error": "Validation failed",
  "details": ["idempotencyKey is required", "docType must be one of PO, GRPO, Counting"]
}
StatusMeaning
400Validation error — see details array
401Missing or invalid API key / session expired
403Action not permitted in current batch status
404Batch or line not found
500Internal server error
502SAP B1 Service Layer unreachable or returned an error
POST /upload/batch
🔑 x-api-key header

Upload a scanned batch from App 1. Idempotent: re-uploading the same idempotencyKey updates the batch if it is in status Uploaded or Rejected, and silently ignores the re-upload if the batch is already past review (Approved, Posting, Posted, etc.).

App 1 should generate a stable idempotencyKey (e.g. UUID) once per batch session and reuse it for retries. The server guarantees at-most-one batch creation per key.
Request body (JSON)
FieldTypeReq?Description
idempotencyKeystringrequiredStable unique key for this batch (e.g. UUID). Used for idempotent upsert.
docTypestringrequiredOne of PO, GRPO, Counting
linesLine[]requiredNon-empty array of scanned lines (see Line schema below)
batchNamestringoptionalHuman-readable name shown in the web UI
cardCodestringoptionalVendor BP code (required by SAP for PO/GRPO at posting time)
warehousestringoptionalDefault warehouse code
docRefstringoptionalBusiness reference from App 1 → written to SAP as NumAtCard (PO/GRPO) or Remarks (Counting)
baseDocRefstringoptionalBase PO DocEntry for GRPO batches
operatorstringoptionalScanner operator name / ID
sourceHoststringoptionalHostname / site identifier of the uploading App 1 instance
commentsstringoptionalFree-text comments
Line object schema
FieldTypeReq?Description
lineNointegeroptionalLine sequence number (defaults to index+1)
scannedBarcodestringoptionalRaw barcode value read by the scanner
itemCodestringoptionalSAP item code. If missing → validationStatus = Unresolved
itemNamestringoptionalSAP item description (cached from lookup)
quantitydecimaloptionalScanned quantity
uomstringoptionalUnit of measure
warehousestringoptionalLine-level warehouse (overrides header warehouse)
batchNumberstringoptionalSAP batch/lot number
serialstringoptionalSerial number
unitPricedecimaloptionalUnit price (used for PO lines)
scannedBystringoptionalOperator who scanned this line
scannedAtdatetimeoptionalISO 8601 scan timestamp
sourcestringoptionalTerminal commID or "import"
validationStatusstringoptionalOK | Unresolved | Warning. Auto-derived from itemCode if omitted.
validationMessagestringoptionalHuman-readable validation detail
Example request
POST /upload/batch
Content-Type: application/json
x-api-key: devkey123

{
  "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
  "batchName":      "Delivery 2024-001",
  "docType":        "GRPO",
  "cardCode":       "V001",
  "warehouse":      "WH01",
  "docRef":         "DN-2024-001",
  "operator":       "john.doe",
  "sourceHost":     "WAREHOUSE-PC-01",
  "lines": [
    {
      "scannedBarcode": "5901234123457",
      "itemCode":       "ITEM-A",
      "itemName":       "Widget A",
      "quantity":       10,
      "uom":            "EA",
      "validationStatus": "OK"
    },
    {
      "scannedBarcode": "UNKNOWN-BAR",
      /* itemCode absent — validationStatus auto-set to Unresolved */
      "quantity":       2,
      "validationStatus": "Unresolved"
    }
  ]
}
Responses
StatusBodyWhen
201 Created{ status:"created", batchId, batchStatus:"Uploaded" }New batch was created
200 OK{ status:"updated", batchId, batchStatus:"Uploaded" }Existing Uploaded/Rejected batch was updated
200 OK{ status:"duplicate", batchId, batchStatus }Batch already past review — upload ignored
400{ error, details[] }Payload validation failed
401{ error:"Invalid or missing API key" }Missing or wrong API key
GET /items/vendors
🍪 Session cookie

Searches SAP B1 Business Partners of type Supplier. Used by the batch detail form to look up vendor card codes.

Query parameters
ParamTypeReq?Description
qstringoptionalFilter by card code or name. Returns all suppliers when omitted.
Response — 200 OK
{
  "value": [
    { "CardCode": "V001", "CardName": "Acme Supplies" }
  ]
}
Batches entity set
🍪 Session cookie

Full OData V4 CRUD on batches. Base path: /odata/v4/batch/Batches

Common queries
# List all batches (top 20)
GET /odata/v4/batch/Batches?$top=20&$orderby=uploadedAt desc

# Filter by status and doc type
GET /odata/v4/batch/Batches?$filter=status eq 'Uploaded' and docType eq 'PO'

# Expand lines in a single request
GET /odata/v4/batch/Batches?$expand=lines

# Get a single batch with lines
GET /odata/v4/batch/Batches('<uuid>')?$expand=lines
Key batch fields
FieldTypeDescription
IDGuidPrimary key (UUID)
idempotencyKeyString(100)Stable upload key from App 1
batchNameString(100)Human-readable name
docTypePO|GRPO|CountingTarget SAP document type
statusBatchStatusLifecycle status (see below)
cardCodeString(50)Vendor BP code
warehouseString(20)Default warehouse
docRefString(100)Business reference → SAP NumAtCard/Remarks
sapDocNumIntegerSAP document number (set after posting)
lastErrorString(1000)Last posting error message
uploadedAtTimestampUpload timestamp
Lines entity set
🍪 Session cookie

Scanned lines within a batch. Always accessed via the parent batch expand or directly.

# Lines for a specific batch
GET /odata/v4/batch/Lines?$filter=batch_ID eq '<uuid>'

# Unresolved lines across all batches
GET /odata/v4/batch/Lines?$filter=validationStatus eq 'Unresolved'
Key line fields
FieldTypeDescription
IDGuidPrimary key
batch_IDGuidParent batch foreign key
lineNoIntegerSequence within batch
scannedBarcodeString(100)Raw barcode
itemCodeString(50)SAP item code
itemNameString(200)SAP item description
quantityDecimal(15,4)Scanned quantity
validationStatusOK|Unresolved|WarningLine validation result
validationMessageString(255)Human-readable validation detail
Bound Actions
🍪 Session cookie

Custom actions are bound to the Batches entity. Call them via HTTP POST with the batch ID in the URL.

approve — approve a batch for posting
⚠️ Approval is blocked if any line has validationStatus = Unresolved. Resolve all lines before approving.
POST /odata/v4/batch/Batches('<uuid>')/inventory.approve
Content-Type: application/json

{ "comment": "Verified by supervisor" }

// Response 200
{ "value": { "status": "Approved" } }
rejectBatch — reject a batch back to App 1
POST /odata/v4/batch/Batches('<uuid>')/inventory.rejectBatch
Content-Type: application/json

{ "reason": "Wrong vendor code" }

// Response 200
{ "value": { "status": "Rejected" } }
postToSap — post an approved batch to SAP B1
💡 Only available when batch status is Approved or PostFailed. The action is idempotent — SAP is checked for a prior document via Comments / Reference2 before creating a new one.
POST /odata/v4/batch/Batches('<uuid>')/inventory.postToSap
Content-Type: application/json

{}

// Response 200 — success
{ "value": { "status": "Posted", "sapDocNum": 1042, "sapDocEntry": 37 } }

// Response 200 — failure (SAP rejected the document)
{ "value": { "status": "PostFailed", "error": "Item ITEM-X does not exist in company db" } }
Batch status lifecycle
StatusSet byMeaningNext states
Uploaded Upload endpoint Batch received from App 1, awaiting review Approved, Rejected, Archived
Approved approve action All lines valid; batch cleared for SAP posting Posting
Posting postToSap action SAP API call in progress Posted, PostFailed
Posted postToSap action SAP document created. sapDocNum is set. Batch is locked. Archived
PostFailed postToSap action SAP rejected the document. lastError contains the reason. Posting (via retry)
Rejected rejectBatch action Batch rejected by reviewer. App 1 can re-upload to this key. Uploaded (via re-upload)
Archived Manual / future Batch moved to archive view.
SAP B1 field mapping

How batch/line fields are mapped to SAP B1 document fields when posting.

PO — PurchaseOrders
Batch/Line fieldSAP B1 fieldNote
cardCodeCardCodeVendor code — required
docRefNumAtCardVendor reference number
commentsCommentsAlso includes idempotency key: BatchRef:<key>
idempotencyKeyReference2Used to detect existing documents for idempotency
line.itemCodeDocumentLines[].ItemCode
line.quantityDocumentLines[].Quantity
line.unitPriceDocumentLines[].UnitPrice
line.warehouseDocumentLines[].WarehouseCodeFalls back to header warehouse
GRPO — PurchaseDeliveryNotes
Batch/Line fieldSAP B1 fieldNote
cardCodeCardCodeRequired
docRefNumAtCardVendor delivery note reference
baseDocRefDocumentLines[].BaseEntryBase PO DocEntry for linking
comments / idempotencyKeyComments / Reference2Same as PO
Counting — InventoryCountings
Batch/Line fieldSAP B1 fieldNote
docRef / commentsRemarksConcatenated
idempotencyKeyReference2Idempotency check
line.itemCodeInventoryCountingLines[].ItemCode
line.quantityInventoryCountingLines[].CountedQuantity
line.warehouseInventoryCountingLines[].WarehouseCode