flexops 2.0.0 → 2.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99bb604e4e35131d7525633fbfad7103b380170556b47250599192256636eec1
4
- data.tar.gz: b9fb3ccd224541332bd03fd9e8510937fbed4d093c6ab3443dfd41fee0565229
3
+ metadata.gz: 0176ab2f4e6ee84a35449cc7c3cc2b0d13708b62fdef7552013db7cce25bff08
4
+ data.tar.gz: d6c47bc34a6f23aa44bb799c4a1165c87eab2a592439a8e8a10bd39774f5e6f4
5
5
  SHA512:
6
- metadata.gz: 12823e90e21cd1eeefcf883ab5c7ed9a4e9d008b2e26bd80ac6014a05e3b6e6b57e4054a3757574173886b2c8ed6a08e6c8d3a2863f6034eb592e6e8d03da5c1
7
- data.tar.gz: fb7a48fb211dfb1af4ddd9a0be588eb58318b8ffd445578a09dcacdef625ff9dc8e3dbc593b59a21da18acf678d67d9ca0bd76aef55a22843be9748bfddcf5fa
6
+ metadata.gz: cf5f9b203dbf64a94243192fd7db18fc131275b0da8ceff6a5165bc88fb64b85e26efb0d71062e030a1acf45cec9e5a3717da17833636ab6685ddc010dcb6b62
7
+ data.tar.gz: 570d832da16f3449283d3d064197efe2eeba19e6fad36183179490564829a1217f488038bd81970357a67bfef685be2d3dd06b1a3ced0e6eb0ac1272cc48a752
data/README.md CHANGED
@@ -29,8 +29,8 @@ require "flexops"
29
29
 
30
30
  # API key authentication (recommended for server-to-server)
31
31
  client = FlexOps::Client.new(
32
- api_key: "fxk_live_...",
33
- workspace_id: "ws_abc123"
32
+ api_key: "live_...",
33
+ workspace_id: "your-workspace-id"
34
34
  )
35
35
 
36
36
  # Get shipping rates from all carriers
@@ -90,7 +90,7 @@ separate contracts. The SDK never automatically confirms a preview.
90
90
  ### API key (recommended)
91
91
 
92
92
  ```ruby
93
- client = FlexOps::Client.new(api_key: "fxk_live_...", workspace_id: "ws_abc123")
93
+ client = FlexOps::Client.new(api_key: "live_...", workspace_id: "your-workspace-id")
94
94
  ```
95
95
 
96
96
  ### Email / password
@@ -99,15 +99,15 @@ client = FlexOps::Client.new(api_key: "fxk_live_...", workspace_id: "ws_abc123")
99
99
  client = FlexOps::Client.new(base_url: "https://gateway.flexops.io")
100
100
  session = client.auth.login(email: "user@example.com", password: "password")
101
101
  client.set_access_token(session["data"]["token"])
102
- client.workspace_id = "ws_abc123"
102
+ client.workspace_id = "your-workspace-id"
103
103
  ```
104
104
 
105
105
  ## Sandbox / test keys
106
106
 
107
- Use `fxk_test_...` (instead of `fxk_live_...`) to route to the sandbox environment. Mock carriers respond, nothing hits real carrier APIs, no charges, no real labels. Perfect for CI and integration tests.
107
+ Use `test_...` (instead of `live_...`) to route to the sandbox environment. Mock carriers respond, nothing hits real carrier APIs, no charges, no real labels. Perfect for CI and integration tests.
108
108
 
109
109
  ```ruby
110
- client = FlexOps::Client.new(api_key: "fxk_test_...", workspace_id: "ws_abc123")
110
+ client = FlexOps::Client.new(api_key: "test_...", workspace_id: "your-workspace-id")
111
111
  ```
112
112
 
113
113
  ## Carrier-specific calls
@@ -209,6 +209,30 @@ client.email_templates.upsert(eventType: 3, subjectTemplate: "Delivered: {{track
209
209
  client.email_templates.preview(subject_template: "Hi {{recipient_name}}", html_body_template: "<p>{{status}}</p>")
210
210
  ```
211
211
 
212
+ ## Multi-package labels
213
+
214
+ One order shipped in several boxes with the same carrier and service; each box gets its own label and
215
+ a box that fails does not stop the others. Multi-package labels cannot carry HAZMAT: send
216
+ `FlexOps::HazmatClassification::NONE` or leave `hazmatClassification` out.
217
+
218
+ ```ruby
219
+ begin
220
+ shipment = client.shipping.create_multi_package(
221
+ { orderId: 12345, carrierCode: "USPS", serviceCode: "PRIORITY",
222
+ hazmatClassification: FlexOps::HazmatClassification::NONE,
223
+ packages: [{ weightInOunces: 16, lengthInInches: 10, widthInInches: 8, heightInInches: 4, packageReference: "Box 1 of 2" },
224
+ { weightInOunces: 8, lengthInInches: 6, widthInInches: 6, heightInInches: 6, packageReference: "Box 2 of 2" }] },
225
+ idempotency_key: "order-12345-boxes" # optional; reuse it when you retry
226
+ )
227
+ rescue FlexOps::Error => e
228
+ # No label was bought: 422 AllPackagesFailed, or 409 OutcomeUnknown when a label may exist
229
+ # (check the order before buying again). e.data holds the per-box results.
230
+ end
231
+ ```
232
+
233
+ `client.returns.get` reads a received return by its numeric return id; read an RMA with
234
+ `client.returns.get_rma("RMA-1001")` or page through them with `client.returns.list_rmas`.
235
+
212
236
  ## Wallet balance
213
237
 
214
238
  ```ruby
@@ -287,7 +311,7 @@ Every SDK method is a thin wrapper around the FlexOps REST API. If you want to v
287
311
  ```bash
288
312
  # Shop rates across all connected carriers
289
313
  curl -X POST https://gateway.flexops.io/api/shipping/rates \
290
- -H "X-API-Key: fxk_live_..." \
314
+ -H "X-API-Key: live_..." \
291
315
  -H "Content-Type: application/json" \
292
316
  -d '{
293
317
  "origin": {"addressLine1": "123 Main St", "city": "New York", "stateProvince": "NY", "postalCode": "10001", "countryCode": "US"},
@@ -298,7 +322,7 @@ curl -X POST https://gateway.flexops.io/api/shipping/rates \
298
322
  # Preview a live label; this does not purchase it.
299
323
  # hazmatClassification 0 = not hazardous; required when there is no orderId.
300
324
  curl -X POST https://gateway.flexops.io/api/shipping/labels \
301
- -H "X-API-Key: fxk_live_..." \
325
+ -H "X-API-Key: live_..." \
302
326
  -H "Content-Type: application/json" \
303
327
  -d '{
304
328
  "carrierCode": "USPS", "serviceCode": "GROUND_ADVANTAGE",
@@ -315,14 +339,14 @@ curl -X POST https://gateway.flexops.io/api/shipping/labels \
315
339
 
316
340
  # Track a shipment
317
341
  curl https://gateway.flexops.io/api/shipping/track/9400111899223456789012 \
318
- -H "X-API-Key: fxk_live_..."
342
+ -H "X-API-Key: live_..."
319
343
 
320
344
  # Cancel a label
321
345
  curl -X DELETE "https://gateway.flexops.io/api/shipping/labels/lbl_123?carrierCode=USPS" \
322
- -H "X-API-Key: fxk_live_..."
346
+ -H "X-API-Key: live_..."
323
347
  ```
324
348
 
325
- Use an `fxk_test_...` key instead of `fxk_live_...` to hit the sandbox environment; mock carriers respond, no real charges, no real labels.
349
+ Use a `test_...` key instead of `live_...` to hit the sandbox environment; mock carriers respond, no real charges, no real labels.
326
350
 
327
351
  ## Resources
328
352
 
@@ -330,12 +354,12 @@ Use an `fxk_test_...` key instead of `fxk_live_...` to hit the sandbox environme
330
354
  |----------|-------------|
331
355
  | `client.auth` | Login, token refresh, logout |
332
356
  | `client.workspaces` | List and read workspaces; list, invite and remove members, change roles |
333
- | `client.shipping` | Rate shopping, labels, tracking, address validation, batch, cancel |
357
+ | `client.shipping` | Rate shopping, labels, multi-package labels, tracking, address validation, batch, cancel |
334
358
  | `client.carriers` | USPS, UPS, FedEx and DHL calls through the legacy proxy; writes are deprecated and need a user token |
335
359
  | `client.webhooks` | Subscription create/update/delete, secret rotation, test events, delivery logs, signature verification |
336
360
  | `client.wallet` | Workspace balance |
337
361
  | `client.insurance` | Providers, quotes, policies, void, claims |
338
- | `client.returns` | List and read returns; create RMAs, return labels, receive |
362
+ | `client.returns` | List and read received returns; create, read and list RMAs; return labels; receive |
339
363
  | `client.analytics` | Shipments, orders, carrier performance (legacy proxy reads) |
340
364
  | `client.orders` | Order reads (legacy proxy) |
341
365
  | `client.inventory` | Inventory reads (legacy proxy) |
@@ -355,8 +379,8 @@ API keys, workspace settings and account management are managed in the FlexOps p
355
379
  ```ruby
356
380
  client = FlexOps::Client.new(
357
381
  base_url: "https://gateway.flexops.io", # API base URL
358
- api_key: "fxk_live_...", # API key auth
359
- workspace_id: "ws_abc123", # Default workspace
382
+ api_key: "live_...", # API key auth
383
+ workspace_id: "your-workspace-id", # Default workspace
360
384
  timeout: 30 # Request timeout (seconds)
361
385
  )
362
386
  ```
@@ -8,13 +8,16 @@
8
8
 
9
9
  module FlexOps
10
10
  class Error < StandardError
11
- attr_reader :status, :code, :errors
11
+ # data is the envelope's "data" on an error reply, when the Gateway sends one. A
12
+ # multi-package 422 AllPackagesFailed or 409 OutcomeUnknown carries the per-box results there.
13
+ attr_reader :status, :code, :errors, :data
12
14
 
13
- def initialize(message, status: 0, code: nil, errors: nil)
15
+ def initialize(message, status: 0, code: nil, errors: nil, data: nil)
14
16
  super(message)
15
17
  @status = status
16
18
  @code = code
17
19
  @errors = errors
20
+ @data = data
18
21
  end
19
22
  end
20
23
 
data/lib/flexops/http.rb CHANGED
@@ -117,7 +117,8 @@ module FlexOps
117
117
  error_body["message"] || "HTTP #{status}: #{response.message}",
118
118
  status: status,
119
119
  code: error_body["errorCode"] || error_body["code"] || (status == 403 ? "FORBIDDEN" : nil),
120
- errors: error_body["errors"]
120
+ errors: error_body["errors"],
121
+ data: error_body["data"]
121
122
  )
122
123
 
123
124
  raise error unless retryable?(method, status, error.code)
@@ -6,6 +6,8 @@
6
6
  # Copyright (c) 2021-2026 by FlexOps, LLC. All rights reserved.
7
7
  # ***********************************************************************
8
8
 
9
+ require "erb"
10
+
9
11
  module FlexOps
10
12
  module Resources
11
13
  class Returns
@@ -14,15 +16,30 @@ module FlexOps
14
16
  @ws_id = ws_id_proc
15
17
  end
16
18
 
19
+ # Lists received returns (start_date and end_date are ISO 8601; the Gateway defaults to the
20
+ # last 30 days). To list return authorizations, use list_rmas.
17
21
  def list(page: nil, page_size: nil, start_date: nil, end_date: nil)
18
22
  query = { page: page, pageSize: page_size, startDate: start_date, endDate: end_date }.compact
19
23
  @http.get("#{ws_path}/returns", query: query.empty? ? nil : query)
20
24
  end
21
25
 
26
+ # Reads a received return by its numeric return id ("returnId" on a listed return or an RMA).
27
+ # An RMA number such as "RMA-1001" is not a return id and matches no Gateway route; use get_rma.
22
28
  def get(return_id)
23
29
  @http.get("#{ws_path}/returns/#{return_id}")
24
30
  end
25
31
 
32
+ # Lists return authorizations (RMAs), one page at a time.
33
+ def list_rmas(page: nil, page_size: nil)
34
+ query = { page: page, pageSize: page_size }.compact
35
+ @http.get("#{ws_path}/returns/rma", query: query.empty? ? nil : query)
36
+ end
37
+
38
+ # Reads a return authorization by its RMA number, for example "RMA-1001".
39
+ def get_rma(rma_number)
40
+ @http.get("#{ws_path}/returns/rma/#{ERB::Util.url_encode(rma_number.to_s)}")
41
+ end
42
+
26
43
  # Creates an RMA and returns the enveloped RMA (201).
27
44
  #
28
45
  # request:: { orderId: (a number), orderNumber: (optional), reason:,
@@ -60,6 +60,31 @@ module FlexOps
60
60
  @http.post("/api/addresses/validate", body: to_validation_address(address))
61
61
  end
62
62
 
63
+ # Buys labels for one order shipped in 1 to 99 boxes with the same carrier and service
64
+ # (POST /api/shipping/multi-package, the Gateway MultiPackageShipmentRequest):
65
+ #
66
+ # { orderId: 12345, carrierCode: "USPS", serviceCode: (optional), labelFormat: (optional),
67
+ # shipDate: (optional), hazmatClassification: (optional),
68
+ # packages: [{ weightInOunces:, lengthInInches:, widthInInches:, heightInInches:,
69
+ # deliveryConfirmationRequired: (optional), signatureConfirmationRequired: (optional),
70
+ # insuranceCoverageRequested: (optional), insuredValue: (optional),
71
+ # packageReference: (optional) }] }
72
+ #
73
+ # Weight is in ounces and every dimension in inches; all four are required. Multi-package
74
+ # labels cannot carry HAZMAT yet: send HazmatClassification::NONE or leave hazmatClassification
75
+ # out (any other class is refused with 400 HazmatNotSupported).
76
+ #
77
+ # Returns the envelope when at least one label was bought (check data["failedPackages"] and
78
+ # each box in data["labels"]). When none was, the Gateway answers 422 AllPackagesFailed, or 409
79
+ # OutcomeUnknown when a box's carrier outcome is unknown and a label may exist: both raise
80
+ # FlexOps::Error whose data holds the per-box results. After OutcomeUnknown, check the order
81
+ # before buying again. Pass your own idempotency_key (and reuse it) to make a retry you run
82
+ # yourself safe.
83
+ def create_multi_package(request, idempotency_key: nil)
84
+ @http.post("/api/shipping/multi-package", body: request,
85
+ headers: idempotency_key.nil? ? {} : { "Idempotency-Key" => idempotency_key })
86
+ end
87
+
63
88
  # The payload is sent as given. Set hazmatClassification on an item only when you have a declaration.
64
89
  def create_batch(request)
65
90
  @http.post("#{ws_path}/labels/batch", body: request)
@@ -7,5 +7,5 @@
7
7
  # ***********************************************************************
8
8
 
9
9
  module FlexOps
10
- VERSION = "2.0.0"
10
+ VERSION = "2.1.0"
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flexops
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - FlexOps, LLC
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-20 00:00:00.000000000 Z
11
+ date: 2026-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json