# .NET SDK

<figure><img src="https://762006384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MJXOXEifAmpyvNVL1to%2Fuploads%2FCatD9Fz7a1mtDPXCYrur%2FKSM%20%2B%20Net%20Icon.png?alt=media&#x26;token=00f4fa03-b694-4e64-a1bd-ec4cfa7c21e3" alt=""><figcaption></figcaption></figure>

## Download and Installation

### Prerequisites

> `dotnet add package Keeper.SecretsManager`

For more information, see <https://www.nuget.org/packages/Keeper.SecretsManager/>

### Source Code

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

## Using the SDK

### Initialize Storage

{% 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 local storage on your machine.

```csharp
SecretsManagerClient.InitializeStorage(storage: IKeyValueStorage, oneTimeToken: string, hostName: string = null)
```

<table data-header-hidden><thead><tr><th width="162">Parameter</th><th width="186">Type</th><th width="150">Required</th><th width="150">Default</th><th>Description</th></tr></thead><tbody><tr><td>Parameter</td><td>Type</td><td>Required</td><td>Default</td><td>Description</td></tr><tr><td><code>storage</code></td><td><code>IKeyValueStorage</code></td><td>Yes</td><td></td><td>Storage implementation for persisting config</td></tr><tr><td><code>oneTimeToken</code></td><td><code>string</code></td><td>Yes</td><td></td><td>One Time Access Token used to initialize the config</td></tr><tr><td><code>hostName</code></td><td><code>string</code></td><td>Optional</td><td>null</td><td>Keeper server hostname (auto-detected from token if omitted)</td></tr></tbody></table>

#### **Example Usage**

```csharp
var storage = new LocalConfigStorage("ksm-config.json");
SecretsManagerClient.InitializeStorage(storage, "[One Time Access Token]");
// Using token only to generate a config (for later usage)
// requires at least one access operation to bind the token
var options = new SecretsManagerOptions(storage);
await SecretsManagerClient.GetSecrets(options);
```

{% hint style="success" %}
**Automatic File Security** (v17.1.0+)

The .NET SDK automatically secures configuration and cache files:

* **Unix/macOS**: Sets `chmod 600` (owner read/write only)
* **Windows**: Restricts ACLs to current user only

Files protected:

* `ksm-config.json` (or your custom config file name)
* `cache.dat` (if using caching)

No additional configuration needed - the SDK handles this automatically.
{% endhint %}

#### Secrets Manager Options

```csharp
SecretsManagerOptions(
        IKeyValueStorage storage, 
        QueryFunction queryFunction = null, 
        bool allowUnverifiedCertificate = false, 
        string proxyUrl = null
)
```

<table data-header-hidden><thead><tr><th width="256.1796875">Parameter</th><th width="186">Type</th><th width="150">Required</th><th width="150">Default</th><th data-hidden>Description</th></tr></thead><tbody><tr><td>Parameter</td><td>Type</td><td>Required</td><td>Default</td><td>Description</td></tr><tr><td><code>storage</code></td><td><code>IKeyValueStorage</code></td><td>Yes</td><td></td><td></td></tr><tr><td><code>queryFunction</code></td><td><code>QueryFunction</code></td><td>Optional</td><td>null</td><td></td></tr><tr><td><code>allowUnverifiedCertificate</code></td><td><code>bool</code></td><td>Optional</td><td>false</td><td></td></tr><tr><td><code>proxyUrl</code></td><td><code>string</code></td><td>Optional</td><td>null</td><td></td></tr></tbody></table>

### Retrieve Secrets

```csharp
GetSecrets(options: SecretsManagerOptions, recordsFilter: string[] = null): Task<KeeperSecrets>
```

| Parameter       | Type                    | Required | Default | Description                     |
| --------------- | ----------------------- | -------- | ------- | ------------------------------- |
| `options`       | `SecretsManagerOptions` | Yes      |         | Storage and query configuration |
| `recordsFilter` | `string[]`              | Optional | `null`  | Record search filters           |

**Response**

Type: `KeeperSecrets`

Object containing all Keeper records, or records that match the given filter criteria

**Example Usage**

Retrieve all Secrets

```csharp
var options = new SecretsManagerOptions(storage, testPostFunction);
var secrets = await SecretsManagerClient.GetSecrets(options);
```

### Retrieve Secrets by Title

```csharp
// get all matching records
async Task<IEnumerable<KeeperRecord>> GetSecretsByTitle(SecretsManagerOptions options, string recordTitle)

// get only the first matching record
async Task<KeeperRecord> GetSecretByTitle(SecretsManagerOptions options, string recordTitle)
```

<table><thead><tr><th width="178.10385756676556">Parameter</th><th width="226.46137991694417">Type</th><th width="150">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>options</code></td><td>SecretsManagerOptions</td><td>Yes</td><td>Preconfigured options</td></tr><tr><td><code>recordTitle</code></td><td>string</td><td>Yes</td><td>Record title to search for</td></tr></tbody></table>

**Example Usage**

```csharp
using System;
using System.Threading.Tasks;
using SecretsManager;

private static async Task getOneIndividualSecret()
{
    var storage = new LocalConfigStorage("ksm-config.json");
    var options = new SecretsManagerOptions(storage);
    var records = await SecretsManagerClient.GetSecretsByTitle(
            options, "My Credentials");
    foreach (var record in records)
    {
        Console.WriteLine(record.RecordUid + " - " + record.Data.title);
        foreach (var field in record.Data.fields)
        {
            Console.WriteLine("\t" + field.label + " (" + field.type + "): [" + String.Join(", ", field.value) + "]");
        }
    }
}
```

### Retrieve Values from a Secret

**Retrieve a Password**

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

```csharp
secret.FieldValue("password")
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
var storage = new LocalConfigStorage(configName);
Console.WriteLine($"Local Config Storage opened from the file {configName}");
if (clientKey != null)
    SecretsManagerClient.InitializeStorage(storage, "<One Time Access Token>");

var options = new SecretsManagerOptions(storage);
//get secrets
var secrets= (await SecretsManagerClient.GetSecrets(options)).Records;

// get the password from the first secret
var firstSecret= secrets[0];
var password = firstSecret.FieldValue("password").ToString();
```

{% endtab %}
{% endtabs %}

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.

**Retrieve Other Fields Using Keeper Notation**

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

```csharp
GetValue(KeeperSecrets secrets, string notation)
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
var storage = new LocalConfigStorage(configName);
Console.WriteLine($"Local Config Storage opened from the file {configName}");
if (clientKey != null)
    SecretsManagerClient.InitializeStorage(storage, "<One Time Access Token>");

var options = new SecretsManagerOptions(storage);
//get secrets
var secrets = (await SecretsManagerClient.GetSecrets(options)).Records;

// get login field value using dot notation
var password = Notation.GetValue(secrets, "BediNKCMG21ztm5xGYgNww/field/login");
```

{% 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 %}

<table data-header-hidden><thead><tr><th>Parameter</th><th>Type</th><th width="200">Required</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td>Parameter</td><td>Type</td><td>Required</td><td>Default</td><td>Description</td></tr><tr><td><code>secrets</code></td><td><code>KeeperSecrets</code></td><td>Yes</td><td></td><td>Secrets to query</td></tr><tr><td><code>notation</code></td><td><code>string</code></td><td>Yes</td><td></td><td>Field query in dot notation format</td></tr></tbody></table>

**Retrieve TOTP Code**

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

```csharp
CryptoUtils.GetTotpCode(string url)
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
var storage = new LocalConfigStorage(configName);
Console.WriteLine($"Local Config Storage opened from the file {configName}");
if (clientKey != null)
    SecretsManagerClient.InitializeStorage(storage, "<One Time Access Token>");

var options = new SecretsManagerOptions(storage);
//get secrets
var secrets = (await SecretsManagerClient.GetSecrets(options)).Records;

// get TOTP url from a record
var url = Notation.GetValue(secrets, "BediNKCMG21ztm5xGYgNww/field/OneTimeCode");

// get TOTP code
var totp = CryptoUtils.GetTotpCode(url);
Console.WriteLine(totp.Code);
```

{% endtab %}
{% endtabs %}

<table data-header-hidden><thead><tr><th>Parameter</th><th>Type</th><th width="200">Required</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td>Parameter</td><td>Type</td><td>Required</td><td>Default</td><td>Description</td></tr><tr><td><code>url</code></td><td><code>string</code></td><td>Yes</td><td></td><td>TOTP Url</td></tr></tbody></table>

### Update Values in 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 %}

#### Save Changes to a Secret

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

```csharp
UpdateSecret(options: SecretsManagerOptions, record: KeeperRecord, transactionType: UpdateTransactionType? = null);
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
var options = new SecretsManagerOptions(storage, testPostFunction);
await SecretsManagerClient.UpdateSecret(options, secret);
```

{% endtab %}

{% tab title="Transactional updates" %}

```csharp
var storage = new LocalConfigStorage(configName);
var options = new SecretsManagerOptions(storage);

//get secrets
var secrets = (await SecretsManagerClient.GetSecrets(options)).Records;

// get the first secret
var secret = secrets[0];

// rotate password on the record
secret.UpdateFieldValue("password", "MyNewPassword");
//start a transaction
await SecretsManagerClient.UpdateSecret(options, secret, UpdateTransactionType.Rotation);
// rotate password on remote host
success = rotateRemoteSshPassword("MyNewPassword");
// complete the transaction - commit or rollback
await SecretsManagerClient.CompleteTransaction(options, secret.RecordUid, rollback: !success);
```

{% endtab %}
{% endtabs %}

<table data-header-hidden><thead><tr><th width="162">Parameter</th><th width="243">Type</th><th width="103">Required</th><th width="89">Default</th><th>Description</th></tr></thead><tbody><tr><td>Parameter</td><td>Type</td><td>Required</td><td>Default</td><td>Description</td></tr><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td><td>Storage and query configuration</td></tr><tr><td><code>record</code></td><td><code>KeeperRecord</code></td><td>Yes</td><td></td><td>The record with updated field values</td></tr><tr><td><code>transactionType</code></td><td><code>UpdateTransactionType?</code></td><td>Optional</td><td>null</td><td>Set to <code>Rotation</code> for transactional (two-phase) updates</td></tr></tbody></table>

Use UpdateSecret to save changes made to a secret record. Changes will not be reflected in the Keeper Vault until UpdateSecret is performed.

**Update a Field Value**

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

```csharp
UpdateFieldValue(string fieldType, object value)
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
var storage = new LocalConfigStorage(configName);
Console.WriteLine($"Local Config Storage opened from the file {configName}");
if (clientKey != null)
    SecretsManagerClient.InitializeStorage(storage, "<One Time Access Token>");

var options = new SecretsManagerOptions(storage);
//get secrets
var secrets= (await SecretsManagerClient.GetSecrets(options)).Records;

// get the password from the first secret
var firstSecret= secrets[0];

// update the login field
firstSecret.UpdateFieldValue("login", "My New Login");

// update title and notes
firstSecret.Data.title = "New Title";
firstSecret.Data.notes = "New Notes";

// save changes
await SecretsManagerClient.UpdateSecret(options, firstSecret);
```

{% endtab %}
{% endtabs %}

| Parameter   | Type     | Required | Default | Description               |
| ----------- | -------- | -------- | ------- | ------------------------- |
| `fieldType` | `string` | Yes      |         | The field to update       |
| `value`     | `object` | Yes      |         | Value to set the field to |

#### Generate a Random Password

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

```csharp
CryptoUtils.GeneratePassword(
    int minLength = 32, 
    int? lowercase = null, 
    int? uppercase = null, 
    int? digits = null, 
    int? specialCharacters = null, 
    string specialCharacterSet = AsciiSpecialCharacters
)
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
// generate a 32-character password with balanced character distribution
var password = CryptoUtils.GeneratePassword();

// generate a 64-character password with at least 10 digits and 5 special characters
var strongPassword = CryptoUtils.GeneratePassword(minLength: 64, digits: 10, specialCharacters: 5);

// generate a 16-character password using only alphanumeric characters (no special chars)
var alphanumeric = CryptoUtils.GeneratePassword(minLength: 16, specialCharacters: 0);

// update a record with the new password
firstRecord.UpdateFieldValue("password", password);
await SecretsManagerClient.UpdateSecret(options, firstRecord);
```

{% endtab %}
{% endtabs %}

<table><thead><tr><th width="203.7142857142857">Parameter</th><th width="150">Type</th><th width="150">Required</th><th>Default</th></tr></thead><tbody><tr><td>minLength</td><td><code>int</code></td><td>Optional</td><td>32</td></tr><tr><td>lowercase</td><td><code>int?</code></td><td>Optional</td><td>null</td></tr><tr><td>uppercase</td><td><code>int?</code></td><td>Optional</td><td>null</td></tr><tr><td>digits</td><td><code>int?</code></td><td>Optional</td><td>null</td></tr><tr><td>specialCharacters</td><td><code>int?</code></td><td>Optional</td><td>null</td></tr><tr><td>specialCharacterSet</td><td><code>string</code></td><td>Optional</td><td><code>"!@#$%()+;&#x3C;>=?[]{}^.,</code></td></tr></tbody></table>

Each parameter indicates the minimum number of that character type to include. When `null`, the generator uses a balanced distribution across all character categories. The `specialCharacterSet` parameter controls which special characters are eligible for inclusion.

### Download a File

```csharp
DownloadFile(file: KeeperFile): ByteArray
```

| Parameter | Type         | Required | Default | Description      |
| --------- | ------------ | -------- | ------- | ---------------- |
| `file`    | `KeeperFile` | Yes      |         | File to download |

**Response**

Type: `ByteArray`

ByteArray of file for download

### Download a **Thumbnail**

```csharp
DownloadThumbnail(file: KeeperFile): ByteArray
```

| Parameter | Type         | Required | Default | Description                     |
| --------- | ------------ | -------- | ------- | ------------------------------- |
| `file`    | `KeeperFile` | Yes      |         | File with thumbnail to download |

**Response**

Type: `ByteArray`

ByteArray of thumbnail for download

### Upload a File

Upload File:

```csharp
UploadFile(SecretsManagerOptions options, KeeperRecord ownerRecord, KeeperFileUpload file)
```

<table><thead><tr><th width="184.14763231197773">Parameter</th><th width="224.99431983802478">Type</th><th width="150">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>options</code></td><td>SecretsManagerOptions</td><td>Yes</td><td>Storage and query configuration</td></tr><tr><td><code>ownerRecord</code></td><td>KeeperRecord</td><td>Yes</td><td>The record to attach the uploaded file to</td></tr><tr><td><code>file</code></td><td>KeeperFileUpload</td><td>Yes</td><td>The File to upload</td></tr></tbody></table>

Creating the Keeper File Upload Object:

```csharp
KeeperFileUpload(string name, string title, string type, byte[] data)
```

<table><thead><tr><th width="169">Parameter</th><th width="150">Type</th><th width="150">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td>string</td><td>Yes</td><td>What the name of the file will be in Keeper once uploaded</td></tr><tr><td><code>title</code></td><td>string</td><td>Yes</td><td>What the title of the file will be in Keeper once uploaded</td></tr><tr><td><code>type</code></td><td>string</td><td>Yes</td><td>The mime type of data in the file. 'application/octet-stream' for example</td></tr><tr><td><code>data</code></td><td>byte[]</td><td>Yes</td><td>File data as bytes</td></tr></tbody></table>

**Example Usage**

```csharp
using System;
using System.Threading.Tasks;
using SecretsManager;

private static async Task uploadFile()
{
    // initalize storage and options
    var storage = new LocalConfigStorage("ksm-config.json");
    var options = new SecretsManagerOptions(storage);
    
    // get a record to attach the file to
    var records = (await SecretsManagerClient.GetSecrets(
            options, new[] { "XXX" })
        ).Records;
        
    var ownerRecord = records[0];
    
    // get file data to upload
    var bytes = await File.ReadAllBytesAsync("my-file.json");
    var myFile = new KeeperFileUpload(
        "my-file1.json", 
        "My File", 
        null, 
        bytes
    );
       
    // upload file to selected record                     
    await SecretsManagerClient.UploadFile(options, ownerRecord, myFile);
    
}
```

### 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" %}

```csharp
SecretsManagerClient.CreateSecret(options, folderUid, record)
```

<table><thead><tr><th width="168">Parameter</th><th width="261">Type</th><th>Required</th><th>Default</th></tr></thead><tbody><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td></tr><tr><td><code>folderUid</code></td><td><code>string</code></td><td>Yes</td><td></td></tr><tr><td><code>record</code></td><td><code>KeeperRecordData</code></td><td>Yes</td><td></td></tr></tbody></table>
{% endtab %}

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

```csharp
SecretsManagerClient.CreateSecret2(options, createOptions, record, folders)
```

<table><thead><tr><th width="168">Parameter</th><th width="261">Type</th><th>Required</th><th>Default</th></tr></thead><tbody><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td></tr><tr><td><code>createOptions</code></td><td><code>CreateOptions</code></td><td>Yes</td><td></td></tr><tr><td><code>record</code></td><td><code>KeeperRecordData</code></td><td>Yes</td><td></td></tr><tr><td><code>folders</code></td><td><code>KeeperFolder[]</code></td><td>No</td><td></td></tr></tbody></table>
{% 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 %}

```csharp
var newRecord = new KeeperRecordData{type = "login", title = "Sample KSM Record: C#"};

newRecord.fields = new[]
{
    new KeeperRecordField { type = "login", value = new[] { "My Username" } },
    new KeeperRecordField { type = "password", value = new[] { CryptoUtils.GeneratePassword() } },
};
newRecord.notes = "This is a C# record creation example";

var recordUid = await SecretsManagerClient.CreateSecret(options, folderUid, newRecord);
```

{% endtab %}

{% tab title="Custom Type Example" %}
This example creates a record with a custom record type.

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

```csharp
var newRecord = new KeeperRecordData();
newRecord.type = "Custom Login";
newRecord.title = "Sample Custom Type KSM Record: C#";
newRecord.fields = new[]
{
    new KeeperRecordField { 
        type = "host", 
        value = new[] 
        {
            new Dictionary<string, string>
            {
                { "hostName", "127.0.0.1"},
                { "port", "8080"}
            }
        },
        label = "My Custom Host lbl",
        required = true
    },
    
    new KeeperRecordField { 
        type = "login", 
        value = new[] { "login@email.com" }, 
        required = true, 
        label = "My Custom Login lbl"
    },
    
    new KeeperRecordField
    {
        type = "password", 
        value = new[] { CryptoUtils.GeneratePassword() }, 
        required = true, 
        label = "My Custom Password lbl"
    },
    
    new KeeperRecordField
    {
        type = "url",
        value = new[] { "http://localhost:8080/login" },
        label = "My Login Page",
        required = true
    },
    
    new KeeperRecordField { 
        type = "securityQuestion", 
        value = new[]
        {
            new Dictionary<string, string>
            {
                {"question", "What is one plus one (write just a number)"}, 
                { "answer", "2" }
            }
        }, 
        label = "My Question 1",
        required = true
    },
    
    new KeeperRecordField { 
        type = "phone", 
        value = new[]
        {
            new Dictionary<string,string>
            {
                { "region", "US" },
                { "number", "510-444-3333" },
                { "ext", "2345" },
                { "type", "Mobile" }
            }
        }, 
        label = "My Private Phone", 
        privacyScreen = true
    },

    new KeeperRecordField
    {
        type = "date", 
        value = new[] {(object) 1641934793000 }, 
        label = "My Date Lbl"
    },
    
    new KeeperRecordField { 
        type = "name", 
        value = new[]
        {
            new Dictionary<string, string>
            {
                {"first", "John"},
                {"middle", "Patrick"},
                {"last", "Smith"}
            }
        },
        required = true
    },
    new KeeperRecordField
    {
        type = "oneTimeCode", 
        value = new[]
        {
            "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"
        }, 
        label = "My TOTP",
        required = true
    }
};
newRecord.notes = "\tThis custom type record was created\n\tvia .NET SDK copied from https://docs.keeper.io/secrets-manager/secrets-manager/developer-sdk-library/.net-sdk";

var recordUid = await SecretsManagerClient.CreateSecret(options, "[FOLDER_UID]", newRecord);

```

{% endtab %}
{% endtabs %}

### Delete a Secret

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

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

```csharp
DeleteSecret(smOptions, recordsUids);
```

<table><thead><tr><th>Parameter</th><th width="250.66666666666666">Type</th><th align="center">Required</th></tr></thead><tbody><tr><td><code>smOptions</code></td><td><code>SecretsManagerOptions</code></td><td align="center">Yes</td></tr><tr><td><code>recordsUids</code></td><td><code>string[]</code></td><td align="center">Yes</td></tr></tbody></table>
{% endtab %}

{% tab title="Example" %}

<pre class="language-csharp"><code class="lang-csharp">using SecretsManager;

// setup secrets manager
var storage = new LocalConfigStorage("ksm-config.json");
//SecretsManagerClient.InitializeStorage(storage, "&#x3C;One Time Access Token>");
var smOptions = new SecretsManagerOptions(storage);

// delete a specific secret by record UID
<strong>await SecretsManagerClient.DeleteSecret(smOptions, new string[] {"EG6KdJaaLG7esRZbMnfbFA"});
</strong></code></pre>

{% endtab %}
{% endtabs %}

### Caching

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

**Setup and Configure Cache**

In order to setup caching in the .Net SDK, include a caching post function as the second argument when instantiating a`SecretsManagerOptions` object.

The .Net SDK includes a default caching function `cachingPostFunction` which stores cached queries to a file.

```csharp
var options = new SecretsManagerOptions(storage, SecretsManagerClient.CachingPostFunction);
var secrets = await SecretsManagerClient.GetSecrets(options);
```

### Folders

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

### Read Folders

Downloads full folder hierarchy.

```csharp
Task<KeeperFolder[]> GetFolders(SecretsManagerOptions options)
```

**Response**

Type: `KeeperFolder[]`

**Example Usage**

```csharp
using SecretsManager;

var options = new SecretsManagerOptions(new LocalConfigStorage("ksm-config.json"));
var folders = await SecretsManagerClient.GetFolders(options);
```

### 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, a 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.

```csharp
Task<string> CreateFolder(SecretsManagerOptions options, CreateOptions createOptions, string folderName, KeeperFolder[] folders = null)
```

<table><thead><tr><th width="181.85161529417937">Parameter</th><th width="242.99431983802478">Type</th><th width="102">Required</th><th width="87">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td><td>Preconfigured options</td></tr><tr><td><code>createOptions</code></td><td><code>CreateOptions</code></td><td>Yes</td><td></td><td>The parent and sub-folder UIDs</td></tr><tr><td><code>folderName</code></td><td><code>string</code></td><td>Yes</td><td></td><td>The Folder name</td></tr><tr><td><code>folders</code></td><td><code>KeeperFolder[]</code></td><td>No</td><td><code>null</code></td><td>List of folders to use in the search for parent and sub-folder from CreateOptions</td></tr></tbody></table>

```csharp
public class CreateOptions {
    public string FolderUid { get; }
    public string SubFolderUid { get; }
}
```

```csharp
public class KeeperFolder {
        public byte[] FolderKey { get; }
        public string FolderUid { get; }
        public string ParentUid { get; }
        public string Name { get; }
}
```

**Example Usage**

```csharp
using SecretsManager;

var options = new SecretsManagerOptions(new LocalConfigStorage("ksm-config.json"));
var co = new CreateOptions("[PARENT_SHARED_FOLDER_UID]");
var folderUid = await SecretsManagerClient.CreateFolder(options, co, "new_folder");
```

### Update a Folder

Updates the folder metadata - currently folder name only.

```csharp
Task UpdateFolder(SecretsManagerOptions options, string folderUid, string folderName, KeeperFolder[] folders = null)
```

<table><thead><tr><th width="154.85161529417937">Parameter</th><th width="243.99431983802478">Type</th><th width="100">Required</th><th width="88">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td><td>Preconfigured options</td></tr><tr><td><code>folderUid</code></td><td><code>string</code></td><td>Yes</td><td></td><td>The folder UID</td></tr><tr><td><code>folderName</code></td><td><code>string</code></td><td>Yes</td><td></td><td>The new folder name</td></tr><tr><td><code>folders</code></td><td><code>KeeperFolder[]</code></td><td>No</td><td><code>null</code></td><td>List of folders to use in the search for parent folder</td></tr></tbody></table>

**Example Usage**

```csharp
using SecretsManager;

var options = new SecretsManagerOptions(new LocalConfigStorage("ksm-config.json"));
await SecretsManagerClient.UpdateFolder(options, "[FOLDER_UID]", "new_folder_name");
```

### 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 %}

```csharp
Task DeleteFolder(SecretsManagerOptions options, string[] folderUids, bool forceDeletion = false)
```

<table><thead><tr><th width="180.85161529417937">Parameter</th><th width="242.99431983802478">Type</th><th width="100">Required</th><th width="89">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td><td>Preconfigured options</td></tr><tr><td><code>folderUids</code></td><td><code>string[]</code></td><td>Yes</td><td></td><td>The folder UID list</td></tr><tr><td><code>forceDeletion</code></td><td><code>bool</code></td><td>No</td><td><code>false</code></td><td>Force deletion of non-empty folders</td></tr></tbody></table>

**Example Usage**

```csharp
using SecretsManager;

var options = new SecretsManagerOptions(new LocalConfigStorage("ksm-config.json"));
await SecretsManagerClient.DeleteFolder(options, new string[]{"[FOLDER_UID1]", "[FOLDER_UID2]"}, true);
```

### Proxy support

.NET SDK supports setting proxy through environment variables and passing proxy url to `SecretsManagerOptions` directly

{% hint style="info" %}
Keeper recommends using **environment variables** for proxy settings. This approach keeps configuration details out of code, ensures consistency across tools and environments, and simplifies deployment and maintenance
{% endhint %}

**Example usage through environment variables**

```
HTTPS_PROXY=http://user:password@127.0.0.1:3128 dotnet run
```

Example usage passing url to `SecretsManagerOptions`

```csharp
var options = new SecretsManagerOptions(storage, null, false, "http://user:password@127.0.0.1:3128");
```


---

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