exporto_api 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 18c37f30a8751a2ca78301ecb299f4b3caf58958442d8d66b03bc6d2f62aa796
4
+ data.tar.gz: 1919fdc84b42b1b74542c61778fe9ccd82339bc7294afa77e0f0b7ca3c9d1b14
5
+ SHA512:
6
+ metadata.gz: 583ed97d21592c273c8e9ccb1e8f95634a0e1dfac4ab6c0c7f71e5d2b28c19071ccfe1a7c60a96fb2ab58ab3a0dee3e433ce83d2e59c2c575e1a2c56caa66a18
7
+ data.tar.gz: 97c0adbe8af0faf45090f10ed227204da9d37b945593b7cca964d098ba0f40bfeafa9ad9fa6214cbe8f30164410337ff70f0d27824721086729620be945b7de3
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.1.0] - 2026-09-02
6
+
7
+ ### Added
8
+
9
+ - Rails-independent live and staging clients with injectable Faraday adapters.
10
+ - OAuth client-credentials authentication and Bearer-authenticated API requests.
11
+ - Snake-case response objects with deeply frozen access to the original provider response.
12
+ - Typed authentication, validation, not-found, rate-limit, server, and transport errors with safe metadata.
13
+ - Label-method discovery through `client.label_method.all`.
14
+ - Shipment search and retrieval through `client.shipment.search` and `client.shipment.find`.
15
+ - Label creation through `client.label.create` without automatic mutation retries.
16
+ - Return-shipment registration through `client.return_shipment.create`, returning a typed success response for every `2xx` response.
17
+
18
+ ### Out of scope
19
+
20
+ - Token caching, expiry buffers, refresh, and concurrency policy remain caller responsibilities.
21
+ - Shipment deletion and carrier-label cancellation are not part of the initial public API.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 PostCo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # ExportoAPI
2
+
3
+ Rails-independent Ruby client for the Exporto API.
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "exporto_api", "~> 0.1.0"
11
+ ```
12
+
13
+ Then run `bundle install`.
14
+
15
+ ## Usage
16
+
17
+ ### Initialize clients
18
+
19
+ Exporto uses a separate OAuth client to issue an access token. Set the environment once and pass the same `sandbox:` value to both clients so the token is used only in the environment that issued it.
20
+
21
+ ```ruby
22
+ require "exporto_api"
23
+
24
+ sandbox = true # Use Exporto's staging environment
25
+
26
+ auth_client = ExportoAPI::AuthClient.new(
27
+ username: "EXPORTO_USERNAME",
28
+ password: "EXPORTO_PASSWORD",
29
+ sandbox: sandbox
30
+ )
31
+
32
+ token = auth_client.token
33
+
34
+ client = ExportoAPI::Client.new(
35
+ access_token: token.access_token,
36
+ sandbox: sandbox
37
+ )
38
+ ```
39
+
40
+ `AuthClient#token` also accepts an optional space-delimited `scope:` string. Its response exposes `access_token`, `token_type`, `expires_in`, and `scope`.
41
+
42
+ The gem does not cache or refresh tokens. The caller owns token caching by Exporto account, environment, and requested scope, using the returned `expires_in` value and an application-defined safety buffer.
43
+
44
+ ### List label methods
45
+
46
+ ```ruby
47
+ label_methods = client.label_method.all
48
+
49
+ label_methods.each do |method|
50
+ puts [method.method_id, method.name, method.direction, method.carrier_name]
51
+ end
52
+ ```
53
+
54
+ The gem returns every method available to the authenticated account. The caller is responsible for selecting an approved method.
55
+
56
+ ### Search shipments
57
+
58
+ ```ruby
59
+ shipments = client.shipment.search(
60
+ type: "outbound",
61
+ foreign_outbound_tracking_id: "OUTBOUND-TRACKING-ID",
62
+ page: 0,
63
+ page_size: 50
64
+ )
65
+ ```
66
+
67
+ Shipment search also accepts `foreign_inbound_tracking_id`, `processed_at`, `carrier_received_at_updated_at`, and `carrier_delivered_at_updated_at`.
68
+
69
+ ### Retrieve a shipment
70
+
71
+ ```ruby
72
+ shipment = client.shipment.find(shipment_id: "EXPORTO-SHIPMENT-ID")
73
+
74
+ puts shipment.shipment_id
75
+ puts shipment.order_id
76
+ puts shipment.status
77
+ puts shipment.carrier_state
78
+ ```
79
+
80
+ ### Create a label
81
+
82
+ Pass an Exporto-shaped payload to `client.label.create`:
83
+
84
+ ```ruby
85
+ label = client.label.create(
86
+ "order" => {"customerFacingId" => "ORDER-123"},
87
+ "product" => {
88
+ "methodId" => 123,
89
+ "format" => "PDF"
90
+ },
91
+ "package" => {
92
+ "weight" => 1_000,
93
+ "reference" => "RETURN-123"
94
+ },
95
+ "address" => {
96
+ "name" => "Return Sender",
97
+ "line1" => "1 Example Street",
98
+ "city" => "London",
99
+ "postCode" => "SW1A 1AA",
100
+ "countryCode" => "GB",
101
+ "email" => "sender@example.com"
102
+ }
103
+ )
104
+
105
+ puts label.carrier_name
106
+ puts label.tracking_code
107
+ puts label.tracking_url
108
+ ```
109
+
110
+ For an inbound label, `address` is the return sender's address. The account-specific `methodId` determines the carrier and shipment direction.
111
+
112
+ ### Register a return shipment
113
+
114
+ ```ruby
115
+ registration = client.return_shipment.create(
116
+ order_id: "EXPORTO-ORDER-ID",
117
+ shipment_id: "POSTCO-GENERATED-SHIPMENT-ID",
118
+ foreign_inbound_tracking_id: label.tracking_code
119
+ )
120
+
121
+ registration.success # => true
122
+ ```
123
+
124
+ Return-shipment registration maps its Ruby keyword arguments to Exporto's request body for `POST /order/return-shipment`. Provide exactly one of `order_id` or `customer_facing_id`, together with `shipment_id` and `foreign_inbound_tracking_id`. Because Exporto returns no response body for this operation, a successful `2xx` response returns an `ExportoAPI::Objects::ReturnShipmentResponse` with `success: true`; unsuccessful responses raise the corresponding `ExportoAPI` error.
125
+
126
+ The gem does not automatically retry mutating requests. The caller owns idempotency, persistence, retry, and reconciliation policy.
127
+
128
+ ### Response objects
129
+
130
+ Response objects expose Exporto's camel-case keys through snake-case Ruby methods, including nested hashes and arrays. The original provider response remains available as a deeply frozen snapshot through `raw`.
131
+
132
+ ```ruby
133
+ shipment.shipment_id
134
+ shipment.line_items.first.article_id
135
+ shipment.raw
136
+ shipment.raw.frozen? # => true
137
+ ```
138
+
139
+ ### Error handling
140
+
141
+ HTTP failures raise typed subclasses of `ExportoAPI::Error`:
142
+
143
+ ```ruby
144
+ begin
145
+ client.shipment.find(shipment_id: "EXPORTO-SHIPMENT-ID")
146
+ rescue ExportoAPI::AuthenticationError => error
147
+ # 401/403 responses
148
+ puts error.message
149
+ rescue ExportoAPI::ValidationError => error
150
+ # 400 responses
151
+ puts error.message
152
+ rescue ExportoAPI::NotFoundError => error
153
+ # 404 responses
154
+ puts error.message
155
+ rescue ExportoAPI::RateLimitError => error
156
+ # 429 responses
157
+ puts error.retry_after
158
+ rescue ExportoAPI::ServerError => error
159
+ # 500-599 responses
160
+ puts error.message
161
+ rescue ExportoAPI::APIError => error
162
+ # Other HTTP and transport failures
163
+ puts error.message
164
+ end
165
+ ```
166
+
167
+ Errors retain safe metadata where available through `status_code`, `request_id`, and `retry_after`. Transport failures retain the original Faraday exception as their cause.
168
+
169
+ ### Sandbox mode
170
+
171
+ Both clients use Exporto's live environment by default. Set `sandbox: true` for staging, and always use the same value for token creation and authenticated requests:
172
+
173
+ ```ruby
174
+ sandbox = false # Live: https://api.exporto.de/v1/
175
+ # sandbox = true # Staging: https://staging.api.exporto.de/v1/
176
+ ```
177
+
178
+ ## Development
179
+
180
+ ```sh
181
+ bundle install
182
+ bundle exec rspec
183
+ bundle exec standardrb
184
+ gem build exporto_api.gemspec
185
+ ```
186
+
187
+ ## License
188
+
189
+ MIT License. See [LICENSE.txt](LICENSE.txt).
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ module ExportoAPI
6
+ class AuthClient
7
+ attr_reader :username, :password, :adapter
8
+
9
+ def initialize(username:, password:, sandbox: false, adapter: Faraday.default_adapter)
10
+ @username = username
11
+ @password = password
12
+ @sandbox = sandbox
13
+ @adapter = adapter
14
+ end
15
+
16
+ def token(scope: nil)
17
+ auth.token(scope: scope)
18
+ end
19
+
20
+ def connection
21
+ @connection ||= Faraday.new do |connection|
22
+ connection.url_prefix = sandbox? ? Client::TEST_BASE_URL : Client::LIVE_BASE_URL
23
+ connection.headers["Accept"] = "application/json"
24
+ connection.request :authorization, :basic, username, password
25
+ connection.request :json
26
+ connection.response :json, content_type: /\bjson/
27
+ connection.adapter adapter
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def auth
34
+ @auth ||= AuthResource.new(self)
35
+ end
36
+
37
+ def sandbox?
38
+ @sandbox
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ module ExportoAPI
6
+ class Client
7
+ LIVE_BASE_URL = "https://api.exporto.de/v1/"
8
+ TEST_BASE_URL = "https://staging.api.exporto.de/v1/"
9
+
10
+ attr_reader :access_token, :adapter
11
+
12
+ def initialize(access_token:, sandbox: false, adapter: Faraday.default_adapter)
13
+ @access_token = access_token
14
+ @sandbox = sandbox
15
+ @adapter = adapter
16
+ end
17
+
18
+ def label_method
19
+ @label_method ||= LabelMethodResource.new(self)
20
+ end
21
+
22
+ def shipment
23
+ @shipment ||= ShipmentResource.new(self)
24
+ end
25
+
26
+ def connection
27
+ @connection ||= Faraday.new do |connection|
28
+ connection.url_prefix = sandbox? ? TEST_BASE_URL : LIVE_BASE_URL
29
+ connection.headers["Authorization"] = "Bearer #{access_token}"
30
+ connection.headers["Accept"] = "application/json"
31
+ connection.request :json
32
+ connection.response :json, content_type: /\bjson/
33
+ connection.adapter adapter
34
+ end
35
+ end
36
+
37
+ def label
38
+ @label ||= LabelResource.new(self)
39
+ end
40
+
41
+ def return_shipment
42
+ @return_shipment ||= ReturnShipmentResource.new(self)
43
+ end
44
+
45
+ private
46
+
47
+ def sandbox?
48
+ @sandbox
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ class Error < StandardError
5
+ attr_reader :response, :status_code, :request_id, :retry_after
6
+
7
+ def initialize(message = nil, response: nil, status_code: nil, request_id: nil, retry_after: nil)
8
+ super(message)
9
+ @response = response
10
+ @status_code = status_code
11
+ @request_id = request_id
12
+ @retry_after = retry_after
13
+ end
14
+ end
15
+
16
+ class APIError < Error; end
17
+
18
+ class AuthenticationError < Error; end
19
+
20
+ class ValidationError < Error; end
21
+
22
+ class NotFoundError < Error; end
23
+
24
+ class RateLimitError < Error; end
25
+
26
+ class ServerError < Error; end
27
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+ require "active_support/core_ext/string/inflections"
5
+
6
+ module ExportoAPI
7
+ class Base < OpenStruct
8
+ attr_reader :original_response
9
+
10
+ def self.new(attributes)
11
+ return attributes.map { |item| super(item) } if attributes.is_a?(Array)
12
+
13
+ super
14
+ end
15
+
16
+ def initialize(attributes)
17
+ @original_response = deep_freeze(attributes)
18
+ super(to_ostruct(attributes))
19
+ end
20
+
21
+ def to_hash
22
+ ostruct_to_hash(self)
23
+ end
24
+
25
+ alias_method :to_h, :to_hash
26
+
27
+ def raw
28
+ @original_response
29
+ end
30
+
31
+ private
32
+
33
+ def to_ostruct(object)
34
+ case object
35
+ when Hash
36
+ OpenStruct.new(
37
+ object.transform_keys { |key| key.to_s.underscore }
38
+ .transform_values { |value| to_ostruct(value) }
39
+ )
40
+ when Array
41
+ object.map { |value| to_ostruct(value) }
42
+ else
43
+ object
44
+ end
45
+ end
46
+
47
+ def deep_freeze(object)
48
+ case object
49
+ when Hash then object.transform_values { |value| deep_freeze(value) }.freeze
50
+ when Array then object.map { |item| deep_freeze(item) }.freeze
51
+ else object.respond_to?(:freeze) ? object.freeze : object
52
+ end
53
+ end
54
+
55
+ def ostruct_to_hash(object)
56
+ case object
57
+ when OpenStruct
58
+ object.each_pair.to_h
59
+ .transform_keys(&:to_s)
60
+ .transform_values { |value| ostruct_to_hash(value) }
61
+ when Array
62
+ object.map { |value| ostruct_to_hash(value) }
63
+ when Hash
64
+ object.transform_keys(&:to_s)
65
+ .transform_values { |value| ostruct_to_hash(value) }
66
+ else
67
+ object
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ module Objects
5
+ class LabelMethodResponse < Base; end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ module Objects
5
+ class LabelResponse < Base; end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ module Objects
5
+ class ReturnShipmentResponse < Base; end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ module Objects
5
+ class ShipmentResponse < Base; end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ module Objects
5
+ class TokenResponse < Base; end
6
+ end
7
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module ExportoAPI
6
+ class Resource
7
+ attr_reader :client
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+
13
+ private
14
+
15
+ def get_request(path, params: {}, headers: {})
16
+ handle_response(client.connection.get(path, params, headers))
17
+ rescue Faraday::Error => error
18
+ raise_transport_error(error)
19
+ end
20
+
21
+ def post_request(path, body:, headers: {})
22
+ handle_response(client.connection.post(path, body, headers))
23
+ rescue Faraday::Error => error
24
+ raise_transport_error(error)
25
+ end
26
+
27
+ def handle_response(response)
28
+ body = parse_body(response.body)
29
+ return body if response.status.between?(200, 299)
30
+
31
+ error_class, prefix = error_mapping(response.status)
32
+ message = "#{prefix} (HTTP #{response.status}): #{extract_error_message(body)}"
33
+ raise error_class.new(
34
+ message,
35
+ response: response,
36
+ status_code: response.status,
37
+ request_id: extract_request_id(response, body),
38
+ retry_after: extract_retry_after(response)
39
+ )
40
+ end
41
+
42
+ def parse_body(body)
43
+ return body unless body.is_a?(String)
44
+
45
+ JSON.parse(body)
46
+ rescue JSON::ParserError
47
+ body
48
+ end
49
+
50
+ def extract_error_message(body)
51
+ case body
52
+ when Hash
53
+ [body["message"], body[:message], body["error"], body[:error]]
54
+ .find { |value| value.is_a?(String) } || "Unknown error"
55
+ when String then body
56
+ else "Unknown error"
57
+ end
58
+ end
59
+
60
+ def error_mapping(status)
61
+ case status
62
+ when 400 then [ValidationError, "Bad request"]
63
+ when 401, 403 then [AuthenticationError, "Authentication failed"]
64
+ when 404 then [NotFoundError, "Resource not found"]
65
+ when 429 then [RateLimitError, "Rate limited"]
66
+ when 500..599 then [ServerError, "Server error"]
67
+ else [APIError, "API error"]
68
+ end
69
+ end
70
+
71
+ def extract_request_id(response, body)
72
+ response.headers["x-request-id"] || request_id_from(body)
73
+ end
74
+
75
+ def request_id_from(body)
76
+ return unless body.is_a?(Hash)
77
+
78
+ body["requestId"] || body[:requestId] || body["request_id"] || body[:request_id]
79
+ end
80
+
81
+ def extract_retry_after(response)
82
+ Integer(response.headers["retry-after"], exception: false)
83
+ end
84
+
85
+ def raise_transport_error(error)
86
+ wrapped_error = APIError.new(
87
+ "Network request failed",
88
+ response: error.response,
89
+ status_code: error.response_status
90
+ )
91
+ raise wrapped_error, cause: error
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ class AuthResource < Resource
5
+ def token(scope: nil)
6
+ body = {"grant_type" => "client_credentials"}
7
+ body["scope"] = scope unless scope.nil?
8
+
9
+ Objects::TokenResponse.new(post_request("auth/token", body: body))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ class LabelMethodResource < Resource
5
+ def all
6
+ Objects::LabelMethodResponse.new(get_request("label/methods"))
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ class LabelResource < Resource
5
+ def create(payload)
6
+ Objects::LabelResponse.new(post_request("label", body: payload))
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ class ReturnShipmentResource < Resource
5
+ def create(
6
+ shipment_id:,
7
+ foreign_inbound_tracking_id:,
8
+ order_id: nil,
9
+ customer_facing_id: nil
10
+ )
11
+ body = {
12
+ "orderId" => order_id,
13
+ "customerFacingId" => customer_facing_id,
14
+ "shipmentId" => shipment_id,
15
+ "foreignInboundTrackingId" => foreign_inbound_tracking_id
16
+ }.compact
17
+
18
+ post_request("order/return-shipment", body: body)
19
+
20
+ # Exporto returns no response body for a successful return-shipment registration.
21
+ Objects::ReturnShipmentResponse.new(success: true)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ class ShipmentResource < Resource
5
+ def search(
6
+ foreign_outbound_tracking_id: nil,
7
+ foreign_inbound_tracking_id: nil,
8
+ processed_at: nil,
9
+ carrier_received_at_updated_at: nil,
10
+ carrier_delivered_at_updated_at: nil,
11
+ page: nil,
12
+ page_size: nil,
13
+ type: nil
14
+ )
15
+ params = {
16
+ "foreignOutboundTrackingId" => foreign_outbound_tracking_id,
17
+ "foreignInboundTrackingId" => foreign_inbound_tracking_id,
18
+ "processedAt" => processed_at,
19
+ "carrierReceivedAtUpdatedAt" => carrier_received_at_updated_at,
20
+ "carrierDeliveredAtUpdatedAt" => carrier_delivered_at_updated_at,
21
+ "page" => page,
22
+ "pageSize" => page_size,
23
+ "type" => type
24
+ }.compact
25
+
26
+ Objects::ShipmentResponse.new(get_request("shipment/search", params: params))
27
+ end
28
+
29
+ def find(shipment_id:)
30
+ Objects::ShipmentResponse.new(get_request("shipment/#{shipment_id}"))
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExportoAPI
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "exporto_api/version"
4
+
5
+ module ExportoAPI
6
+ autoload :AuthClient, "exporto_api/auth_client"
7
+ autoload :Client, "exporto_api/client"
8
+ autoload :Base, "exporto_api/object"
9
+ autoload :Resource, "exporto_api/resource"
10
+ autoload :Error, "exporto_api/errors"
11
+ autoload :APIError, "exporto_api/errors"
12
+ autoload :AuthenticationError, "exporto_api/errors"
13
+ autoload :ValidationError, "exporto_api/errors"
14
+ autoload :NotFoundError, "exporto_api/errors"
15
+ autoload :RateLimitError, "exporto_api/errors"
16
+ autoload :ServerError, "exporto_api/errors"
17
+ autoload :AuthResource, "exporto_api/resources/auth_resource"
18
+ autoload :LabelResource, "exporto_api/resources/label_resource"
19
+ autoload :LabelMethodResource, "exporto_api/resources/label_method_resource"
20
+ autoload :ReturnShipmentResource, "exporto_api/resources/return_shipment_resource"
21
+ autoload :ShipmentResource, "exporto_api/resources/shipment_resource"
22
+
23
+ module Objects
24
+ autoload :LabelResponse, "exporto_api/objects/label_response"
25
+ autoload :LabelMethodResponse, "exporto_api/objects/label_method_response"
26
+ autoload :ReturnShipmentResponse, "exporto_api/objects/return_shipment_response"
27
+ autoload :ShipmentResponse, "exporto_api/objects/shipment_response"
28
+ autoload :TokenResponse, "exporto_api/objects/token_response"
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: exporto_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - PostCo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-09-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-net_http
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '7.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '7.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: standard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: A small Ruby client foundation for integrating with Exporto without depending
112
+ on Rails.
113
+ email:
114
+ - engineering@postco.co
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - CHANGELOG.md
120
+ - LICENSE.txt
121
+ - README.md
122
+ - lib/exporto_api.rb
123
+ - lib/exporto_api/auth_client.rb
124
+ - lib/exporto_api/client.rb
125
+ - lib/exporto_api/errors.rb
126
+ - lib/exporto_api/object.rb
127
+ - lib/exporto_api/objects/label_method_response.rb
128
+ - lib/exporto_api/objects/label_response.rb
129
+ - lib/exporto_api/objects/return_shipment_response.rb
130
+ - lib/exporto_api/objects/shipment_response.rb
131
+ - lib/exporto_api/objects/token_response.rb
132
+ - lib/exporto_api/resource.rb
133
+ - lib/exporto_api/resources/auth_resource.rb
134
+ - lib/exporto_api/resources/label_method_resource.rb
135
+ - lib/exporto_api/resources/label_resource.rb
136
+ - lib/exporto_api/resources/return_shipment_resource.rb
137
+ - lib/exporto_api/resources/shipment_resource.rb
138
+ - lib/exporto_api/version.rb
139
+ homepage: https://github.com/PostCo/exporto_api
140
+ licenses:
141
+ - MIT
142
+ metadata:
143
+ source_code_uri: https://github.com/PostCo/exporto_api
144
+ changelog_uri: https://github.com/PostCo/exporto_api/blob/main/CHANGELOG.md
145
+ rubygems_mfa_required: 'true'
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: 3.3.0
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubygems_version: 3.5.22
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Rails-independent Ruby client for the Exporto API
165
+ test_files: []