Skip to content

Repository files navigation

@m8t-jacob/inpost-shipx

CI npm version npm downloads bundle size license: MIT

A fully typed TypeScript/Node client for the InPost ShipX API: create and track parcel-locker and courier shipments, search Paczkomat/PaczkoPunkt points, and download shipping labels.

  • Strict TypeScript, ships dual ESM + CJS builds with .d.ts
  • Zero runtime dependencies — uses the global fetch (Node.js 20+), or an injected implementation via ShipXClientConfig.fetch
  • Tree-shakeable; import the whole package or a single subpath (/shipments, /points, /tracking, /labels)
  • Bearer token always sent in the Authorization header, never in the URL, and never logged
  • Every request has a configurable timeout; every failure mode (network error, timeout, non-2xx status, malformed JSON) is normalized to a single ShipXError type, with status/details populated from the ShipX API's own { status, error, message, details } error body when available
  • Zero real network calls in tests — the test suite mocks fetch, so CI runs deterministically offline

Install

npm install @m8t-jacob/inpost-shipx

Quickstart

import { ShipXClient } from '@m8t-jacob/inpost-shipx';

const client = new ShipXClient({
  token: process.env.SHIPX_TOKEN!,
  organizationId: process.env.SHIPX_ORGANIZATION_ID, // required for createShipment/getShipments
  sandbox: true, // omit or set to false for production
});

// Search Paczkomat points (public endpoint, no valid token strictly required)
const points = await client.getPoints({ city: 'Warszawa', type: 'parcel_locker' });
console.log(points.count, points.items[0]?.name);

// Create a locker shipment
const shipment = await client.createShipment({
  receiver: { email: 'jan@example.com', phone: '500600700' },
  parcels: [{ template: 'small' }],
  service: 'inpost_locker_standard',
  custom_attributes: { target_point: 'WAW198M' },
});

// Track it
const tracking = await client.trackParcel(shipment.tracking_number!);

// Download the label as a PDF buffer
const label = await client.getLabel(shipment.id, { format: 'pdf', type: 'a6' });
await fs.promises.writeFile('label.pdf', Buffer.from(label));

You can also import a subpath if you only need one module, which keeps bundlers from pulling in the others (note these export plain functions taking a ShipXRequestContext, not a pre-bound client — see Advanced: calling module functions directly):

import { getPoints } from '@m8t-jacob/inpost-shipx/points';
import { trackParcel } from '@m8t-jacob/inpost-shipx/tracking';

API

ShipXClient

new ShipXClient({
  token: string;
  organizationId?: string | number; // required for createShipment/getShipments
  sandbox?: boolean;                // default: false (production)
  timeoutMs?: number;               // default: 15000
  fetch?: typeof fetch;             // default: global fetch
})
Method Description
createShipment(payload): Promise<Shipment> Creates a shipment under organizationId. Requires organizationId.
getShipment(id): Promise<Shipment> Fetches a single shipment by id. Not organization-scoped.
getShipments(params?): Promise<PaginatedResult<Shipment>> Lists shipments under organizationId, filterable by status/tracking_number/reference.
getPoints(params?): Promise<PaginatedResult<Point>> Searches Paczkomat/PaczkoPunkt points by type/city/postCode or geo radius.
getPoint(name): Promise<Point> Fetches a single point by name, e.g. 'WAW198M'.
trackParcel(trackingNumber): Promise<TrackingResult> Public parcel tracking.
getLabel(shipmentId, options?): Promise<ArrayBuffer> Downloads the shipping label as a binary buffer (PDF or ZPL).

Shipments (shipments)

interface CreateShipmentPayload {
  receiver: Receiver; // { first_name?, last_name?, company_name?, email?, phone?, address? }
  sender?: Sender;
  parcels: Parcel[]; // [{ template: 'small' }] or [{ dimensions: {...}, weight: {...} }]
  service: ShipmentService; // e.g. 'inpost_locker_standard', 'inpost_courier_standard'
  reference?: string;
  comments?: string;
  custom_attributes?: CustomAttributes; // e.g. { target_point: 'WAW198M' } for locker services
  additional_services?: string[];
  insurance?: { amount: number; currency?: string };
  cod?: { amount: number; currency?: string };
}

createShipment validates that receiver, at least one parcels entry, and service are present before making a network call, throwing ShipXError immediately otherwise. getShipments accepts { status?, tracking_number?, reference?, page?, per_page? } and returns a PaginatedResult<Shipment> (see Pagination below).

Points (points)

await client.getPoints({
  type: 'parcel_locker',
  city: 'Warszawa',
  postCode: '01-494',
  relativePoint: { latitude: 52.2297, longitude: 21.0122 },
  maxDistance: 1000, // meters
  page: 1,
  perPage: 25,
});

relativePoint + maxDistance map to the API's relative_point ("lat,lng") and max_distance query parameters for geo-radius search.

Tracking (tracking)

await client.trackParcel('681646828000000000000000');

Confirmed to work without an Authorization header against the production API (see Endpoints and versions confirmed below) — this client still sends the configured token for consistency.

Labels (labels)

const pdf = await client.getLabel(shipmentId); // defaults to { format: 'pdf' }
const zpl = await client.getLabel(shipmentId, { format: 'zpl', type: 'a6' });

Returns an ArrayBuffer — the response is never parsed as JSON, so a successful call always yields the raw label bytes regardless of format.

Pagination

List endpoints (getShipments, getPoints) return a { href, count, page, per_page, total_pages, items } response from the API. This package normalizes that to:

interface PaginatedResult<T> {
  items: T[];
  count: number;
  page: number;
  perPage: number;
  totalPages?: number;
  href?: string;
}

There is no built-in "fetch all pages" helper in this release — call getShipments/getPoints again with an incremented page/page param using totalPages as the stop condition. See GOOD_FIRST_ISSUES.md for a tracked helper idea.

Errors

Every operation throws ShipXError for invalid input (checked before any network call), a non-2xx response, a network error, a timeout, or malformed JSON:

export class ShipXError extends Error {
  readonly status?: number;   // HTTP status, when applicable
  readonly details?: unknown; // the API's `details` field, or the full error body as a fallback
}
try {
  await client.createShipment({ /* missing target_point */ });
} catch (error) {
  if (error instanceof ShipXError) {
    console.error(error.status, error.message, error.details);
  }
}

Advanced: calling module functions directly

Every ShipXClient method delegates to a plain function exported from its module (createShipment, getShipment, getShipments, getPoints, getPoint, trackParcel, getLabel), taking a ShipXRequestContext as the first argument instead of the ShipXClient class. This is mostly useful for testing or building your own thin wrapper; most users should just use ShipXClient.

Endpoints and versions confirmed

This package targets the ShipX API (v1) at:

  • Production: https://api-shipx-pl.easypack24.net/v1
  • Sandbox: https://sandbox-api-shipx-pl.easypack24.net/v1

The following were confirmed by hand (curl) against the production API on 2026-07-13:

Endpoint Auth required? Confirmed response shape
GET /points No (200 without a token) { href, count, page, per_page, total_pages, items: Point[] }
GET /points/{name} No (200 without a token) A single Point object
GET /tracking/{trackingNumber} No (404 without a token, not 401) { trackingNumber, message } for an unknown number; success shape not independently confirmed (see GOOD_FIRST_ISSUES.md)
GET /shipments/{id} Yes (401 token_invalid without a token) Not independently confirmed for a real shipment (requires a valid token)
GET /shipments/{id}/label Yes (401 token_invalid without a token) Not independently confirmed for a real shipment
POST /organizations/{organizationId}/shipments Yes (401 token_invalid without a token) Not independently confirmed for a real organization

The 401/404 error body shape confirmed above — {"status":401,"error":"token_invalid","message":"Token is missing or invalid.","details":{}} — is what ShipXError.status/.details are populated from. If InPost changes any of these endpoints, please open an issue.

🇵🇱 Po polsku

@m8t-jacob/inpost-shipx to w pełni typowany klient TS/Node dla API InPost ShipX: tworzenie i śledzenie przesyłek (paczkomatowych i kurierskich), wyszukiwanie punktów Paczkomat/PaczkoPunkt oraz pobieranie etykiet nadania. Pakiet nie ma żadnych zależności uruchomieniowych — korzysta z globalnego fetch (Node.js 20+). Token API jest zawsze wysyłany w nagłówku Authorization, nigdy w adresie URL i nigdy nie jest logowany. Każde żądanie ma konfigurowalny timeout, a każdy rodzaj błędu (błąd sieci, timeout, status inny niż 2xx, niepoprawny JSON) jest ujednolicony do jednego typu ShipXError z polami status/details wypełnionymi na podstawie własnego formatu błędów ShipX ({ status, error, message, details }). Testy nie wykonują żadnych realnych zapytań sieciowych (mockowany fetch), dzięki czemu CI działa deterministycznie offline.

Contributing

Contributions are welcome! See CONTRIBUTING.md for the development workflow and GOOD_FIRST_ISSUES.md for ideas if you're looking for a place to start. This project follows the Contributor Covenant.

License

MIT © 2026 Jakub Jagiełło

About

Typed TypeScript client for InPost ShipX: shipments, parcel lockers, tracking, labels. Zero dependencies.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages