From ab7f6a5b1d4e844c8364fde58ea3965b9db30a92 Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Tue, 7 Apr 2026 09:33:29 -0700 Subject: [PATCH 1/6] chore(docs): restructure Discovery page and add Architecture Response Objects (#274) Discovery: - Add Setup section with client init for Go, Java, JS - Add Signature/Parameters/Returns to all 5 methods - Strip inline JS auth setup from examples - Link Returns to Policy type references Architecture: - Add Response Objects section showing getter patterns across Go/Java/JS with four examples: GetAttributeValue, ListAttributeValues, GetDecision, GetEntitlements Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/sdks/discovery.mdx | 436 ++++++++++++++++++++++++---------- docs/sdks/platform-client.mdx | 105 ++++++++ 2 files changed, 414 insertions(+), 127 deletions(-) diff --git a/docs/sdks/discovery.mdx b/docs/sdks/discovery.mdx index ec7a2c4b..305833ed 100644 --- a/docs/sdks/discovery.mdx +++ b/docs/sdks/discovery.mdx @@ -13,6 +13,57 @@ Before encrypting data with `CreateTDF`, it helps to verify that the attributes The Go, Java, and JavaScript SDKs provide five methods to discover and validate attributes without manual platform queries. +## Setup + +All examples on this page assume you have created a platform client. See [Authentication](/sdks/authentication) for full details including DPoP key binding. + + + + +```go +client, err := sdk.New("http://localhost:8080", + sdk.WithClientCredentials("opentdf", "secret", nil), +) +if err != nil { + log.Fatal(err) +} + +// All Go snippets below use `client` and `context.Background()`. +``` + + + + +```java +SDK sdk = SDKBuilder.newBuilder() + .platformEndpoint("http://localhost:8080") + .clientSecret("opentdf", "secret") + .useInsecurePlaintextConnection(true) + .build(); + +// All Java snippets below use `sdk`. +``` + + + + +```typescript +import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF } from '@opentdf/sdk'; + +const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ + clientId: 'opentdf', clientSecret: 'secret', + oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', +}))] }; + +const platformUrl = 'http://localhost:8080'; + +// All JavaScript snippets below use `auth` and `platformUrl`. +// Standalone functions like listAttributes() take these as arguments. +``` + + + + --- @@ -21,6 +72,41 @@ The Go, Java, and JavaScript SDKs provide five methods to discover and validate Returns all active attributes on the platform. Use this to see what's available before choosing which attributes to apply to a TDF. +**Signature** + + + + +```go +client.ListAttributes(ctx, ...namespaceFilter) +``` + + + + +```java +sdk.listAttributes() +sdk.listAttributes(namespaceFilter) +``` + + + + +```typescript +await listAttributes(platformUrl, auth, namespaceFilter?) +``` + + + + +**Parameters** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `namespaceFilter` | `string` | No | When provided, returns only attributes in this namespace (e.g., `"opentdf.io"`). | + +**Example** + @@ -67,13 +153,8 @@ List attrs = sdk.listAttributes("opentdf.io"); -```ts -import { authTokenInterceptor, clientCredentialsTokenProvider, listAttributes } from '@opentdf/sdk'; - -const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', -}))] }; +```typescript +import { listAttributes } from '@opentdf/sdk'; const attrs = await listAttributes(platformUrl, auth); @@ -87,27 +168,57 @@ for (const a of attrs) { To filter by namespace: -```ts +```typescript const attrs = await listAttributes(platformUrl, auth, 'opentdf.io'); ``` -`listAttributes` / `ListAttributes` paginates automatically — you always get the full list back without needing to manage page tokens. +**Returns** + +A list of [Attribute](/sdks/policy#attribute-object) objects, each with nested [values](/sdks/policy#attribute-value-object). Paginates automatically — you always get the full list back without managing page tokens. --- ## AttributeExists -Reports whether an attribute definition exists on the platform. Returns `true`/`false` — useful for a quick existence check before adding values to an attribute or constructing an access policy. +Reports whether an attribute definition exists on the platform. Returns `true`/`false` — useful for a quick existence check before adding values or constructing an access policy. + +**Signature** + + + + +```go +client.AttributeExists(ctx, attributeFqn) +``` -`attributeFqn` should be an **attribute-level** FQN (no `/value/` segment): + + +```java +sdk.attributeExists(attributeFqn) ``` -https:///attr/ + + + + +```typescript +await attributeExists(platformUrl, auth, attributeFqn) ``` + + + +**Parameters** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `attributeFqn` | `string` | Yes | An attribute-level FQN (no `/value/` segment): `https:///attr/` | + +**Example** + @@ -134,13 +245,8 @@ if (!exists) { -```ts -import { authTokenInterceptor, clientCredentialsTokenProvider, attributeExists } from '@opentdf/sdk'; - -const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', -}))] }; +```typescript +import { attributeExists } from '@opentdf/sdk'; const exists = await attributeExists(platformUrl, auth, 'https://opentdf.io/attr/department'); if (!exists) { @@ -151,18 +257,50 @@ if (!exists) { +**Returns** + +`true` if the attribute exists, `false` otherwise. + --- ## AttributeValueExists Reports whether a specific attribute value FQN exists on the platform. Returns `true`/`false` — useful for spot-checking one pre-registered value. -`valueFqn` should be a **full attribute value** FQN (with `/value/` segment): +**Signature** + + + + +```go +client.AttributeValueExists(ctx, valueFqn) +``` + + + +```java +sdk.attributeValueExists(valueFqn) ``` -https:///attr//value/ + + + + +```typescript +await attributeValueExists(platformUrl, auth, valueFqn) ``` + + + +**Parameters** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `valueFqn` | `string` | Yes | A full attribute value FQN: `https:///attr//value/` | + +**Example** + @@ -189,13 +327,8 @@ if (!exists) { -```ts -import { authTokenInterceptor, clientCredentialsTokenProvider, attributeValueExists } from '@opentdf/sdk'; - -const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', -}))] }; +```typescript +import { attributeValueExists } from '@opentdf/sdk'; const exists = await attributeValueExists(platformUrl, auth, 'https://opentdf.io/attr/department/value/finance'); if (!exists) { @@ -206,12 +339,50 @@ if (!exists) { +**Returns** + +`true` if the value exists, `false` otherwise. + --- ## ValidateAttributes Checks that a list of attribute value FQNs exist on the platform **before** calling `CreateTDF`. This catches misspellings and missing attributes at the point of use rather than at decryption time. +**Signature** + + + + +```go +client.ValidateAttributes(ctx, fqns...) +``` + + + + +```java +sdk.validateAttributes(fqns) +``` + + + + +```typescript +await validateAttributes(platformUrl, auth, fqns) +``` + + + + +**Parameters** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `fqns` | `[]string` | Yes | Attribute value FQNs to validate (up to 250 per call). | + +**Example** + @@ -223,8 +394,7 @@ fqns := []string{ if err := client.ValidateAttributes(ctx, fqns...); err != nil { log.Fatalf("attribute validation failed: %v", err) - // err will name the specific FQNs that are missing, e.g.: - // "attribute not found: https://opentdf.io/attr/clearance/value/executive" + // err will name the specific FQNs that are missing } // Safe to encrypt — all attributes confirmed present @@ -247,8 +417,7 @@ try { sdk.validateAttributes(fqns); } catch (SDK.AttributeNotFoundException e) { System.err.println("attribute validation failed: " + e.getMessage()); - // getMessage() names the specific FQNs that are missing, e.g.: - // "attribute not found: https://opentdf.io/attr/clearance/value/executive" + // getMessage() names the specific FQNs that are missing } // Safe to encrypt — all attributes confirmed present @@ -262,13 +431,8 @@ sdk.createTDF(inputStream, outputStream, config); -```ts -import { authTokenInterceptor, clientCredentialsTokenProvider, validateAttributes, AttributeNotFoundError } from '@opentdf/sdk'; - -const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', -}))] }; +```typescript +import { validateAttributes, AttributeNotFoundError } from '@opentdf/sdk'; const fqns = [ 'https://opentdf.io/attr/department/value/marketing', @@ -280,8 +444,7 @@ try { } catch (e) { if (e instanceof AttributeNotFoundError) { console.error('attribute validation failed:', e.message); - // e.message names the specific FQNs that are missing, e.g.: - // "attribute not found: https://opentdf.io/attr/clearance/value/executive" + // e.message names the specific FQNs that are missing } } @@ -291,17 +454,57 @@ try { -**Why this matters**: `CreateTDF` succeeds even when the attribute doesn't exist on the platform (it embeds the FQN string in the TDF policy). The failure only surfaces later when a decryption client attempts to resolve the attribute through the KAS and gets a `not_found` response. `ValidateAttributes` makes that failure happen immediately, at the point of encryption, with a clear message. +**Returns** + +No return value on success. Throws/returns an error naming the specific FQNs that are missing. + +**Why this matters**: `CreateTDF` succeeds even when the attribute doesn't exist on the platform — it embeds the FQN string in the TDF policy. The failure only surfaces later when a decryption client attempts to resolve the attribute through the KAS. `ValidateAttributes` makes that failure happen immediately, with a clear message. :::tip -`validateAttributes` / `ValidateAttributes` accepts up to 250 FQNs per call, matching the platform's limit. For larger sets, batch your FQNs across multiple calls. +Accepts up to 250 FQNs per call, matching the platform's limit. For larger sets, batch across multiple calls. ::: --- ## GetEntityAttributes -Returns the attribute value FQNs assigned to a specific entity (person entity or non-person entity). Use this to inspect what a user, service account, or other principal has been granted — for example, to understand why someone can or cannot decrypt a TDF. +Returns the attribute value FQNs assigned to a specific entity (person or non-person). Use this to inspect what a user or service account has been granted — for example, to understand why someone can or cannot decrypt a TDF. + +**Signature** + + + + +```go +client.GetEntityAttributes(ctx, entity) +``` + + + + +```java +sdk.getEntityAttributes(entity) +``` + + + + +```typescript +await platformClient.v2.authorization.getEntitlements({ ... }) +``` + +The JS SDK does not expose a standalone `getEntityAttributes` function. Use `getEntitlements` via `PlatformClient` in a Node.js server environment. + + + + +**Parameters** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `entity` | `Entity` | Yes | The entity to look up. Supports email, username, client ID, and UUID. | + +**Example** @@ -374,14 +577,11 @@ Entity.newBuilder().setId("e1").setUuid("550e8400-e29b-41d4-a716-446655440000"). -The JavaScript SDK does not expose a `getEntityAttributes` convenience function because `GetEntitlements` is a server-side/PEP operation that should not be called from browser contexts. In a Node.js server environment, use the `PlatformClient` directly: - -```ts +```typescript import { PlatformClient } from '@opentdf/sdk/platform'; const platform = new PlatformClient({ ...auth, platformUrl }); -// By email address const resp = await platform.v2.authorization.getEntitlements({ entityIdentifier: { identifier: { @@ -406,7 +606,7 @@ if (resp.entitlements.length > 0) { Other supported entity types (placed inside the `entities` array): -```ts +```typescript // By username { ephemeralId: 'e1', entityType: { case: 'userName', value: 'alice' } } @@ -417,8 +617,12 @@ Other supported entity types (placed inside the `entities` array): +**Returns** + +A list of [attribute value](/sdks/policy#attribute-value-object) FQN strings that the entity is entitled to access. + :::warning Authorization required -Querying another entity's attributes requires your caller to have appropriate platform permissions on the `GetEntitlements` endpoint. Without this, the call will return a `permission_denied` error. Contact your platform administrator to confirm your service account has the necessary policy access. +Querying another entity's attributes requires your caller to have appropriate platform permissions on the `GetEntitlements` endpoint. Without this, the call will return a `permission_denied` error. ::: --- @@ -431,100 +635,78 @@ A common pattern is to validate attributes at application startup and surface he ```go -func main() { - client, err := sdk.New(platformEndpoint, - sdk.WithClientCredentials("opentdf", "secret", nil), - sdk.WithInsecureSkipVerifyConn(), - ) - if err != nil { - log.Fatalf("sdk init failed: %v", err) - } - - // 1. See what's available on the platform - attrs, err := client.ListAttributes(ctx) - if err != nil { - log.Fatalf("could not reach platform: %v", err) - } - log.Printf("platform has %d active attributes", len(attrs)) +// 1. See what's available on the platform +attrs, err := client.ListAttributes(ctx) +if err != nil { + log.Fatalf("could not reach platform: %v", err) +} +log.Printf("platform has %d active attributes", len(attrs)) - // 2. Check a specific attribute exists before using it - exists, err := client.AttributeExists(ctx, "https://opentdf.io/attr/department") - if err != nil { - log.Fatalf("service error: %v", err) - } - if !exists { - log.Fatalf("attribute missing — create it first") - } +// 2. Check a specific attribute exists before using it +exists, err := client.AttributeExists(ctx, "https://opentdf.io/attr/department") +if err != nil { + log.Fatalf("service error: %v", err) +} +if !exists { + log.Fatalf("attribute missing — create it first") +} - // 3. Validate the specific values before encrypting - required := []string{ - "https://opentdf.io/attr/department/value/marketing", - } - if err := client.ValidateAttributes(ctx, required...); err != nil { - log.Fatalf("required attributes missing — create them first: %v", err) - } +// 3. Validate the specific values before encrypting +required := []string{ + "https://opentdf.io/attr/department/value/marketing", +} +if err := client.ValidateAttributes(ctx, required...); err != nil { + log.Fatalf("required attributes missing: %v", err) +} - // 4. Encrypt with confidence - _, err = client.CreateTDF(encryptedBuffer, dataReader, - sdk.WithDataAttributes(required...), - sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}), - ) - if err != nil { - log.Fatalf("encryption failed: %v", err) - } - log.Println("data encrypted successfully") +// 4. Encrypt with confidence +_, err = client.CreateTDF(encryptedBuffer, dataReader, + sdk.WithDataAttributes(required...), + sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}), +) +if err != nil { + log.Fatalf("encryption failed: %v", err) } +log.Println("data encrypted successfully") ``` ```java -try (SDK sdk = new SDKBuilder() - .platformEndpoint(platformEndpoint) - .clientSecret("opentdf", "secret") - .build()) { - - // 1. See what's available on the platform - List attrs = sdk.listAttributes(); - System.out.printf("platform has %d active attributes%n", attrs.size()); - - // 2. Check a specific attribute exists before using it - if (!sdk.attributeExists("https://opentdf.io/attr/department")) { - System.err.println("attribute missing — create it first"); - return; - } +// 1. See what's available on the platform +List attrs = sdk.listAttributes(); +System.out.printf("platform has %d active attributes%n", attrs.size()); - // 3. Validate the specific values before encrypting - List required = List.of("https://opentdf.io/attr/department/value/marketing"); - try { - sdk.validateAttributes(required); - } catch (SDK.AttributeNotFoundException e) { - System.err.println("required attributes missing — create them first: " + e.getMessage()); - return; - } +// 2. Check a specific attribute exists before using it +if (!sdk.attributeExists("https://opentdf.io/attr/department")) { + System.err.println("attribute missing — create it first"); + return; +} - // 4. Encrypt with confidence - Config.TDFConfig config = Config.newTDFConfig( - Config.withDataAttributes(required), - Config.withKasInformation(new Config.KASInfo(platformEndpoint, null)) - ); - sdk.createTDF(inputStream, outputStream, config); - System.out.println("data encrypted successfully"); +// 3. Validate the specific values before encrypting +List required = List.of("https://opentdf.io/attr/department/value/marketing"); +try { + sdk.validateAttributes(required); +} catch (SDK.AttributeNotFoundException e) { + System.err.println("required attributes missing: " + e.getMessage()); + return; } + +// 4. Encrypt with confidence +Config.TDFConfig config = Config.newTDFConfig( + Config.withDataAttributes(required), + Config.withKasInformation(new Config.KASInfo(platformEndpoint, null)) +); +sdk.createTDF(inputStream, outputStream, config); +System.out.println("data encrypted successfully"); ``` -```ts -import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF, listAttributes, attributeExists, validateAttributes, AttributeNotFoundError } from '@opentdf/sdk'; - -// Define auth once — reuse for standalone functions and the OpenTDF client -const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: oidcEndpoint, -}))] }; +```typescript +import { listAttributes, attributeExists, validateAttributes, AttributeNotFoundError, OpenTDF } from '@opentdf/sdk'; // 1. See what's available on the platform const attrs = await listAttributes(platformUrl, auth); @@ -543,7 +725,7 @@ try { await validateAttributes(platformUrl, auth, required); } catch (e) { if (e instanceof AttributeNotFoundError) { - console.error('required attributes missing — create them first:', e.message); + console.error('required attributes missing:', e.message); process.exit(1); } throw e; diff --git a/docs/sdks/platform-client.mdx b/docs/sdks/platform-client.mdx index cfeaf4f7..f6457af8 100644 --- a/docs/sdks/platform-client.mdx +++ b/docs/sdks/platform-client.mdx @@ -89,3 +89,108 @@ const platform = new PlatformClient({ ...auth, platformUrl: 'http://localhost:80 + +## Response Objects + +All platform API calls return protobuf response objects. The way you access fields differs by language: **Go** uses generated getter methods prefixed with `Get`, **Java** uses standard getters, and **JavaScript** uses direct property access with optional chaining. + +**GetAttributeValue** — accessing a policy attribute value: + +```go +resp, err := client.Attributes.GetAttributeValue(ctx, req) +v := resp.GetValue() +v.GetValue() // "confidential" +v.GetFqn() // "https://example.com/attr/classification/value/confidential" +v.GetId() // "a1b2c3d4-..." +``` + +```java +var resp = sdk.getServices().attributes() + .getAttributeValueBlocking(req, Collections.emptyMap()).execute(); +var v = resp.getValue(); +v.getValue(); // "confidential" +v.getFqn(); // "https://example.com/attr/classification/value/confidential" +v.getId(); // "a1b2c3d4-..." +``` + +```typescript +const resp = await platform.v1.attributes.getAttributeValue({ fqn: '...' }); +resp.value?.value; // "confidential" +resp.value?.fqn; // "https://example.com/attr/classification/value/confidential" +resp.value?.id; // "a1b2c3d4-..." +``` + +**ListAttributeValues** — iterating over a list response: + +```go +resp, err := client.Attributes.ListAttributeValues(ctx, req) +for _, v := range resp.GetValues() { + v.GetValue() // "confidential" + v.GetFqn() // "https://example.com/attr/classification/value/confidential" +} +``` + +```java +var resp = sdk.getServices().attributes() + .listAttributeValuesBlocking(req, Collections.emptyMap()).execute(); +for (var v : resp.getValuesList()) { + v.getValue(); // "confidential" + v.getFqn(); // "https://example.com/attr/classification/value/confidential" +} +``` + +```typescript +const resp = await platform.v1.attributes.listAttributeValues({ attributeId: '...' }); +for (const v of resp.values) { + v.value; // "confidential" + v.fqn; // "https://example.com/attr/classification/value/confidential" +} +``` + +**GetDecision** — accessing an authorization decision: + +```go +resp, err := client.AuthorizationV2.GetDecision(ctx, req) +d := resp.GetDecision() +d.GetDecision() // DECISION_PERMIT or DECISION_DENY +d.GetRequiredObligations() // []string of obligation value FQNs +``` + +```java +var resp = sdk.getServices().authorization().getDecision(req).get(); +var d = resp.getDecision(); +d.getDecision(); // DECISION_PERMIT or DECISION_DENY +d.getRequiredObligationsList(); // List of obligation value FQNs +``` + +```typescript +const resp = await platform.v2.authorization.getDecision({ ... }); +resp.decision?.decision; // Decision.PERMIT or Decision.DENY +resp.decision?.requiredObligations; // string[] of obligation value FQNs +``` + +**GetEntitlements** — accessing entity entitlements: + +```go +resp, err := client.AuthorizationV2.GetEntitlements(ctx, req) +for _, e := range resp.GetEntitlements() { + e.GetActionsPerAttributeValueFqn() // map of FQN → permitted actions +} +``` + +```java +var resp = sdk.getServices().authorization().getEntitlements(req).get(); +for (var e : resp.getEntitlementsList()) { + e.getActionsPerAttributeValueFqnMap(); // Map +} +``` + +```typescript +const resp = await platform.v2.authorization.getEntitlements({ ... }); +for (const e of resp.entitlements) { + e.actionsPerAttributeValueFqn; // Record +} +``` + +This pattern applies to all platform objects. Each SDK page includes type reference tables listing the available fields. + From 7a92f1e2cef8a14ad8bc43ec28449769f28a7486 Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Tue, 7 Apr 2026 10:30:58 -0700 Subject: [PATCH 2/6] chore(docs): rename "Putting It Together" to "Typical Workflow" Consistent naming with the Authorization page. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/sdks/discovery.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sdks/discovery.mdx b/docs/sdks/discovery.mdx index 305833ed..2ad18788 100644 --- a/docs/sdks/discovery.mdx +++ b/docs/sdks/discovery.mdx @@ -627,7 +627,7 @@ Querying another entity's attributes requires your caller to have appropriate pl --- -## Putting It Together +## Typical Workflow A common pattern is to validate attributes at application startup and surface helpful errors before any encryption happens: From 85f6008fd474044ada4733981184cb044eaf962b Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Tue, 7 Apr 2026 10:49:31 -0700 Subject: [PATCH 3/6] fix: Discovery page review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use language-neutral "list of strings" instead of Go-specific []string - Fix platformClient → platform variable name in GetEntityAttributes signature - Clarify that JS returns EntityEntitlements, not plain FQN strings Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/sdks/discovery.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sdks/discovery.mdx b/docs/sdks/discovery.mdx index 2ad18788..60208db0 100644 --- a/docs/sdks/discovery.mdx +++ b/docs/sdks/discovery.mdx @@ -379,7 +379,7 @@ await validateAttributes(platformUrl, auth, fqns) | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `fqns` | `[]string` | Yes | Attribute value FQNs to validate (up to 250 per call). | +| `fqns` | `list of strings` | Yes | Attribute value FQNs to validate (up to 250 per call). | **Example** @@ -490,7 +490,7 @@ sdk.getEntityAttributes(entity) ```typescript -await platformClient.v2.authorization.getEntitlements({ ... }) +await platform.v2.authorization.getEntitlements({ ... }) ``` The JS SDK does not expose a standalone `getEntityAttributes` function. Use `getEntitlements` via `PlatformClient` in a Node.js server environment. @@ -619,7 +619,7 @@ Other supported entity types (placed inside the `entities` array): **Returns** -A list of [attribute value](/sdks/policy#attribute-value-object) FQN strings that the entity is entitled to access. +**Go/Java:** A list of [attribute value](/sdks/policy#attribute-value-object) FQN strings that the entity is entitled to access. **JavaScript:** An [EntityEntitlements](/sdks/authorization#entityentitlements) response from `getEntitlements`, containing a map of FQNs to permitted actions. :::warning Authorization required Querying another entity's attributes requires your caller to have appropriate platform permissions on the `GetEntitlements` endpoint. Without this, the call will return a `permission_denied` error. From 1e00f953c44e648744c7c097e0e939475480c99f Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Tue, 7 Apr 2026 10:58:28 -0700 Subject: [PATCH 4/6] chore(docs): wrap Response Objects examples in Tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All four examples already had Go/Java/JS — now wrapped in Tabs components so the language selector is visible. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/sdks/platform-client.mdx | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/sdks/platform-client.mdx b/docs/sdks/platform-client.mdx index f6457af8..6fdffb13 100644 --- a/docs/sdks/platform-client.mdx +++ b/docs/sdks/platform-client.mdx @@ -96,6 +96,9 @@ All platform API calls return protobuf response objects. The way you access fiel **GetAttributeValue** — accessing a policy attribute value: + + + ```go resp, err := client.Attributes.GetAttributeValue(ctx, req) v := resp.GetValue() @@ -104,6 +107,9 @@ v.GetFqn() // "https://example.com/attr/classification/value/confidential" v.GetId() // "a1b2c3d4-..." ``` + + + ```java var resp = sdk.getServices().attributes() .getAttributeValueBlocking(req, Collections.emptyMap()).execute(); @@ -113,6 +119,9 @@ v.getFqn(); // "https://example.com/attr/classification/value/confidential" v.getId(); // "a1b2c3d4-..." ``` + + + ```typescript const resp = await platform.v1.attributes.getAttributeValue({ fqn: '...' }); resp.value?.value; // "confidential" @@ -120,8 +129,14 @@ resp.value?.fqn; // "https://example.com/attr/classification/value/confident resp.value?.id; // "a1b2c3d4-..." ``` + + + **ListAttributeValues** — iterating over a list response: + + + ```go resp, err := client.Attributes.ListAttributeValues(ctx, req) for _, v := range resp.GetValues() { @@ -130,6 +145,9 @@ for _, v := range resp.GetValues() { } ``` + + + ```java var resp = sdk.getServices().attributes() .listAttributeValuesBlocking(req, Collections.emptyMap()).execute(); @@ -139,6 +157,9 @@ for (var v : resp.getValuesList()) { } ``` + + + ```typescript const resp = await platform.v1.attributes.listAttributeValues({ attributeId: '...' }); for (const v of resp.values) { @@ -147,8 +168,14 @@ for (const v of resp.values) { } ``` + + + **GetDecision** — accessing an authorization decision: + + + ```go resp, err := client.AuthorizationV2.GetDecision(ctx, req) d := resp.GetDecision() @@ -156,6 +183,9 @@ d.GetDecision() // DECISION_PERMIT or DECISION_DENY d.GetRequiredObligations() // []string of obligation value FQNs ``` + + + ```java var resp = sdk.getServices().authorization().getDecision(req).get(); var d = resp.getDecision(); @@ -163,14 +193,23 @@ d.getDecision(); // DECISION_PERMIT or DECISION_DENY d.getRequiredObligationsList(); // List of obligation value FQNs ``` + + + ```typescript const resp = await platform.v2.authorization.getDecision({ ... }); resp.decision?.decision; // Decision.PERMIT or Decision.DENY resp.decision?.requiredObligations; // string[] of obligation value FQNs ``` + + + **GetEntitlements** — accessing entity entitlements: + + + ```go resp, err := client.AuthorizationV2.GetEntitlements(ctx, req) for _, e := range resp.GetEntitlements() { @@ -178,6 +217,9 @@ for _, e := range resp.GetEntitlements() { } ``` + + + ```java var resp = sdk.getServices().authorization().getEntitlements(req).get(); for (var e : resp.getEntitlementsList()) { @@ -185,6 +227,9 @@ for (var e : resp.getEntitlementsList()) { } ``` + + + ```typescript const resp = await platform.v2.authorization.getEntitlements({ ... }); for (const e of resp.entitlements) { @@ -192,5 +237,8 @@ for (const e of resp.entitlements) { } ``` + + + This pattern applies to all platform objects. Each SDK page includes type reference tables listing the available fields. From e8da59833dbcc9308644510972ff9cc92f41cb52 Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Tue, 7 Apr 2026 11:00:21 -0700 Subject: [PATCH 5/6] chore(docs): rename Architecture to SDK Overview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Better reflects the page content — SDK structure, client setup, and response patterns rather than system architecture. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/sdks/platform-client.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sdks/platform-client.mdx b/docs/sdks/platform-client.mdx index 6fdffb13..6995b5cc 100644 --- a/docs/sdks/platform-client.mdx +++ b/docs/sdks/platform-client.mdx @@ -1,13 +1,13 @@ --- sidebar_position: 1 -title: Architecture +title: Overview --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import JsAuthNote from '../../code_samples/js_auth_note.mdx' -# Architecture +# Overview Some SDK functionality — including policy management and authorization decisions — is provided through a **platform service client** rather than through the core SDK. This page explains the difference and when you'll use each. From 4ee7581fcd93d7ad2c76b0ec4e259c56095f3de3 Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Wed, 8 Apr 2026 07:44:14 -0700 Subject: [PATCH 6/6] fix: clarify JS uses different parameter shape for GetEntityAttributes JS uses getEntitlements with EntityIdentifier, not the same entity parameter as Go/Java. Add a note linking to the EntityIdentifier docs. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/sdks/discovery.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/sdks/discovery.mdx b/docs/sdks/discovery.mdx index 60208db0..04ba3ba3 100644 --- a/docs/sdks/discovery.mdx +++ b/docs/sdks/discovery.mdx @@ -500,10 +500,14 @@ The JS SDK does not expose a standalone `getEntityAttributes` function. Use `get **Parameters** +**Go/Java:** + | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `entity` | `Entity` | Yes | The entity to look up. Supports email, username, client ID, and UUID. | +**JavaScript** uses `getEntitlements` instead (see [EntityIdentifier](/sdks/authorization#entityidentifier) for the request shape). + **Example**