flexops 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,60 @@
1
+ # ***********************************************************************
2
+ # Package : flexops
3
+ # Author : FlexOps, LLC
4
+ # Created : 2026-10-19
5
+ #
6
+ # Copyright (c) 2021-2026 by FlexOps, LLC. All rights reserved.
7
+ # ***********************************************************************
8
+
9
+ module FlexOps
10
+ module Resources
11
+ # Carbon offsets for shipments. Answers use the { success, data, message, errorCode } envelope.
12
+ class CarbonOffsets
13
+ def initialize(http, ws_id_proc)
14
+ @http = http
15
+ @ws_id = ws_id_proc
16
+ end
17
+
18
+ def quote(weight_lbs:, distance_miles:)
19
+ @http.post("#{ws_path}/quote", body: { weightLbs: weight_lbs, distanceMiles: distance_miles })
20
+ end
21
+
22
+ # Buys an offset for a shipment and debits the workspace wallet.
23
+ # Pass an idempotency_key you store with the purchase to make your own retry safe: after an
24
+ # error or a timeout, retry with the same key and the Gateway returns the first result instead
25
+ # of buying again. Without one, the SDK's own retries still share a key, but a new call gets a new key.
26
+ def purchase(tracking_number:, weight_lbs:, distance_miles:, idempotency_key: nil)
27
+ @http.post("#{ws_path}/purchase",
28
+ body: { trackingNumber: tracking_number, weightLbs: weight_lbs, distanceMiles: distance_miles },
29
+ headers: idempotency_key.nil? ? {} : { "Idempotency-Key" => idempotency_key })
30
+ end
31
+
32
+ def list(page: nil, page_size: nil)
33
+ query = { page: page, pageSize: page_size }.compact
34
+ @http.get(ws_path, query: query.empty? ? nil : query)
35
+ end
36
+
37
+ def get(offset_id)
38
+ @http.get("#{ws_path}/#{offset_id}")
39
+ end
40
+
41
+ def summary
42
+ @http.get("#{ws_path}/summary")
43
+ end
44
+
45
+ def get_settings
46
+ @http.get("#{ws_path}/settings")
47
+ end
48
+
49
+ def update_settings(auto_offset_enabled:)
50
+ @http.put("#{ws_path}/settings", body: { autoOffsetEnabled: auto_offset_enabled })
51
+ end
52
+
53
+ private
54
+
55
+ def ws_path
56
+ "/api/workspaces/#{@ws_id.call}/carbon-offsets"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -8,6 +8,11 @@
8
8
 
9
9
  module FlexOps
10
10
  module Resources
11
+ # Carrier-specific calls through the legacy /api/ApiProxy passthrough (sunset 2027-06-30).
12
+ # Lookups (GET) work with an API key or a user token. The writes (POST/PUT/DELETE) are
13
+ # deprecated and need a user token (auth.login): the Gateway refuses API-key writes on the
14
+ # proxy with 403 API_KEY_SCOPE_REQUIRED. Label generation through the proxy is closed (410 Gone);
15
+ # buy, cancel and schedule through client.shipping, client.pickups and client.scan_forms.
11
16
  class Carriers
12
17
  attr_reader :usps, :ups, :fedex, :dhl
13
18
 
@@ -30,20 +35,37 @@ module FlexOps
30
35
  def validate_address(params) = @http.get("#{PROXY}/api/v3/AddressValidation/getUspsValidateAndCorrectAddress", query: params)
31
36
  def city_state_lookup(zip_code) = @http.get("#{PROXY}/api/v3/AddressValidation/getUspsCityStateLookupByZipCode", query: { zipCode: zip_code })
32
37
  def zip_code_lookup(params) = @http.get("#{PROXY}/api/v3/AddressValidation/getUspsZipCodeLookupByAddress", query: params)
38
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
39
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
33
40
  def get_domestic_rates(body) = @http.post("#{PROXY}/api/v3/RateCalculator/postUspsSearchDomesticBaseRates", body: body)
41
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
42
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
34
43
  def get_domestic_products(body) = @http.post("#{PROXY}/api/v3/RateCalculator/postUspsSearchEligibleDomesticProducts", body: body)
44
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
45
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
35
46
  def get_domestic_prices(body) = @http.post("#{PROXY}/api/v3/RateCalculator/postUspsSearchEligibleDomesticPrices", body: body)
47
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
48
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
36
49
  def get_international_rates(body) = @http.post("#{PROXY}/api/v3/RateCalculator/postUspsSearchInternationalBaseRates", body: body)
50
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
51
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
37
52
  def get_international_prices(body) = @http.post("#{PROXY}/api/v3/RateCalculator/postUspsSearchEligibleInternationalPrices", body: body)
38
- def create_domestic_label(body) = @http.post("#{PROXY}/api/v3/Shipping/postUspsGenerateDomesticShippingLabel", body: body)
39
- def create_return_label(body) = @http.post("#{PROXY}/api/v3/Shipping/postUspsGenerateDomesticReturnsShippingLabel", body: body)
40
- def create_international_label(body) = @http.post("#{PROXY}/api/v3/Shipping/postUspsGenerateInternationalShippingLabel", body: body)
53
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
54
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
41
55
  def cancel_domestic_label = @http.delete("#{PROXY}/api/v3/Shipping/cancelUspsDomesticShipmentLabel")
56
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
57
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
42
58
  def cancel_international_label = @http.delete("#{PROXY}/api/v3/Shipping/cancelUspsInternationalShipmentLabel")
43
59
  def track_summary(params) = @http.get("#{PROXY}/api/v3/Tracking/getUspsTrackingSummaryInformation", query: params)
44
60
  def track_detail(params) = @http.get("#{PROXY}/api/v3/Tracking/getUspsTrackingDetailInformation", query: params)
61
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
62
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
45
63
  def create_pickup(body) = @http.post("#{PROXY}/api/v3/CarrierPickup/postUspsCreateCarrierPickupSchedule", body: body)
64
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
65
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
46
66
  def cancel_pickup = @http.delete("#{PROXY}/api/v3/CarrierPickup/cancelUspsCarrierPickupSchedule")
67
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
68
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
47
69
  def create_scan_form(body) = @http.post("#{PROXY}/api/v3/ScanForm/postUspsCreateScanFormLabelShipment", body: body)
48
70
  def delivery_standards(params) = @http.get("#{PROXY}/api/v3/ServiceStandards/getUspsGetDeliveryStandardsEstimates", query: params)
49
71
  def find_drop_off_locations(params) = @http.get("#{PROXY}/api/v3/LocationSearch/getUspsFindValidDropOffLocations", query: params)
@@ -57,17 +79,36 @@ module FlexOps
57
79
  @http = http
58
80
  end
59
81
 
82
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
83
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
60
84
  def validate_address(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postUpsVerifyAddress", body: body)
85
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
86
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
61
87
  def get_rates(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postUpsRateCheck", body: body)
62
- def create_label(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/generateNewUpsShipLabel", body: body)
63
88
  def track(params) = @http.get("#{PROXY}/api/v2/ShippingLabel/getSingleUpsTrackingDetail", query: params)
89
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
90
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
64
91
  def create_pickup(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postUpsCreatePickup", body: body)
92
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
93
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
65
94
  def cancel_pickup = @http.delete("#{PROXY}/api/v2/ShippingLabel/deleteUpsPickup")
95
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
96
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
66
97
  def get_transit_times(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postUpsGetTransitTimes", body: body)
98
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
99
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
67
100
  def get_landed_cost(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postUpsGetLandedCostQuote", body: body)
101
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
102
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
68
103
  def search_locations(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postUpsSearchLocations", body: body)
104
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
105
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
69
106
  def upload_document(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postUpsUploadPaperlessDocument", body: body)
107
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
108
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
70
109
  def create_freight_shipment(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postUpsCreateFreightShipment", body: body)
110
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
111
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
71
112
  def get_freight_rate(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postUpsGetFreightRate", body: body)
72
113
  end
73
114
 
@@ -78,26 +119,62 @@ module FlexOps
78
119
  @http = http
79
120
  end
80
121
 
122
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
123
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
81
124
  def validate_address(body) = @http.post("#{PROXY}/api/v3/AddressValidation/postFedExValidateAndCorrectDomesticAddress", body: body)
125
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
126
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
82
127
  def validate_postal_code(body) = @http.post("#{PROXY}/api/v3/AddressValidation/postFedExValidatePostalCode", body: body)
128
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
129
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
83
130
  def get_rates(body) = @http.post("#{PROXY}/api/v3/RateCalculator/postRetrieveFedExRateAndTransitTimesAsync", body: body)
84
- def create_shipment(body) = @http.post("#{PROXY}/api/v3/Shipping/postFedExCreateNewShipment", body: body)
131
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
132
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
85
133
  def cancel_shipment(body) = @http.put("#{PROXY}/api/v3/Shipping/putFedExCancelShipment", body: body)
134
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
135
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
86
136
  def validate_shipment(body) = @http.post("#{PROXY}/api/v3/Shipping/postFedExValidateShipment", body: body)
87
- def create_return_shipment(body) = @http.post("#{PROXY}/api/v3/Shipping/postFedExCreateNewReturnShipment", body: body)
137
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
138
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
88
139
  def track(body) = @http.post("#{PROXY}/api/v3/Tracking/postFedExRetrieveTrackingInfoByTrackingNumber", body: body)
140
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
141
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
89
142
  def track_multi_piece(body) = @http.post("#{PROXY}/api/v3/Tracking/postFedExRetrieveTrackingInfoForMultiPieceShipment", body: body)
143
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
144
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
90
145
  def register_tracking_notification(body) = @http.post("#{PROXY}/api/v3/Tracking/postFedExRegisterForTrackingNotification", body: body)
146
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
147
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
91
148
  def create_pickup(body) = @http.post("#{PROXY}/api/v3/CarrierPickup/postFedExCreateCarrierPickupRequest", body: body)
149
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
150
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
92
151
  def cancel_pickup(body) = @http.put("#{PROXY}/api/v3/CarrierPickup/putFedExCancelCarrierPickupRequest", body: body)
152
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
153
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
93
154
  def search_locations(body) = @http.post("#{PROXY}/api/v3/LocationSearch/postFedExSearchValidLocations", body: body)
155
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
156
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
94
157
  def get_service_standards(body) = @http.post("#{PROXY}/api/v3/ServiceStandards/postFedExRetrieveServicesAndTransitTimes", body: body)
158
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
159
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
95
160
  def get_freight_rate(body) = @http.post("#{PROXY}/api/v3/Freight/postFedExGetFreightRateQuote", body: body)
161
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
162
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
96
163
  def create_freight_shipment(body) = @http.post("#{PROXY}/api/v3/Freight/postFedExCreateFreightShipment", body: body)
164
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
165
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
97
166
  def ground_close(body) = @http.post("#{PROXY}/api/v3/GroundClose/postFedExCloseWithDocuments", body: body)
167
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
168
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
98
169
  def upload_trade_documents(body) = @http.post("#{PROXY}/api/v3/Trade/postFedExUploadTradeDocuments", body: body)
170
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
171
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
99
172
  def create_open_shipment(body) = @http.post("#{PROXY}/api/v3/OpenShip/postFedExCreateOpenShipment", body: body)
173
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
174
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
100
175
  def add_packages_to_open_shipment(body) = @http.post("#{PROXY}/api/v3/OpenShip/postFedExAddPackagesToOpenShipment", body: body)
176
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
177
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
101
178
  def confirm_open_shipment(body) = @http.post("#{PROXY}/api/v3/OpenShip/postFedExConfirmOpenShipment", body: body)
102
179
  end
103
180
 
@@ -110,16 +187,26 @@ module FlexOps
110
187
 
111
188
  def validate_address(params) = @http.get("#{PROXY}/api/v2/ShippingLabel/getDhlValidateAddress", query: params)
112
189
  def get_rates(params) = @http.get("#{PROXY}/api/v2/ShippingLabel/getDhlRates", query: params)
190
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
191
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
113
192
  def get_multi_piece_rates(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postDhlMultiPieceRates", body: body)
114
193
  def get_products(params) = @http.get("#{PROXY}/api/v2/ShippingLabel/getDhlProducts", query: params)
115
- def create_shipment(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postDhlCreateShipment", body: body)
116
194
  def track(params) = @http.get("#{PROXY}/api/v2/ShippingLabel/getDhlTrackSingleShipment", query: params)
117
195
  def track_multiple(params) = @http.get("#{PROXY}/api/v2/ShippingLabel/getDhlTrackMultipleShipments", query: params)
196
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
197
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
118
198
  def create_pickup(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postDhlCreatePickup", body: body)
119
- def update_pickup(body) = @http.patch("#{PROXY}/api/v2/ShippingLabel/patchDhlUpdatePickup", body: body)
199
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
200
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
120
201
  def cancel_pickup = @http.delete("#{PROXY}/api/v2/ShippingLabel/deleteDhlPickup")
202
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
203
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
121
204
  def calculate_landed_cost(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postDhlCalculateLandedCost", body: body)
205
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
206
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
122
207
  def screen_shipment(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postDhlScreenShipment", body: body)
208
+ # @deprecated Legacy carrier proxy (sunset 2027-06-30). Needs a user token: API keys get 403
209
+ # API_KEY_SCOPE_REQUIRED. Use client.shipping / client.pickups / client.scan_forms where they cover this.
123
210
  def upload_invoice(body) = @http.post("#{PROXY}/api/v2/ShippingLabel/postDhlUploadInvoice", body: body)
124
211
  def get_proof_of_delivery(params) = @http.get("#{PROXY}/api/v2/ShippingLabel/getDhlElectronicProofOfDelivery", query: params)
125
212
  def get_reference_data(params) = @http.get("#{PROXY}/api/v2/ShippingLabel/getDhlReferenceData", query: params)
@@ -1,15 +1,22 @@
1
1
  # ***********************************************************************
2
2
  # Package : flexops
3
3
  # Author : FlexOps, LLC
4
- # Created : 2026-03-31
4
+ # Created : 2026-10-19
5
5
  #
6
6
  # Copyright (c) 2021-2026 by FlexOps, LLC. All rights reserved.
7
7
  # ***********************************************************************
8
8
 
9
- # frozen_string_literal: true
10
-
11
9
  module FlexOps
12
10
  module Resources
11
+ # Branded tracking email templates, one per tracking event. Answers are bare JSON (no
12
+ # envelope); ids are GUID strings.
13
+ #
14
+ # Reads work with any API key or a user token. Writes (upsert, delete, preview) need a user
15
+ # token (auth.login) or an API key granted the opt-in "email-templates:write" scope; a key
16
+ # without it gets 403 API_KEY_SCOPE_REQUIRED.
17
+ #
18
+ # +eventType+ is the Gateway TrackingEmailEvent number: LabelCreated 0, InTransit 1,
19
+ # OutForDelivery 2, Delivered 3, Exception 4, ReturnToSender 5.
13
20
  class EmailTemplates
14
21
  def initialize(http, ws_id_proc)
15
22
  @http = http
@@ -17,33 +24,36 @@ module FlexOps
17
24
  end
18
25
 
19
26
  def list
20
- @http.get("#{ws_path}/shipment-email-templates/")
21
- end
22
-
23
- def get(id)
24
- @http.get("#{ws_path}/shipment-email-templates/#{id}")
27
+ @http.get(ws_path)
25
28
  end
26
29
 
27
- def create(request)
28
- @http.post("#{ws_path}/shipment-email-templates/", body: request)
30
+ def get(template_id)
31
+ @http.get("#{ws_path}/#{template_id}")
29
32
  end
30
33
 
31
- def update(id, request)
32
- @http.put("#{ws_path}/shipment-email-templates/#{id}", body: request)
34
+ # Creates or replaces the template for request's +eventType+.
35
+ #
36
+ # request:: { eventType: (0-5), subjectTemplate: (<= 500 chars), htmlBodyTemplate:,
37
+ # isEnabled: (default true) }. Returns the template.
38
+ def upsert(request)
39
+ @http.put(ws_path, body: request)
33
40
  end
34
41
 
35
- def delete(id)
36
- @http.delete("#{ws_path}/shipment-email-templates/#{id}")
42
+ # Returns nil (204).
43
+ def delete(template_id)
44
+ @http.delete("#{ws_path}/#{template_id}")
37
45
  end
38
46
 
39
- def preview(id, context = {})
40
- @http.post("#{ws_path}/shipment-email-templates/#{id}/preview", body: context)
47
+ # Renders a draft with sample placeholder values. Returns { renderedSubject, renderedHtmlBody }.
48
+ def preview(subject_template:, html_body_template:)
49
+ @http.post("#{ws_path}/preview",
50
+ body: { subjectTemplate: subject_template, htmlBodyTemplate: html_body_template })
41
51
  end
42
52
 
43
53
  private
44
54
 
45
55
  def ws_path
46
- "/api/workspaces/#{@ws_id.call}"
56
+ "/api/workspaces/#{@ws_id.call}/email-templates"
47
57
  end
48
58
  end
49
59
  end
@@ -8,31 +8,30 @@
8
8
 
9
9
  # frozen_string_literal: true
10
10
 
11
+ require "erb"
12
+
11
13
  module FlexOps
12
14
  module Resources
15
+ # HS code lookup and landed cost. These routes are not workspace-scoped; each returns a bare
16
+ # (not enveloped) answer.
13
17
  class HsCodes
14
- def initialize(http, ws_id_proc)
18
+ def initialize(http)
15
19
  @http = http
16
- @ws_id = ws_id_proc
17
20
  end
18
21
 
19
22
  def search(query, destination_country: nil, max_results: 10)
20
23
  params = { query: query, destinationCountry: destination_country, maxResults: max_results }.compact
21
- @http.get("#{ws_path}/shipping/hs-codes/search", query: params)
24
+ @http.get("/api/shipping/hs-codes/search", query: params)
22
25
  end
23
26
 
24
27
  def lookup(code)
25
- @http.get("#{ws_path}/shipping/hs-codes/#{code}")
28
+ @http.get("/api/shipping/hs-codes/#{ERB::Util.url_encode(code.to_s)}")
26
29
  end
27
30
 
31
+ # request:: { destinationCountry:, items: [{ description:, quantity:, value:, weightOz:,
32
+ # hsCode: (optional), originCountry:, skuOrProductId: (optional) }], shippingCost: }
28
33
  def estimate_landed_cost(request)
29
- @http.post("#{ws_path}/shipping/landed-cost", body: request)
30
- end
31
-
32
- private
33
-
34
- def ws_path
35
- "/api/workspaces/#{@ws_id.call}"
34
+ @http.post("/api/shipping/landed-cost", body: request)
36
35
  end
37
36
  end
38
37
  end
@@ -8,6 +8,7 @@
8
8
 
9
9
  module FlexOps
10
10
  module Resources
11
+ # Shipping insurance. Answers use the { success, data, message, errorCode } envelope.
11
12
  class Insurance
12
13
  def initialize(http, ws_id_proc)
13
14
  @http = http
@@ -18,24 +19,33 @@ module FlexOps
18
19
  @http.get("#{ws_path}/insurance/providers")
19
20
  end
20
21
 
21
- def get_quote(carrier:, declared_value:, provider: nil)
22
- body = { carrier: carrier, declaredValue: declared_value }
23
- body[:provider] = provider if provider
22
+ def get_quote(declared_value:, carrier_code:, tracking_number: nil, provider: nil)
23
+ body = { declaredValue: declared_value, carrierCode: carrier_code,
24
+ trackingNumber: tracking_number, provider: provider }.compact
24
25
  @http.post("#{ws_path}/insurance/quote", body: body)
25
26
  end
26
27
 
27
- def purchase(tracking_number:, carrier:, declared_value:, provider: nil)
28
- body = { trackingNumber: tracking_number, carrier: carrier, declaredValue: declared_value }
29
- body[:provider] = provider if provider
30
- @http.post("#{ws_path}/insurance/purchase", body: body)
28
+ # Buys a policy and charges the premium to the workspace wallet. A shipment that already has a
29
+ # policy in force is refused with 409 ALREADY_EXISTS.
30
+ # Pass an idempotency_key you store with the purchase to make your own retry safe: after an
31
+ # error or a timeout, retry with the same key and the Gateway returns the first result instead
32
+ # of buying again. Without one, the SDK's own retries still share a key, but a new call gets a new key.
33
+ def purchase(tracking_number:, carrier_code:, declared_value:, coverage_tier: nil, provider: nil,
34
+ idempotency_key: nil)
35
+ body = { trackingNumber: tracking_number, carrierCode: carrier_code, declaredValue: declared_value,
36
+ coverageTier: coverage_tier, provider: provider }.compact
37
+ @http.post("#{ws_path}/insurance/policies", body: body,
38
+ headers: idempotency_key.nil? ? {} : { "Idempotency-Key" => idempotency_key })
31
39
  end
32
40
 
33
41
  def void(policy_id)
34
42
  @http.delete("#{ws_path}/insurance/policies/#{policy_id}")
35
43
  end
36
44
 
37
- def file_claim(policy_id, description:, claim_amount:)
38
- @http.post("#{ws_path}/insurance/policies/#{policy_id}/claims", body: { description: description, claimAmount: claim_amount })
45
+ def file_claim(policy_id:, claim_reason:, claim_amount:, evidence_json: nil)
46
+ body = { policyId: policy_id, claimReason: claim_reason, claimAmount: claim_amount,
47
+ evidenceJson: evidence_json }.compact
48
+ @http.post("#{ws_path}/insurance/claims", body: body)
39
49
  end
40
50
 
41
51
  private
@@ -15,10 +15,6 @@ module FlexOps
15
15
  @http = http
16
16
  end
17
17
 
18
- def post_asn_receipt(receipt)
19
- @http.post("#{PROXY}/api/v1/Inventory/postNewAsnReceipt", body: receipt)
20
- end
21
-
22
18
  def get_warehouse_snapshot(params = nil)
23
19
  @http.get("#{PROXY}/api/v1/Inventory/getWarehouseInventorySnapshot", query: params)
24
20
  end
@@ -30,10 +26,6 @@ module FlexOps
30
26
  def get_part_numbers(params = nil)
31
27
  @http.get("#{PROXY}/api/v1/Inventory/getPartNumberList", query: params)
32
28
  end
33
-
34
- def update_inventory(data)
35
- @http.post("#{PROXY}/api/v2/Inventory/postCustomerInventoryUpdate", body: data)
36
- end
37
29
  end
38
30
  end
39
31
  end
@@ -15,10 +15,6 @@ module FlexOps
15
15
  @http = http
16
16
  end
17
17
 
18
- def create(order)
19
- @http.post("#{BASE}/postNewOrder", body: order)
20
- end
21
-
22
18
  def get_new_orders(params = nil)
23
19
  @http.get("#{BASE}/getNewOrderList", query: params)
24
20
  end
@@ -39,10 +35,6 @@ module FlexOps
39
35
  @http.get("#{BASE}/getIndividualOrderStatusByOrderNumber", query: { orderNumber: order_number })
40
36
  end
41
37
 
42
- def cancel(order_number)
43
- @http.post("#{BASE}/cancelOrderByOrderNumber", body: { orderNumber: order_number })
44
- end
45
-
46
38
  def get_items(order_number)
47
39
  @http.get("#{BASE}/getAllOrderItemsByOrderNumber", query: { orderNumber: order_number })
48
40
  end
@@ -6,6 +6,8 @@
6
6
  # Copyright (c) 2021-2026 by FlexOps, LLC. All rights reserved.
7
7
  # ***********************************************************************
8
8
 
9
+ require "uri"
10
+
9
11
  module FlexOps
10
12
  module Resources
11
13
  class Pickups
@@ -14,20 +16,27 @@ module FlexOps
14
16
  @ws_id = ws_id_proc
15
17
  end
16
18
 
19
+ # request:: { carrierCode:, pickupDate:, street:, city:, state:, postalCode:,
20
+ # country: (optional, "US"), packageCount:, totalWeightOz: (optional),
21
+ # specialInstructions:, readyTime:, closeTime:, contactName:, contactPhone: (all optional) }
17
22
  def schedule(request)
18
23
  @http.post("#{ws_path}/pickups", body: request)
19
24
  end
20
25
 
26
+ # Lists the workspace's scheduled pickups.
21
27
  def list
22
- @http.get("#{ws_path}/pickups")
28
+ @http.get("#{ws_path}/pickups/scheduled")
23
29
  end
24
30
 
25
- def get(pickup_id)
26
- @http.get("#{ws_path}/pickups/#{pickup_id}")
31
+ # The Gateway uses "usps" when carrier_code is left out.
32
+ def get(pickup_id, carrier_code: nil)
33
+ @http.get("#{ws_path}/pickups/#{pickup_id}", query: carrier_code.nil? ? nil : { carrierCode: carrier_code })
27
34
  end
28
35
 
29
- def cancel(pickup_id)
30
- @http.delete("#{ws_path}/pickups/#{pickup_id}")
36
+ # The Gateway uses "usps" when carrier_code is left out.
37
+ def cancel(pickup_id, carrier_code: nil)
38
+ query = carrier_code.nil? ? "" : "?#{URI.encode_www_form(carrierCode: carrier_code)}"
39
+ @http.delete("#{ws_path}/pickups/#{pickup_id}#{query}")
31
40
  end
32
41
 
33
42
  private
@@ -1,58 +1,73 @@
1
1
  # ***********************************************************************
2
2
  # Package : flexops
3
3
  # Author : FlexOps, LLC
4
- # Created : 2026-03-31
4
+ # Created : 2026-10-19
5
5
  #
6
6
  # Copyright (c) 2021-2026 by FlexOps, LLC. All rights reserved.
7
7
  # ***********************************************************************
8
8
 
9
- # frozen_string_literal: true
10
-
11
9
  module FlexOps
12
10
  module Resources
11
+ # Recurring shipment schedules. Answers are bare JSON (no envelope); ids are GUID strings.
12
+ #
13
+ # Reads work with any API key or a user token. Writes (create, update, delete, pause, resume,
14
+ # trigger) need a user token (auth.login) or an API key granted the opt-in
15
+ # "recurring-shipments:write" scope; a key without it gets 403 API_KEY_SCOPE_REQUIRED.
16
+ #
17
+ # +interval+ is the Gateway RecurrenceInterval number: Daily 0, Weekly 1, Biweekly 2,
18
+ # Monthly 3, Custom 4 (Custom runs on +cronExpression+).
13
19
  class RecurringShipments
14
20
  def initialize(http, ws_id_proc)
15
21
  @http = http
16
22
  @ws_id = ws_id_proc
17
23
  end
18
24
 
19
- def list(page: nil, page_size: nil, is_active: nil)
20
- query = { page: page, pageSize: page_size, isActive: is_active }.compact
21
- @http.get("#{ws_path}/recurring-shipments/", query: query.empty? ? nil : query)
25
+ def list
26
+ @http.get(ws_path)
22
27
  end
23
28
 
24
29
  def get(id)
25
- @http.get("#{ws_path}/recurring-shipments/#{id}")
30
+ @http.get("#{ws_path}/#{id}")
26
31
  end
27
32
 
33
+ # request:: { name:, firstRunUtc: (ISO 8601), interval: (0-4), presetId:, addressId:,
34
+ # cronExpression:, maxFailures: (1-100, default 3) }. Each run needs a destination
35
+ # +addressId+; a +presetId+ supplies the package and service. Returns the schedule.
28
36
  def create(request)
29
- @http.post("#{ws_path}/recurring-shipments/", body: request)
37
+ @http.post(ws_path, body: request)
30
38
  end
31
39
 
40
+ # request:: any of name:, presetId:, addressId:, interval:, cronExpression:, maxFailures:.
32
41
  def update(id, request)
33
- @http.put("#{ws_path}/recurring-shipments/#{id}", body: request)
42
+ @http.put("#{ws_path}/#{id}", body: request)
34
43
  end
35
44
 
45
+ # Returns nil (204).
36
46
  def delete(id)
37
- @http.delete("#{ws_path}/recurring-shipments/#{id}")
47
+ @http.delete("#{ws_path}/#{id}")
38
48
  end
39
49
 
50
+ # Returns nil (204).
40
51
  def pause(id)
41
- @http.post("#{ws_path}/recurring-shipments/#{id}/pause")
52
+ @http.post("#{ws_path}/#{id}/pause")
42
53
  end
43
54
 
55
+ # Returns nil (204).
44
56
  def resume(id)
45
- @http.post("#{ws_path}/recurring-shipments/#{id}/resume")
57
+ @http.post("#{ws_path}/#{id}/resume")
46
58
  end
47
59
 
48
- def trigger(id)
49
- @http.post("#{ws_path}/recurring-shipments/#{id}/trigger")
60
+ # Runs the schedule now, which creates an order. Pass the same +idempotency_key+ when you
61
+ # retry a trigger yourself so the Gateway does not create a second order. Returns nil (204).
62
+ def trigger(id, idempotency_key: nil)
63
+ @http.post("#{ws_path}/#{id}/trigger",
64
+ headers: idempotency_key.nil? ? {} : { "Idempotency-Key" => idempotency_key })
50
65
  end
51
66
 
52
67
  private
53
68
 
54
69
  def ws_path
55
- "/api/workspaces/#{@ws_id.call}"
70
+ "/api/workspaces/#{@ws_id.call}/recurring-shipments"
56
71
  end
57
72
  end
58
73
  end