> For the complete documentation index, see [llms.txt](https://docs.keeper.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.keeper.io/keeperpam/secrets-manager/about/linked-credentials.md).

# Linked Credentials

## About

Records in Keeper can be linked to one another through GraphSync. The most common use is Privileged Access Manager: a PAM resource record (a [PAM Machine, Database, Directory, or Remote Browser](/keeperpam/secrets-manager/about/pam-record-types.md)) links to the `pamUser` records that operate it, along with metadata describing what each user is allowed to do.

The Secrets Manager SDKs expose these links on each record so you can discover, for example, which users administer a given machine and which one is the launch credential.

{% hint style="info" %}
Links are returned only when you ask for them. Retrieving links increases response size and processing time, so request them only when you need them.
{% endhint %}

## Retrieving Linked Records

Pass a request-links option to the secrets query. Each SDK exposes this through its query-options object; the method and flag names per language are in the Cross-SDK Reference below.

```python
from keeper_secrets_manager_core import SecretsManager
from keeper_secrets_manager_core.storage import FileKeyValueStorage
from keeper_secrets_manager_core.dto.payload import QueryOptions

secrets_manager = SecretsManager(config=FileKeyValueStorage('ksm-config.json'))

# request links along with the records
# QueryOptions takes records_filter and folders_filter first; pass None for both to fetch everything
query = QueryOptions(records_filter=None, folders_filter=None, request_links=True)
records = secrets_manager.get_secrets_with_options(query)

for record in records:
    for link in record.links:
        print(f"{record.title} ({record.type}) -> {link['recordUid']}")
        if link.get('path'):
            print(f"  path: {link['path']}")
```

{% hint style="warning" %}
You only get links if you request them. Empty-vs-absent behavior differs by SDK: **Java/Kotlin** return `null` when links were not requested and an empty list when requested but none exist; **Python and Rust** return an empty collection in both cases (so you cannot tell "not requested" from "none exist"). Check for emptiness before iterating.
{% endhint %}

{% hint style="info" %}
Links appear on resource records (PAM Machine/Database/Directory/Remote Browser). A `pamUser` record's own `links` are always empty — there is no back-reference. To find every machine a user operates, iterate the resource records.
{% endhint %}

## Link Object Structure

Each link is an object with three fields:

| Field       | Type              | Description                                                       |
| ----------- | ----------------- | ----------------------------------------------------------------- |
| `recordUid` | String            | UID of the linked record                                          |
| `data`      | String (optional) | Base64-encoded link payload; `null` when the edge carries no data |
| `path`      | String (optional) | Identifies what `data` means; `null` for a basic user link        |

The `path` value determines how to interpret `data`:

| `path`         | `data` contents                           | Notes                                                                       |
| -------------- | ----------------------------------------- | --------------------------------------------------------------------------- |
| `null` / empty | User-link metadata, JSON (snake\_case)    | `recordUid` is the linked `pamUser`. Admin / launch / IAM / rotation flags. |
| `meta`         | Resource's own settings, JSON (camelCase) | `recordUid` is the resource's own UID (self-reference).                     |
| `ai_settings`  | Encrypted blob                            | Decrypt with the resource record's own key.                                 |
| `jit_settings` | Encrypted blob                            | Just-In-Time access config; decrypt with the record's own key.              |
| `domain`       | Link edge                                 | Machine → directory reference used by JIT lookups.                          |

### User-link data (path is null)

Decoded JSON, snake\_case keys. All fields optional — treat missing as false/absent.

```json
{
  "is_admin": true,
  "is_launch_credential": true,
  "is_iam_user": false,
  "belongs_to": true,
  "rotation_settings": { "schedule": "", "disabled": false, "noop": false }
}
```

### Meta-link data (path is "meta")

Decoded JSON, camelCase keys.

```json
{
  "version": 1,
  "allowedSettings": {
    "rotation": true,
    "connections": true,
    "portForwards": true,
    "sessionRecording": true,
    "typescriptRecording": true,
    "aiEnabled": false
  },
  "rotateOnTermination": false
}
```

{% hint style="warning" %}
User-link `data` uses **snake\_case** keys; meta-link `data` uses **camelCase**. SDKs that return links untyped (everything except Java/Kotlin) leave this translation to you.
{% endhint %}

## Cross-SDK Reference

All SDKs that support linked records expose the same wire fields; the surface differs by language.

|                   | Query method                      | Request-links flag | Links accessor      | Link shape                                   |
| ----------------- | --------------------------------- | ------------------ | ------------------- | -------------------------------------------- |
| **Java / Kotlin** | `getSecrets2(opts, qopts)`        | `requestLinks`     | `record.getLinks()` | typed `KeeperRecordLink` (22 helper methods) |
| **Python**        | `get_secrets_with_options(qopts)` | `request_links`    | `record.links`      | list of dicts                                |
| **JavaScript**    | `getSecrets2(opts, qopts)`        | `requestLinks`     | `record.links`      | `{recordUid, data?, path?}`                  |
| **.NET**          | `GetSecrets2(opts, qopts)`        | `RequestLinks`     | `record.Links`      | typed `KeeperRecordLink` (properties)        |
| **Go**            | `GetSecretsWithOptions(qopts)`    | `RequestLinks`     | `record.Links`      | typed `RecordLink` struct                    |
| **Rust**          | `get_secrets_with_options(qopts)` | `request_links`    | `record.links`      | `Vec<HashMap<String, Value>>`                |

Only the **Java/Kotlin** SDK provides typed accessor methods over the link data today. The other SDKs return the raw `{recordUid, data, path}` structure and you parse `data` yourself. For the full typed surface, see the [Java Linked Credentials reference](/keeperpam/secrets-manager/developer-sdk-library/java-sdk/linked-credentials-on-pam-records.md).

## Method Reference (Java / Kotlin)

The typed `KeeperRecordLink` methods, for reference. Other SDKs read the same underlying wire fields shown above.

<table><thead><tr><th>Method</th><th width="160">Returns</th><th>Description</th></tr></thead><tbody><tr><td><code>getRecordUid()</code></td><td><code>String</code></td><td>Target record UID</td></tr><tr><td><code>getPath()</code></td><td><code>String</code></td><td>Link metadata type</td></tr><tr><td><code>getData()</code></td><td><code>String</code></td><td>Raw Base64 link data</td></tr><tr><td><code>isAdminUser()</code></td><td><code>boolean</code></td><td>User has admin privileges</td></tr><tr><td><code>isLaunchCredential()</code></td><td><code>boolean</code></td><td>This is a launch credential</td></tr><tr><td><code>allowsRotation()</code></td><td><code>boolean</code></td><td>Password rotation allowed</td></tr><tr><td><code>allowsConnections()</code></td><td><code>boolean</code></td><td>Connections allowed</td></tr><tr><td><code>allowsPortForwards()</code></td><td><code>boolean</code></td><td>Port forwarding allowed</td></tr><tr><td><code>allowsSessionRecording()</code></td><td><code>boolean</code></td><td>Session recording enabled</td></tr><tr><td><code>allowsTypescriptRecording()</code></td><td><code>boolean</code></td><td>Typescript recording enabled</td></tr><tr><td><code>allowsRemoteBrowserIsolation()</code></td><td><code>boolean</code></td><td>RBI allowed</td></tr><tr><td><code>rotatesOnTermination()</code></td><td><code>boolean</code></td><td>Rotates on session termination</td></tr><tr><td><code>getDecodedData()</code></td><td><code>String</code></td><td>Base64 decode without decryption</td></tr><tr><td><code>getDecryptedData(byte[])</code></td><td><code>String</code></td><td>Decrypt data using the record key</td></tr><tr><td><code>getAiSettingsData(byte[])</code></td><td><code>Map</code></td><td>AI settings access</td></tr><tr><td><code>getJitSettingsData(byte[])</code></td><td><code>Map</code></td><td>JIT settings access</td></tr><tr><td><code>getSettingsForPath(String, byte[])</code></td><td><code>Map</code></td><td>Generic settings access by path</td></tr></tbody></table>

## Important Notes

* **`linksToRemove` removes files, not record links.** The `linksToRemove` parameter in update options removes file attachments; it does not delete GraphSync record links.
* **Encryption.** `ai_settings` and `jit_settings` payloads are encrypted with the resource record's own key — decrypt with that key, not a vertex/keychain key.
* **Performance.** Requesting links increases response size and processing time; filter records when you can.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://docs.keeper.io/keeperpam/secrets-manager/about/linked-credentials.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
