# Ruby SDK

<figure><img src="/files/ODu0RcghIskAmE8WvXHY" alt=""><figcaption></figcaption></figure>

## Download and Installation

### Installation

The Ruby SDK supports Ruby version 3.1 and above. For more information, see:

<https://rubygems.org/gems/keeper_secrets_manager>

#### **Install with gem:**

```bash
gem install keeper_secrets_manager -v 17.1.0
```

#### **Or add to your Gemfile:**

```ruby
gem 'keeper_secrets_manager', '~> 17.1'
```

Then run:

```bash
bundle install
```

### Source Code

Find the Ruby source code in the [GitHub repository](https://github.com/Keeper-Security/secrets-manager/tree/master/sdk/ruby)

## Using the SDK

### Initialize Storage

The Keeper Secrets Manager SDK requires a One-Time Access Token to initialize storage on a client device. After initialization, the SDK stores the configuration for future use.

```ruby
KeeperSecretsManager.new(token: token, config: storage, hostname: hostname, verify_ssl_certs: verify_ssl_certs)
```

| Parameter          | Type              | Required | Default                | Description                                                |
| ------------------ | ----------------- | -------- | ---------------------- | ---------------------------------------------------------- |
| `token`            | `String`          | Optional | `nil`                  | One-Time Access Token for initial binding                  |
| `config`           | `KeyValueStorage` | Yes      | -                      | Storage implementation for configuration persistence       |
| `hostname`         | `String`          | Optional | `'keepersecurity.com'` | API hostname (e.g., 'keepersecurity.eu' for EU datacenter) |
| `verify_ssl_certs` | `Boolean`         | Optional | `true`                 | Enable/disable SSL certificate verification                |

> **Note:** Using a One-Time Access Token requires at least one read operation to bind the token and fully populate the configuration.

#### Example Usage

**Using a One-Time Access Token:**

```ruby
require 'keeper_secrets_manager'

token = "US:ONE_TIME_TOKEN_HERE"

# First time setup - bind the token
storage = KeeperSecretsManager::Storage::FileStorage.new('keeper_config.json')
secrets_manager = KeeperSecretsManager.new(token: token, config: storage)

# Complete the binding (requires at least one operation)
records = secrets_manager.get_secrets

# Verify config was saved
File.exist?('keeper_config.json')  # Should return true
```

#### Using File-Based Storage

For persistent configuration across application restarts:

```ruby
require 'keeper_secrets_manager'

# After first time binding, load from file
secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')
```

Alternatively connect with the base64 configuration file generated by your vault:

```ruby
require 'keeper_secrets_manager'

base64_config = File.read('config.base64').strip
storage = KeeperSecretsManager::Storage::InMemoryStorage.new(base64_config)
secrets_manager = KeeperSecretsManager.new(config: storage)
```

#### Using Environment Variables

For read-only configuration from environment:

```ruby
require 'keeper_secrets_manager'

# Set environment variables:
# export KSM_HOSTNAME=keepersecurity.com
# export KSM_CLIENT_ID=your-client-id
# export KSM_PRIVATE_KEY=your-private-key
# export KSM_APP_KEY=your-app-key
# export KSM_SERVER_PUBLIC_KEY_ID=10

# Load from environment
config = KeeperSecretsManager::Storage::EnvironmentStorage.new('KSM_')
secrets_manager = KeeperSecretsManager.new(config: config)
```

### Retrieve Secrets

#### Get Secrets

```ruby
get_secrets(uids = [])
```

| Parameter | Type            | Required | Default | Description                                                 |
| --------- | --------------- | -------- | ------- | ----------------------------------------------------------- |
| `uids`    | `Array<String>` | Optional | `[]`    | Record UIDs to retrieve. Empty array retrieves all secrets. |

**Response:**

**Type:** `Array<KeeperRecord>`

Array containing all Keeper records, or records that match the given UID filter.

#### Example Usage

**Retrieve All Secrets:**

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Retrieve all secrets
records = secrets_manager.get_secrets

records.each do |record|
  puts "#{record.title} (#{record.type})"
end
```

**Retrieve Secrets by UID:**

```ruby
# Get single secret by UID
records = secrets_manager.get_secrets(['RECORD_UID'])
record = records.first

# Get multiple secrets by UIDs
uids = ['UID1', 'UID2', 'UID3']
records = secrets_manager.get_secrets(uids)
```

#### Get Secrets by Title

```ruby
# Get all secrets matching title
get_secrets_by_title(title)

# Get first secret matching title
get_secret_by_title(title)
```

| Parameter | Type     | Required | Default | Description                              |
| --------- | -------- | -------- | ------- | ---------------------------------------- |
| `title`   | `String` | Yes      | -       | Record title to search for (exact match) |

**Response:**

**Type:** `Array<KeeperRecord>` (for `get_secrets_by_title`) or `KeeperRecord` (for `get_secret_by_title`)

Returns all records matching the title, or the first matching record.

#### Example Usage

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Get secret by exact title match
record = secrets_manager.get_secret_by_title('My Database Credentials')

# Get multiple secrets with same title
records = secrets_manager.get_secrets_by_title('My Login')

# Access fields from the record
puts "Login: #{record.login}"
puts "Password: #{record.password}"
```

### Retrieve Values From a Secret

Once a secret is retrieved, individual fields can be accessed using multiple approaches:

#### Using Dynamic Field Access

```ruby
record = secrets_manager.get_secrets(['RECORD_UID']).first

# Access standard fields dynamically
login = record.login
password = record.password
url = record.url

# Access complex fields
host = record.host  # Returns hash with hostName and port
```

#### Using Explicit Field Methods

```ruby
# Get single field value (returns first value)
login = record.get_field_value_single('login')

# Get all values for a field (returns array)
passwords = record.get_field_value('password')

# Access custom fields by label
api_key = record.get_field_value_single('API Key')
environment = record.get_field_value_single('Environment')
```

#### Using Keeper Notation

```ruby
# Access fields using URI-style notation
password = secrets_manager.get_notation("keeper://#{record.uid}/field/password")

# Access by record title
url = secrets_manager.get_notation("keeper://My Login/field/url")

# Access complex field properties
hostname = secrets_manager.get_notation("keeper://#{record.uid}/field/host[hostName]")
port = secrets_manager.get_notation("keeper://#{record.uid}/field/host[port]")

# Access custom fields
env = secrets_manager.get_notation("keeper://#{record.uid}/custom_field/Environment")
```

### Retrieve a TOTP Code

To generate a TOTP code, retrieve the TOTP URL from the record and use the `KeeperSecretsManager::TOTP` module.

```ruby
# Get TOTP URL from record
totp_url = record.get_field_value_single('oneTimeCode')

# Parse and generate code
require 'keeper_secrets_manager/totp'
totp_params = KeeperSecretsManager::TOTP.parse_url(totp_url)
totp_code = KeeperSecretsManager::TOTP.generate_code(
  totp_params['secret'],
  algorithm: totp_params['algorithm'],
  digits: totp_params['digits'],
  period: totp_params['period']
)
```

| Parameter   | Type      | Required | Default  | Description                           |
| ----------- | --------- | -------- | -------- | ------------------------------------- |
| `secret`    | `String`  | Yes      | -        | Base32-encoded TOTP secret            |
| `algorithm` | `String`  | Optional | `'SHA1'` | Hash algorithm (SHA1, SHA256, SHA512) |
| `digits`    | `Integer` | Optional | `6`      | Number of digits in the code          |
| `period`    | `Integer` | Optional | `30`     | Time period in seconds                |

**Response:**

**Type:** `String`

Returns the current Time-Based One-Time Password (TOTP) code for two-factor authentication.

#### Example Usage

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Get record with TOTP
record = secrets_manager.get_secrets(['RECORD_UID']).first

# Get TOTP URL from record
totp_url = record.get_field_value_single('oneTimeCode')

# Parse TOTP parameters
require 'keeper_secrets_manager/totp'
totp_params = KeeperSecretsManager::TOTP.parse_url(totp_url)

# Generate TOTP code
totp_code = KeeperSecretsManager::TOTP.generate_code(
  totp_params['secret'],
  algorithm: totp_params['algorithm'],
  digits: totp_params['digits'],
  period: totp_params['period']
)

puts "Current TOTP code: #{totp_code}"
puts "Expires in: #{30 - (Time.now.to_i % 30)} seconds"
```

> **Note:** TOTP generation requires the `base32` gem for decoding the secret. Install with `gem install base32`.

### Generate a Random Password

Generate cryptographically secure random passwords with customizable character requirements. This utility function is useful when creating or updating secrets programmatically.

```ruby
KeeperSecretsManager::Utils.generate_password(
  length: 64,
  lowercase: 0,
  uppercase: 0,
  digits: 0,
  special_characters: 0
)
```

| Parameter            | Type      | Required | Default | Description                                                 |
| -------------------- | --------- | -------- | ------- | ----------------------------------------------------------- |
| `length`             | `Integer` | Optional | `64`    | Total password length                                       |
| `lowercase`          | `Integer` | Optional | `0`     | Minimum number of lowercase letters (a-z)                   |
| `uppercase`          | `Integer` | Optional | `0`     | Minimum number of uppercase letters (A-Z)                   |
| `digits`             | `Integer` | Optional | `0`     | Minimum number of digit characters (0-9)                    |
| `special_characters` | `Integer` | Optional | `0`     | Minimum number of special characters (!@#$%^&\*()\_+-=\[]{} |

**Response:**

**Type:** `String`

Returns a cryptographically secure random password meeting the specified requirements.

> **Note:** This function uses Ruby's `SecureRandom` for cryptographic-strength randomness and applies Fisher-Yates shuffle to ensure proper character distribution.

#### Example Usage

**Generate a default 64-character password:**

```ruby
require 'keeper_secrets_manager'

# Generate with all defaults (64 random characters)
password = KeeperSecretsManager::Utils.generate_password
puts "Generated: #{password}"
# => "Xk9$mP2..."  (64 characters)
```

**Generate password with specific requirements:**

```ruby
# Generate 32-character password with specific minimums
password = KeeperSecretsManager::Utils.generate_password(
  length: 32,
  lowercase: 2,
  uppercase: 2,
  digits: 2,
  special_characters: 2
)

puts "Generated: #{password}"
# => "aB12$xY34..."  (32 chars with at least 2 of each type)

# Generate strong password with mixed requirements
password = KeeperSecretsManager::Utils.generate_password(
  length: 20,
  lowercase: 3,
  uppercase: 3,
  digits: 3,
  special_characters: 3
)
```

**Use when creating a new secret:**

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Create a new login record with generated password
record_data = {
  type: 'login',
  title: 'Production Database',
  fields: [
    { type: 'login', value: ['db_admin'] },
    {
      type: 'password',
      value: [KeeperSecretsManager::Utils.generate_password(
        length: 32,
        lowercase: 4,
        uppercase: 4,
        digits: 4,
        special_characters: 4
      )]
    },
    { type: 'url', value: ['https://db.example.com'] }
  ],
  notes: 'Auto-generated secure password'
}

# Create options with required folder_uid
options = KeeperSecretsManager::Dto::CreateOptions.new(folder_uid: 'FOLDER_UID')
record_uid = secrets_manager.create_secret(record_data, options)

puts "Created record with secure password: #{record_uid}"
```

**Use when updating an existing secret:**

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Get existing record
record = secrets_manager.get_secrets(['RECORD_UID']).first

# Generate and set new password
new_password = KeeperSecretsManager::Utils.generate_password(
  length: 40,
  lowercase: 5,
  uppercase: 5,
  digits: 5,
  special_characters: 5
)

record.password = new_password

# Save changes
secrets_manager.update_secret(record)

puts "Password updated with new secure value"
```

**Password rotation script:**

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Rotate passwords for multiple records
record_uids = ['UID1', 'UID2', 'UID3']

record_uids.each do |uid|
  record = secrets_manager.get_secrets([uid]).first

  # Generate new password
  new_password = KeeperSecretsManager::Utils.generate_password(
    length: 32,
    lowercase: 3,
    uppercase: 3,
    digits: 3,
    special_characters: 3
  )

  # Update record
  record.password = new_password
  record.notes = "Password rotated on #{Time.now}"

  secrets_manager.update_secret(record)

  puts "✓ Rotated password for: #{record.title}"
end
```

### Update a Secret

```ruby
update_secret(record)
```

| Parameter | Type           | Required | Default | Description                                |
| --------- | -------------- | -------- | ------- | ------------------------------------------ |
| `record`  | `KeeperRecord` | Yes      | -       | The modified record to update in the vault |

**Response:**

**Type:** `void`

Updates the secret in the vault with the modified values.

#### Example Usage

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Get existing record
record = secrets_manager.get_secrets(['RECORD_UID']).first

# Update fields using dynamic access
record.password = 'NewSecurePassword123!'

# Update fields using explicit method
record.set_field('login', 'new_username@example.com')

# Update notes
record.notes = "Updated on #{Time.now}"

# Save changes
secrets_manager.update_secret(record)

puts "Secret updated successfully"
```

### Download a File

```ruby
download_file(file)
```

| Parameter | Type         | Required | Default | Description                                 |
| --------- | ------------ | -------- | ------- | ------------------------------------------- |
| `file`    | `KeeperFile` | Yes      | -       | File object from a KeeperRecord to download |

**Response:**

**Type:** `Hash`

Returns a hash containing:

* `'name'` - File name
* `'data'` - File contents as binary string
* `'size'` - File size in bytes
* `'type'` - MIME type of the file

#### Example Usage

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Get record with files
record = secrets_manager.get_secrets(['RECORD_UID']).first

# Check if record has files
if record.files && record.files.any?
  # Download first file
  file = record.files.first
  downloaded = secrets_manager.download_file(file)

  # Save to disk
  filename = downloaded['name'] || 'downloaded_file'
  File.write(filename, downloaded['data'])

  puts "Downloaded: #{filename}"
  puts "Size: #{downloaded['size']} bytes"
  puts "Type: #{downloaded['type']}"
end
```

### Upload a File

```ruby
upload_file(owner_record_uid, file_data, file_name, file_title = nil)
```

| Parameter          | Type     | Required | Default | Description                             |
| ------------------ | -------- | -------- | ------- | --------------------------------------- |
| `owner_record_uid` | `String` | Yes      | -       | UID of the record to attach the file to |
| `file_data`        | `String` | Yes      | -       | File contents as binary string or text  |
| `file_name`        | `String` | Yes      | -       | Name of the file in Keeper              |
| `file_title`       | `String` | Optional | `nil`   | Title/description of the file in Keeper |

**Response:**

**Type:** `String`

Returns the UID of the uploaded file.

#### Example Usage

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Upload a file
file_uid = secrets_manager.upload_file(
  'RECORD_UID',                          # owner_record_uid
  File.read('/path/to/certificate.pem'), # file_data
  'certificate.pem',                      # file_name
  'Server Certificate'                    # file_title (optional)
)

puts "File uploaded with UID: #{file_uid}"

# Upload text content
file_uid = secrets_manager.upload_file(
  'RECORD_UID',
  "server=localhost\nport=5432\n",
  'config.txt',
  'Database Config'
)
```

### Create a Secret

```ruby
create_secret(record_data, options = nil)
```

| Parameter     | Type            | Required | Default | Description                                                                   |
| ------------- | --------------- | -------- | ------- | ----------------------------------------------------------------------------- |
| `record_data` | `Hash`          | Yes      | -       | Hash containing record structure (type, title, fields, custom, notes)         |
| `options`     | `CreateOptions` | Yes      | -       | CreateOptions object containing `folder_uid` (required) and optional settings |

**Response:**

**Type:** `String`

Returns the UID of the created secret.

> **Prerequisites:**
>
> * Shared folder UID (if specifying folder\_uid)
> * Shared folder must be accessible by the Secrets Manager Application
> * You and the Secrets Manager application must have edit permission
> * There must be at least one record in the shared folder
> * Record fields must be formatted correctly (see field type documentation)

#### Example Usage

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Create a login record
record_data = {
  type: 'login',
  title: 'Production Database',
  fields: [
    { type: 'login', value: ['db_admin'] },
    { type: 'password', value: ['SecurePassword123!'] },
    { type: 'url', value: ['https://db.example.com'] },
    {
      type: 'host',
      value: [{ hostName: '192.168.1.100', port: '5432' }],
      label: 'Database Host'
    }
  ],
  custom: [
    { type: 'text', label: 'Environment', value: ['Production'] },
    { type: 'text', label: 'Database Name', value: ['main_db'] }
  ],
  notes: 'Production database credentials'
}

# Create options with required folder_uid
options = KeeperSecretsManager::Dto::CreateOptions.new(folder_uid: 'FOLDER_UID')

# Create the secret
record_uid = secrets_manager.create_secret(record_data, options)

puts "Secret created with UID: #{record_uid}"

# Alternative: Create options inline
record_uid = secrets_manager.create_secret(
  record_data,
  KeeperSecretsManager::Dto::CreateOptions.new(folder_uid: 'FOLDER_UID')
)
```

#### Create Custom Type Record

```ruby
# Create a database credentials record
record_data = {
  type: 'databaseCredentials',
  title: 'MySQL Production',
  fields: [
    { type: 'text', label: 'Database Type', value: ['MySQL'] },
    {
      type: 'host',
      value: [{ hostName: 'mysql.example.com', port: '3306' }]
    },
    { type: 'login', value: ['root'] },
    { type: 'password', value: ['SecurePassword123!'] }
  ],
  notes: 'MySQL production database'
}

# Create options with required folder_uid
options = KeeperSecretsManager::Dto::CreateOptions.new(folder_uid: 'FOLDER_UID')
record_uid = secrets_manager.create_secret(record_data, options)
```

### Delete a Secret

```ruby
delete_secret(uids)
```

| Parameter | Type                        | Required | Default | Description                               |
| --------- | --------------------------- | -------- | ------- | ----------------------------------------- |
| `uids`    | `String` or `Array<String>` | Yes      | -       | UID or array of UIDs of secrets to delete |

**Response:**

**Type:** `void`

Deletes the specified secret(s) from the vault.

#### Example Usage

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Delete a single secret
secrets_manager.delete_secret('RECORD_UID')

puts "Secret deleted successfully"

# Delete multiple secrets
uids = ['UID1', 'UID2', 'UID3']
secrets_manager.delete_secret(uids)

puts "#{uids.length} secrets deleted"
```

### Folders

The Ruby SDK provides full CRUD support for folder operations.

#### Get Folders

```ruby
get_folders
```

**Response:**

**Type:** `Array<KeeperFolder>`

Returns all folders accessible to the Secrets Manager application.

#### Example Usage

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Get all folders
folders = secrets_manager.get_folders

folders.each do |folder|
  puts "#{folder.name} (UID: #{folder.uid})"
end
```

#### Get Folder Path

```ruby
get_folder_path(folder_uid)
```

| Parameter    | Type     | Required | Default | Description       |
| ------------ | -------- | -------- | ------- | ----------------- |
| `folder_uid` | `String` | Yes      | -       | UID of the folder |

**Response:**

**Type:** `String`

Returns the full path to the folder (breadcrumb trail separated by "/").

#### Example Usage

```ruby
# Get folder path (breadcrumb trail)
path = secrets_manager.get_folder_path('FOLDER_UID')
puts "Folder path: #{path}"  # "Parent/Child/Grandchild"
```

#### Find Folder by Name

```ruby
find_folder_by_name(name, parent_uid: nil)
```

| Parameter    | Type     | Required | Default | Description                           |
| ------------ | -------- | -------- | ------- | ------------------------------------- |
| `name`       | `String` | Yes      | -       | Name of the folder to find            |
| `parent_uid` | `String` | Optional | `nil`   | UID of parent folder to search within |

**Response:**

**Type:** `KeeperFolder` or `nil`

Returns the first folder matching the name, or nil if not found.

#### Example Usage

```ruby
# Find folder by name
folder = secrets_manager.find_folder_by_name('Finance')

# Find folder within specific parent
folder = secrets_manager.find_folder_by_name('Reports', parent_uid: 'PARENT_UID')
```

#### Build Folder Tree

```ruby
# Get folder manager for advanced operations
fm = secrets_manager.folder_manager

# Build complete folder tree structure
tree = fm.build_folder_tree

# Print folder tree to console
fm.print_tree

# Get folder relationships
ancestors = fm.get_ancestors('FOLDER_UID')      # [parent, grandparent, ...]
descendants = fm.get_descendants('FOLDER_UID')   # [children, grandchildren, ...]
```

#### Create a Folder

```ruby
create_folder(name, parent_uid:)
```

| Parameter    | Type     | Required | Default | Description                     |
| ------------ | -------- | -------- | ------- | ------------------------------- |
| `name`       | `String` | Yes      | -       | Name of the folder to create    |
| `parent_uid` | `String` | Yes      | -       | UID of the parent shared folder |

**Response:**

**Type:** `String`

Returns the UID of the created folder.

#### Example Usage

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Create folder at root level (within a shared folder)
folder_uid = secrets_manager.create_folder('New Folder', parent_uid: 'SHARED_FOLDER_UID')

puts "Folder created with UID: #{folder_uid}"

# Create subfolder within existing folder
subfolder_uid = secrets_manager.create_folder(
  'Subfolder',
  parent_uid: folder_uid
)

puts "Subfolder created: #{subfolder_uid}"
```

#### Update a Folder

```ruby
update_folder(folder_uid, new_name)
```

| Parameter    | Type     | Required | Default | Description                 |
| ------------ | -------- | -------- | ------- | --------------------------- |
| `folder_uid` | `String` | Yes      | -       | UID of the folder to update |
| `new_name`   | `String` | Yes      | -       | New name for the folder     |

**Response:**

**Type:** `void`

Renames the specified folder.

#### Example Usage

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Rename a folder
secrets_manager.update_folder('FOLDER_UID', 'New Folder Name')

puts "Folder renamed successfully"
```

#### Delete Folders

```ruby
delete_folder(folder_uid, force: false)
```

| Parameter    | Type      | Required | Default | Description                                                                         |
| ------------ | --------- | -------- | ------- | ----------------------------------------------------------------------------------- |
| `folder_uid` | `String`  | Yes      | -       | UID of the folder to delete                                                         |
| `force`      | `Boolean` | Optional | `false` | If true, deletes folder and all its contents. If false, only deletes empty folders. |

**Response:**

**Type:** `void`

Deletes the specified folder from the vault.

#### Example Usage

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')

# Delete empty folder
secrets_manager.delete_folder('FOLDER_UID')

# Force delete folder (removes all contents)
secrets_manager.delete_folder('FOLDER_UID', force: true)

puts "Folder deleted"

# Delete multiple folders
folder_uids = ['UID1', 'UID2', 'UID3']
folder_uids.each do |uid|
  secrets_manager.delete_folder(uid, force: true)
end
```

### Caching

Improve performance by caching secrets locally:

#### Using CachingStorage

```ruby
require 'keeper_secrets_manager'

# Create base storage
base_storage = KeeperSecretsManager::Storage::FileStorage.new('keeper_config.json')

# Wrap with caching (600 second TTL)
cached_storage = KeeperSecretsManager::Storage::CachingStorage.new(base_storage, 600)

# Use cached storage
secrets_manager = KeeperSecretsManager.new(config: cached_storage)

# First call fetches from server
records = secrets_manager.get_secrets

# Subsequent calls within TTL use cache
records = secrets_manager.get_secrets  # Uses cached data
```

#### Custom Caching with custom\_post\_function

For advanced caching scenarios:

```ruby
require 'keeper_secrets_manager'

# Create custom cache
cache = {}

custom_post = lambda do |url, payload|
  cache_key = "#{url}:#{payload}"

  # Check cache
  if cache[cache_key] && cache[cache_key][:expires_at] > Time.now
    return cache[cache_key][:response]
  end

  # Make actual request
  uri = URI(url)
  request = Net::HTTP::Post.new(uri)
  request['Content-Type'] = 'application/json'
  request.body = payload

  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
    http.request(request)
  end

  # Cache response for 10 minutes
  cache[cache_key] = {
    response: response.body,
    expires_at: Time.now + 600
  }

  response.body
end

secrets_manager = KeeperSecretsManager.from_file(
  'keeper_config.json',
  custom_post_function: custom_post
)
```

## Error Handling

The SDK provides specific exception classes for different error scenarios:

```ruby
require 'keeper_secrets_manager'

begin
  secrets_manager = KeeperSecretsManager.from_file('keeper_config.json')
  records = secrets_manager.get_secrets

rescue KeeperSecretsManager::AuthenticationError => e
  puts "Authentication failed: #{e.message}"
  # Token expired or invalid

rescue KeeperSecretsManager::NetworkError => e
  puts "Network error: #{e.message}"
  # Connection timeout or DNS failure

rescue KeeperSecretsManager::CryptoError => e
  puts "Encryption error: #{e.message}"
  # Decryption failed or key error

rescue KeeperSecretsManager::NotationError => e
  puts "Notation parsing error: #{e.message}"
  # Invalid notation URI format

rescue KeeperSecretsManager::Error => e
  puts "General error: #{e.message}"
  # Other SDK errors

rescue StandardError => e
  puts "Unexpected error: #{e.message}"
end
```

### Common Error Scenarios

```ruby
# Handle missing records gracefully
begin
  record = secrets_manager.get_secret_by_title('Nonexistent Record')
rescue KeeperSecretsManager::Error => e
  puts "Record not found: #{e.message}"
  # Provide fallback or create new record
end

# Retry logic for network errors
max_retries = 3
retries = 0

begin
  records = secrets_manager.get_secrets
rescue KeeperSecretsManager::NetworkError => e
  retries += 1
  if retries < max_retries
    sleep 2 ** retries  # Exponential backoff
    retry
  else
    raise
  end
end
```

## Advanced Configuration

### Custom Hostname

```ruby
require 'keeper_secrets_manager'

# EU datacenter
secrets_manager = KeeperSecretsManager.new(
  token: 'EU:ONE_TIME_TOKEN',
  hostname: 'keepersecurity.eu',
  config: storage
)

# Australian datacenter
secrets_manager = KeeperSecretsManager.new(
  token: 'AU:ONE_TIME_TOKEN',
  hostname: 'keepersecurity.com.au',
  config: storage
)
```

### SSL Certificate Verification

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.new(
  config: storage,
  verify_ssl_certs: true  # Default is true
)
```

### Custom Logging

```ruby
require 'keeper_secrets_manager'
require 'logger'

# Create custom logger
logger = Logger.new(STDOUT)
logger.level = Logger::DEBUG

secrets_manager = KeeperSecretsManager.new(
  config: storage,
  logger: logger,
  log_level: Logger::DEBUG
)
```

### All Configuration Options

```ruby
require 'keeper_secrets_manager'

secrets_manager = KeeperSecretsManager.new(
  token: 'US:ONE_TIME_TOKEN',           # One-time access token
  config: storage,                       # Storage implementation
  hostname: 'keepersecurity.com',        # API hostname
  verify_ssl_certs: true,                # SSL verification
  logger: Logger.new(STDOUT),            # Custom logger
  log_level: Logger::WARN,               # Log level
  custom_post_function: my_post_func     # Custom HTTP handler
)
```

## Field Type Helpers

Optional convenience methods for creating typed fields:

```ruby
require 'keeper_secrets_manager'

# Use field helpers for type safety
fields = [
  KeeperSecretsManager::FieldTypes::Helpers.login('admin'),
  KeeperSecretsManager::FieldTypes::Helpers.password('SecurePass123!'),
  KeeperSecretsManager::FieldTypes::Helpers.url('https://example.com'),
  KeeperSecretsManager::FieldTypes::Helpers.host(
    hostname: '192.168.1.100',
    port: 22
  ),
  KeeperSecretsManager::FieldTypes::Helpers.name(
    first: 'John',
    middle: 'Q',
    last: 'Doe'
  ),
  KeeperSecretsManager::FieldTypes::Helpers.address(
    street1: '123 Main St',
    city: 'New York',
    state: 'NY',
    zip: '10001'
  )
]

# Convert to hashes for record creation
record_data = {
  type: 'login',
  title: 'Server with Helpers',
  fields: fields.map(&:to_h)
}

# Create options with required folder_uid
options = KeeperSecretsManager::Dto::CreateOptions.new(folder_uid: 'FOLDER_UID')
record_uid = secrets_manager.create_secret(record_data, options)
```

## Dynamic Record Access

The Ruby SDK uses `method_missing` to provide JavaScript-style dynamic field access:

```ruby
# Get record
record = secrets_manager.get_secrets(['RECORD_UID']).first

# Dynamic getters
login = record.login          # Returns field value
password = record.password    # Returns field value
url = record.url             # Returns field value

# Dynamic setters
record.password = 'NewPassword123!'
record.url = 'https://newurl.example.com'

# Check if field exists
if record.respond_to?(:oneTimeCode)
  # Generate TOTP code from oneTimeCode field
  require 'keeper_secrets_manager/totp'
  totp_url = record.oneTimeCode
  totp_params = KeeperSecretsManager::TOTP.parse_url(totp_url)
  totp_code = KeeperSecretsManager::TOTP.generate_code(
    totp_params['secret'],
    algorithm: totp_params['algorithm'],
    digits: totp_params['digits'],
    period: totp_params['period']
  )
end

# Access all fields as hash
fields = record.fields
fields.each do |type, values|
  puts "#{type}: #{values.join(', ')}"
end
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.keeper.io/en/keeperpam/secrets-manager/developer-sdk-library/ruby-sdk.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
