Introducing icarry-sdk: A Type-Safe TypeScript SDK for iCarry Shipping & LogisticsA type-safe TypeScript SDK for building shipping and logistics integrations with iCarry

F
Fatima Al-Zahraa Hammoud

Integrating shipping APIs can quickly become complicated. icarry-sdk provides a clean, type-safe TypeScript interface for iCarry, covering merchant, marketplace, and on-demand shipping, rate estimation, tracking, cancellation, and payments.

Shipping and logistics integrations often start with what looks like a simple requirement: calculate a delivery rate, create a shipment, and track it until it reaches the customer.

In practice, a production integration quickly becomes more complex.

Authentication, different shipping models, request formatting, rate estimation, shipment creation, tracking, cancellations, payments, network failures, retries, timeouts, sensitive credentials, and provider-specific API behavior all introduce integration logic that developers need to manage.

That is the problem icarry-sdk was created to solve.

icarry-sdk is an unofficial, open-source TypeScript SDK designed to provide a cleaner developer experience for the iCarry shipping and logistics API.

It provides a structured interface for merchant shipping, marketplace shipping, on-demand delivery, rate estimation, order and shipment creation, tracking, cancellation, packaging slips, warehouse operations, and payments. The SDK currently implements all 21 documented iCarry REST routes while remaining framework-agnostic and dependency-light.


Why Build a TypeScript SDK for the iCarry API?

Working directly with a shipping API usually means working with HTTP endpoints throughout different parts of an application.

A typical shipping workflow may look like this:

1. Authenticate with the shipping provider.
2. Collect pickup, destination, and package information.
3. Estimate available shipping rates.
4. Select a delivery option.
5. Create the order or shipment.
6. Track its progress.
7. Handle cancellation or payment when required.

The HTTP request itself is usually not the difficult part.
The complexity comes from everything surrounding it:

* Shipping workflows: merchant, marketplace, and on-demand delivery, including pickup and drop-off locations, package details, rate estimation, order creation, tracking, and cancellation.
* Integration concerns: authentication, token handling, request formatting, warehouse operations, payments, retries, timeouts, network failures, sensitive data, and provider-specific API inconsistencies.

When this logic is implemented directly inside controllers, services, or route handlers, an application can quickly become tightly coupled to the external shipping provider.

icarry-sdk creates a dedicated integration layer between application code and the iCarry API.

Instead of repeatedly dealing with low-level HTTP behavior, developers can work through a consistent TypeScript interface focused on shipping and logistics operations.


One SDK for Multiple Shipping Workflows

Shipping does not follow one universal model.

An online store shipping directly to customers has different requirements from a marketplace working with warehouses. An application requesting immediate delivery between two locations requires another workflow entirely.

icarry-sdk separates these concepts while keeping them inside the same client.

The SDK provides dedicated interfaces for:

* Merchant shipping
* Marketplace shipping
* On-demand shipping
* Shipments
* Warehouses
* Countries and states
* Payments

This keeps different shipping models easier to understand without requiring separate integration infrastructure for each one.


Merchant Shipping

For traditional e-commerce and merchant applications, icarry-sdk supports the core merchant shipping workflow.

Developers can estimate shipping rates using information such as:

* destination
* coordinates
* package type
* weight
* dimensions
* parcel information
* delivery address
* cash-on-delivery amount and currency

The SDK also provides merchant order creation once the appropriate shipping option has been selected.

A simplified flow looks like this:

```ts
const rates = await icarry.merchant.estimateRates({
dropOffLocation: "Beirut",
to: {
latitude: 33.8938,
longitude: 35.5018,
},
actualWeight: "1.5",
packageType: "parcel",
dimensions: {
length: "30",
width: "20",
height: "10",
},
dropAddress: {
countryCode: "LB",
city: "Beirut",
},
parcels: [
{
quantity: 1,
weight: "1.5",
length: "30",
width: "20",
height: "10",
},
],
cod: {
amount: "25.00",
currency: "USD",
},
});
```

Instead of manually rebuilding iCarry-specific payloads throughout the application, shipping logic can remain inside one dedicated integration layer.


Marketplace Shipping

Marketplace applications introduce another level of complexity because shipments may originate from different merchants, warehouses, or pickup locations.

icarry-sdk supports marketplace shipping using the same overall client while exposing operations specifically designed for the marketplace model.

Marketplace functionality includes:

* shipping rate estimation
* order creation
* pickup locations
* marketplace warehouse creation

The marketplace model extends the merchant workflow with a `pickupLocation`, allowing applications to represent the warehouse or pickup point associated with the shipment.

```ts
const rates = await icarry.marketplace.estimateRates({
pickupLocation: "Main Warehouse",
// remaining shipment information
});
```

Warehouse creation is also available through the SDK:

```ts
await icarry.warehouses.createMarketplaceWarehouse({
name: "Main Warehouse",
isActive: true,
address: {
firstName: "Sender",
lastName: "Name",
email: "sender@example.com",
country: "lebanon",
city: "beirut",
address1: "Pickup Address",
phoneNumber: "0100",
},
});
```

This is useful for multi-vendor marketplaces, fulfillment platforms, warehouse-based applications, and other systems where shipping does not always begin from a single location.


On-Demand Shipping and Delivery

On-demand shipping follows a different model from traditional merchant fulfillment.

Instead of simply shipping an order from a merchant location, an application may need to coordinate delivery between a specific pickup point and destination.

icarry-sdk provides a dedicated on-demand API that works with information such as:

* pickup location
* drop-off location
* country and state identifiers
* geographic coordinates
* package type
* weight
* dimensions
* delivery provider
* shipment information

Developers can estimate on-demand rates and create shipments using the same SDK.

```ts
const rates = await icarry.onDemand.estimateRates({
pickup: {
countryId: 234,
stateProvinceId: 1841,
geo: {
latitude: 25.2,
longitude: 55.3,
},
},
drop: {
countryId: 234,
stateProvinceId: 1841,
geo: {
latitude: 25.1,
longitude: 55.2,
},
},
actualWeight: 5,
packageType: "documents",
dimensions: {
length: 5,
width: 5,
height: 5,
unit: "cm",
},
isVendor: false,
});
```

This makes the SDK useful beyond standard e-commerce fulfillment and suitable for applications that need more flexible logistics workflows.


Shipping Rate Estimation

Shipping rates are often required before an order or shipment can be created.

An e-commerce checkout may need to display delivery options to a customer. A marketplace may need to calculate rates from a particular warehouse. An on-demand platform may need to calculate the cost between two geographic locations.

icarry-sdk supports rate estimation across:

* merchant shipping
* marketplace shipping
* on-demand shipping

Each workflow has its own input structure while sharing the same SDK infrastructure.

This allows shipping rate logic to remain separated from checkout, order management, and other business logic.


Shipment Tracking

Shipping integration does not end when a shipment is created.

Customers, merchants, administrators, and support teams often need to follow the shipment through the rest of its lifecycle.

icarry-sdk exposes shipment tracking through:

```ts
const tracking = await icarry.shipments.track(
"TRACKING_NUMBER"
);
```

The SDK therefore allows applications to build features such as customer tracking pages, merchant dashboards, administrative monitoring, and delivery status interfaces without creating a separate tracking integration.

There is also an important TypeScript detail here.

The exact tracking response structure is not documented reliably by iCarry. Instead of inventing a response interface and presenting it as guaranteed, icarry-sdk returns an `AmbiguousApiResult`.

Developers narrow the result before accessing fields:

```ts
const result = await icarry.shipments.track(
"TRACKING_NUMBER"
);

if (
typeof result === "object" &&
result !== null &&
!Array.isArray(result)
) {
// Safely work with object fields here
}
```

This is intentional: the SDK provides strict types where the API contract is known without pretending that undocumented response structures are guaranteed.


Shipment Cancellation

Real-world shipping workflows also need to handle cancellation.

icarry-sdk provides cancellation through the shipment API:

```ts
await icarry.shipments.cancel(
"TRACKING_NUMBER"
);
```

There is an unusual detail in the underlying iCarry API: cancellation is implemented as a `GET` request even though it performs a state-changing operation.

icarry-sdk recognizes this behavior and treats cancellation as a mutating operation. It is therefore never automatically retried or cached, even though the underlying HTTP method is `GET`.

This is an example of why an SDK can provide value beyond simply wrapping URLs: it can understand the behavior of the underlying API rather than relying only on HTTP conventions.


Packaging Slips

icarry-sdk also supports retrieving shipment packaging slips.

```ts
const slip =
await icarry.shipments.getPackagingSlip(shipmentId);
```

The underlying endpoint is called `Pdf`, but iCarry may return either binary PDF data or a JSON response.

The SDK handles both possibilities through a discriminated result:

```ts
if (slip.kind === "binary") {
// Binary document data
} else {
// JSON response
}
```

The SDK does not automatically write the document to the filesystem, leaving storage and presentation decisions to the application.


Type Safety Without Guessing API Responses

One of the main goals of icarry-sdk is providing a strong TypeScript developer experience.

Developers benefit from:

* Typed request structures
* Typed SDK methods
* Better editor autocomplete
* Clearer method signatures
* Easier refactoring
* Compile-time checking
* Reusable exported types

But type safety should not mean inventing information that the underlying API does not provide.

icarry-sdk therefore makes an important distinction between known API contracts and uncertain response contracts.

High-confidence endpoints such as authentication, countries, states, and warehouses return concrete typed objects.

Operations whose response structures are not sufficiently documented — including several rate, order, shipment, tracking, cancellation, and payment operations — return `AmbiguousApiResult`.

That means TypeScript will intentionally prevent code like this:

```ts
const result =
await icarry.shipments.track("TRACKING_NUMBER");

console.log(result.status);
```

until the developer first verifies what kind of value was returned.

This approach keeps the SDK's type system honest.

Known contracts are strongly typed. Undocumented contracts are not presented as guarantees.


Handling Real iCarry API Inconsistencies

Real-world APIs are not always perfectly consistent.

icarry-sdk is designed around the actual iCarry wire contract, including several differences that application developers would otherwise need to remember themselves.

For example, the same concepts can use different field names or casing depending on the endpoint.

The SDK exposes cleaner camelCase properties publicly while internally preserving exactly what iCarry expects on the wire.

Examples documented by the project include:

| SDK property | iCarry API field |
| --------------------------------------- | ---------------- |
| `address.country` | `County` |
| `cod.currency` for merchant/marketplace | `COdCurrency` |
| `cod.currency` for on-demand | `CODCurrency` |
| `methodId` | `MethodId` |
| `methodName` | `MethodName` |

It also handles unusual API behavior such as cancellation being performed through a mutating `GET` request.

Application code can therefore work with a cleaner API while the SDK handles provider-specific formatting internally.


Authentication Without Repeating the Same Logic

Authentication is another area that often becomes duplicated across direct API integrations.

icarry-sdk supports four authentication approaches:

1. Connector email and password
2. A pre-obtained bearer token
3. An asynchronous token provider
4. Manual token assignment after creating the client

A basic credentials-based configuration looks like:

```ts
import { ICarryClient } from "icarry-sdk";

const icarry = new ICarryClient({
baseUrl: process.env.ICARRY_BASE_URL!,
email: process.env.ICARRY_EMAIL!,
password: process.env.ICARRY_PASSWORD!,
});
```

When the SDK manages authentication, tokens are acquired lazily and cached in memory rather than persisted to disk.

Concurrent initial requests are also deduplicated so that they do not each trigger their own authentication request.

If a request return `401` and the SDK owns a refreshable authentication source, it can authenticate again once and retry the request. A caller-supplied static token is not silently replaced

This keeps authentication behavior inside the integration layer rather than forcing every application service to manage it independently.


Conservative Retry Behavior

Retries are useful for temporary API or network failures.

But automatically retrying every request can be dangerous.

A repeated shipment creation, warehouse creation, cancellation, or payment call could potentially perform an action more than once.

icarry-sdk therefore uses conservative retry behavior.

Read-only operations can be retried automatically when appropriate, including certain temporary network failures and HTTP responses such as `408`, `429`, and selected `5xx` errors.

Rate estimation is not retried unless the developer explicitly enables it for the call.

Mutating operations are not automatically retried, including:

* Order creation
* Shipment creation
* Payment operations
* MontyPay return operations
* Marketplace warehouse creation
* Shipment cancellation

Retries use bounded exponential backoff, jitter, and support for `Retry-After` when applicable.

This keeps retry behavior aligned with the operation being performed rather than blindly retrying every failed request.


Request Timeouts and Cancellation

Network requests should not be allowed to wait forever.

icarry-sdk applies a timeout to every request, with a configurable default.

Developers can also provide an `AbortSignal` when they need to cancel a request themselves.

```ts
const controller = new AbortController();

await icarry.countries.list({
signal: controller.signal,
timeoutMs: 5000,
});
```

Timeouts and caller-initiated cancellation are represented separately through dedicated SDK errors, allowing applications to respond appropriately to each case.


Structured Error Handling

Raw API integrations often end up with generic error handling where the application has to determine whether a request failed because of authentication, configuration, networking, a timeout, parsing, or the remote API itself.

icarry-sdk provides a structured error hierarchy.

Available error types include:

* `ICarryConfigurationError`
* `ICarryValidationError`
* `ICarryAuthenticationError`
* `ICarryApiError`
* `ICarryNetworkError`
* `ICarryTimeoutError`
* `ICarryAbortError`
* `ICarryResponseParseError`

All extend the base `ICarryError`.

This allows application code to handle different failures intentionally:

```ts
try {
await icarry.shipments.track("TRACKING_NUMBER");
} catch (error) {
if (error instanceof ICarryAuthenticationError) {
// Authentication failed
} else if (error instanceof ICarryTimeoutError) {
// Request timed out
} else if (error instanceof ICarryApiError) {
// iCarry returned an API error
}

throw error;
}
```

Structured errors make integration failures easier to understand than relying on arbitrary HTTP response objects throughout an application.


Secure by Default

Shipping integrations may process sensitive information such as:

* connector credentials
* bearer tokens
* customer details
* addresses
* shipment information
* payment information

icarry-sdk applies several security-focused defaults around this integration layer.

Known sensitive values such as passwords, bearer tokens, card numbers, and CVVs are sanitized from SDK error messages and observability boundaries.

Credentials and authentication tokens are kept in runtime-private fields, and authentication tokens are not persisted to disk by the SDK.

The SDK also validates its configured base URL. HTTPS is required outside local development environments, and unsafe URL configurations such as embedded credentials are rejected.

Applications should still follow standard security practices:

* keep iCarry credentials on the server
* use environment variables or a secret manager
* never commit credentials to source control
* never expose bearer tokens to frontend code
* validate application-controlled input
* avoid logging sensitive information
* use HTTPS in production

The SDK improves the security of its own integration boundaries, but it does not replace secure application architecture.


Important Server-Only Payment Behavior

Payment operations deserve additional attention.

The iCarry payment contract requires shipment payment card information to be sent through query-string parameters.

This behavior comes from the underlying iCarry API rather than being a design choice made by icarry-sdk.

Because query strings may be logged by infrastructure such as proxies, gateways, and servers, the SDK explicitly treats this payment operation as server-only.

icarry-sdk redacts known card parameters from its own errors and hooks, does not expose the serialized payment URL through those boundaries, and never automatically retries or caches payment operations. It also makes no claim of storing card information or providing PCI compliance.

Payment operations should therefore only be performed from a trusted backend environment.

```ts
await icarry.payments.createShipmentOrder(
shipmentId,
{
card: {
cardNumber: "XXXXXXXXXXXXXXXX",
cardCvv: "XXX",
cardType: "visa",
cardName: "CARDHOLDER",
cardExpirationMonth: "02",
cardExpirationYear: "2039",
},
paymentMethodSystemName: "Payments.MontyPay",
}
);
```

The project also exposes MontyPay return operations. Because iCarry does not document callback signature verification for those return routes, an application should not treat a return callback alone as proof of successful payment and should verify payment state server-side.


Zero Runtime Dependencies

icarry-sdk has zero runtime dependencies.

It uses platform capabilities such as `fetch` and `AbortController` instead of requiring an additional HTTP client at runtime.

Installation is simply:

```bash
npm install icarry-sdk
```

Keeping the runtime package small provides a simpler dependency footprint and avoids introducing another collection of third-party runtime packages purely for the shipping integration.

The project also allows applications to inject a custom `fetch` implementation when required.


ESM and CommonJS Support

JavaScript applications do not all use the same module system.

Modern projects increasingly use ECMAScript Modules (ESM), while many existing Node.js applications still use CommonJS.

icarry-sdk ships builds for both module systems together with TypeScript declarations.

This makes the SDK easier to integrate into different Node.js and TypeScript application architectures without forcing a project to change its module format just to use the package.


Framework-Agnostic by Design

icarry-sdk is not tied to a specific web framework.

The package is designed to work with server-side TypeScript and JavaScript applications using environments such as:

* Next.js server routes and Server Actions
* Express
* Fastify
* Hono
* other modern runtimes supporting `fetch`

The application remains responsible for its own routes, database operations, business rules, users, and frontend behavior.

icarry-sdk focuses specifically on the iCarry API integration layer.

This separation keeps shipping integration independent from the surrounding application framework.


More Than a Thin HTTP Wrapper

The purpose of icarry-sdk is not simply to replace:

```ts
fetch(...)
```

with another function.

The SDK provides infrastructure around the iCarry API, including:

* merchant shipping
* marketplace shipping
* on-demand shipping
* rate estimation
* order creation
* shipment creation
* shipment tracking
* cancellation
* packaging slips
* warehouse operations
* country and state resources
* payment operations
* authentication management
* token caching
* controlled reauthentication
* structured errors
* configurable timeouts
* request cancellation
* conservative retries
* sensitive-data redaction
* TypeScript models
* observability hooks
* a low-level request escape hatch

All 21 REST routes documented by iCarry are currently represented by the SDK. The plugin sections in iCarry documentation for platforms such as Shopify, WooCommerce, Magento, OpenCart, and MANSATI are documentation rather than callable REST endpoints, so they are intentionally outside that REST route implementation.

The result is an integration that allows developers to think in terms of shipping operations instead of repeatedly rebuilding the infrastructure surrounding individual endpoints.


A Cleaner Shipping Architecture

Without a dedicated SDK, provider-specific code can easily become scattered across an application:

```text
Checkout Service → iCarry API
Order Service → iCarry API
Tracking Route → iCarry API
Payment Service → iCarry API
Admin Tools → iCarry API
```

Each part of the application may then need its own authentication handling, error logic, request formatting, timeout handling, and knowledge of iCarry-specific behavior.

With a dedicated integration layer, the architecture becomes easier to separate:

```text
Application
↓
Business Logic
↓
icarry-sdk
↓
iCarry Shipping & Logistics API
```

The application communicates through the SDK while provider-specific behavior stays concentrated in one integration layer.

This makes the surrounding business code easier to understand and reduces the need to duplicate shipping-specific HTTP logic throughout the project.


Who Is icarry-sdk For?

icarry-sdk is designed for developers building applications that need to communicate with the iCarry shipping API.

Potential use cases include:

* e-commerce platforms
* online marketplaces
* delivery management systems
* order management systems
* warehouse and fulfillment platforms
* courier applications
* logistics software
* custom business applications
* backend APIs that require shipping functionality

It is particularly useful for TypeScript and Node.js developers who want a reusable integration layer rather than implementing the same authentication, request, error, tracking, and shipping behavior repeatedly.


Open Source

icarry-sdk is an open-source project.

Developers can inspect its implementation, review how requests are transformed, report issues, improve documentation, test additional iCarry behavior, and contribute changes as the integration evolves.

The project is available on GitHub:

[github.com/Mohammad-AlBaker-Zaytoun/icarry-sdk](https://github.com/Mohammad-AlBaker-Zaytoun/icarry-sdk)

Install the package with:

```bash
npm install icarry-sdk
```


Building Better iCarry Integrations with TypeScript

The main idea behind icarry-sdk is simple:

application developers should be able to focus on their shipping workflow instead of repeatedly rebuilding the infrastructure required to communicate with the iCarry API.

A typical integration becomes easier to reason about:

```text
Authenticate
↓
Estimate
↓
Create
↓
Track
↓
Manage
```

Behind that workflow, the SDK handles much of the integration-specific work: API formatting, authentication, errors, timeouts, safe retry decisions, known iCarry inconsistencies, and TypeScript contracts.

For undocumented response behavior, the SDK remains intentionally conservative instead of pretending uncertain API contracts are known.


Final Thoughts

External API integrations often begin with a few HTTP requests and gradually become important infrastructure inside an application.

Shipping is no different.

As an application grows, developers need to handle multiple delivery models, rate estimation, orders, shipments, warehouses, tracking, cancellations, payments, authentication, failures, security, and provider-specific API behavior.

icarry-sdk provides a dedicated TypeScript layer for that responsibility.

It brings together merchant, marketplace, and on-demand shipping, along with rate estimation, shipment tracking, cancellation, packaging slips, warehouse operations, and payments, while providing TypeScript support, zero runtime dependencies, ESM and CommonJS builds, structured error handling, controlled retries, timeout support, and security-focused defaults.

The goal is not to hide the shipping workflow.

The goal is to make the integration behind it cleaner, safer, more predictable, and easier to maintain.

If you are building a TypeScript application that needs to integrate with iCarry, you can explore the project, review the documentation, and contribute to its development:

[github.com/Mohammad-AlBaker-Zaytoun/icarry-sdk](https://github.com/Mohammad-AlBaker-Zaytoun/icarry-sdk)

> Note: icarry-sdk is an unofficial, community-created SDK. It is not affiliated with, endorsed by, or maintained by iCarry. The iCarry API documentation contains known inconsistencies and incomplete response examples, so applications should verify the endpoint behavior they depend on against their own iCarry environment before relying on it in production.

Our Services

  • PC & Laptop Sales and Repairs
  • IT Infrastructure & Support
  • Custom Software Development
  • AI & Machine Learning Services
  • Cybersecurity Solutions
  • UI/UX Design Services

Location

Beirut, Lebanon