# Ruby SDK

<figure><img src="https://762006384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MJXOXEifAmpyvNVL1to%2Fuploads%2FpnHkc8gVJXdn7WWFPxxF%2FKSM%20%2B%20Ruby%20Icon.png?alt=media&#x26;token=6498bdf2-aadb-4026-9d0c-9e44dc009993" 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
```
