1C-Bitrix Integration with PEK Delivery Service
PEK (Pervaya Ekspeditsionnaya Kompaniya / First Expeditionary Company) is one of the key transport companies for cargo and consolidated freight delivery in Russia. The main integration specifics: PEK works with cargo from 1 kg and above, the API is tailored for cargo space parameters (length, width, height, and weight of each unit separately) rather than the standard retail format of 1C-Bitrix. If your online store sells oversized goods — sofas, building materials, industrial equipment — PEK often becomes the primary carrier.
PEK API: Specifics
PEK provides a REST API with Bearer token authorization. The token is obtained via POST /v2/sign-in with the login and password from your personal account. The token has no hard TTL (in practice it lives 24–48 hours), but it is recommended to cache it and refresh upon receiving a 401 response.
Key endpoints:
-
POST /v2/calculator— cost and delivery time calculation -
POST /v2/orders— order creation -
GET /v2/orders/{id}— order status -
GET /v2/departments— list of PEK terminals
Base URL: https://api.pek.ru. Documentation is available in the partner personal account.
Cost Calculation
class PekDeliveryHandler extends \Bitrix\Sale\Delivery\Services\Base
{
protected function calculateConcrete(
\Bitrix\Sale\Shipment $shipment
): \Bitrix\Sale\Delivery\CalculationResult {
$result = new \Bitrix\Sale\Delivery\CalculationResult();
$token = $this->getApiToken();
$payload = $this->buildCalcPayload($shipment);
$response = $this->apiPost('/v2/calculator', $payload, $token);
if (empty($response['price'])) {
$result->addError(new \Bitrix\Main\Error('Calculation unavailable'));
return $result;
}
$result->setDeliveryPrice((float)$response['price']);
$result->setPeriodDescription($response['period_min'] . '–' . $response['period_max'] . ' days');
return $result;
}
private function buildCalcPayload(\Bitrix\Sale\Shipment $shipment): array
{
$order = $shipment->getOrder();
return [
'senderCityId' => (int)$this->getOption('SENDER_CITY_ID'),
'receiverCityId' => $this->getReceiverCityId($shipment),
'cargo' => $this->buildCargoPlaces($shipment),
'service' => $this->getOption('SERVICE_TYPE', 'door_door'),
'declaredValue' => round($order->getPrice(), 2),
];
}
private function buildCargoPlaces(\Bitrix\Sale\Shipment $shipment): array
{
// PEK requires individual parameters for each cargo unit
$weight = max($shipment->getWeight() / 1000, 1); // g → kg, minimum 1 kg
return [
[
'weight' => $weight,
'length' => (int)$this->getOption('DEFAULT_LENGTH', 50),
'width' => (int)$this->getOption('DEFAULT_WIDTH', 50),
'height' => (int)$this->getOption('DEFAULT_HEIGHT', 50),
],
];
}
}
Important: PEK calculates based on volumetric weight. If the actual weight is less than the volumetric weight (L×W×H / 5000 for air, / 4000 for road), the volumetric weight is used. For oversized goods this is critical — provide the actual dimensions of the items.
Obtaining cityId
PEK uses its own numeric city identifiers. City search:
public function findCityId(string $cityName): ?int
{
$response = $this->apiGet('/v2/city?name=' . urlencode($cityName), $this->getApiToken());
return $response[0]['id'] ?? null;
}
Alternative: export the PEK city directory and store the mapping city_name → pek_city_id in an infoblock or a custom table. This approach is recommended for initial setup — the city search API returns ambiguous results for localities with identical names.
Order Creation and Terminals
private function createPekOrder(\Bitrix\Sale\Shipment $shipment): string
{
$order = $shipment->getOrder();
$props = $order->getPropertyCollection();
$payload = [
'senderCityId' => (int)$this->getOption('SENDER_CITY_ID'),
'receiverCityId' => $this->getReceiverCityId($shipment),
'cargo' => $this->buildCargoPlaces($shipment),
'service' => $this->getOption('SERVICE_TYPE', 'door_door'),
'declaredValue' => round($order->getPrice(), 2),
'sender' => [
'company' => $this->getOption('SENDER_COMPANY'),
'contact' => $this->getOption('SENDER_CONTACT'),
'phone' => $this->getOption('SENDER_PHONE'),
'address' => $this->getOption('SENDER_ADDRESS'),
],
'receiver' => [
'contact' => $props->getItemByOrderPropertyCode('FIO')?->getValue(),
'phone' => $props->getItemByOrderPropertyCode('PHONE')?->getValue(),
'address' => $props->getItemByOrderPropertyCode('ADDRESS')?->getValue(),
],
];
$response = $this->apiPost('/v2/orders', $payload, $this->getApiToken());
return (string)($response['id'] ?? '');
}
For door_terminal or terminal_door service types, the PEK terminal identifier must be provided. Terminal list: GET /v2/departments. Filter by cityId. The website implements a terminal dropdown with a map — a widget or custom implementation using Leaflet/Yandex Maps.
Case Study: Building Materials Store
Client — a wholesale and retail building materials store, average order weight 50–200 kg, many sheet materials. Problem during implementation: 1C-Bitrix stores shipment weight as a single number, while PEK for orders with multiple items of different sizes requires a list of cargo units with individual dimensions. A splitting logic had to be implemented: each product in the infoblock has properties DELIVERY_LENGTH, DELIVERY_WIDTH, DELIVERY_HEIGHT. When forming a shipment, each unit of goods becomes a separate cargo unit.
This increased calculation accuracy (discrepancy with actual cost reduced from ±30% to ±5%) and eliminated surcharges upon cargo receipt.
Status Tracking
| PEK Status | Meaning |
|---|---|
accepted |
Accepted for shipment |
in_transit |
In transit |
arrived |
Arrived at destination terminal |
out_for_delivery |
Handed to courier |
delivered |
Delivered |
returned |
Returned |
PEK has no webhooks — polling only. A 1C-Bitrix agent runs every 4 hours, querying statuses of active shipments via GET /v2/orders/{id} and updating the order status in the store.
Timelines
| Scope | Timeline |
|---|---|
| Cost calculation + order creation | 4–5 days |
| + Terminal list + map | +2–3 days |
| + Per-item cargo unit splitting | +2 days |
| + Status polling + notifications | +2 days |







