Cargo Carrier Accounting API
v1Manage cargo carrier deposits, balances, submissions, and per-location configuration for comprehensive cargo carrier accounting in the Zendera system.
A cargo carrier in Zendera is a returnable item that must be tracked and accounted for separately from the goods on an order — typically pallets, crates, beer kegs, gas cylinders, or any equipment that customers return. Cargo carrier accounting tracks per-location balances and supports submission flows that reconcile what was delivered against what was returned.
This page documents the endpoints available with an API key. The balance overview dashboards (org-wide balances grouped by carrier or location) are part of the Zendera web application and are not exposed through the API.
Where this fits in your operation
Returnables are money — this API keeps the ledger straight between you and your customers:
- Pallet ledger reconciliation: read per-location balances (
location/list-by-ids) to see how many EUR pallets each customer location is holding. - Deposit invoicing loop: fetch new deposits (each handed to you exactly once), invoice them in your ERP, then confirm them as received — see Deposits.
- Register returns: when empties come back outside the normal delivery flow, record them with a submission so the balance stays correct.
Interactive API Explorer
Loading API Documentation...
Authentication
Authorization: apikey YOUR_API_KEY_HEREBase URLs
- Production:
https://app.zenderatms.com/api/ - Staging:
https://staging.zenderatms.com/api/
Key Concepts
Cargo carriers
Cargo carriers are products that can be tracked for deposits and returns (e.g. pallets, containers, crates). The list returned by carriers/list are the cargo-carrier types defined for your organization.
Submissions
A submission is a recorded movement of cargo carriers — at a given location and time, the operator registered these quantities of these carriers. Each submission has a head (who/where/when) and entries (the per-carrier quantity changes).
Balances
The system maintains real-time balances of cargo carriers per location and per carrier. Over the API, per-location balances are read via location/list-by-ids (the aggregate.quantityAggregates in the response).
Deposits
Deposits track cargo carrier transactions with details about products, locations, and quantities. Each deposit moves through a lifecycle: not fetched → fetched (your system has retrieved it) → received (you have confirmed the money side is settled).
deposits and submissions are different things. Deposits are monetary records (what the customer owes for unreturned carriers); submissions are quantity changes (what was physically received or taken).
Endpoints Overview
Core carrier operations
GET /v1/cargocarrier/carriers/list- List all cargo carrier typesGET /v1/cargocarrier/configuration- Get organization configuration
Deposits
POST /v1/cargocarrier/deposits- Fetch cargo carrier deposits (filterable by lifecycle status)POST /v1/cargocarrier/deposits/confirm- Confirm fetched deposits as received
Product linking
POST /v1/cargocarrier/link-product- Link a product as a cargo carrierPOST /v1/cargocarrier/unlink-product- Unlink a product
Submission management
GET /v1/cargocarrier/submission/by-id/{id}- Get submission by IDPOST /v1/cargocarrier/submission/create-location-submission- Create a location submissionGET /v1/cargocarrier/submission/list- List submissions (cursor paginated)GET /v1/cargocarrier/submission/list-by-location-id/{locationId}- List submissions by locationGET /v1/cargocarrier/submission/list-by-user-id/{userId}- List submissions by user
Location configuration
POST /v1/cargocarrier/location/list-by-ids- Read accounting state by location IDsPOST /v1/cargocarrier/location/update-configurations- Update per-location configuration
Mobile app endpoints
GET /v1/mobile-app/cargocarrier/configuration- Mobile organization configurationPOST /v1/mobile-app/cargocarrier/submission/create-batch-stop-submission- Create batch stop submissionPOST /v1/mobile-app/cargocarrier/submission/get-batch-stop-submissions- Get batch stop submissions
Configuration
Read your organization’s configuration
GET /v1/cargocarrier/configuration
{
"accountingEnabled": true,
"promptNormalDeliveries": true,
"promptDepotDeliveries": false
}| Field | Meaning |
|---|---|
accountingEnabled | Whether cargo-carrier accounting is on for this org. |
promptNormalDeliveries | Whether the driver app prompts for cargo-carrier counts on regular deliveries. |
promptDepotDeliveries | Same, for depot deliveries. |
A parallel mobile-app variant exists at GET /v1/mobile-app/cargocarrier/configuration for driver-app integrations.
List available cargo carriers
GET /v1/cargocarrier/carriers/list
{
"cargoCarriers": [
{ "id": 1, "name": "EUR pallet" },
{ "id": 2, "name": "Beer keg 50L" }
]
}Linking products as cargo carriers
A product becomes a cargo carrier by being linked. Linking enables balance tracking and (optionally) deposit accounting for that product.
Link a product
POST /v1/cargocarrier/link-product
{
"productId": 4242,
"negativeBalanceAllowed": false,
"depositInUse": true,
"depositOnAction": "DepositAction_DELIVERY",
"depositPreserveBalanceWhenFetched": true
}| Field | Notes |
|---|---|
productId | The product to link. |
negativeBalanceAllowed | If false, balance can’t go below zero — operations that would push it negative are rejected. |
depositInUse | Whether deposits apply for this carrier. |
depositOnAction | When deposits are charged — see Deposit Actions. |
depositPreserveBalanceWhenFetched | When true, fetching deposits doesn’t zero the running balance. |
Response: the created cargoCarrier record.
Unlink a product
POST /v1/cargocarrier/unlink-product
{ "productId": 4242 }Per-location accounting
Read accounting state for one or more locations
POST /v1/cargocarrier/location/list-by-ids
{ "locationIds": [999, 1000] }Response (response[] of LocationWithAggregate):
{
"response": [
{
"locationId": 999,
"aggregate": {
"locationId": 999,
"quantityAggregates": [
{ "cargoCarrierId": 1, "cargoCarrierName": "EUR pallet", "quantity": 42 }
]
},
"configuration": {
"locationId": 999,
"requireAccounting": { "value": true },
"cargoCarrierConfiguration": [
{
"locationId": 999,
"cargoCarrierId": 1,
"cargoCarrierName": "EUR pallet",
"minQuantity": { "value": 0 },
"maxQuantity": { "value": 100 }
}
]
},
"disableAccounting": false
}
]
}Update per-location configuration
POST /v1/cargocarrier/location/update-configurations
The setAskForAccounting wrapper is intentional: null means “no decision”, a value containing null removes a prior decision, and a value with true/false overrides the location specification.
{
"configurations": [
{
"locationId": 999,
"setAskForAccounting": {
"optionalValue": {
"value": true
}
},
"cargocarrierConfigurations": [
{
"cargoCarrierId": 1,
"minQuantity": {
"optional": {
"value": 0
}
},
"maxQuantity": {
"optional": {
"value": 100
}
}
}
],
"setDisableAccounting": {
"value": false
}
}
]
}Response: configurations[] of the resulting LocationConfiguration records.
Deposit Actions
The depositOnAction field in product linking (DepositActions) accepts these exact literals:
DepositAction_UNSPECIFIED- Default/unspecified actionDepositAction_PICKUP- Deposit recorded at pickupDepositAction_DELIVERY- Deposit recorded at deliveryDepositAction_BALANCE- Balance-based deposit
Submissions
A submission records a movement of cargo carriers at a location.
Create a submission for a location
POST /v1/cargocarrier/submission/create-location-submission
Each entry is a cargoCarrierId with add and/or subtract counts:
{
"locationId": 999,
"comment": "Reconciliation after Friday delivery",
"entries": [
{
"cargoCarrierId": 1,
"add": 10,
"subtract": 0
},
{
"cargoCarrierId": 2,
"add": 0,
"subtract": 5
}
]
}Request entries use add / subtract, not a signed delta. The request schema (InputSubmissionEntryDelta) takes cargoCarrierId, add, and subtract. The stored submission response entries (SubmissionEntry) report a single signed delta instead — don’t confuse the write shape with the read shape.
Fetch a submission by ID
GET /v1/cargocarrier/submission/by-id/{id}
Returns a submission with its head and entries:
{
"submission": {
"head": {
"id": 123,
"organizationId": 1,
"createdAt": "2024-01-15T10:00:00Z",
"createdByUserId": 456,
"referencedLocationId": 789,
"comment": "Daily return",
"metadata": {},
"internalLocationNumber": ["LOC_001"]
},
"entries": [
{
"id": 1,
"submissionId": 123,
"cargoCarrierId": 1,
"delta": 10,
"createdAt": "2024-01-15T10:00:00Z"
}
]
}
}List submissions (cursor paginated)
GET /v1/cargocarrier/submission/list?token=&pageSize=100&ascending=true
Query params:
token— pass empty on the first call; use the response’snextTokenthereafter.pageSize— default 100, max 1000.idGt— optional explicit cursor (ID greater-than); useful for syncing between systems.ascending— direction. You must use the same value for every call in a paged sequence.
Response (SubmissionPaginatedResponse):
{
"nextToken": "...",
"result": [ { "head": { "...": "..." }, "entries": [] } ]
}The list response array is result, not submissions. The submissions[] field name only appears in the mobile get-batch-stop-submissions response.
Two parallel filtered listings share the same paginated shape:
GET /v1/cargocarrier/submission/list-by-location-id/{locationId}?token=&pageSize=100&ascending=trueGET /v1/cargocarrier/submission/list-by-user-id/{userId}?token=&pageSize=100&ascending=true
Deposits
Deposits move through a three-stage lifecycle, tracked per submission entry:
- Not fetched — created by a submission, not yet retrieved by your system.
- Fetched — returned by a
depositscall; stamped withfetchedAt. - Received — you confirmed settlement via
deposits/confirm; stamped withreceivedAt.
Fetch deposits
POST /v1/cargocarrier/deposits
All three fields are optional filters:
{
"internalLocationNumbers": ["LOC-warehouse-1"],
"internalProductNumbers": ["PROD-PALLET-EUR"],
"status": "DEPOSIT_FETCH_STATUS_NOT_FETCHED"
}status (DepositFetchStatus) selects which lifecycle state to return:
DEPOSIT_FETCH_STATUS_NOT_FETCHED— the default (and whatDEPOSIT_FETCH_STATUS_UNSPECIFIEDor omitting the field resolves to)DEPOSIT_FETCH_STATUS_FETCHED— already fetched but not yet confirmed receivedDEPOSIT_FETCH_STATUS_RECEIVED— fully settled
Fetching with the default status mutates state. A NOT_FETCHED call stamps the returned deposits’ fetchedAt — they won’t appear in subsequent default calls. FETCHED and RECEIVED queries are pure reads. This makes the default behave like a queue: each call hands you the new deposits exactly once.
Response: matching deposits[] records with submissionId, submissionEntryId, productId, productName, value, location fields, createdAt, and the lifecycle timestamps fetchedAt / receivedAt (null until reached).
Confirm deposits as received
POST /v1/cargocarrier/deposits/confirm
After your accounting system has settled fetched deposits, confirm them by their submissionEntryId:
{
"submissionEntryIds": [1001, 1002, 1003]
}Response:
{
"confirmedSubmissionEntryIds": [1001, 1003]
}The response lists only the entries that actually transitioned to received in this call — IDs that were already received, not yet fetched, or not part of your organization are silently omitted. Compare the response against your request to detect entries that didn’t transition.
Mobile app integration
The /mobile-app/ endpoints are intended for driver-app integrations rather than back-office work:
GET /v1/mobile-app/cargocarrier/configuration— mobile-specific config view.POST /v1/mobile-app/cargocarrier/submission/create-batch-stop-submission— submit movements across a batch of stops (uses the sameadd/subtractentry shape, plus astopIds[]array).POST /v1/mobile-app/cargocarrier/submission/get-batch-stop-submissions— read submissions made for a batch of stops (response field issubmissions[]).
You won’t typically need these unless you’re building or extending a driver-app surface.
Error Handling
The API uses standard gRPC status codes wrapped in HTTP responses:
{
"code": 5,
"message": "NOT_FOUND: Cargo carrier not found",
"details": []
}Common error scenarios:
- Invalid location or carrier IDs
- Insufficient permissions
- Invalid configuration values
- Pagination token errors
Common Gotchas
accountingEnabledcontrols whether any of this matters. Check it once at startup; if false, skip the cargo-carrier integration entirely.- Write entries use
add/subtract; read entries use a signeddelta. Don’t senddeltaon a create request. negativeBalanceAllowed: falsewill reject submissions that would push the balance negative. Handle that gracefully in your UI.depositsandsubmissionsare different things. Deposits are monetary records; submissions are quantity changes.- Fetching deposits with the default status marks them as fetched. If you only want to look, query with
status: "DEPOSIT_FETCH_STATUS_FETCHED"(orRECEIVED) — those are pure reads. deposits/confirmdoesn’t error on bad IDs. Entries that can’t transition are silently dropped from the response — diff request vs response to catch them.ascendingmust be consistent. When paging throughsubmission/list*, switchingascendingmid-stream gives incorrect results.- Enum values are prefixed. Use
DepositAction_PICKUPandDEPOSIT_FETCH_STATUS_FETCHED— not barePICKUP/FETCHED.
Best Practices
- Use pagination: Always paginate when listing submissions.
- Filter efficiently: Use location and product filters to reduce response sizes.
- Close the deposit loop: Fetch deposits, settle them in your accounting system, then
deposits/confirm— unconfirmed entries stay queryable asFETCHED. - Configuration caching: Cache organization configuration to reduce API calls.
- Error handling: Implement retry logic for transient failures.
Related Documentation
- Authentication - API authentication details