# Go SDK

<figure><img src="/files/9auw1nynKPy95T8vI5Ru" alt=""><figcaption></figcaption></figure>

## Requirements

The Go SDK requires **Go 1.16 or higher**.

To check your Go version:

```bash
$ go version
```

If you need to upgrade Go, visit <https://go.dev/dl/>

{% hint style="warning" %}
**Breaking Change in v1.7.0:** The minimum Go version has been upgraded to 1.16+.  &#x20;
{% endhint %}

## Download and Installation

### Install from GitHub

Find the latest Go SDK release at: <https://github.com/Keeper-Security/secrets-manager-go>

```bash
$ go get github.com/keeper-security/secrets-manager-go/core
```

For more information, see <https://pkg.go.dev/github.com/keeper-security/secrets-manager-go/core>

### Source Code

Find the Go source code in the [GitHub repository](https://github.com/keeper-security/secrets-manager-go)

## Using the SDK

### Initialize

{% hint style="info" %}
Using token only to generate a new config (for later usage) requires at least one read operation to bind the token and fully populate `config.json`
{% endhint %}

In order to retrieve secrets, you must first initialize the secrets manager client.

```go
package main

import (	
    ksm "github.com/keeper-security/secrets-manager-go/core"	
)

func main() {
	clientOptions := &ksm.ClientOptions{
		Token:  "[One Time Access Token]",
		Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
	sm := ksm.NewSecretsManager(clientOptions)
	// Using token only to generate a config (for later usage)
	// requires at least one access operation to bind the token
	//sm.GetSecrets([]string{})
}
```

**ClientOptions Parameters:**

<table><thead><tr><th width="158.1484375">Parameter</th><th width="171.421875">Type</th><th width="114.85546875">Required</th><th width="125.6015625">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>Token</code></td><td><code>string</code></td><td>First run only</td><td>-</td><td>One-Time Access Token with region prefix (e.g., <code>US:...</code>). Required for initial binding.</td></tr><tr><td><code>Config</code></td><td><code>IKeyValueStorage</code></td><td>Yes</td><td>-</td><td>Storage implementation for configuration persistence (file or memory-based).</td></tr><tr><td><code>HostName</code></td><td><code>string</code></td><td>No</td><td>Derived from token</td><td>Override server hostname. Automatically set based on token region prefix.</td></tr><tr><td><code>VerifySslCerts</code></td><td><code>bool</code></td><td>No</td><td><code>true</code></td><td>Enable/disable SSL certificate verification.*</td></tr></tbody></table>

The `NewSecretsManager` function will initialize Secrets Manager from provided parameters and store settings from `ClientOptions` struct.

```go
sm := ksm.NewSecretsManager(options)
```

### Retrieve Secrets

```go
records, err := sm.GetSecrets([]string{})
```

| Parameter | Type       | Description                                                                    |
| --------- | ---------- | ------------------------------------------------------------------------------ |
| `uids`    | `[]string` | Record UIDs to retrieve. Pass empty slice `[]string{}` to retrieve all records |

**Response**

Type: `[]*Record`

Records with the specified UIDs, or all records shared with the Secrets Manager client if no UIDs are provided

**Example Usage**

Retrieve all Secrets

```go
package main

import (	
    ksm "github.com/keeper-security/secrets-manager-go/core"	
)

func main() {
	clientOptions := &ksm.ClientOptions{
		Token:  "[One Time Access Token]",
		Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
	sm := ksm.NewSecretsManager(clientOptions)
	allRecords, err := sm.GetSecrets([]string{})
}

```

Retrieve Secrets with a Filter

```go
package main

import (	
    ksm "github.com/keeper-security/secrets-manager-go/core"	
)

func main() {
	clientOptions := &ksm.ClientOptions{
		Token:  "[One Time Access Token]",
		Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
	sm := ksm.NewSecretsManager(clientOptions)
	records, err := sm.GetSecrets([]string{"[Secret UID]"})
}
```

### Retrieve Secrets by Title

```go
// get all matching records
GetSecretsByTitle(recordTitle string) (records []*Record, err error)

// get only the first matching record
GetSecretByTitle(recordTitle string) (record *Record, err error)
```

| Parameter     | Type     | Description                |
| ------------- | -------- | -------------------------- |
| `recordTitle` | `string` | Record title to search for |

**Example Usage**

```go
package main

// Import Secrets Manager
import (
  "fmt"
  ksm "github.com/keeper-security/secrets-manager-go/core"
)

func main() {

  options := &ksm.ClientOptions{
    // One time tokens can be used only once - afterwards use the generated config file
    Config: ksm.NewFileKeyValueStorage("ksm-config.json")}

  sm := ksm.NewSecretsManager(options)

  // Retrieve the first matching record by title
  aRecord, err := sm.GetSecretByTitle("My Credentials")
  if err != nil {
    fmt.Printf("Error retrieving record: %v\n", err)
    return
  }
  fmt.Printf("Found record: %s\n", aRecord.Title())

  // Retrieve all matching records by title
  allRecords, err := sm.GetSecretsByTitle("My Credentials")
  
  if err != nil {
    fmt.Printf("Error retrieving records: %v\n", err)
    return
  }

  for _, record := range allRecords {
    fmt.Printf("UID %s, title [%s]\n", record.Uid, record.Title())
  }
}
```

### Get Values From a Secret

**Get a Password**

{% tabs %}
{% tab title="Get Password" %}

```go
(r *Record) Password() string
```

Returns the password field value for `login` type records. Returns an empty string for other record types.
{% endtab %}

{% tab title="Example Usage" %}

```go
records, err := sm.GetSecrets([]string{"[Record UID]"})
if err != nil || len(records) == 0 {
    return
}
password := records[0].Password()
```

{% endtab %}
{% endtabs %}

**Get a Password by Field Value**

{% tabs %}
{% tab title="Get Field Value" %}

```go
(r *Record) GetFieldValueByType(fieldType string) string
```

| Parameter   | Type     | Description                      |
| ----------- | -------- | -------------------------------- |
| `fieldType` | `string` | Field type (e.g. `login`, `url`) |

Returns the first value of the matching standard field as a string. Returns an empty string if the field does not exist. For multi-value fields or structured fields, use `GetFieldsByType`.

Field types are based on the Keeper [Record Type](/en/enterprise-guide/record-types.md). For a detailed list of available fields based on the Keeper Record Type, see the [record-type-info](/en/keeperpam/commander-cli/command-reference/record-commands/record-type-commands.md#record-type-info-command) command in Keeper Commander.
{% endtab %}

{% tab title="Example" %}

```go
records, err := sm.GetSecrets([]string{"[Record UID]"})
if err != nil || len(records) == 0 {
    return
}
login := records[0].GetFieldValueByType("login")
```

{% endtab %}
{% endtabs %}

#### Retrieve Values using Keeper Notation

**GetNotation**

{% tabs %}
{% tab title="Get Notation" %}

```go
sm.GetNotation(query)
```

{% endtab %}

{% tab title="Example Usage" %}

```go
	clientOptions := &ksm.ClientOptions{
		Token:  "[One Time Access Token]",
		Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
	sm := ksm.NewSecretsManager(clientOptions)

	// Get password from record with the given UID
	value, err := sm.GetNotation("[Record UID]/field/password")
	if err == nil && len(value) > 0 {
		if password, ok := value[0].(string); ok {
			println("Successfully retrieved field value using notation")
		}
	}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
See [Keeper Notation documentation](/en/keeperpam/secrets-manager/about/keeper-notation.md) to learn about Keeper Notation format and capabilities
{% endhint %}

| Parameter | Type     | Description                                                      |
| --------- | -------- | ---------------------------------------------------------------- |
| `query`   | `string` | Keeper Notation query for getting a value from a specified field |

#### Returns

Type: `[]interface{}`

The value of the queried field

#### **GetNotationResults**

```go
(c *SecretsManager) GetNotationResults(notation string) ([]string, error)
(c *SecretsManager) TryGetNotationResults(notation string) []string
```

| Parameter  | Type     | Description           |
| ---------- | -------- | --------------------- |
| `notation` | `string` | Keeper Notation query |

`GetNotationResults` returns field values as `[]string`. Use this instead of `GetNotation` when the field value is a string. `TryGetNotationResults` is the error-suppressing variant. It returns an empty slice instead of propagating the error.

**Example Usage**

```go
// Returns []string directly — no type assertion needed
values, err := sm.GetNotationResults("[Record UID]/field/password")
if err == nil && len(values) > 0 {
    password := values[0]
}

// Suppress error — useful in templates or config loading
password := ""
if values := sm.TryGetNotationResults("[Record UID]/field/password"); len(values) > 0 {
    password = values[0]
}
```

#### Retrieve TOTP Code

{% tabs %}
{% tab title="Get TOTP Code" %}

```go
GetTotpCode(url)
```

{% endtab %}

{% tab title="Example Usage" %}

```go
clientOptions := &ksm.ClientOptions{
	Token:  "[One Time Access Token]",
	Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
sm := ksm.NewSecretsManager(clientOptions)

urls, err := sm.GetNotation("[Record UID]/field/oneTimeCode")
if err == nil && len(urls) == 1 {
    url := urls[0].(string)
    totp, err := ksm.GetTotpCode(url)
    if err == nil {
        println(totp.Code, totp.TimeLeft)
    }
}
```

{% endtab %}
{% endtabs %}

| Parameter | Type     | Description     |
| --------- | -------- | --------------- |
| `url`     | `string` | TOTP URL string |

### Update a Secret

{% hint style="warning" %}
Record update commands don't update local record data on success *(esp. updated record revision)* so any consecutive updates to an already updated record will fail due to **revision** mismatch. Make sure to reload all updated records after each update batch.
{% endhint %}

**Update Password**

```go
(r *Record) SetPassword(password string) 
```

| Parameter  | Type     | Description                       |
| ---------- | -------- | --------------------------------- |
| `password` | `string` | New password to set to the record |

**Update Other Fields**

```go
(r *Record) SetFieldValueSingle(field string, value string) 
```

| Parameter | Type     | Description                 |
| --------- | -------- | --------------------------- |
| `field`   | `string` | Name of the field to update |
| `value`   | `string` | Value to set the field to   |

**Update Secret in Vault**

Save the record to make the changes made appear in the Keeper Vault.

```go
(c *SecretsManager) Save(record *Record) (err error)
```

| Parameter | Type      | Description                                   |
| --------- | --------- | --------------------------------------------- |
| `record`  | `*Record` | Record with updated field to save changes for |

**Example Usage**

Update Password

```go
package main

import (
  "fmt"
  ksm "github.com/keeper-security/secrets-manager-go/core"
)

func main() {
  clientOptions := &ksm.ClientOptions{
    Token:  "[One Time Access Token]",
    Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
  sm := ksm.NewSecretsManager(clientOptions)

  records, err := sm.GetSecrets([]string{"[Record UID]"})
  if err != nil {
    fmt.Printf("Error: %v\n", err)
    return
  }

  if len(records) == 0 {
    fmt.Println("No records found")
    return
  }

  // get record to update
  record := records[0]

  // set new password
  record.SetPassword("NewPassword123$")

  // save changes
  err = sm.Save(record)
  if err != nil {
    fmt.Printf("Error saving record: %v\n", err)
    return
  }

  // Transactional updates
  // rotate password on the record
  record.SetPassword("NewPassword1234$")

  // start a transaction
  err = sm.SaveBeginTransaction(record, ksm.TransactionTypeRotation)
  if err != nil {
    fmt.Printf("Error starting transaction: %v\n", err)
    return
  }

  // Call your application's custom function to rotate password on remote system
  success := rotateRemoteSshPassword("NewPassword1234$")  // Your custom function

  // complete the transaction - commit or rollback
  err = sm.CompleteTransaction(record.Uid, !success)
  if err != nil {
    fmt.Printf("Error completing transaction: %v\n", err)
    return
  }
}

// Example placeholder for your custom password rotation function
func rotateRemoteSshPassword(newPassword string) bool {
  // Your application-specific logic to rotate password on remote SSH server
  // Return true on success, false on failure
  return true
}
```

{% hint style="info" %}
`CompleteTransaction(uid, rollback bool)` passes `false` to commit the rotation, `true` to roll back. The example passes `!success` which evaluates to `true` (rollback) when your rotation function returns `false` (failed).
{% endhint %}

Update other fields

```go
package main

import (
  "fmt"
  ksm "github.com/keeper-security/secrets-manager-go/core"
)

func main() {
  clientOptions := &ksm.ClientOptions{
    Token:  "[One Time Access Token]",
    Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
  sm := ksm.NewSecretsManager(clientOptions)

  records, err := sm.GetSecrets([]string{"[Record UID]"})
  if err != nil {
    fmt.Printf("Error: %v\n", err)
    return
  }

  if len(records) == 0 {
    fmt.Println("No records found")
    return
  }

  // get record to update
  record := records[0]

  // set login field to a new value
  record.SetFieldValueSingle("login", "New Login Value")

  // update title and notes
  record.SetTitle("New Title")
  record.SetNotes("Updated notes with additional context")

  // save changes
  err = sm.Save(record)
  if err != nil {
    fmt.Printf("Error saving record: %v\n", err)
    return
  }
}
```

{% hint style="info" %}
**v1.7.0**: `SetNotes` now uses UPSERT behavior. It creates the notes field if it does not exist on the record. In v1.6.x and earlier, calling `SetNotes` on a record without an existing notes field was a silent no-op.
{% endhint %}

Each record field type is represented by a class. Cast the field to the corresponding class in order to correctly access the field's value.  Check the [Record Types](/en/enterprise-guide/record-types.md) documentation for a list of field types.

### Generate a Random Password

{% tabs %}
{% tab title="Generate Password" %}

```go
GeneratePassword(length, lowercase, uppercase, digits, specialCharacters, specialCharacterSet)
```

{% endtab %}

{% tab title="Example Usage" %}

```go
// get a record
allRecords, err := sm.GetSecrets([]string{})
if err != nil {
  fmt.Printf("Error: %v\n", err)
  return
}

if len(allRecords) == 0 {
  fmt.Println("No records found")
  return
}

record := allRecords[0]

// generate a random password
password, err := ksm.GeneratePassword(64, "", "", "", "", "")
if err != nil {
  fmt.Printf("Error generating password: %v\n", err)
  return
}

// update record with new password
record.SetPassword(password)

// save changes
err = sm.Save(record)
if err != nil {
  fmt.Printf("Error saving record: %v\n", err)
  return
}
```

{% endtab %}
{% endtabs %}

| Parameter             | Type     | Description                                                                      |
| --------------------- | -------- | -------------------------------------------------------------------------------- |
| `length`              | `int`    | Password length. Pass `0` or negative to use default (32 characters)             |
| `lowercase`           | `string` | Minimum lowercase letters. Pass `""` for no minimum                              |
| `uppercase`           | `string` | Minimum uppercase letters. Pass `""` for no minimum                              |
| `digits`              | `string` | Minimum digits. Pass `""` for no minimum                                         |
| `specialCharacters`   | `string` | Minimum special characters. Pass `""` for no minimum                             |
| `specialCharacterSet` | `string` | Custom special character set. Pass `""` to use default: `"!@#$%()+;<>=?[]{}^.,"` |

### Download a File

**DownloadFileByTitle**

```go
(r *Record) DownloadFileByTitle(title string, path string) error
```

| Parameter | Type     | Description              |
| --------- | -------- | ------------------------ |
| `title`   | `string` | Name of file to download |
| `path`    | `string` | Path to save the file to |

**Response**

Type: `error`

Returns `nil` on success. Returns an error describing the failure: file title not found, directory does not exist, or write failure. Pass the error to your logger or wrap it for the caller.

**Example Usage**

```go
import (
  "fmt"
  ksm "github.com/keeper-security/secrets-manager-go/core"
)

clientOptions := &ksm.ClientOptions{
  Token:  "[One Time Access Token]",
  Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
sm := ksm.NewSecretsManager(clientOptions)

// get record
records, err := sm.GetSecrets([]string{"[Record UID]"})
if err != nil {
  fmt.Printf("Error: %v\n", err)
  return
}

if len(records) == 0 {
  fmt.Println("No records found")
  return
}

record := records[0]

// download a file attached to the record
record.DownloadFileByTitle("certs.txt","secret_files/certs.txt")
```

***

**DownloadFile**

```go
(r *Record) DownloadFile(fileUid string, path string) error
```

| Parameter | Type     | Description                 |
| --------- | -------- | --------------------------- |
| `fileUid` | `string` | UID of the file to download |
| `path`    | `string` | Path to save the file to    |

**Response**

Type: `error`

Returns `nil` on success. Returns an error if no file with the given UID is attached to the record, the directory does not exist, or the write fails.

**Example Usage**

```go
records, err := sm.GetSecrets([]string{"[Record UID]"})
if err != nil || len(records) == 0 {
    return
}
for _, file := range records[0].Files {
    if err := records[0].DownloadFile(file.Uid, "/tmp/"+file.Name); err != nil {
        fmt.Printf("error downloading %s: %v\n", file.Name, err)
    }
}
```

***

**SaveFile**

```go
(f *KeeperFile) SaveFile(path string, createFolders bool) error
```

| Parameter       | Type     | Description                                   |
| --------------- | -------- | --------------------------------------------- |
| `path`          | `string` | Full file path to write to                    |
| `createFolders` | `bool`   | If `true`, creates missing parent directories |

**Response**

Type: `error`

Returns `nil` on success. Returns an error if the directory does not exist (and `createFolders` is `false`) or the write fails. Use when you have a `*KeeperFile` directly from `record.Files`.

**Example Usage**

```go
records, err := sm.GetSecrets([]string{"[Record UID]"})
if err != nil || len(records) == 0 {
    return
}
for _, file := range records[0].Files {
    if err := file.SaveFile("/tmp/"+file.Name, true); err != nil {
        fmt.Printf("error saving %s: %v\n", file.Name, err)
    }
}
```

### Upload a File

```go
UploadFile(record *Record, file *KeeperFileUpload) (uid string, err error)
```

| Parameter     | Type               | Description                               |
| ------------- | ------------------ | ----------------------------------------- |
| `ownerRecord` | `Record`           | The record to attach the uploaded file to |
| `file`        | `KeeperFileUpload` | The file to upload                        |

```go
type KeeperFileUpload struct {
	Name  string
	Title string
	Type  string
	Data  []byte
}
```

| Field   | Type     | Description                                                                 |
| ------- | -------- | --------------------------------------------------------------------------- |
| `Name`  | `string` | **Required.** File name in Keeper once uploaded                             |
| `Title` | `string` | **Required.** File title in Keeper once uploaded                            |
| `Type`  | `string` | **Required.** MIME type of the file data (e.g., `application/octet-stream`) |
| `Data`  | `[]byte` | **Required.** File data as bytes                                            |

**Example Usage**

```go
package main

import (
  "fmt"
  "os"
  ksm "github.com/keeper-security/secrets-manager-go/core"
)

func main() {

  options := &ksm.ClientOptions{
    // One time tokens can be used only once - afterwards use the generated config file
    Config: ksm.NewFileKeyValueStorage("ksm-config.json")}

  sm := ksm.NewSecretsManager(options)

  // Get a record to upload the file to
  allRecords, err := sm.GetSecrets([]string{"[Record UID]"})
  if err != nil {
    fmt.Printf("Error: %v\n", err)
    return
  }

  if len(allRecords) == 0 {
    fmt.Println("No records found")
    return
  }

  ownerRecord := allRecords[0]

  // get file data and prepare for upload
  dat, err := os.ReadFile("/myFile.json")
  if err != nil {
    fmt.Printf("Error reading file: %v\n", err)
    return
  }

  myFile := ksm.KeeperFileUpload{
    Name: "myFile.json",
    Title: "My File",
    Type: "application/json",
    Data: dat,
  }

  // upload file to selected record
  _, err = sm.UploadFile(ownerRecord, &myFile)
  if err != nil {
    fmt.Printf("Error uploading file: %v\n", err)
    return
  }

  fmt.Println("File uploaded successfully")
}
```

### Upload a File from Path

```go
(c *SecretsManager) UploadFilePath(record *Record, filePath string) (uid string, err error)
```

| Parameter  | Type      | Description                               |
| ---------- | --------- | ----------------------------------------- |
| `record`   | `*Record` | The record to attach the uploaded file to |
| `filePath` | `string`  | Local path of the file to upload          |

**Response**

Type: `string, error`

Returns the UID of the uploaded file attachment. Use this instead of `UploadFile` when uploading directly from disk — it reads the file, infers the MIME type, and calls `UploadFile` internally.

**Example Usage**

```go
records, err := sm.GetSecrets([]string{"[Record UID]"})
if err != nil || len(records) == 0 {
    return
}
uid, err := sm.UploadFilePath(records[0], "/path/to/myFile.json")
if err != nil {
    fmt.Printf("error uploading file: %v\n", err)
    return
}
fmt.Printf("uploaded file UID: %s\n", uid)
```

### Removing Files from Records

To remove files from a record, specify the file UIDs in the `LinksToRemove` field when calling `SaveWithOptions()`:

```go
import (
    "fmt"
    ksm "github.com/keeper-security/secrets-manager-go/core"
)

func removeFileFromRecord(sm *ksm.SecretsManager, recordUid string, fileUid string) error {
    // Retrieve the record
    records, err := sm.GetSecrets([]string{recordUid})
    if err != nil {
        return err
    }

    if len(records) == 0 {
        return fmt.Errorf("record not found: %s", recordUid)
    }

    record := records[0]

    // Specify file UID(s) to remove
    updateOptions := ksm.UpdateOptions{
        LinksToRemove: []string{fileUid},
    }

    // Save the record (file will be removed)
    err = sm.SaveWithOptions(record, updateOptions)
    if err != nil {
        return fmt.Errorf("failed to remove file: %w", err)
    }

    fmt.Printf("File %s removed from record %s\n", fileUid, recordUid)
    return nil
}
```

**Removing multiple files:**

```go
// Remove multiple files in a single Save operation
updateOptions := ksm.UpdateOptions{
    LinksToRemove: []string{"FILE_UID_1", "FILE_UID_2", "FILE_UID_3"},
}
err := sm.SaveWithOptions(record, updateOptions)
```

### UpdateOptions

`UpdateOptions` controls optional behavior for `SaveWithOptions` and `SaveBeginTransaction`.

| Field             | Type                    | Description                                                                     |
| ----------------- | ----------------------- | ------------------------------------------------------------------------------- |
| `TransactionType` | `UpdateTransactionType` | Set to `Rotation` for two-phase transactional updates. Omit for standard saves. |
| `LinksToRemove`   | `[]string`              | File UIDs to detach from the record on save.                                    |

### Create a Secret

#### Prerequisites:

* Shared 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
* Created records and record fields must be formatted correctly
  * See the [documentation](/en/enterprise-guide/record-types.md) for expected field formats for each record type
* TOTP fields accept only URL generated outside of the KSM SDK
* After record creation, you can upload file attachments using [UploadFile](#upload-a-file)

{% tabs %}
{% tab title="Create a Record" %}

```go
secretsManager.CreateSecretWithRecordData(recordUid, folderUid, record)
```

| Parameter   | Type            | Required | Default                   |
| ----------- | --------------- | :------: | ------------------------- |
| `recordUid` | `string`        |    No    | auto generated random UID |
| `folderUid` | `string`        |    Yes   |                           |
| `record`    | `*RecordCreate` |    Yes   |                           |

`recordUid` is optional. Pass `""` and the SDK generates a UID automatically.
{% endtab %}

{% tab title="Create Record in Sub-folder" %}

```go
secretsManager.CreateSecretWithRecordDataAndOptions(createOptions, recordData, folders)
```

| Parameter       | Type              | Required |
| --------------- | ----------------- | :------: |
| `createOptions` | `*CreateOptions`  |    Yes   |
| `recordData`    | `*RecordCreate`   |    Yes   |
| `folders`       | `[]*KeeperFolder` |    No    |

Pass `nil` for `folders` and the SDK fetches the folder list automatically.
{% endtab %}

{% tab title="Login Record Example" %}
This example creates a login type record with a login value and a generated password.

{% hint style="info" %}
Replace `[FOLDER UID]` in the example with the UID of a shared folder that your Secrets Manager has access to.
{% endhint %}

```go
// create a new login record
newLoginRecord := ksm.NewRecordCreate("login", "Sample KSM Record: Go SDK")

// generate password
password, err := ksm.GeneratePassword(32, "", "", "", "", "")
if err != nil {
  fmt.Printf("Error generating password: %v\n", err)
  return
}

// fill in login and password fields
newLoginRecord.Fields = append(newLoginRecord.Fields,
  ksm.NewLogin("username@email.com"),
  ksm.NewPassword(password))

// fill in notes
newLoginRecord.Notes = "This is a Go record creation example"

// create the new record
recordUid, err := sm.CreateSecretWithRecordData("", "[FOLDER UID]", newLoginRecord)
if err != nil {
  fmt.Printf("Error creating record: %v\n", err)
  return
}

fmt.Printf("Record created with UID: %s\n", recordUid)
```

{% endtab %}

{% tab title="Custom Type Example" %}
{% hint style="info" %}
Replace `[FOLDER UID]` in the example with the UID of a shared folder that your Secrets Manager has access to.
{% endhint %}

This example creates a record with a custom record type.

```go
customLogin := ksm.NewRecordCreate("Custom Login", "Sample Custom Type KSM Record: Go SDK")

password, err := ksm.GeneratePassword(32, "", "", "", "", "")
if err != nil {
  fmt.Printf("Error generating password: %v\n", err)
  return
}

customLogin.Fields = append(customLogin.Fields,
  ksm.NewHosts(ksm.Host{Hostname: "127.0.0.1", Port: "8080"}),
  ksm.NewLogin("login@email.com"),
  ksm.NewPassword(password),
  ksm.NewUrl("http://localhost:8080/login"),
  ksm.NewSecurityQuestions(ksm.SecurityQuestion{Question: "What is one plus one (write just a number)", Answer: "2"}),
  ksm.NewPhones(ksm.Phone{Region: "US", Number: "510-444-3333", Ext: "2345", Type: "Mobile"}),
  ksm.NewDate(1641934793000),
  ksm.NewNames(ksm.Name{First: "John", Middle: "Patrick", Last: "Smith"}),
  ksm.NewOneTimeCode("otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"),
)

customLogin.Custom = append(customLogin.Custom,
  ksm.NewPhones(ksm.Phone{Region: "US", Number: "510-222-5555", Ext: "99887", Type: "Mobile"}),
  ksm.NewPhones(ksm.Phone{Region: "US", Number: "510-111-3333", Ext: "45674", Type: "Mobile"}),
)

customLogin.Notes = "\tThis custom type record was created\n\tvia Go SDK copied from https://docs.keeper.io/secrets-manager/secrets-manager/developer-sdk-library/golang-sdk"

recordUid, err := sm.CreateSecretWithRecordData("", "[FOLDER UID]", customLogin)
if err != nil {
  fmt.Printf("Error creating record: %v\n", err)
  return
}

fmt.Printf("Custom record created with UID: %s\n", recordUid)
```

{% endtab %}
{% endtabs %}

### Delete a Secret

The Go KSM SDK can delete records in the Keeper Vault.

{% tabs %}
{% tab title="Delete Secret" %}

```go
statuses, err := secretsManager.DeleteSecrets(recordUids)
```

| Parameter    | Type       | Description               |
| ------------ | ---------- | ------------------------- |
| `recordUids` | `[]string` | UIDs of records to delete |

| Return     | Type                | Description             |
| ---------- | ------------------- | ----------------------- |
| `statuses` | `map[string]string` | Per-UID deletion status |
| `err`      | `error`             | Error, if any           |

UIDs not found in the vault or not accessible to the KSM Application are silently skipped and do not produce an error.
{% endtab %}

{% tab title="Example" %}

```go
import (
  "fmt"
  ksm "github.com/keeper-security/secrets-manager-go/core"
)

func main() {
  options := &ksm.ClientOptions{
    Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
  secretsManager := ksm.NewSecretsManager(options)

  statuses, err := secretsManager.DeleteSecrets([]string{"EG6KdJaaLG7esRZbMnfbFA"})
  if err != nil {
    fmt.Printf("Error deleting secrets: %v\n", err)
    return
  }

  for uid, status := range statuses {
    fmt.Printf("%s: %s\n", uid, status)
  }
}
```

{% endtab %}
{% endtabs %}

### Caching

To protect against losing access to your secrets when network access is lost, the Go SDK allows caching of secrets to the local machine in an encrypted file.

**Setup and Configure Cache**

In order to setup caching in the Go SDK, use the function `SetCache(cache ICache)` to set the cache to either one of the built-in memory or file based caches or use your own implementation.

```go
type ICache interface {
	SaveCachedValue(data []byte) error
	GetCachedValue() ([]byte, error)
	Purge() error
}
```

For a complete working `ICache` implementation demonstrating offline-fallback semantics, see [`example/custom-cache/main.go`](https://github.com/keeper-security/secrets-manager-go/blob/main/example/custom-cache/main.go) in the SDK repository.

The Go SDK includes a memory based cache and a file based cache for convenience.

```go
options := &ksm.ClientOptions{Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
sm := ksm.NewSecretsManager(options)

// Memory based cache
memCache := ksm.NewMemoryCache()
sm.SetCache(memCache)

// File based cache
fileCache := ksm.NewFileCache("ksm_cache.bin")
sm.SetCache(fileCache)
```

The SDK always attempts a live network request first. The cache is consulted as a fallback in two situations: when the server returns a non-200 HTTP response, and when the request fails at the transport layer (DNS resolution failure, connection refused, TLS handshake failure, or timeout). When cached records are served because of a network-level error, a warning is logged. On every successful `GetSecrets` call, the cache is updated with the latest response.

{% hint style="warning" %}
**Last request only**: The cache stores only the most recent response. If your last successful call fetched records by UID filter, a cache fallback will return only those records — not all records shared with the application. Design your cache usage accordingly.
{% endhint %}

{% hint style="warning" %}
**After updates**: Saving a record changes its revision in the vault. If the SDK falls back to cache after an update, the cached revision is stale and further updates to the same record will fail with a revision mismatch. Always call `GetSecrets` after saving to refresh the cache.
{% endhint %}

### Folders

Folders have full CRUD support - create, read, update and delete operations.

### Read Folders

Downloads full folder hierarchy.

```go
GetFolders() ([]*KeeperFolder, error)
```

**Response**

Type: `[]*KeeperFolder, error`

**Example Usage**

```go
package main

import (
  "fmt"
  ksm "github.com/keeper-security/secrets-manager-go/core"
)

func main() {
  options := &ksm.ClientOptions{Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
  sm := ksm.NewSecretsManager(options)

  folders, err := sm.GetFolders()
  if err != nil {
    fmt.Printf("Error: %v\n", err)
    return
  }

  fmt.Printf("Retrieved %d folder(s)\n", len(folders))
}
```

### Create a Folder

Requires `CreateOptions` and folder name to be provided. The folder UID parameter in `CreateOptions` is required - UID of a shared folder, while sub-folder UID is optional and if missing new regular folder is created directly under the parent (shared folder). There's no requirement for the sub-folder to be a direct descendant of the parent shared folder - it could be many levels deep.

```go
CreateFolder(createOptions CreateOptions, folderName string, folders []*KeeperFolder) (folderUid string, err error)
```

| Parameter       | Type              | Description                                                             |
| --------------- | ----------------- | ----------------------------------------------------------------------- |
| `createOptions` | `CreateOptions`   | Parent and sub-folder UIDs                                              |
| `folderName`    | `string`          | Folder name                                                             |
| `folders`       | `[]*KeeperFolder` | Optional: List of folders for search. Pass `nil` to fetch automatically |

```go
type CreateOptions struct {
	FolderUid    string
	SubFolderUid string
}
```

```go
type KeeperFolder struct {
	FolderKey []byte
	FolderUid string
	ParentUid string
	Name      string
}
```

**Example Usage**

```go
package main

import (
  "fmt"
  ksm "github.com/keeper-security/secrets-manager-go/core"
)

func main() {
  options := &ksm.ClientOptions{Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
  sm := ksm.NewSecretsManager(options)

  co := ksm.CreateOptions{FolderUid: "[PARENT_SHARED_FOLDER_UID]", SubFolderUid: ""}
  uid, err := sm.CreateFolder(co, "new_folder", nil)
  if err != nil {
    fmt.Printf("Error: %v\n", err)
    return
  }

  fmt.Printf("Folder created with UID: %s\n", uid)
}
```

### Update a Folder

Updates the folder metadata - currently folder name only.

```go
UpdateFolder(folderUid, folderName string, folders []*KeeperFolder) (err error)
```

| Parameter    | Type              | Description                                                             |
| ------------ | ----------------- | ----------------------------------------------------------------------- |
| `folderUid`  | `string`          | UID of the folder to update                                             |
| `folderName` | `string`          | New folder name                                                         |
| `folders`    | `[]*KeeperFolder` | Optional: List of folders for search. Pass `nil` to fetch automatically |

**Example Usage**

```go
package main

import (
  "fmt"
  ksm "github.com/keeper-security/secrets-manager-go/core"
)

func main() {
  options := &ksm.ClientOptions{Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
  sm := ksm.NewSecretsManager(options)

  err := sm.UpdateFolder("[FOLDER_UID]", "new_folder_name", nil)
  if err != nil {
    fmt.Printf("Error: %v\n", err)
    return
  }

  fmt.Println("Folder updated successfully")
}
```

### Delete Folders

Removes a list of folders. Use `forceDeletion` flag to remove non-empty folders.

{% hint style="info" %}
When using forceDeletion avoid sending parent with its children folder UIDs. Depending on the delete order you may get an error - *ex.* if parent force-deleted child first. There's no guarantee that list will always be processed in FIFO order.
{% endhint %}

{% hint style="info" %}
Any folders UIDs missing from the vault or not shared to the KSM Application will not result in error.
{% endhint %}

<pre class="language-go"><code class="lang-go"><strong>DeleteFolder(folderUids []string, forceDeletion bool) (statuses map[string]string, err error)
</strong></code></pre>

| Parameter       | Type       | Description                               |
| --------------- | ---------- | ----------------------------------------- |
| `folderUids`    | `[]string` | UIDs of folders to delete                 |
| `forceDeletion` | `bool`     | Set to `true` to delete non-empty folders |

**Example Usage**

```go
package main

import (
  "fmt"
  ksm "github.com/keeper-security/secrets-manager-go/core"
)

func main() {
  options := &ksm.ClientOptions{Config: ksm.NewFileKeyValueStorage("ksm-config.json")}
  sm := ksm.NewSecretsManager(options)

  stats, err := sm.DeleteFolder([]string{"[FOLDER_UID1]", "[FOLDER_UID2]"}, true)
  if err != nil {
    fmt.Printf("Error: %v\n", err)
    return
  }

  fmt.Printf("Deleted %d folder(s)\n", len(stats))
}
```

## Advanced Configuration

### Token Region Prefixes

Tokens include a region prefix to automatically connect to the correct Keeper data center:

| Region Code | Data Center         | Hostname                     |
| ----------- | ------------------- | ---------------------------- |
| `US`        | United States       | `keepersecurity.com`         |
| `EU`        | Europe              | `keepersecurity.eu`          |
| `AU`        | Australia           | `keepersecurity.com.au`      |
| `GOV`       | US Government Cloud | `govcloud.keepersecurity.us` |
| `JP`        | Japan               | `keepersecurity.jp`          |
| `CA`        | Canada              | `keepersecurity.ca`          |

**Example tokens:**

```
US:KBChlYeZ15wLzvhLVXmT61euw0DJO0cTVfkD-b-qesw
EU:JhGxK92pLmNqR5TvWx3YzAbC8dEfGhI0jKlMnOpQrSt
```

The SDK automatically parses the region prefix and connects to the appropriate server. If your token does not include a region prefix, you must specify the `Hostname` parameter explicitly in `ClientOptions`.

### HTTP Proxy Configuration

The Go SDK automatically detects proxy settings from standard environment variables:

**Environment Variables:**

The SDK checks the following environment variables (in order of precedence):

1. `HTTPS_PROXY` or `https_proxy` - Preferred for HTTPS traffic
2. `HTTP_PROXY` or `http_proxy` - Fallback proxy configuration
3. `NO_PROXY` or `no_proxy` - Comma-separated list of hosts to bypass proxy

**Examples:**

**Basic proxy:**

```bash
export HTTPS_PROXY=http://proxy.company.com:8080
export HTTP_PROXY=http://proxy.company.com:8080
```

**Authenticated proxy:**

```bash
export HTTPS_PROXY=http://username:password@proxy.company.com:8080
```

**Proxy with bypass list:**

```bash
export HTTPS_PROXY=http://proxy.company.com:8080
export NO_PROXY=localhost,127.0.0.1,.internal.company.com
```

**Example:**

```go
import (
    "os"
    ksm "github.com/keeper-security/secrets-manager-go/core"
)

func main() {
    // Set proxy programmatically (optional - usually set via environment)
    os.Setenv("HTTPS_PROXY", "http://proxy.company.com:8080")

    clientOptions := &ksm.ClientOptions{
        Token:  "US:[One Time Access Token]",
        Config: ksm.NewFileKeyValueStorage("ksm-config.json"),
    }
    sm := ksm.NewSecretsManager(clientOptions)

    // SDK automatically uses proxy for all requests
    records, err := sm.GetSecrets([]string{})
    if err != nil {
        // Handle error
    }
}
```

{% hint style="warning" %}
**Special characters in credentials**: URL-encode special characters in usernames and passwords using percent-encoding (e.g., `@` becomes `%40`, `!` becomes `%21`).
{% endhint %}

### KSM\_CONFIG Environment Variable

For CI/CD pipelines and containerized environments, the SDK automatically loads configuration from the `KSM_CONFIG` environment variable when no `Config` is provided in `ClientOptions`.

Set the environment variable to the base64-encoded JSON config generated after initial device binding:

```bash
export KSM_CONFIG="<base64-encoded config JSON>"
```

Then initialize without a config file:

```go
sm := ksm.NewSecretsManager(&ksm.ClientOptions{})
// SDK automatically reads KSM_CONFIG from the environment
```

{% hint style="info" %}
Generate the base64 config value from an existing `ksm-config.json` file:

```bash
base64 -w 0 ksm-config.json
```

{% endhint %}

{% hint style="warning" %}
`KSM_CONFIG` is only read when `ClientOptions.Config` is nil. If you pass a `Config` value explicitly, the environment variable is ignored.
{% endhint %}

### Configuration File Security

When using `NewFileKeyValueStorage()`, the SDK automatically:

1. **Creates new configuration files** with permissions `0600` (user read/write only)
2. **Restricts access** to the file owner, preventing other users from reading credentials
3. **Stores encrypted keys** and device tokens securely

**Check file permissions:**

```bash
# Verify configuration file permissions
$ ls -l ksm-config.json
-rw------- 1 user user 1234 Dec 26 10:00 ksm-config.json
```

The `rw-------` (0600) permissions ensure only the file owner can read or write the configuration.

**Security best practices:**

* Never commit configuration files to version control (add `*.json` to `.gitignore`)
* Use separate configurations for development, staging, and production environments
* Rotate One-Time Access Tokens every 90 days
* Store configuration files in secure, encrypted directories

### Error Handling

{% hint style="info" %}
**Improved in v1.7.0**: HTTP errors are now returned as a typed `*core.KeeperHTTPError` with `StatusCode`, `ResultCode`, and `Message` fields. Use `errors.As` to inspect them programmatically rather than parsing error strings. Error message strings also consistently include the HTTP status code on all error paths.
{% endhint %}

**Common HTTP status codes:**

| Status Code | Error Type            | Recommended Action                               |
| ----------- | --------------------- | ------------------------------------------------ |
| `401`       | Unauthorized          | Re-initialize with new One-Time Token            |
| `403`       | Forbidden             | Check application permissions in Secrets Manager |
| `404`       | Not Found             | Verify UID is correct and accessible             |
| `429`       | Too Many Requests     | Implement exponential backoff retry logic        |
| `500`       | Internal Server Error | Retry with exponential backoff                   |
| `503`       | Service Unavailable   | Retry after delay                                |
|             |                       |                                                  |

{% hint style="warning" %}
**Breaking Change in v1.7.0: nil returns on decryption failure**

The following functions now return `nil` when decryption fails, instead of returning an empty stub:

* `NewRecordFromJson` on record key or record data decryption failure
* `NewFolderFromJson` / `NewKeeperFolder` on folder decryption failure
* `NewKeeperFileFromJson` on file key decryption failure

`GetSecrets` now returns an error when app key decryption fails in the just-bound flow, instead of returning an empty list with a nil error.

If you call these functions directly, add nil checks before dereferencing the return value.
{% endhint %}

{% hint style="warning" %}
**Breaking Change in v1.7.0: HTTP error string format**

The error string returned for JSON error responses now includes an `HTTPStatus=N` prefix (e.g., `POST Error: HTTPStatus=403 Error: access_denied, message=...`). Previously the JSON-error path returned no status code in the error string. Callers that parse error strings should migrate to `errors.As(err, &khe)` and inspect `khe.StatusCode`, `khe.ResultCode`, and `khe.Message` directly.
{% endhint %}

#### Typed HTTP errors

All HTTP errors from `GetSecrets` and related calls are returned as `*core.KeeperHTTPError`. Use `errors.As` to extract structured information instead of parsing error strings.

```go
type KeeperHTTPError struct {
    StatusCode int    // HTTP status code (e.g., 401, 403, 502)
    ResultCode string // Server-provided code (e.g., "access_denied"); empty when body is non-JSON
    Message    string // Server-provided message; empty when body is non-JSON
    Body       []byte // Raw response body
}
```

`Error()` formats as:

* JSON response: `HTTPStatus=403 Error: access_denied, message=Unable to validate application access`
* Non-JSON response: `HTTPStatus=502 HTTPError: Bad Gateway`

When returned from `GetSecrets`, the caller sees: `POST Error: HTTPStatus=403 Error: access_denied, message=...`

**Basic error handling:**

```go
records, err := sm.GetSecrets([]string{"UID1", "UID2"})
if err != nil {
    // Error format: "POST Error: HTTPStatus=401 Error: access_denied, message=..."
    // Use errors.As(err, &khe) to inspect StatusCode and ResultCode directly.
    fmt.Printf("Error retrieving secrets: %v\n", err)
    return
}
```

**Checking for specific HTTP status codes:**

```go
import (
    "errors"
    "fmt"
    ksm "github.com/keeper-security/secrets-manager-go/core"
)

records, err := sm.GetSecrets([]string{"UID1"})
if err != nil {
    var khe *ksm.KeeperHTTPError
    if errors.As(err, &khe) {
        switch khe.StatusCode {
        case 401:
            fmt.Println("Authentication failed. Please re-initialize with a new token.")
        case 403:
            fmt.Printf("Access denied (%s). Check application permissions in Secrets Manager.\n", khe.ResultCode)
        case 404:
            fmt.Println("Record not found. Verify the UID is correct.")
        case 429:
            fmt.Println("Rate limit exceeded. Retrying with exponential backoff...")
            // Implement retry logic
        default:
            fmt.Printf("HTTP %d (%s): %s\n", khe.StatusCode, khe.ResultCode, khe.Message)
        }
        return
    }
    // Non-HTTP error (network, config, decryption)
    fmt.Printf("Unexpected error: %v\n", err)
    return
}
```

**Graceful handling of broken records:**

{% hint style="info" %}
**Resilience Improvement in v1.7.0**: The SDK now gracefully handles broken encryption in records, files, and folders instead of failing completely.
{% endhint %}

If the SDK encounters records or files with broken encryption:

* **Broken records** are skipped with a warning logged
* **Broken files** are excluded from the file list
* **Broken folders** are handled without causing complete failure
* **Valid records** are still returned successfully

```go
records, err := sm.GetSecrets([]string{})
if err != nil {
    fmt.Printf("Error: %v\n", err)
    return
}

fmt.Printf("Retrieved %d accessible records\n", len(records))
// Note: Broken records are automatically skipped
```


---

# 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/golang-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.
