Russian Post delivery service integration
Russian Post is the country's widest delivery network with offices even in remote areas. API (pochta.ru) allows calculating cost, creating orders, requesting courier, and tracking packages. REST + JSON with token authorization.
Authorization
Russian Post uses tokens from personal account. For some methods — basic auth (login + password in base64), for others — AccessToken header:
class RussianPostClient
{
private string $baseUrl = 'https://otpravka-api.pochta.ru/1.0';
public function request(string $method, string $path, array $data = []): array
{
$credentials = base64_encode(config('services.russian_post.login') . ':' . config('services.russian_post.password'));
$response = Http::withHeaders([
'Authorization' => 'AccessToken ' . config('services.russian_post.token'),
'X-User-Authorization' => 'Basic ' . $credentials,
'Content-Type' => 'application/json;charset=UTF-8',
])->{strtolower($method)}($this->baseUrl . $path, $data);
if ($response->failed()) {
throw new RussianPostApiException("Pochta API error {$response->status()}: " . $response->body());
}
return $response->json() ?? [];
}
}
Address normalization
Before creating order, address must be normalized:
public function normalizeAddress(string $rawAddress): array
{
$response = Http::post($this->baseUrl . '/clean/address', [
['id' => '1', 'original-address' => $rawAddress]
]);
$result = $response->json('0');
if ($result['quality-code'] === 'UNDEF_05') {
throw new \InvalidArgumentException('Address not found');
}
return [
'index' => $result['index'],
'city' => $result['place'],
'street' => $result['street'],
];
}
Rate calculation
public function calculateDelivery(
string $fromIndex,
string $toIndex,
string $mailType,
int $weightGrams,
int $declaredValueKopecks = 0
): array {
$response = $this->request('POST', '/tariff', [
'index-from' => $fromIndex,
'index-to' => $toIndex,
'mail-category' => 'ORDINARY',
'mail-type' => $mailType,
'mass' => $weightGrams,
'payment' => $declaredValueKopecks,
]);
return [
'total_kopecks' => $response['total-rate'] + ($response['total-vat'] ?? 0),
'delivery_days_min' => $response['delivery-time']['min-days'] ?? null,
'delivery_days_max' => $response['delivery-time']['max-days'] ?? null,
];
}
Mail type: POSTAL_PARCEL (standard), ECOM_MARKETPLACE (marketplaces), EMS (express).
Order creation and batch processing
public function createOrder(Order $order): array
{
$payload = [[
'order-num' => (string)$order->id,
'index-to' => $order->normalized_index,
'place-to' => $order->normalized_city,
'street-to' => $order->normalized_street,
'mail-type' => 'POSTAL_PARCEL',
'mass' => (int)($order->total_weight_kg * 1000),
'recipient-name' => $order->recipient_name,
'tel-address' => preg_replace('/\D/', '', $order->recipient_phone),
]];
$response = $this->request('PUT', '/user/backlog', $payload);
return [
'result_id' => $response[0]['result-id'],
'barcode' => $response[0]['barcode'] ?? null,
];
}
public function createBatch(string $batchName, array $orderIds): void
{
$this->request('POST', "/batch/{$batchName}/backlog", $orderIds);
}
Tracking
Russian Post provides separate tracking API:
public function trackParcel(string $barcode): array
{
$response = Http::get('https://tracking.pochta.ru/tracking/api/v1/operations-history', [
'Barcode' => $barcode,
'Language' => 'RUS',
]);
return collect($response->json('OperationHistoryData.historyRecord'))
->map(fn($op) => [
'date' => $op['OperationParameters']['OperDate'],
'type' => $op['OperationParameters']['OperType']['Name'],
'city' => $op['AddressParameters']['DestinationAddress']['PostalAddress']['City'] ?? '',
])->toArray();
}
Batch label printing
public function printLabels(array $orderIds, string $sendingType = 'ONE_SIDED'): string
{
$response = Http::post($this->baseUrl . '/forms/orders/backlog?' . http_build_query([
'print-type' => $sendingType,
]), $orderIds);
return $response->body();
}
Features
Max weight 20 kg, max volume 2 m³. Sandbox: same URL with test credentials. Real parcels not created, but barcodes generated.
Timeline
Tariff calculation + address normalization + order creation + labels — 6–8 working days. More complex than CDEK due to two-step model and separate tracking API.







