balikobot_dart
A Dart client for the BalĂkobot shipping API v2: packages, labels, tracking,
pickups, and carrier capabilities. The client authenticates with HTTP Basic
credentials, validates every answer, and maps every failure to one sentinel
error. The package uses the http and json_rest_client libraries.
Install
dart pub add balikobot_dart
You need Dart 3.13 or later. Only a loopback test server can use the http
scheme. Every other base URL must use https.
Quick start
Create a client with your API user and API key. The client sends the credentials as HTTP Basic authentication. Then add a package, get a label URL, and read the tracking status.
import 'package:balikobot_dart/balikobot_dart.dart';
Future<void> main() async {
final client = BalikobotClient(
const Config(
user: 'api-user',
apiKey: 'api-key',
timeout: Duration(seconds: 15),
),
);
try {
final result = await client.addPackage(
Carrier.ppl,
const AddPackageRequest(
eid: 'order-2026-000123-S1',
serviceType: '1',
recName: 'Example Recipient',
recStreet: 'Example 1',
recCity: 'Praha',
recZip: '11000',
recCountry: Country.cz,
recPhone: '+420777000000',
weight: 1.5,
length: 30,
width: 20,
height: 10,
price: 1000,
codCurrency: Currency.czk,
),
);
final labelUrl = await client.labels(Carrier.ppl, result.packageId);
print(labelUrl);
final status = await client.trackStatus(Carrier.ppl, result.carrierId);
print(status.statusText);
} finally {
client.close();
}
}
The example/main.dart file holds the same code.
Methods
| Method | Endpoint | Purpose |
|---|---|---|
branches |
GET /{carrier}/branches/... |
Lists the branches of a service and country |
addPackage |
POST /{carrier}/add |
Creates one package. ADD is idempotent on eid |
overview |
GET /{carrier}/overview |
Lists the packages that ORDER has not closed |
labels |
POST /{carrier}/labels |
Gets a fresh label URL for one package |
orderViewLabels |
GET /{carrier}/orderview/{order_id} |
Gets the label URL of a closed order |
downloadLabel |
GET the label URL |
Downloads the label body |
trackStatus |
POST /{carrier}/trackstatus |
Reads the tracking status of one package |
orderBatch |
POST /{carrier}/order |
Hands one package to the carrier batch |
dropPackage |
POST /{carrier}/drop |
Removes one package before ORDER |
orderPickup |
POST /{carrier}/orderpickup |
Books one physical collection |
whoAmI |
GET /info/whoami |
Reads the account and carrier data |
activatedServices |
GET /{carrier}/activatedservices |
Lists the activated services |
countries |
GET /{carrier}/countries4service |
Lists the destination countries |
cod |
GET /{carrier}/cod4services |
Lists the cash-on-delivery destinations |
carrierCapabilities |
GET the discovery endpoints |
Discovers the contracted carriers and services |
resolveBranchId |
none | Chooses the branch id or the branch zip for an ADD request |
Codes
Carrier, currency, and country values are typed, not plain strings.
| Type | Format | Common constants |
|---|---|---|
Carrier |
^[a-z0-9]{2,32}$ |
ppl, dpd, dpdcz, dpdsk, geis, gls, intime, cp, ceskaposta, balikovna, zasilkovna, sp, ulozenka |
Currency |
ISO 4217 ^[A-Z]{3}$ |
czk, eur, usd, gbp, pln, huf, ron, bgn, hrk, chf, nok, sek, dkk |
Country |
ISO 3166-1 alpha-2 ^[A-Z]{2}$ |
EU member states plus gb, ch, no, isIceland, li, ua, rs, ba, me, mk, al, tr, us, ca |
Every type has a fromString function and an isValid getter. fromString
trims the value, normalizes the case, and accepts any well-formed code. A
malformed value throws a FormatException. Use fromString for a value that
has no constant, so custom carriers, currencies, and countries work:
final custom = Carrier.fromString('MyCarrier99');
final result = await client.addPackage(custom, request);
The types keep the wire values compile-time safe: a function that expects a
Carrier rejects a bare string, and a mistyped constant fails the build. The
JSON form stays a plain string, so the wire contract does not change.
ADD accepts only Currency.czk and Currency.eur as codCurrency, because
the carriers require one of those two values. The client rejects every other
well-formed currency code before the request.
Errors
The client reports every failure with a BalikobotException. The code field
holds one of six BalikobotError values.
| Error | Meaning | Action |
|---|---|---|
BalikobotError.invalidRequest |
The arguments are not valid. The client sent no request. | Correct the input. Do not retry. |
BalikobotError.rejected |
The provider refused the data permanently. | Correct the data. Do not retry. |
BalikobotError.unavailable |
The provider is unavailable, or the request never left the client. | Retry later. |
BalikobotError.notFound |
The carrier has no tracking data yet. | Poll again later. |
BalikobotError.ambiguous |
A mutating call can have reached the provider. | Reconcile with overview. Then retry. |
BalikobotError.invalidResponse |
The answer violates the protocol. | Inspect the provider. Do not retry blindly. |
When a 429 response carries a Retry-After header, an unavailable failure
carries the retryAfter field on BalikobotException. Read the field and wait
before the next call:
try {
await client.orderBatch(Carrier.ppl, packageId);
} on BalikobotException catch (error) {
final retryAfter = error.retryAfter;
if (retryAfter != null) {
await Future<void>.delayed(retryAfter);
}
}
orderPickup maps HTTP 429 to rejected. The branch, label download, and
capability calls return unavailable without a hint.
The client sends no automatic retry. You control the retry policy.
Response limits
The client reads every JSON body with a hard limit of 8 MiB. Set
Config.maxResponseBytes to change the limit. Label downloads use a fixed
limit of 4 MiB. The client refuses redirects. It compares the response
Content-Type with the expected media type before it decodes the body.
Account mode
Set Config.liveAccount to true or false to verify the account before each
non-GET call. The client calls WHOAMI and compares the live_account flag. A
mismatch blocks the write before the client sends it. A successful result stays
valid for five minutes. If Config.liveAccount is null, the client skips this
check.
Label hosts
The client accepts label URLs only from pdf.balikobot.cz and the
*.balikobot.cz subdomains. It also accepts the base URL origin of a loopback
test server. Set Config.labelHosts to replace the
default allowlist with other hosts. A leading dot selects a subdomain suffix
match; it does not match the bare domain.
Development
Run the checks from the package root:
dart analyze --fatal-infos
dart test
The tests use mocked HTTP clients. They use no real credentials and no external network.
License
MIT. See LICENSE.
Libraries
- balikobot_dart
- A Dart client for the Balikobot shipping API v2.