sashalenz/nova-poshta-api

Nova Poshta API SDK for Laravel

Maintainers

Package info

github.com/sashalenz/nova-poshta-api

pkg:composer/sashalenz/nova-poshta-api

Statistics

Installs: 410

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

3.4.0 2026-05-07 14:37 UTC

This package is auto-updated.

Last update: 2026-05-07 14:38:24 UTC


README

Latest Version on Packagist Total Downloads

A Laravel SDK for the Nova Poshta JSON API. Wraps every public NP method behind a fluent, fully-typed builder backed by spatie/laravel-data request and response objects, with built-in caching, retries and a typed exception hierarchy.

Requirements

Installation

composer require sashalenz/nova-poshta-api

The service provider registers itself via Laravel's package discovery — no manual wiring needed.

Publish the config file (optional, only needed if you want to override the API URL or pin a default API key in source):

php artisan vendor:publish --tag="nova-poshta-api-config"

Configuration

NOVA_POSHTA_API_KEY=your-default-api-key
NOVA_POSHTA_API_URL=https://api.novaposhta.ua/v2.0/json/   # optional, this is the default

The default API key is used whenever you call a model with ::make() and no argument. Pass an explicit key (::make($apiKey)) when you need to talk to a specific counterparty's account — useful in multi-sender systems where every sender has their own NP cabinet.

use Sashalenz\NovaPoshtaApi\ApiModels\Address;

// Uses NOVA_POSHTA_API_KEY from env
$cities = Address::make()->getCities($request);

// Uses a different counterparty's key for this call only
$senderInvoices = InternetDocument::make($sender->np_api_key)->getDocumentList($request);

Quick start

use Sashalenz\NovaPoshtaApi\ApiModels\Address;
use Sashalenz\NovaPoshtaApi\ApiModels\Address\RequestData\GetCitiesRequest;

$cities = Address::make()
    ->cache(60 * 60) // optional: cache the response for 1 hour
    ->getCities(GetCitiesRequest::from([
        'limit' => 20,
        'findByString' => 'Київ',
    ]));

foreach ($cities as $city) {
    echo "{$city->ref}  {$city->description}\n";
}

Every method returns either a single Spatie\LaravelData\Data object or a Spatie\LaravelData\DataCollection — see spatie/laravel-data for the available helpers (->toArray(), ->toCollection(), iteration, etc.).

API Models

The SDK is split into one class per NP API module. Each class is a fluent builder — call ::make($apiKey) then the method you want; arguments are typed *Request data objects.

Address — locations & address book

Lookup of cities, settlements, warehouses, streets, plus CRUD over an address book stored under your counterparty.

Method Returns Purpose
getCities(GetCitiesRequest) DataCollection<CityData> City lookup by name or ref
getSettlements(GetSettlementsRequest) DataCollection<SettlementData> Full settlement directory (cities + villages)
searchSettlements(SearchSettlementsRequest) SearchSettlementData Autocomplete-style settlement search
searchSettlementStreets(SearchSettlementStreetsRequest) SettlementStreetData Street search inside a settlement
getStreet(GetStreetRequest) DataCollection<StreetData> Street lookup
getAreas() DataCollection<AreaData> Oblast list
getWarehouses(GetWarehousesRequest) DataCollection<WarehouseData> NP branch list (with filters by city, type, etc.)
getWarehouseTypes() DataCollection<WarehouseTypeData> Branch types directory
save(AddressRequest) AddressData Create an address book entry
update(AddressRequest) AddressData Update one
delete(RefRequest) RefData Delete one
use Sashalenz\NovaPoshtaApi\ApiModels\Address;
use Sashalenz\NovaPoshtaApi\ApiModels\Address\RequestData\GetWarehousesRequest;

$warehouses = Address::make()
    ->cache(60 * 60 * 24)
    ->getWarehouses(GetWarehousesRequest::from([
        'cityRef' => $city->ref,
        'limit' => 500,
    ]));

Common — reference dictionaries

Read-only directories used when filling out shipment forms — cargo types, payer types, packaging, time intervals. Most are excellent candidates for long-lived caching since they barely change.

Method Purpose
getCargoTypes() Cargo type list (parcel, documents, cargo, pallets, tires)
getBackwardDeliveryCargoTypes() Cargo types valid for return-delivery
getPayersForRedelivery (via getTypesOfPayersForRedelivery()) Who pays the cash-on-delivery fee
getPalletsList() Standard pallet sizes
getPackList(GetPackListRequest?) Available packaging
getTiresWheelsList() Tire/wheel reference list
getOwnershipFormsList() Legal ownership forms (LLC, FOP…)
getCargoDescriptionList(GetCargoDescriptionListRequest?) Allowed free-text cargo descriptions
getTimeIntervals(GetTimeIntervalsRequest) Time slots for "Доставка у точно визначений час"
getMessageCodeText() NP API message code dictionary

Counterparty — senders, recipients, third parties

Manages counterparties (юр./фіз. особи) registered to your account — anyone you ship from or to.

Method Purpose
getCounterparties(GetCounterpartiesRequest) List your counterparties (Sender / Recipient / ThirdPerson)
getCatalogCounterparty(GetCatalogCounterpartyRequest) Search the public NP catalog by EDRPOU/name
getCounterpartyOptions(RefRequest) Allowed shipment options for a counterparty
getCounterpartyAddresses(GetCounterpartyAddressesRequest) Addresses bound to a counterparty
getCounterpartyContactPersons(RefRequest) Contact persons under a counterparty
save(SaveCounterpartyRequest) Create a counterparty
update(UpdateCounterpartyRequest) Update one
delete(RefRequest) Delete one

ContactPerson — counterparty contacts

CRUD for contact persons under a counterparty (e.g. a department lead inside a Sender LLC).

Method Purpose
save(SaveContactPersonRequest) Create
update(UpdateContactPersonRequest) Update
delete(RefRequest) Delete

InternetDocument — express waybills (TTN)

The bread and butter — create and manage shipments.

Method Purpose
getDocumentPrice(GetDocumentPriceRequest) Price estimate before creating a TTN
getDocumentDeliveryDate(GetDocumentDeliveryDateRequest) ETA estimate before creating a TTN
save(SaveInternetDocumentRequest) Issue a new TTN
update(UpdateInternetDocumentRequest) Update an existing TTN (only while it's not yet at NP's hands)
delete(DeleteInternetDocumentRequest) Cancel a TTN
getDocument(RefRequest) Fetch one document by ref
getDocumentList(GetDocumentListRequest) List your documents (by date range, status, etc.)
use Sashalenz\NovaPoshtaApi\ApiModels\InternetDocument;
use Sashalenz\NovaPoshtaApi\ApiModels\InternetDocument\RequestData\GetDocumentPriceRequest;
use Sashalenz\NovaPoshtaApi\Enums\CargoType;
use Sashalenz\NovaPoshtaApi\Enums\ServiceType;

$price = InternetDocument::make($sender->api_key)->getDocumentPrice(
    GetDocumentPriceRequest::from([
        'citySender' => $sender->city_ref,
        'cityRecipient' => $recipientCityRef,
        'weight' => 5.5,
        'serviceType' => ServiceType::WAREHOUSE_WAREHOUSE,
        'cargoType' => CargoType::CARGO,
        'cost' => 1000,
        'seatsAmount' => 1,
    ])
);

echo $price->cost; // UAH

ScanSheet — registries

Bundles TTNs into a daily registry for the courier hand-off.

Method Purpose
getScanSheetList() List your registries
getScanSheet(GetScanSheetRequest) Detail of a single registry
insertDocuments(InsertDocumentsRequest) Add TTNs to a registry
removeDocuments(RemoveDocumentsRequest) Remove TTNs from a registry
deleteScanSheet(DeleteScanSheetData) Delete an empty registry

TrackingDocument — status polling

use Sashalenz\NovaPoshtaApi\ApiModels\TrackingDocument\TrackingDocument;
use Sashalenz\NovaPoshtaApi\ApiModels\TrackingDocument\RequestData\GetStatusDocumentsRequest;
use Sashalenz\NovaPoshtaApi\ApiModels\TrackingDocument\RequestData\DocumentData;

$statuses = TrackingDocument::make()->getStatusDocuments(
    GetStatusDocumentsRequest::from([
        'documents' => [
            DocumentData::from(['documentNumber' => '20400000000001', 'phone' => '380501234567']),
            DocumentData::from(['documentNumber' => '20400000000002', 'phone' => '380501234567']),
        ],
    ])
);

foreach ($statuses as $status) {
    echo "{$status->number}: {$status->status} (statusCode={$status->statusCode})\n";
}

statusCode follows NP's published numeric status table — see NP docs for the full list.

AdditionalService — returns, redirects, EW changes

Operations on TTNs that already left your hands.

Method Purpose
checkPossibilityCreateReturn(CheckPossibilityCreateReturnRequest) Can this TTN be returned, and to where
checkPossibilityForRedirecting(...) Can this TTN be redirected
checkPossibilityChangeEW(CheckPossibilityChangeEWRequest) Can this TTN be edited
getReturnReasons() Pickable return reasons
getReturnReasonsSubtypes(GetReturnReasonsSubtypesRequest) Reason subtypes for a given reason
save(SaveAdditionalServiceRequest) Create a return / redirect / EW-change order
delete(RefRequest) Cancel one
getReturnOrdersList(GetOrdersListRequest?) Your existing return orders
getRedirectionOrdersList(GetOrdersListRequest?) Your existing redirect orders
getChangeEWOrdersList(GetOrdersListRequest?) Your existing change-EW orders

save() returns AdditionalServiceSaveData{ number, ref }number is the new return / redirect TTN, persist it to track the chain.

Caching

Any model can cache its response by chaining ->cache($seconds) before the method call. Use -1 (the default when no argument is passed) to cache forever.

// Cache cities for 24h — the directory rarely changes
Address::make()->cache(60 * 60 * 24)->getCities($request);

// Cache forever (until you flush manually)
Common::make()->cache()->getCargoTypes();

The cache key is composed of the model name, the called method and a base64-serialized hash of the request payload, so different inputs to the same method get different cache slots.

Enums

Enum Use
CargoType Cargo type for InternetDocument::save (CARGO, DOCUMENTS, PARCEL, TIRES_WHEELS, PALLET)
ServiceType Pickup/delivery model (WAREHOUSE_WAREHOUSE, WAREHOUSE_DOORS, DOORS_WAREHOUSE, DOORS_DOORS)
PayerType Who pays (SENDER, RECIPIENT, THIRD_PERSON)
PaymentMethod CASH or NON_CASH
OrderType AdditionalService order type — ORDER_CARGO_RETURN, ORDER_REDIRECTING, ORDER_CHANGE_EW
CounterpartyType SENDER, RECIPIENT, THIRD_PERSON
CounterpartyProperty PRIVATE_PERSON or ORGANIZATION

Marking — print URLs

Sashalenz\NovaPoshtaApi\Marking builds public NP cabinet URLs for label / scan-sheet PDFs. They're plain https://my.novaposhta.ua/... links you can hand to the browser or pipe into a PDF printer — no JSON API call involved.

use Sashalenz\NovaPoshtaApi\Marking;

// 85x85 label, HTML or pdf8
$labelUrl = Marking::printMarking('pdf8', ['20400000000001'], $apiKey);

// 100x100 zebra-printer label
$zebraUrl = Marking::printZebraMarking('pdf', ['20400000000001'], $apiKey);

// Scan sheet (registry) PDF
$sheetUrl = Marking::printScanSheet($scanSheetRef, $apiKey);

// Combined documents print
$docsUrl = Marking::printDocument(['ref-1', 'ref-2'], 'pdf', $apiKey);

printMarking() and printDocument() return null if you pass an unsupported $type — only 'html' and 'pdf'/'pdf8' are accepted.

Error handling

Every request goes through Request::make(), which raises one of two exception types — both extending NovaPoshtaException:

Exception When Typical use
NovaPoshtaApiUnavailableException NP itself is unreachable: cURL connection reset, DNS failure, timeout, or any 5xx response Treat as a transient infrastructure issue — retry later, render an empty/cached fallback, alert the on-call channel separately from code errors
NovaPoshtaException NP returned an HTTP 4xx, or a 200 response whose body contained an errors[] array / success: false Application-level error — the request itself is the problem (bad TTN, missing API key, malformed payload). Surface to the user / log and move on

Existing catch (NovaPoshtaException) sites pick up the unavailable subclass automatically, so legacy code keeps working. Catch the subclass first when you need to react differently to downtime:

use Sashalenz\NovaPoshtaApi\Exceptions\NovaPoshtaApiUnavailableException;
use Sashalenz\NovaPoshtaApi\Exceptions\NovaPoshtaException;

try {
    $cities = Address::make()->getCities($request);
} catch (NovaPoshtaApiUnavailableException $e) {
    // NP is down — fall back to cached results, suppress UI noise
    return $cachedCities;
} catch (NovaPoshtaException $e) {
    // Something is wrong with our request — log and surface to the user
    report($e);
    return collect();
}

For Bugsnag / Sentry, route NovaPoshtaApiUnavailableException to a lower severity (e.g. warning) so the dashboard isn't flooded during NP's evening flapping windows:

// bootstrap/app.php — Laravel 11+
->withExceptions(fn (Exceptions $exceptions) => $exceptions
    ->report(function (NovaPoshtaApiUnavailableException $e) {
        Bugsnag::notifyException($e, fn ($report) => $report->setSeverity('warning'));
        return false; // skip default reporting
    })
    ->dontReport([NovaPoshtaException::class])
)

The HTTP layer also auto-retries 3 times with a 100ms backoff and a 3s per-attempt timeout before giving up — so by the time an exception leaves the SDK, NP really wasn't reachable.

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.