apertur-sdk 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.
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apertur
4
+ module Resources
5
+ # Manage API keys within a project.
6
+ class Keys
7
+ # @param http [Apertur::HttpClient]
8
+ def initialize(http)
9
+ @http = http
10
+ end
11
+
12
+ # List all API keys for a project.
13
+ #
14
+ # @param project_id [String] the project ID
15
+ # @return [Array<Hash>] list of API keys
16
+ def list(project_id)
17
+ @http.request(:get, "/api/v1/projects/#{project_id}/keys")
18
+ end
19
+
20
+ # Create a new API key.
21
+ #
22
+ # @param project_id [String] the project ID
23
+ # @param options [Hash] key configuration (e.g. +name+, +scopes+)
24
+ # @return [Hash] the created key, including the plaintext secret (shown only once)
25
+ def create(project_id, **options)
26
+ @http.request(:post, "/api/v1/projects/#{project_id}/keys", body: options)
27
+ end
28
+
29
+ # Update an existing API key.
30
+ #
31
+ # @param project_id [String] the project ID
32
+ # @param key_id [String] the key ID
33
+ # @param options [Hash] fields to update
34
+ # @return [Hash] the updated key
35
+ def update(project_id, key_id, **options)
36
+ @http.request(:patch, "/api/v1/projects/#{project_id}/keys/#{key_id}", body: options)
37
+ end
38
+
39
+ # Delete an API key.
40
+ #
41
+ # @param project_id [String] the project ID
42
+ # @param key_id [String] the key ID
43
+ # @return [nil]
44
+ def delete(project_id, key_id)
45
+ @http.request(:delete, "/api/v1/projects/#{project_id}/keys/#{key_id}")
46
+ end
47
+
48
+ # Set the destinations and long-polling configuration for a key.
49
+ #
50
+ # @param key_id [String] the key ID
51
+ # @param destination_ids [Array<String>] destination IDs to associate
52
+ # @param long_polling_enabled [Boolean] whether to enable long-polling (default: false)
53
+ # @return [Hash] the updated key-destinations mapping
54
+ def set_destinations(key_id, destination_ids, long_polling_enabled: false)
55
+ body = { destination_ids: destination_ids }
56
+ body[:long_polling_enabled] = long_polling_enabled unless long_polling_enabled.nil?
57
+ @http.request(:put, "/api/v1/keys/#{key_id}/destinations", body: body)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apertur
4
+ module Resources
5
+ # Poll upload sessions for new images and download them.
6
+ #
7
+ # Provides a blocking polling loop ({#poll_and_process}) that fetches,
8
+ # downloads, and acknowledges new images automatically.
9
+ class Polling
10
+ # @param http [Apertur::HttpClient]
11
+ def initialize(http)
12
+ @http = http
13
+ end
14
+
15
+ # List pending (un-acknowledged) images in a session.
16
+ #
17
+ # @param uuid [String] the session UUID
18
+ # @return [Hash] poll result containing an +images+ array
19
+ def list(uuid)
20
+ @http.request(:get, "/api/v1/upload-sessions/#{uuid}/poll")
21
+ end
22
+
23
+ # Download an image from a session.
24
+ #
25
+ # @param uuid [String] the session UUID
26
+ # @param image_id [String] the image ID
27
+ # @return [String] raw binary image data
28
+ def download(uuid, image_id)
29
+ @http.request_raw(:get, "/api/v1/upload-sessions/#{uuid}/images/#{image_id}")
30
+ end
31
+
32
+ # Acknowledge (mark as processed) an image.
33
+ #
34
+ # @param uuid [String] the session UUID
35
+ # @param image_id [String] the image ID
36
+ # @return [Hash] acknowledgement status
37
+ def ack(uuid, image_id)
38
+ @http.request(:post, "/api/v1/upload-sessions/#{uuid}/images/#{image_id}/ack")
39
+ end
40
+
41
+ # Blocking polling loop that fetches, downloads, and acknowledges images.
42
+ #
43
+ # Calls the provided block for each new image. The loop runs until the
44
+ # calling thread is interrupted or the block raises an exception.
45
+ #
46
+ # @param uuid [String] the session UUID
47
+ # @param interval [Numeric] seconds between poll cycles (default: 3)
48
+ # @yield [image, data] called for each new image
49
+ # @yieldparam image [Hash] image metadata from the poll response
50
+ # @yieldparam data [String] raw binary image data
51
+ # @return [void]
52
+ def poll_and_process(uuid, interval: 3, &handler)
53
+ raise ArgumentError, "A block is required" unless block_given?
54
+
55
+ loop do
56
+ result = list(uuid)
57
+ images = result["images"] || []
58
+
59
+ images.each do |image|
60
+ data = download(uuid, image["id"])
61
+ handler.call(image, data)
62
+ ack(uuid, image["id"])
63
+ end
64
+
65
+ sleep(interval)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apertur
4
+ module Resources
5
+ # Manage upload sessions.
6
+ #
7
+ # Upload sessions represent a time-limited context in which one or more
8
+ # images can be uploaded. Each session has a unique UUID, optional password
9
+ # protection, and configurable constraints.
10
+ class Sessions
11
+ # @param http [Apertur::HttpClient]
12
+ def initialize(http)
13
+ @http = http
14
+ end
15
+
16
+ # Create a new upload session.
17
+ #
18
+ # @param destination_ids [Array<String>, nil] destination IDs to deliver images to
19
+ # @param long_polling [Boolean, nil] whether to enable long-polling on this session
20
+ # @param tags [Array<String>, nil] tags to attach to the session
21
+ # @param expires_in_hours [Integer, nil] hours until the session expires
22
+ # @param expires_at [String, nil] ISO 8601 expiry timestamp
23
+ # @param max_images [Integer, nil] maximum number of images allowed
24
+ # @param allowed_mime_types [Array<String>, nil] allowed MIME types for uploads
25
+ # @param max_image_dimension [Integer, nil] maximum image dimension in pixels
26
+ # @param password [String, nil] optional password to protect the session
27
+ # @return [Hash] the created session details including +uuid+ and +upload_url+
28
+ def create(**options)
29
+ @http.request(:post, "/api/v1/upload-sessions", body: options)
30
+ end
31
+
32
+ # Retrieve an existing upload session by UUID.
33
+ #
34
+ # @param uuid [String] the session UUID
35
+ # @return [Hash] session details
36
+ def get(uuid)
37
+ @http.request(:get, "/api/v1/upload/#{uuid}/session")
38
+ end
39
+
40
+ # Update an existing upload session.
41
+ #
42
+ # @param uuid [String] the session UUID
43
+ # @param options [Hash] fields to update (same options as {#create})
44
+ # @return [Hash] the updated session
45
+ def update(uuid, **options)
46
+ @http.request(:patch, "/api/v1/upload-sessions/#{uuid}", body: options)
47
+ end
48
+
49
+ # List upload sessions with pagination.
50
+ #
51
+ # @param page [Integer, nil] page number
52
+ # @param page_size [Integer, nil] number of results per page
53
+ # @return [Hash] paginated list with +data+ and pagination metadata
54
+ def list(**params)
55
+ query = {}
56
+ query["page"] = params[:page].to_s if params[:page]
57
+ query["pageSize"] = params[:page_size].to_s if params[:page_size]
58
+ @http.request(:get, "/api/v1/sessions", query: query)
59
+ end
60
+
61
+ # List recent upload sessions.
62
+ #
63
+ # @param limit [Integer, nil] maximum number of sessions to return
64
+ # @return [Array<Hash>] recent sessions
65
+ def recent(**params)
66
+ query = {}
67
+ query["limit"] = params[:limit].to_s if params[:limit]
68
+ @http.request(:get, "/api/v1/sessions/recent", query: query)
69
+ end
70
+
71
+ # Get the QR code image for an upload session.
72
+ #
73
+ # @param uuid [String] the session UUID
74
+ # @param format [String, nil] image format (e.g. "png", "svg")
75
+ # @param size [Integer, nil] QR code size in pixels
76
+ # @param style [String, nil] QR code style
77
+ # @param fg [String, nil] foreground color
78
+ # @param bg [String, nil] background color
79
+ # @param border_size [Integer, nil] border size in pixels
80
+ # @param border_color [String, nil] border color
81
+ # @return [String] raw binary image data
82
+ def qr(uuid, **options)
83
+ query = {}
84
+ query["format"] = options[:format] if options[:format]
85
+ query["size"] = options[:size].to_s if options[:size]
86
+ query["style"] = options[:style] if options[:style]
87
+ query["fg"] = options[:fg] if options[:fg]
88
+ query["bg"] = options[:bg] if options[:bg]
89
+ query["borderSize"] = options[:border_size].to_s if options[:border_size]
90
+ query["borderColor"] = options[:border_color] if options[:border_color]
91
+ @http.request_raw(:get, "/api/v1/upload-sessions/#{uuid}/qr", query: query)
92
+ end
93
+
94
+ # Verify a session password.
95
+ #
96
+ # @param uuid [String] the session UUID
97
+ # @param password [String] the password to verify
98
+ # @return [Hash] result with +valid+ boolean
99
+ def verify_password(uuid, password)
100
+ @http.request(:post, "/api/v1/upload/#{uuid}/verify-password", body: { password: password })
101
+ end
102
+
103
+ # Get the delivery status for a session.
104
+ #
105
+ # @param uuid [String] the session UUID
106
+ # @return [Array<Hash>] delivery status records
107
+ def delivery_status(uuid)
108
+ @http.request(:get, "/api/v1/upload-sessions/#{uuid}/delivery-status")
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apertur
4
+ module Resources
5
+ # Retrieve account usage statistics.
6
+ class Stats
7
+ # @param http [Apertur::HttpClient]
8
+ def initialize(http)
9
+ @http = http
10
+ end
11
+
12
+ # Get current account statistics.
13
+ #
14
+ # @return [Hash] usage statistics (uploads, sessions, storage, etc.)
15
+ def get
16
+ @http.request(:get, "/api/v1/stats")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apertur
4
+ module Resources
5
+ # Upload images to a session.
6
+ #
7
+ # Supports both plaintext multipart uploads and client-side encrypted
8
+ # uploads using AES-256-GCM with RSA-OAEP key wrapping.
9
+ class Upload
10
+ # @param http [Apertur::HttpClient]
11
+ def initialize(http)
12
+ @http = http
13
+ end
14
+
15
+ # Upload an image to a session via multipart/form-data.
16
+ #
17
+ # @param uuid [String] the session UUID
18
+ # @param file [String, IO] a file path (String), an IO-like object responding
19
+ # to +read+, or raw image bytes (binary String)
20
+ # @param filename [String] the filename to send (default: "image.jpg")
21
+ # @param mime_type [String] the MIME type of the image (default: "image/jpeg")
22
+ # @param source [String, nil] an optional source identifier
23
+ # @param password [String, nil] session password if the session is protected
24
+ # @return [Hash] upload result
25
+ def image(uuid, file, filename: "image.jpg", mime_type: "image/jpeg", source: nil, password: nil)
26
+ file_data = read_file(file)
27
+
28
+ fields = {}
29
+ fields["source"] = source if source
30
+
31
+ headers = {}
32
+ headers["x-session-password"] = password if password
33
+
34
+ @http.request_multipart(
35
+ "/api/v1/upload/#{uuid}/images",
36
+ file_data,
37
+ filename: filename,
38
+ mime_type: mime_type,
39
+ fields: fields,
40
+ headers: headers
41
+ )
42
+ end
43
+
44
+ # Upload an encrypted image to a session.
45
+ #
46
+ # The image is encrypted client-side using AES-256-GCM, with the AES key
47
+ # wrapped by the server's RSA public key. The encrypted payload is sent as
48
+ # JSON with the +X-Aptr-Encrypted: default+ header.
49
+ #
50
+ # @param uuid [String] the session UUID
51
+ # @param file [String, IO] a file path, IO, or raw bytes
52
+ # @param public_key [String] the RSA public key in PEM format
53
+ # @param filename [String] the filename (default: "image.jpg")
54
+ # @param mime_type [String] the MIME type (default: "image/jpeg")
55
+ # @param source [String, nil] optional source identifier
56
+ # @param password [String, nil] session password if the session is protected
57
+ # @return [Hash] upload result
58
+ def image_encrypted(uuid, file, public_key, filename: "image.jpg", mime_type: "image/jpeg", source: nil, password: nil)
59
+ file_data = read_file(file)
60
+ encrypted = Apertur::Crypto.encrypt_image(file_data, public_key)
61
+
62
+ payload = encrypted.merge(
63
+ "filename" => filename,
64
+ "mimeType" => mime_type,
65
+ "source" => source || "sdk"
66
+ )
67
+
68
+ headers = {
69
+ "X-Aptr-Encrypted" => "default"
70
+ }
71
+ headers["x-session-password"] = password if password
72
+
73
+ @http.request(:post, "/api/v1/upload/#{uuid}/images", body: payload, headers: headers)
74
+ end
75
+
76
+ private
77
+
78
+ # Normalize file input to raw bytes.
79
+ #
80
+ # @param file [String, IO] file path, IO object, or raw bytes
81
+ # @return [String] raw binary data
82
+ def read_file(file)
83
+ if file.respond_to?(:read)
84
+ file.read.b
85
+ elsif file.is_a?(String) && File.exist?(file) && file.length < 1024
86
+ # Treat short strings that point to existing files as paths.
87
+ # Raw image bytes will almost never be < 1024 bytes AND match an
88
+ # existing filename, so this heuristic is safe in practice.
89
+ File.binread(file)
90
+ elsif file.is_a?(String)
91
+ file.b
92
+ else
93
+ raise ArgumentError, "Unsupported file input. Use a file path String, IO object, or raw String bytes."
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apertur
4
+ module Resources
5
+ # Browse completed uploads.
6
+ class Uploads
7
+ # @param http [Apertur::HttpClient]
8
+ def initialize(http)
9
+ @http = http
10
+ end
11
+
12
+ # List uploads with pagination.
13
+ #
14
+ # @param page [Integer, nil] page number
15
+ # @param page_size [Integer, nil] number of results per page
16
+ # @return [Hash] paginated list with +data+ and pagination metadata
17
+ def list(**params)
18
+ query = {}
19
+ query["page"] = params[:page].to_s if params[:page]
20
+ query["pageSize"] = params[:page_size].to_s if params[:page_size]
21
+ @http.request(:get, "/api/v1/uploads", query: query)
22
+ end
23
+
24
+ # List recent uploads.
25
+ #
26
+ # @param limit [Integer, nil] maximum number of uploads to return
27
+ # @return [Array<Hash>] recent uploads
28
+ def recent(**params)
29
+ query = {}
30
+ query["limit"] = params[:limit].to_s if params[:limit]
31
+ @http.request(:get, "/api/v1/uploads/recent", query: query)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apertur
4
+ module Resources
5
+ # Manage event webhooks within a project.
6
+ class Webhooks
7
+ # @param http [Apertur::HttpClient]
8
+ def initialize(http)
9
+ @http = http
10
+ end
11
+
12
+ # List all webhooks for a project.
13
+ #
14
+ # @param project_id [String] the project ID
15
+ # @return [Array<Hash>] list of webhooks
16
+ def list(project_id)
17
+ @http.request(:get, "/api/v1/projects/#{project_id}/webhooks")
18
+ end
19
+
20
+ # Create a new webhook.
21
+ #
22
+ # @param project_id [String] the project ID
23
+ # @param config [Hash] webhook configuration (e.g. +url+, +events+, +secret+)
24
+ # @return [Hash] the created webhook
25
+ def create(project_id, **config)
26
+ @http.request(:post, "/api/v1/projects/#{project_id}/webhooks", body: config)
27
+ end
28
+
29
+ # Update an existing webhook.
30
+ #
31
+ # @param project_id [String] the project ID
32
+ # @param webhook_id [String] the webhook ID
33
+ # @param config [Hash] fields to update
34
+ # @return [Hash] the updated webhook
35
+ def update(project_id, webhook_id, **config)
36
+ @http.request(:patch, "/api/v1/projects/#{project_id}/webhooks/#{webhook_id}", body: config)
37
+ end
38
+
39
+ # Delete a webhook.
40
+ #
41
+ # @param project_id [String] the project ID
42
+ # @param webhook_id [String] the webhook ID
43
+ # @return [nil]
44
+ def delete(project_id, webhook_id)
45
+ @http.request(:delete, "/api/v1/projects/#{project_id}/webhooks/#{webhook_id}")
46
+ end
47
+
48
+ # Send a test event to a webhook.
49
+ #
50
+ # @param project_id [String] the project ID
51
+ # @param webhook_id [String] the webhook ID
52
+ # @return [Hash] test result
53
+ def test(project_id, webhook_id)
54
+ @http.request(:post, "/api/v1/projects/#{project_id}/webhooks/#{webhook_id}/test")
55
+ end
56
+
57
+ # List delivery attempts for a webhook.
58
+ #
59
+ # @param project_id [String] the project ID
60
+ # @param webhook_id [String] the webhook ID
61
+ # @param page [Integer, nil] page number
62
+ # @param limit [Integer, nil] number of results per page
63
+ # @return [Hash] paginated delivery list
64
+ def deliveries(project_id, webhook_id, **options)
65
+ query = {}
66
+ query["page"] = options[:page].to_s if options[:page]
67
+ query["limit"] = options[:limit].to_s if options[:limit]
68
+ @http.request(:get, "/api/v1/projects/#{project_id}/webhooks/#{webhook_id}/deliveries", query: query)
69
+ end
70
+
71
+ # Retry a failed delivery attempt.
72
+ #
73
+ # @param project_id [String] the project ID
74
+ # @param webhook_id [String] the webhook ID
75
+ # @param delivery_id [String] the delivery ID
76
+ # @return [Hash] retry result
77
+ def retry_delivery(project_id, webhook_id, delivery_id)
78
+ @http.request(
79
+ :post,
80
+ "/api/v1/projects/#{project_id}/webhooks/#{webhook_id}/deliveries/#{delivery_id}/retry"
81
+ )
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module Apertur
6
+ # Webhook signature verification utilities.
7
+ #
8
+ # Provides constant-time signature verification for three webhook formats
9
+ # used by the Apertur platform.
10
+ module Signature
11
+ module_function
12
+
13
+ # Verify an image delivery webhook signature.
14
+ #
15
+ # The signature header is formatted as +sha256=<hex>+ and is computed as
16
+ # +HMAC-SHA256(body, secret)+.
17
+ #
18
+ # @param body [String] the raw request body
19
+ # @param signature [String] the signature header value (e.g. "sha256=abc123...")
20
+ # @param secret [String] the webhook signing secret
21
+ # @return [Boolean] true if the signature is valid
22
+ def verify_webhook(body, signature, secret)
23
+ expected = OpenSSL::HMAC.hexdigest("SHA256", secret, body)
24
+ sig = signature.start_with?("sha256=") ? signature[7..] : signature
25
+ secure_compare(expected, sig)
26
+ end
27
+
28
+ # Verify an event webhook signature (HMAC SHA256 method).
29
+ #
30
+ # The signed payload is +"\#{timestamp}.\#{body}"+ and the signature header
31
+ # is formatted as +sha256=<hex>+.
32
+ #
33
+ # @param body [String] the raw request body
34
+ # @param timestamp [String] the X-Apertur-Timestamp header value
35
+ # @param signature [String] the X-Apertur-Signature header value
36
+ # @param secret [String] the webhook signing secret
37
+ # @return [Boolean] true if the signature is valid
38
+ def verify_event(body, timestamp, signature, secret)
39
+ signature_base = "#{timestamp}.#{body}"
40
+ expected = OpenSSL::HMAC.hexdigest("SHA256", secret, signature_base)
41
+ sig = signature.start_with?("sha256=") ? signature[7..] : signature
42
+ secure_compare(expected, sig)
43
+ end
44
+
45
+ # Verify an event webhook signature (Svix method).
46
+ #
47
+ # The signed payload is +"\#{svix_id}.\#{timestamp}.\#{body}"+ and the
48
+ # signing key is the secret decoded from hex. The signature header is
49
+ # formatted as +v1,<base64>+.
50
+ #
51
+ # @param body [String] the raw request body
52
+ # @param svix_id [String] the svix-id header value
53
+ # @param timestamp [String] the svix-timestamp header value
54
+ # @param signature [String] the svix-signature header value (e.g. "v1,base64...")
55
+ # @param secret [String] the webhook signing secret (hex-encoded)
56
+ # @return [Boolean] true if the signature is valid
57
+ def verify_svix(body, svix_id, timestamp, signature, secret)
58
+ signature_base = "#{svix_id}.#{timestamp}.#{body}"
59
+ key = [secret].pack("H*")
60
+ expected = OpenSSL::HMAC.digest("SHA256", key, signature_base)
61
+ expected_b64 = [expected].pack("m0")
62
+ sig = signature.start_with?("v1,") ? signature[3..] : signature
63
+ secure_compare(expected_b64, sig)
64
+ end
65
+
66
+ # Constant-time string comparison to prevent timing attacks.
67
+ #
68
+ # @param a [String]
69
+ # @param b [String]
70
+ # @return [Boolean]
71
+ def secure_compare(a, b)
72
+ return false unless a.bytesize == b.bytesize
73
+
74
+ OpenSSL.fixed_length_secure_compare(a, b)
75
+ rescue StandardError
76
+ false
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apertur
4
+ VERSION = "0.1.0"
5
+ end
data/lib/apertur.rb ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "apertur/version"
4
+ require_relative "apertur/errors"
5
+ require_relative "apertur/http_client"
6
+ require_relative "apertur/signature"
7
+ require_relative "apertur/crypto"
8
+ require_relative "apertur/resources/sessions"
9
+ require_relative "apertur/resources/upload"
10
+ require_relative "apertur/resources/uploads"
11
+ require_relative "apertur/resources/polling"
12
+ require_relative "apertur/resources/destinations"
13
+ require_relative "apertur/resources/keys"
14
+ require_relative "apertur/resources/webhooks"
15
+ require_relative "apertur/resources/encryption"
16
+ require_relative "apertur/resources/stats"
17
+ require_relative "apertur/client"
18
+
19
+ # Ruby SDK for the Apertur image upload API.
20
+ #
21
+ # @example Quick start
22
+ # require "apertur"
23
+ #
24
+ # client = Apertur::Client.new(api_key: "aptr_test_abc123")
25
+ # session = client.sessions.create(max_images: 5)
26
+ # client.upload.image(session["uuid"], "/path/to/photo.jpg")
27
+ #
28
+ # @example Verify a webhook signature
29
+ # Apertur::Signature.verify_webhook(request_body, signature_header, secret)
30
+ #
31
+ # @see Apertur::Client
32
+ # @see Apertur::Signature
33
+ # @see Apertur::Crypto
34
+ module Apertur; end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apertur-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Apertur
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-04-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Official Ruby client for the Apertur image upload and delivery API. Supports
14
+ session management, image uploads (including client-side encryption), polling, destinations,
15
+ webhooks, and more.
16
+ email:
17
+ - support@aptr.ca
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE
23
+ - README.md
24
+ - lib/apertur.rb
25
+ - lib/apertur/client.rb
26
+ - lib/apertur/crypto.rb
27
+ - lib/apertur/errors.rb
28
+ - lib/apertur/http_client.rb
29
+ - lib/apertur/resources/destinations.rb
30
+ - lib/apertur/resources/encryption.rb
31
+ - lib/apertur/resources/keys.rb
32
+ - lib/apertur/resources/polling.rb
33
+ - lib/apertur/resources/sessions.rb
34
+ - lib/apertur/resources/stats.rb
35
+ - lib/apertur/resources/upload.rb
36
+ - lib/apertur/resources/uploads.rb
37
+ - lib/apertur/resources/webhooks.rb
38
+ - lib/apertur/signature.rb
39
+ - lib/apertur/version.rb
40
+ homepage: https://github.com/Apertur-dev/apertur-ruby
41
+ licenses:
42
+ - MIT
43
+ metadata:
44
+ homepage_uri: https://github.com/Apertur-dev/apertur-ruby
45
+ source_code_uri: https://github.com/Apertur-dev/apertur-ruby
46
+ changelog_uri: https://github.com/Apertur-dev/apertur-ruby/blob/main/CHANGELOG.md
47
+ documentation_uri: https://docs.apertur.ca
48
+ rubygems_mfa_required: 'true'
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '3.0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubygems_version: 3.3.5
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: Ruby SDK for the Apertur API
68
+ test_files: []