flexops 1.0.2 → 1.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: 46267de883ed8970bdcfc87d77c86edcf9b33a5d1ffe0d312a10961ea43646fb
4
- data.tar.gz: 9c828d5d8fbc74b2b3abee2640b1742de692a800e51fe60f3fd671b728564dc6
3
+ metadata.gz: ae38cf941276d4053203fda4a44fb9ab1adb873d24bba9ec1de7fc634be3e7e2
4
+ data.tar.gz: 43b602d72cbac545bff0391aa2dee8ea28f73bd7807a6042ec17731fe7943881
5
5
  SHA512:
6
- metadata.gz: 439f0b3d74903968585f2d980e80d00798f9784903100851804949a7602c3bc163f0ce3600e5602ebfe6fca0e85d93b64f2bc5b5269abfebd9eb573535db20b5
7
- data.tar.gz: eff6ea869f9cfd93dca01e4bc9bfd794a64fb36f51825743ae7f0bef5783e8698b08324b976048b0a360622d6d49de9c355083fac6072007266080e5461aa874
6
+ metadata.gz: '0959231eed12a9abd27bc7d370ab9576e32393a131248e231f2d7bebb622c74c29c21596f55036c1af8df73a277a3cbb8ee282c516aca90fc787839c76f102dc'
7
+ data.tar.gz: 42fa01ac062f0f54ed7632e403a0d3a5ed37e3e942e6f11a45797b047f7f343c3b0419d354e83af9be217d58d1877a322ee7fee7fa6c79e9749e069cfca0f7cf
data/README.md CHANGED
@@ -40,23 +40,49 @@ rates = client.shipping.get_rates(
40
40
  package: {weight: 16, weightUnit: "oz"}
41
41
  )
42
42
 
43
- # Create a label with the cheapest rate
44
- cheapest = rates["rates"].min_by { |r| r["rate"].to_f }
45
- label = client.shipping.create_label(
46
- carrier: cheapest["carrier"],
47
- service: cheapest["service"],
48
- from_address: {name: "Warehouse", street1: "123 Main St", city: "New York", state: "NY", zip: "10001", country: "US"},
49
- to_address: {name: "Customer", street1: "456 Oak Ave", city: "Los Angeles", state: "CA", zip: "90210", country: "US"},
50
- parcel: {weight: 16, weight_unit: "oz"}
51
- )
52
-
53
- puts "Label URL: #{label["data"]["labelUrl"]}"
54
- puts "Tracking: #{label["data"]["trackingNumber"]}"
43
+ request = {
44
+ carrierCode: "USPS", serviceCode: "GROUND_ADVANTAGE",
45
+ origin: {name: "Warehouse", addressLine1: "123 Main St", city: "New York", stateProvince: "NY", postalCode: "10001", countryCode: "US"},
46
+ destination: {name: "Customer", addressLine1: "456 Oak Ave", city: "Los Angeles", stateProvince: "CA", postalCode: "90210", countryCode: "US"},
47
+ package: {weight: 16, weightUnit: "oz"},
48
+ maximumPostageAmount: 10.25 # Caller-approved ceiling in USD.
49
+ }
50
+ preview = client.shipping.create_label(request)
51
+ puts preview["quotedPostageAmount"]
52
+
53
+ # Call only after explicit caller approval, within five minutes.
54
+ purchase_approved_label = lambda do |purchase_key|
55
+ client.shipping.create_label(
56
+ request.merge(confirmationToken: preview["confirmationToken"]),
57
+ idempotency_key: purchase_key # Persist per purchase; reuse on retries.
58
+ )
59
+ end
55
60
 
56
61
  # Track a shipment
57
62
  info = client.shipping.track("9400111899223456789012")
58
63
  ```
59
64
 
65
+ ### Live label approval (1.1.0+)
66
+
67
+ The example below requires SDK 1.1.0 or later. Version 1.0.2 does not
68
+ include the per-call idempotency argument.
69
+
70
+ For live domestic single-label requests, `maximumPostageAmount` is required: positive
71
+ USD, at most two decimal places, up to 1,000,000. Missing or invalid values return
72
+ 400 `ApprovalRequired`. Omitting `confirmationToken` returns a raw 200 preview
73
+ (`status`, `quotedPostageAmount`, `maximumPostageAmount`, `currency`, `expiresAt`,
74
+ `confirmationToken`). After explicit approval, resubmit the same shipment and ceiling
75
+ with the token and a unique per-purchase `Idempotency-Key` header. The token expires
76
+ after five minutes. A successful purchase returns the raw 201 label, including
77
+ `labelId`, `trackingNumber`, `carrierCode`, and `labelData`, without a `data` wrapper.
78
+
79
+ Keep the same key and request across retries. An uncertain outcome needs reconciliation;
80
+ do not start another purchase with a new key. An expired approval returns 409
81
+ `ApprovalExpired`; obtain and explicitly approve a fresh preview when no purchase is
82
+ unresolved. The ceiling bounds postage authorization, not later adjustments or separate
83
+ fees. Sandbox execution bypasses approval; batch, return, and raw carrier routes have
84
+ separate contracts. The SDK never automatically confirms a preview.
85
+
60
86
  ## Authentication
61
87
 
62
88
  ### API key (recommended)
@@ -128,18 +154,22 @@ curl -X POST https://gateway.flexops.io/api/shipping/rates \
128
154
  "package": {"weight": 16, "weightUnit": "oz"}
129
155
  }'
130
156
 
131
- # Create a label
157
+ # Preview a live label; this does not purchase it.
132
158
  curl -X POST https://gateway.flexops.io/api/workspaces/ws_abc123/shipping/labels \
133
159
  -H "X-API-Key: fxk_live_..." \
134
160
  -H "Content-Type: application/json" \
135
161
  -d '{
136
- "carrier": "USPS",
137
- "service": "PRIORITY_MAIL",
138
- "fromAddress": {"name": "Warehouse", "street1": "123 Main St", "city": "New York", "state": "NY", "zip": "10001", "country": "US"},
139
- "toAddress": {"name": "Customer", "street1": "456 Oak Ave", "city": "Los Angeles", "state": "CA", "zip": "90210", "country": "US"},
140
- "parcel": {"weight": 16, "weightUnit": "oz"}
162
+ "carrierCode": "USPS", "serviceCode": "GROUND_ADVANTAGE",
163
+ "origin": {"name": "Warehouse", "addressLine1": "123 Main St", "city": "New York", "stateProvince": "NY", "postalCode": "10001", "countryCode": "US"},
164
+ "destination": {"name": "Customer", "addressLine1": "456 Oak Ave", "city": "Los Angeles", "stateProvince": "CA", "postalCode": "90210", "countryCode": "US"},
165
+ "package": {"weight": 16, "weightUnit": "oz"},
166
+ "maximumPostageAmount": 10.25
141
167
  }'
142
168
 
169
+ # After explicit approval, resend the same body with confirmationToken from
170
+ # the preview and an Idempotency-Key header unique to this purchase.
171
+ # See the SDK example above; retain that key for retries.
172
+
143
173
  # Track a shipment
144
174
  curl https://gateway.flexops.io/api/workspaces/ws_abc123/shipping/track/9400111899223456789012 \
145
175
  -H "X-API-Key: fxk_live_..."
data/lib/flexops/http.rb CHANGED
@@ -38,8 +38,8 @@ module FlexOps
38
38
  request(:get, path, query: query)
39
39
  end
40
40
 
41
- def post(path, body: nil, query: nil)
42
- request(:post, path, body: body, query: query)
41
+ def post(path, body: nil, query: nil, headers: {})
42
+ request(:post, path, body: body, query: query, headers: headers)
43
43
  end
44
44
 
45
45
  def put(path, body: nil)
@@ -56,7 +56,7 @@ module FlexOps
56
56
 
57
57
  private
58
58
 
59
- def request(method, path, body: nil, query: nil)
59
+ def request(method, path, body: nil, query: nil, headers: {})
60
60
  uri = build_uri(path, query)
61
61
  last_error = nil
62
62
 
@@ -66,6 +66,7 @@ module FlexOps
66
66
  end
67
67
 
68
68
  req = build_request(method, uri, body)
69
+ headers.each { |name, value| req[name] = value }
69
70
  begin
70
71
  response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
71
72
  open_timeout: @timeout, read_timeout: @timeout) do |http|
@@ -97,6 +98,7 @@ module FlexOps
97
98
  error = Error.new(
98
99
  error_body["message"] || "HTTP #{status}: #{response.message}",
99
100
  status: status,
101
+ code: error_body["errorCode"] || error_body["code"],
100
102
  errors: error_body["errors"]
101
103
  )
102
104
 
@@ -26,8 +26,11 @@ module FlexOps
26
26
  @http.post("/api/shipping/rates/fastest", body: request)
27
27
  end
28
28
 
29
- def create_label(request)
30
- @http.post("#{ws_path}/shipping/labels", body: request)
29
+ # Returns a raw preview or label; confirmation always belongs to the caller.
30
+ def create_label(request = {}, idempotency_key: nil, **fields)
31
+ request = request.merge(fields)
32
+ @http.post("#{ws_path}/shipping/labels", body: request,
33
+ headers: idempotency_key.nil? ? {} : { "Idempotency-Key" => idempotency_key })
31
34
  end
32
35
 
33
36
  def cancel_label(label_id)
@@ -7,5 +7,5 @@
7
7
  # ***********************************************************************
8
8
 
9
9
  module FlexOps
10
- VERSION = "1.0.2"
10
+ VERSION = "1.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: 1.0.2
4
+ version: 1.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-09-01 00:00:00.000000000 Z
11
+ date: 2026-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json