Get Product Units by Customer
Returns the product units currently held by a customer, as a paginated list.
/api/integration/customer/{customerId}/product-unitsRequest
curl -X GET "https://dev.integration.cl.sodtrack-shared.sodtrack.com/api/integration/customer/{customerId}/product-units" \
-H "Authorization: Bearer $SODTRACK_TOKEN"Base URL: https://dev.integration.cl.sodtrack-shared.sodtrack.com · other environments
Path parameters
| Name | Type | Description |
|---|---|---|
customerIdrequired | string | — |
Responses
{
"data": [
{
"id": 85,
"serialNumber": "SN-0001-C",
"status": "in_use",
"type": "component",
"entryDate": "2026-08-09T00:19:48.993Z",
"cost": null,
"product": {
"id": 28310,
"name": "Thermostat",
"sku": "TH-01"
},
"lastMovement": {
"id": 77,
"createdDate": "2026-08-29T00:19:48.994Z",
"fromLocationType": "warehouse",
"fromId": "1",
"fromName": "Central warehouse",
"fromAddress": null,
"toLocationType": "customer",
"toId": "32841",
"toName": "María González",
"toAddress": {
"id": 13,
"formattedAddress": "Av. Corrientes 1234, Buenos Aires",
"extraInfo": "Floor 3, Apt B"
}
}
},
{
"id": 83,
"serialNumber": "SN-0001-A",
"status": "in_use",
"type": "product",
"entryDate": "2026-08-09T00:19:48.979Z",
"cost": null,
"product": {
"id": 990001,
"name": "Water heater 80L",
"sku": "WH-80L"
},
"lastMovement": {
"id": 75,
"createdDate": "2026-08-29T00:19:48.984Z",
"fromLocationType": "warehouse",
"fromId": "1",
"fromName": "Central warehouse",
"fromAddress": null,
"toLocationType": "customer",
"toId": "32841",
"toName": "María González",
"toAddress": {
"id": 13,
"formattedAddress": "Av. Corrientes 1234, Buenos Aires",
"extraInfo": "Floor 3, Apt B"
}
}
}
],
"metadata": {
"count": 2,
"pageNumber": 1,
"pageSize": 10,
"totalPages": 1
}
}Error responses for this endpoint follow the shared error reference.
Purpose
Returns the product units currently held by a customer, as a paginated list.
The customer is identified by its Sodtrack id in the path. To resolve that id from your own data, search the customer first with GET /api/integration/customer/v2 (by reference, phoneNumber, identificationNumber or a booking) and take id from the result.
Sodtrack returns every product unit whose current physical location is that customer, newest first. Each unit comes with its parent product, its type (product or component), its status, and the movement that brought it to the customer, including the delivery address.
Authentication
| Header | Required | Description |
|---|---|---|
x-api-key | ✅ | Sodtrack inbound integration API key. |
The endpoint is restricted to Sodtrack's private integration network. Requests from unauthorized origins are rejected.
Endpoint
GET /api/integration/customer/{customerId}/product-units
Field Definitions
🧍 Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
customerId | number | ✅ | Sodtrack customer id. Must be an integer. |
📄 Pagination (query parameters)
| Field | Type | Required | Description |
|---|---|---|---|
pageNumber | number | ❌ | 1-based page number. Defaults to 1. |
pageSize | number | ❌ | Product units per page. Between 1 and 50. Defaults to 10. |
No other parameter is accepted; any other query parameter is ignored.
Example Request
GET /api/integration/customer/32841/product-unitsSecond page of 5 units:
GET /api/integration/customer/32841/product-units?pageNumber=2&pageSize=5Response Example
{
"data": [
{
"id": 85,
"serialNumber": "SN-0001-C",
"status": "in_use",
"type": "component",
"entryDate": "2026-08-09T00:19:48.993Z",
"cost": null,
"product": {
"id": 28310,
"name": "Thermostat",
"sku": "TH-01"
},
"lastMovement": {
"id": 77,
"createdDate": "2026-08-29T00:19:48.994Z",
"fromLocationType": "warehouse",
"fromId": "1",
"fromName": "Central warehouse",
"fromAddress": null,
"toLocationType": "customer",
"toId": "32841",
"toName": "María González",
"toAddress": {
"id": 13,
"formattedAddress": "Av. Corrientes 1234, Buenos Aires",
"extraInfo": "Floor 3, Apt B"
}
}
},
{
"id": 83,
"serialNumber": "SN-0001-A",
"status": "in_use",
"type": "product",
"entryDate": "2026-08-09T00:19:48.979Z",
"cost": null,
"product": {
"id": 990001,
"name": "Water heater 80L",
"sku": "WH-80L"
},
"lastMovement": {
"id": 75,
"createdDate": "2026-08-29T00:19:48.984Z",
"fromLocationType": "warehouse",
"fromId": "1",
"fromName": "Central warehouse",
"fromAddress": null,
"toLocationType": "customer",
"toId": "32841",
"toName": "María González",
"toAddress": {
"id": 13,
"formattedAddress": "Av. Corrientes 1234, Buenos Aires",
"extraInfo": "Floor 3, Apt B"
}
}
}
],
"metadata": {
"count": 2,
"pageNumber": 1,
"pageSize": 10,
"totalPages": 1
}
}Response Fields
| Field | Type | Description |
|---|---|---|
data[] | array | Product units in the page, newest first. Empty when the customer holds none. |
data[].id | number | Sodtrack product unit id. Use it with the product unit detail endpoint. |
data[].serialNumber | `string | null` |
data[].status | string | One of: "available", "not_available", "committed", "in_use", "potential_conflict", "movement_request". |
data[].type | string | "product" or "component". |
data[].entryDate | `string (datetime) | null` |
data[].cost | `number | null` |
data[].product | object | Parent product. |
data[].product.id | number | Sodtrack product id. |
data[].product.name | string | Product name. |
data[].product.sku | string | Product SKU. |
data[].lastMovement | `object | null` |
data[].lastMovement.id | number | Movement id. |
data[].lastMovement.createdDate | `string (datetime) | null` |
data[].lastMovement.fromLocationType | `string | null` |
data[].lastMovement.fromId | `string | null` |
data[].lastMovement.fromName | `string | null` |
data[].lastMovement.fromAddress | `object | null` |
data[].lastMovement.toLocationType | string | Destination type: "warehouse", "customer" or "provider". |
data[].lastMovement.toId | string | Id of the destination warehouse or customer, according to toLocationType. |
data[].lastMovement.toName | `string | null` |
data[].lastMovement.toAddress | `object | null` |
data[].lastMovement.toAddress.id | number | Sodtrack address id. |
data[].lastMovement.toAddress.formattedAddress | string | Full formatted address. |
data[].lastMovement.toAddress.extraInfo | `string | null` |
metadata.count | number | Total product units held by the customer, across all pages. |
metadata.pageNumber | number | Page returned. |
metadata.pageSize | number | Page size applied. |
metadata.totalPages | number | Total number of pages. |
Error Responses
| HTTP Status | Error | Description |
|---|---|---|
400 Bad Request | Validation failed (numeric string is expected) | customerId is not an integer. |
400 Bad Request | — | A pagination parameter has the wrong type or is out of range (for example pageSize above 50). |
401 Unauthorized | — | Missing or invalid x-api-key, or the request does not come from an authorized origin. |
404 Not Found | CUSTOMER_NOT_FOUND: {customerId} | No customer exists with that id. |
503 Service Unavailable | PRODUCT_MODULE_UNAVAILABLE | Sodtrack's product inventory service could not be reached. Retry later. |
500 Internal Server Error | — | Unexpected server error. Contact Sodtrack support. |
Business Rules & Constraints
The customer must exist
customerId must be the id of a Sodtrack customer. Unknown ids, and ids of providers, network users or other account types, are answered with 404 Not Found. A customer that exists but holds no product unit is answered with 200 OK and an empty data.
Product units are the ones currently at the customer
data contains only the units whose current physical location is that customer. Units that were delivered and later returned to a warehouse, or moved to another customer, are not included. To see the full history of a unit use the product unit detail endpoint with data[].id.
Location names and addresses are resolved when possible
fromName, toName, fromAddress and toAddress in lastMovement are resolved from the warehouse or customer referenced by the movement. If that warehouse or customer no longer exists, the name or address comes back as null while the raw fromId / toId is still returned.
Ordering
Product units are ordered by id, newest first.
Page boundaries
A pageNumber beyond the last page is not an error: it returns an empty data with the same metadata.count and metadata.totalPages.