Skip to Content
Welcome to Zendera Knowledge Hub
For DevelopersCargo Carrier Accounting

Cargo Carrier Accounting API

v1

Manage 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_HERE

Base 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 fetchedfetched (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 types
  • GET /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 carrier
  • POST /v1/cargocarrier/unlink-product - Unlink a product

Submission management

  • GET /v1/cargocarrier/submission/by-id/{id} - Get submission by ID
  • POST /v1/cargocarrier/submission/create-location-submission - Create a location submission
  • GET /v1/cargocarrier/submission/list - List submissions (cursor paginated)
  • GET /v1/cargocarrier/submission/list-by-location-id/{locationId} - List submissions by location
  • GET /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 IDs
  • POST /v1/cargocarrier/location/update-configurations - Update per-location configuration

Mobile app endpoints

  • GET /v1/mobile-app/cargocarrier/configuration - Mobile organization configuration
  • POST /v1/mobile-app/cargocarrier/submission/create-batch-stop-submission - Create batch stop submission
  • POST /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 }
FieldMeaning
accountingEnabledWhether cargo-carrier accounting is on for this org.
promptNormalDeliveriesWhether the driver app prompts for cargo-carrier counts on regular deliveries.
promptDepotDeliveriesSame, 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.

POST /v1/cargocarrier/link-product

{ "productId": 4242, "negativeBalanceAllowed": false, "depositInUse": true, "depositOnAction": "DepositAction_DELIVERY", "depositPreserveBalanceWhenFetched": true }
FieldNotes
productIdThe product to link.
negativeBalanceAllowedIf false, balance can’t go below zero — operations that would push it negative are rejected.
depositInUseWhether deposits apply for this carrier.
depositOnActionWhen deposits are charged — see Deposit Actions.
depositPreserveBalanceWhenFetchedWhen true, fetching deposits doesn’t zero the running balance.

Response: the created cargoCarrier record.

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 action
  • DepositAction_PICKUP - Deposit recorded at pickup
  • DepositAction_DELIVERY - Deposit recorded at delivery
  • DepositAction_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’s nextToken thereafter.
  • 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=true
  • GET /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:

  1. Not fetched — created by a submission, not yet retrieved by your system.
  2. Fetched — returned by a deposits call; stamped with fetchedAt.
  3. Received — you confirmed settlement via deposits/confirm; stamped with receivedAt.

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 what DEPOSIT_FETCH_STATUS_UNSPECIFIED or omitting the field resolves to)
  • DEPOSIT_FETCH_STATUS_FETCHED — already fetched but not yet confirmed received
  • DEPOSIT_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 same add/subtract entry shape, plus a stopIds[] array).
  • POST /v1/mobile-app/cargocarrier/submission/get-batch-stop-submissions — read submissions made for a batch of stops (response field is submissions[]).

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

  • accountingEnabled controls 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 signed delta. Don’t send delta on a create request.
  • negativeBalanceAllowed: false will reject submissions that would push the balance negative. Handle that gracefully in your UI.
  • deposits and submissions are 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" (or RECEIVED) — those are pure reads.
  • deposits/confirm doesn’t error on bad IDs. Entries that can’t transition are silently dropped from the response — diff request vs response to catch them.
  • ascending must be consistent. When paging through submission/list*, switching ascending mid-stream gives incorrect results.
  • Enum values are prefixed. Use DepositAction_PICKUP and DEPOSIT_FETCH_STATUS_FETCHED — not bare PICKUP/FETCHED.

Best Practices

  1. Use pagination: Always paginate when listing submissions.
  2. Filter efficiently: Use location and product filters to reduce response sizes.
  3. Close the deposit loop: Fetch deposits, settle them in your accounting system, then deposits/confirm — unconfirmed entries stay queryable as FETCHED.
  4. Configuration caching: Cache organization configuration to reduce API calls.
  5. Error handling: Implement retry logic for transient failures.
Last updated on