Open Source

UPS direct was harder than it should have been. So we open-sourced it.

ShipFair's fair-rate benchmark runs on a direct UPS REST API integration — bypassing the reseller and aggregator layer entirely. We built it for our own commerce operation (WellSpr.ing), published the design as ShipFair's benchmark engine, and now open-source the generalised client so any SMB, city, or developer can access real negotiated rates without pain.

The generalised UPS integration code is available in the WellSpr.ing Forgejo instance at git.wellspr.ing. It is published under the WellSpr.ing Covenant License (WCL-1.0): freely given, so freely given.

What the integration does

The UPSDirectClient is a Node.js/TypeScript class that wraps the UPS REST API v2205. It handles OAuth2 token acquisition and caching, rate shopping across all available service levels, dimensional weight calculation, and residential delivery classification. It returns structured rate results with per-service totals and itemised charges.

It does not use the Shopify Shipping API, ShipStation, EasyPost, Shippo, or any other aggregator layer. It talks directly to UPS. That's the point.

Prerequisites

  • A UPS account (free to create at ups.com)
  • UPS Developer credentials — create at developer.ups.com (free tier available)
  • Your UPS account number (found in account settings)
  • Node.js 18+ or Bun

Setup

# Set environment variables export UPS_CLIENT_ID=your_client_id export UPS_CLIENT_SECRET=your_client_secret export UPS_ACCOUNT_ID=your_account_number # Clone from WellSpr.ing Forgejo git clone https://git.wellspr.ing/WellBuilder/ups-direct-client cd ups-direct-client npm install

Basic usage

// TypeScript import { UPSDirectClient } from './ups-direct-client'; const ups = new UPSDirectClient({ clientId: process.env.UPS_CLIENT_ID, clientSecret: process.env.UPS_CLIENT_SECRET, accountId: process.env.UPS_ACCOUNT_ID, origin: { name: 'My Business', addressLine: '123 Main St', city: 'Seattle', state: 'WA', zip: '98101', country: 'US', } }); // Get rates for a ground package const rates = await ups.getRates({ originZip: '98101', destZip: '10001', weightLb: 3, dimsIn: { l: 12, w: 9, h: 6 }, destIsResidential: true, }); // Rates sorted by total cost ascending console.log(rates[0]); // { // serviceCode: '03', // serviceName: 'UPS Ground', // totalCharges: 14.82, // currency: 'USD', // guaranteedDays: 5, // charges: [...itemised] // }

How it works — key implementation

Two methods do the work. getToken() acquires and caches the OAuth bearer token; getRates() builds the rate-shop payload and returns structured results sorted cheapest-first.

// Step 1 — OAuth2 token acquisition (cached; one network round-trip per hour) const creds = Buffer .from(this.clientId + ":" + this.clientSecret) .toString("base64"); const tokenRes = await fetch( "https://onlinetools.ups.com/security/v1/oauth/token", { method: "POST", headers: { "Authorization": "Basic " + creds, "Content-Type": "application/x-www-form-urlencoded", "x-merchant-id": this.accountId, }, body: "grant_type=client_credentials", } ); const { access_token, expires_in } = await tokenRes.json(); // Token is cached in this._token; re-acquired 30 s before expiry. // Step 2 — Rate shop payload (RequestOption "Shop" returns all service levels) const payload = { RateRequest: { Request: { RequestOption: "Shop" }, Shipment: { Shipper: { ShipperNumber: this.accountId, // accountId triggers negotiated rates Address: { PostalCode: originZip, CountryCode: "US" }, }, ShipTo: { Address: { PostalCode: destZip, CountryCode: "US", ResidentialAddressIndicator: destIsResidential ? "" : undefined, }, }, Package: { PackagingType: { Code: "02" }, Dimensions: dimsIn ? { UnitOfMeasurement: { Code: "IN" }, Length: String(dimsIn.l), Width: String(dimsIn.w), Height: String(dimsIn.h) } : undefined, PackageWeight: { UnitOfMeasurement: { Code: "LBS" }, Weight: String(Math.max(0.1, weightLb)), }, }, }, }, }; const rateRes = await fetch( "https://onlinetools.ups.com/api/rating/v2205/shop", { method: "POST", headers: { "Authorization": "Bearer " + access_token, "Content-Type": "application/json", }, body: JSON.stringify(payload), } ); const data = await rateRes.json(); // data.RateResponse.RatedShipment[] — one entry per service level // Each entry: Service.Code, TotalCharges.MonetaryValue, ItemizedCharges[]

The full source with itemised charge parsing, DIM-weight handling, address-validation hooks, and multi-tenant instance management is in the repository. The above captures the OAuth handshake and rate-shop payload pattern.

Multi-tenant usage

Each UPSDirectClient instance is independent: it holds its own OAuth token cache and origin parameters. Construct separate instances for different merchant accounts. This is how ShipFair supports multi-tenant city covenant deployment.

Why carrier-direct

Aggregators like EasyPost and ShipStation add a margin on top of the rates they access on your behalf. The margin is disclosed in their pricing, but the net result is that the rate you see through an aggregator is higher than what you'd get through a direct negotiated account. ShipFair's benchmark uses our own direct account precisely because the direct rate is the honest reference — it reflects what the carrier actually charges when the information asymmetry is removed.

View on NotGit (Forgejo) → Try the rate calculator