SpyBara
Go Premium

Documentation 2026-10-14 23:59 UTC to 2026-10-15 23:00 UTC

6 files changed +761 −2. View all changes and history on the product overview
2026
Mon 19 06:01 Sun 18 23:00 Sat 17 22:57 Fri 16 23:02 Thu 15 23:00 Wed 14 23:59 Tue 13 23:00 Sun 11 23:59 Sat 10 23:58 Fri 9 22:57 Thu 8 23:01 Wed 7 23:57 Tue 6 22:58 Mon 5 22:59 Sun 4 22:58 Fri 2 23:58 Thu 1 22:59

deprecations.md +1 −0

Details

82| Feb 26, 2027 | `gpt-4o-transcribe` | `gpt-live-transcribe` or `gpt-transcribe` |82| Feb 26, 2027 | `gpt-4o-transcribe` | `gpt-live-transcribe` or `gpt-transcribe` |

83| Feb 26, 2027 | `gpt-4o-mini-transcribe` | `gpt-live-transcribe` or `gpt-transcribe` |83| Feb 26, 2027 | `gpt-4o-mini-transcribe` | `gpt-live-transcribe` or `gpt-transcribe` |

84| Feb 26, 2027 | `gpt-4o-transcribe-diarize` | `gpt-live-transcribe` or `gpt-transcribe` |84| Feb 26, 2027 | `gpt-4o-transcribe-diarize` | `gpt-live-transcribe` or `gpt-transcribe` |

85| Feb 26, 2027 | `/v1/audio/translations` | `v1/realtime/translations` |

85 86 

86### 2026-07-20: Legacy audio, realtime, and transcription models87### 2026-07-20: Legacy audio, realtime, and transcription models

87 88 

Details

74- **[Oracle Cloud Infrastructure](https://developers.openai.com/api/docs/guides/workload-identity-federation/oracle-cloud)**: Use instance principal tokens from an Oracle identity domain.74- **[Oracle Cloud Infrastructure](https://developers.openai.com/api/docs/guides/workload-identity-federation/oracle-cloud)**: Use instance principal tokens from an Oracle identity domain.

75- **[GitHub Actions](https://developers.openai.com/api/docs/guides/workload-identity-federation/github-actions)**: Use OIDC tokens in continuous integration workflows.75- **[GitHub Actions](https://developers.openai.com/api/docs/guides/workload-identity-federation/github-actions)**: Use OIDC tokens in continuous integration workflows.

76- **[SPIFFE](https://developers.openai.com/api/docs/guides/workload-identity-federation/spiffe)**: Use SPIFFE JWT-SVIDs issued by SPIRE or a compatible provider.76- **[SPIFFE](https://developers.openai.com/api/docs/guides/workload-identity-federation/spiffe)**: Use SPIFFE JWT-SVIDs issued by SPIRE or a compatible provider.

77- **[Idira](https://developers.openai.com/api/docs/guides/workload-identity-federation/idira)**: Use SPIFFE JWT-SVIDs issued by Idira Secure Workload Access.

77 78 

78 79 

79 80 

Details

1# Configuring workload identity federation for Idira

2 

3> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

4 

5Use Idira Secure Workload Access (SWA) to call the OpenAI API without storing an OpenAI API key. SWA assigns your workload a SPIFFE ID and issues a JWT-SVID, a signed token that represents that identity. The OpenAI SDK exchanges the token for a short-lived OpenAI access token with the permissions of a mapped service account.

6 

7This guide covers the OpenAI API setup. For Codex, use the token delivery steps here, then follow [Use workload identity with Codex](https://developers.openai.com/api/docs/guides/workload-identity-federation#use-workload-identity-with-codex).

8 

9## Before you begin

10 

11You need:

12 

13- An SWA deployment with a trust domain and an attestation policy that assigns the expected SPIFFE ID to your workload.

14- Access to the SWA Agent's Workload API socket from the workload.

15- Permission to manage Workload Identity Providers, service account mappings, projects, and service accounts in your OpenAI organization.

16- An OpenAI SDK that supports workload identity credentials.

17 

18Complete the SWA infrastructure setup using the prerequisites in [Idira's OpenAI integration guide](https://docs.cyberark.com/secrets-manager-saas/latest/en/content/conjurcloud/ccl-swa-openai.htm).

19 

20## Get the issuer and signing keys

21 

22Retrieve your trust domain's `jwt.discovery_endpoints.oidc_discovery_url` and `jwt.discovery_endpoints.jwks_uri`. Follow [Idira's OIDC configuration instructions](https://docs.cyberark.com/secrets-manager-saas/latest/en/content/conjurcloud/ccl-swa-oidc.htm) to find these values.

23 

24The discovery document and JWKS must be reachable by OpenAI over HTTPS without credentials. The issuer is the HTTPS URL in the token's `iss` claim. The SPIFFE ID in `sub` identifies your workload.

25 

26Choose a dedicated audience for OpenAI access, such as `https://api.openai.com/v1`. Use the same value when requesting the JWT-SVID and configuring the OpenAI provider. The audience identifies the token's intended recipient; it doesn't set the token exchange endpoint.

27 

28SWA defaults to `RS512` signing with a 4096-bit RSA key. OpenAI's JWT verifier accepts this combination. For other SWA signing options, see [Idira's JWT requirements](https://docs.cyberark.com/secrets-manager-saas/latest/en/content/conjurcloud/ccl-swa-jwt.htm).

29 

30## Deliver a fresh JWT-SVID to the workload

31 

32Install [`spiffe-helper`](https://github.com/spiffe/spiffe-helper) in the workload environment to request and refresh a JWT-SVID from the SWA Agent. Save the following configuration as `helper.conf`. The helper writes `/var/run/spiffe/openai.jwt`, the path used by the shared SPIFFE SDK examples:

33 

34```hcl

35agent_address = "/tmp/swa-agent/public/api.sock"

36cert_dir = "/var/run/spiffe"

37daemon_mode = true

38 

39jwt_svids = [{

40 jwt_audience = "https://api.openai.com/v1"

41 jwt_svid_file_name = "openai.jwt"

42}]

43```

44 

45In Kubernetes, run the helper as a sidecar in the workload's pod. Share a memory-backed `emptyDir` volume at `/var/run/spiffe`, and mount the SWA socket's parent directory, `/tmp/swa-agent/public`, into the helper. On VMs, use `/run/swa-agent/api.sock` for `agent_address` and keep the token directory on memory-backed storage. Follow Idira's deployment instructions for socket access and attestation.

46 

47Create the token directory before starting the helper, and give the helper write access. Start the helper with this configuration in the sidecar or VM service:

48 

49```bash

50spiffe-helper -config helper.conf

51```

52 

53Restrict access to the token file and agent socket to the processes that need them. Keep the helper running so it replaces the token before it expires.

54 

55If your language has a SPIFFE Workload API client, you can instead fetch a fresh JWT-SVID inside the subject token provider. See the direct Workload API option in [Idira's integration guide](https://docs.cyberark.com/secrets-manager-saas/latest/en/content/conjurcloud/ccl-swa-openai.htm).

56 

57## Inspect the token

58 

59From inside the workload, read the token into the local decoder's environment:

60 

61```bash

62export TOKEN="$(cat /var/run/spiffe/openai.jwt)"

63```

64 

65```javascript

66const parts = process.env.TOKEN?.split(".") ?? [];

67if (parts.length !== 3) {

68 throw new Error("Expected a compact JWT with three segments");

69}

70 

71const decode = (segment) => {

72 if (!/^[A-Za-z0-9_-]+$/.test(segment) || segment.length % 4 === 1) {

73 throw new Error("JWT segment is not valid Base64URL");

74 }

75 const bytes = Buffer.from(segment, "base64url");

76 if (bytes.toString("base64url") !== segment) {

77 throw new Error("JWT segment is not valid Base64URL");

78 }

79 const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);

80 const value = JSON.parse(decoded);

81 if (value === null || Array.isArray(value) || typeof value !== "object") {

82 throw new Error("JWT segment is not a JSON object");

83 }

84 return decoded;

85};

86 

87console.log("Header:");

88console.log(decode(parts[0]));

89console.log("\nPayload:");

90console.log(decode(parts[1]));

91```

92 

93```python

94import base64

95import json

96import os

97import re

98 

99 

100def reject_non_json_constant(value):

101 raise ValueError(f"JWT segment contains non-JSON constant: {value}")

102 

103 

104parts = os.environ.get("TOKEN", "").split(".")

105if len(parts) != 3:

106 raise ValueError("Expected a compact JWT with three segments")

107 

108 

109def decode(segment):

110 if re.fullmatch(r"[A-Za-z0-9_-]+", segment) is None or len(segment) % 4 == 1:

111 raise ValueError("JWT segment is not valid Base64URL")

112 padded_segment = segment + "=" * (-len(segment) % 4)

113 decoded = base64.b64decode(padded_segment, altchars=b"-_", validate=True)

114 if base64.urlsafe_b64encode(decoded).rstrip(b"=").decode("ascii") != segment:

115 raise ValueError("JWT segment is not valid Base64URL")

116 decoded_text = decoded.decode("utf-8")

117 value = json.loads(decoded_text, parse_constant=reject_non_json_constant)

118 if not isinstance(value, dict):

119 raise ValueError("JWT segment is not a JSON object")

120 return decoded_text

121 

122 

123print("Header:")

124print(decode(parts[0]))

125print("\nPayload:")

126print(decode(parts[1]))

127```

128 

129```go

130package main

131 

132import (

133 "bytes"

134 "encoding/base64"

135 "encoding/json"

136 "fmt"

137 "os"

138 "strings"

139 "unicode/utf8"

140)

141 

142func decodeSegment(segment string) (json.RawMessage, error) {

143 if !isBase64URLSegment(segment) {

144 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

145 }

146 decoded, err := base64.RawURLEncoding.DecodeString(segment)

147 if err != nil {

148 return nil, err

149 }

150 if base64.RawURLEncoding.EncodeToString(decoded) != segment {

151 return nil, fmt.Errorf("JWT segment is not valid Base64URL")

152 }

153 if !utf8.Valid(decoded) {

154 return nil, fmt.Errorf("JWT segment is not valid UTF-8")

155 }

156 

157 var value json.RawMessage

158 if err := json.Unmarshal(decoded, &value); err != nil {

159 return nil, err

160 }

161 if trimmed := bytes.TrimSpace(value); len(trimmed) == 0 || trimmed[0] != '{' {

162 return nil, fmt.Errorf("JWT segment is not a JSON object")

163 }

164 return value, nil

165}

166 

167func isBase64URLSegment(segment string) bool {

168 if segment == "" || len(segment)%4 == 1 {

169 return false

170 }

171 for _, character := range segment {

172 if !('A' <= character && character <= 'Z') &&

173 !('a' <= character && character <= 'z') &&

174 !('0' <= character && character <= '9') &&

175 character != '-' &&

176 character != '_' {

177 return false

178 }

179 }

180 return true

181}

182 

183func printJSON(label string, value json.RawMessage) error {

184 formatted, err := json.MarshalIndent(value, "", " ")

185 if err != nil {

186 return err

187 }

188 fmt.Printf("%s:\n%s\n", label, formatted)

189 return nil

190}

191 

192func main() {

193 parts := strings.Split(os.Getenv("TOKEN"), ".")

194 if len(parts) != 3 {

195 panic("Expected a compact JWT with three segments")

196 }

197 

198 header, err := decodeSegment(parts[0])

199 if err != nil {

200 panic(err)

201 }

202 payload, err := decodeSegment(parts[1])

203 if err != nil {

204 panic(err)

205 }

206 if err := printJSON("Header", header); err != nil {

207 panic(err)

208 }

209 fmt.Println()

210 if err := printJSON("Payload", payload); err != nil {

211 panic(err)

212 }

213}

214```

215 

216```java

217// Add Jackson (com.fasterxml.jackson.core:jackson-databind) to your project.

218import com.fasterxml.jackson.databind.DeserializationFeature;

219import com.fasterxml.jackson.databind.JsonNode;

220import com.fasterxml.jackson.databind.ObjectMapper;

221import java.io.IOException;

222import java.nio.ByteBuffer;

223import java.nio.charset.CharacterCodingException;

224import java.nio.charset.CodingErrorAction;

225import java.nio.charset.StandardCharsets;

226import java.util.Base64;

227 

228public final class DecodeJwtExample {

229 private static final ObjectMapper JSON =

230 new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

231 

232 private DecodeJwtExample() {}

233 

234 static String decodeUtf8(byte[] bytes) throws IOException {

235 try {

236 return StandardCharsets.UTF_8

237 .newDecoder()

238 .onMalformedInput(CodingErrorAction.REPORT)

239 .onUnmappableCharacter(CodingErrorAction.REPORT)

240 .decode(ByteBuffer.wrap(bytes))

241 .toString();

242 } catch (CharacterCodingException exception) {

243 throw new IOException("JWT segment is not valid UTF-8", exception);

244 }

245 }

246 

247 static String decodeSegment(String segment) throws IOException {

248 if (!isBase64UrlSegment(segment)) {

249 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

250 }

251 byte[] bytes = Base64.getUrlDecoder().decode(segment);

252 if (!Base64.getUrlEncoder().withoutPadding().encodeToString(bytes).equals(segment)) {

253 throw new IllegalArgumentException("JWT segment is not valid Base64URL");

254 }

255 String decoded = decodeUtf8(bytes);

256 JsonNode value = JSON.readTree(decoded);

257 if (value == null || value.isMissingNode() || !value.isObject()) {

258 throw new IOException("JWT segment is not a JSON object");

259 }

260 return decoded;

261 }

262 

263 static boolean isBase64UrlSegment(String segment) {

264 if (segment.isEmpty() || segment.length() % 4 == 1) {

265 return false;

266 }

267 return segment

268 .chars()

269 .allMatch(

270 character ->

271 character >= 'A' && character <= 'Z'

272 || character >= 'a' && character <= 'z'

273 || character >= '0' && character <= '9'

274 || character == '-'

275 || character == '_');

276 }

277 

278 static String[] requireCompactJwt(String token) {

279 if (token == null) {

280 throw new IllegalArgumentException("Expected a compact JWT with three segments");

281 }

282 String[] parts = token.split("\\.", -1);

283 if (parts.length != 3) {

284 throw new IllegalArgumentException("Expected a compact JWT with three segments");

285 }

286 return parts;

287 }

288 

289 public static void main(String[] args) throws IOException {

290 String[] parts = requireCompactJwt(System.getenv("TOKEN"));

291 System.out.println("Header:");

292 System.out.println(decodeSegment(parts[0]));

293 System.out.println("\nPayload:");

294 System.out.println(decodeSegment(parts[1]));

295 }

296}

297```

298 

299```csharp

300using System.Text;

301using System.Text.Json;

302 

303static string DecodeSegment(string segment)

304{

305 if (

306 segment.Length % 4 == 1 ||

307 segment.Any(

308 character =>

309 !(

310 character is >= 'A' and <= 'Z' ||

311 character is >= 'a' and <= 'z' ||

312 character is >= '0' and <= '9' ||

313 character is '-' or '_'

314 )

315 )

316 )

317 {

318 throw new FormatException("JWT segment is not valid Base64URL");

319 }

320 

321 byte[] decoded = Convert.FromBase64String(

322 segment.Replace('-', '+').Replace('_', '/') +

323 new string('=', (4 - segment.Length % 4) % 4)

324 );

325 string canonicalSegment = Convert

326 .ToBase64String(decoded)

327 .TrimEnd('=')

328 .Replace('+', '-')

329 .Replace('/', '_');

330 if (canonicalSegment != segment)

331 {

332 throw new FormatException("JWT segment is not valid Base64URL");

333 }

334 string decodedJson = new UTF8Encoding(false, true).GetString(decoded);

335 using JsonDocument document = JsonDocument.Parse(decodedJson);

336 if (document.RootElement.ValueKind is not JsonValueKind.Object)

337 {

338 throw new FormatException("JWT segment is not a JSON object");

339 }

340 return decodedJson;

341}

342 

343string? token = Environment.GetEnvironmentVariable("TOKEN");

344if (token is null)

345{

346 throw new InvalidOperationException(

347 "Expected a compact JWT with three segments"

348 );

349}

350string[] parts = token.Split('.');

351if (parts.Length != 3)

352{

353 throw new InvalidOperationException(

354 "Expected a compact JWT with three segments"

355 );

356}

357 

358Console.WriteLine("Header:");

359Console.WriteLine(DecodeSegment(parts[0]));

360Console.WriteLine("\nPayload:");

361Console.WriteLine(DecodeSegment(parts[1]));

362```

363 

364```ruby

365require "base64"

366require "json"

367 

368parts = ENV.fetch("TOKEN", "").split(".", -1)

369raise "Expected a compact JWT with three segments" unless parts.length == 3

370 

371decode = lambda do |segment|

372 unless segment.match?(/\A[A-Za-z0-9_-]+\z/) && segment.length % 4 != 1

373 raise "JWT segment is not valid Base64URL"

374 end

375 

376 padded = segment.ljust((segment.length + 3) & ~3, "=")

377 begin

378 decoded = Base64.urlsafe_decode64(padded)

379 rescue ArgumentError

380 raise "JWT segment is not valid Base64URL"

381 end

382 unless Base64.urlsafe_encode64(decoded, padding: false) == segment

383 raise "JWT segment is not valid Base64URL"

384 end

385 

386 decoded.force_encoding(Encoding::UTF_8)

387 raise "JWT segment is not valid UTF-8" unless decoded.valid_encoding?

388 

389 value = JSON.parse(decoded)

390 raise "JWT segment is not a JSON object" unless value.is_a?(Hash)

391 

392 decoded

393end

394 

395puts("Header:")

396puts(decode.call(parts[0]))

397puts("\nPayload:")

398puts(decode.call(parts[1]))

399```

400 

401 

402Check these fields before creating the OpenAI provider:

403 

404| Field | Expected value |

405| --------------- | -------------------------------------------------------------------- |

406| Header `alg` | The configured signing algorithm, such as `RS512`. |

407| Header `kid` | The ID of a signing key in the trust domain's public JWKS. |

408| `iss` | The trust domain's HTTPS issuer URL. |

409| `sub` | The SPIFFE ID assigned to this workload. |

410| `aud` | Contains the audience you chose for OpenAI access. |

411| `iat` and `exp` | The token's issue and expiry times. Use a token that hasn't expired. |

412 

413The decoder reads the claims without verifying the signature. Don't log the token or paste it into a web-based decoder. Unset `TOKEN` after inspection.

414 

415## Configure OpenAI workload identity federation

416 

417### Create the Workload Identity Provider

418 

419Go to [Organization Settings &gt; Security &gt; Workload Identity Provider](https://platform.openai.com/settings/organization/security/workload-identity-provider) and create a provider with these settings:

420 

421| Setting | Value |

422| ---------------------------------------- | -------------------------------------------------------------------------- |

423| Name | A unique name, such as `idira-prod`. |

424| Description | The application and environment that use this provider. |

425| OIDC Issuer URL | The JWT-SVID's `iss` value. |

426| Audience | The audience requested by the helper, such as `https://api.openai.com/v1`. |

427| Use uploaded JWKS for token verification | Keep this option off to use OIDC discovery. |

428| Attribute transformations | Leave empty when mapping directly from `sub`. |

429 

430For OpenAI API token exchange, issuer comparison ignores trailing slashes. For

431 example, `https://issuer.example.com` and `https://issuer.example.com/` match.

432 All other characters must match, including the scheme and path. Audience

433 values must match exactly. Copy the issuer as issued to keep your

434 configuration consistent.

435 

436If OpenAI can't reach the discovery document or JWKS, follow the [shared provider configuration guidance](https://developers.openai.com/api/docs/guides/workload-identity-federation#configure-an-oidc-workload-identity-provider) for uploaded public JWKS. Update uploaded keys when SWA rotates its signing keys.

437 

438### Map the SPIFFE ID to a service account

439 

4401. Create a service account mapping in the provider and give it a name, such as `idira-production-workload`.

4412. Set **Key** to `sub` and **Value** to the exact SPIFFE ID from the workload's token, such as `spiffe://prod.example.com/k8s-nodegroup/ns/my-namespace/sa/my-service-account`.

4423. Select the **Project** and **Service account** that the workload can use.

4434. Select the **Permissions** the workload needs, such as `api.model.request` for model requests. These permissions can narrow the service account's access; they cannot grant more access. Leaving permissions blank adds no WIF-specific restriction.

444 

445Use a separate service account for each application and environment. Prefer an exact SPIFFE ID. Use a trailing wildcard only when every identity under that prefix should receive the same access. Avoid overlapping mappings. See [SPIFFE service account mappings](https://developers.openai.com/api/docs/guides/workload-identity-federation/spiffe#set-up-the-service-account-mapping) for details.

446 

447## Use the token in code

448 

449Set the IDs from your OpenAI provider and selected service account in the workload's environment:

450 

451```bash

452export OPENAI_IDENTITY_PROVIDER_ID="<workload identity provider ID>"

453export OPENAI_SERVICE_ACCOUNT_ID="<service account ID>"

454```

455 

456The following shared SPIFFE examples read `/var/run/spiffe/openai.jwt`. The subject token provider returns the current token whenever the SDK needs to exchange or refresh an OpenAI access token. SWA and the helper refresh the external JWT-SVID; the OpenAI SDK manages the OpenAI access token.

457 

458Authenticate with an Idira-issued SPIFFE JWT-SVID

459 

460```javascript

461import { readFile } from "node:fs/promises";

462import OpenAI from "openai";

463 

464const tokenPath = "/var/run/spiffe/openai.jwt";

465const identityProviderId = process.env.OPENAI_IDENTITY_PROVIDER_ID;

466const serviceAccountId = process.env.OPENAI_SERVICE_ACCOUNT_ID;

467 

468if (!identityProviderId || !serviceAccountId) {

469 throw new Error(

470 "Set OPENAI_IDENTITY_PROVIDER_ID and OPENAI_SERVICE_ACCOUNT_ID"

471 );

472}

473 

474function spiffeJwtSvidProvider(path) {

475 return {

476 tokenType: "jwt",

477 getToken: async () => {

478 const token = (await readFile(path, "utf8")).trim();

479 if (!token) {

480 throw new Error("The SPIFFE JWT-SVID file is empty.");

481 }

482 return token;

483 },

484 };

485}

486 

487const client = new OpenAI({

488 workloadIdentity: {

489 identityProviderId,

490 serviceAccountId,

491 provider: spiffeJwtSvidProvider(tokenPath),

492 },

493});

494 

495const response = await client.responses.create({

496 model: "gpt-5.6-terra",

497 input: "Say hello from SPIFFE workload identity federation.",

498});

499 

500console.log(response.output_text);

501```

502 

503```python

504import os

505from pathlib import Path

506 

507from openai import OpenAI

508from openai.auth import SubjectTokenProvider

509 

510TOKEN_PATH = "/var/run/spiffe/openai.jwt"

511 

512 

513def spiffe_jwt_svid_provider(token_path: str) -> SubjectTokenProvider:

514 def get_token() -> str:

515 token = Path(token_path).read_text().strip()

516 if not token:

517 raise RuntimeError("The SPIFFE JWT-SVID file is empty.")

518 return token

519 

520 return {"token_type": "jwt", "get_token": get_token}

521 

522 

523client = OpenAI(

524 workload_identity={

525 "identity_provider_id": os.environ["OPENAI_IDENTITY_PROVIDER_ID"],

526 "service_account_id": os.environ["OPENAI_SERVICE_ACCOUNT_ID"],

527 "provider": spiffe_jwt_svid_provider(TOKEN_PATH),

528 },

529)

530 

531response = client.responses.create(

532 model="gpt-5.6-terra",

533 input="Say hello from SPIFFE workload identity federation.",

534)

535 

536print(response.output_text)

537```

538 

539```go

540package main

541 

542import (

543 "context"

544 "fmt"

545 "log"

546 "os"

547 "strings"

548 

549 "github.com/openai/openai-go/v3"

550 "github.com/openai/openai-go/v3/auth"

551 "github.com/openai/openai-go/v3/option"

552 "github.com/openai/openai-go/v3/responses"

553)

554 

555const tokenPath = "/var/run/spiffe/openai.jwt"

556 

557type spiffeJWTSVIDProvider struct {

558 path string

559}

560 

561func (p spiffeJWTSVIDProvider) TokenType() auth.SubjectTokenType {

562 return auth.SubjectTokenTypeJWT

563}

564 

565func (p spiffeJWTSVIDProvider) GetToken(ctx context.Context, _ auth.HTTPDoer) (string, error) {

566 data, err := os.ReadFile(p.path)

567 if err != nil {

568 return "", &auth.SubjectTokenProviderError{

569 Provider: "spiffe",

570 Message: "failed to read SPIFFE JWT-SVID",

571 Cause: err,

572 }

573 }

574 

575 token := strings.TrimSpace(string(data))

576 if token == "" {

577 return "", &auth.SubjectTokenProviderError{

578 Provider: "spiffe",

579 Message: "SPIFFE JWT-SVID file is empty",

580 }

581 }

582 

583 return token, nil

584}

585 

586func main() {

587 client := openai.NewClient(

588 option.WithWorkloadIdentity(auth.WorkloadIdentity{

589 IdentityProviderID: os.Getenv("OPENAI_IDENTITY_PROVIDER_ID"),

590 ServiceAccountID: os.Getenv("OPENAI_SERVICE_ACCOUNT_ID"),

591 Provider: spiffeJWTSVIDProvider{

592 path: tokenPath,

593 },

594 }),

595 )

596 

597 response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{

598 Model: openai.ChatModelGPT4_1Mini,

599 Input: responses.ResponseNewParamsInputUnion{

600 OfString: openai.String("Say hello from SPIFFE workload identity federation."),

601 },

602 })

603 if err != nil {

604 log.Fatal(err)

605 }

606 

607 fmt.Println(response.OutputText())

608}

609```

610 

611```java

612import com.fasterxml.jackson.databind.json.JsonMapper;

613import com.openai.auth.SubjectTokenProvider;

614import com.openai.auth.SubjectTokenType;

615import com.openai.auth.WorkloadIdentity;

616import com.openai.client.OpenAIClient;

617import com.openai.client.okhttp.OpenAIOkHttpClient;

618import com.openai.core.http.HttpClient;

619import com.openai.errors.SubjectTokenProviderException;

620import com.openai.models.responses.ResponseCreateParams;

621import java.nio.file.Files;

622import java.nio.file.Path;

623import java.util.concurrent.CompletableFuture;

624 

625public final class SpiffeWorkloadIdentityExample {

626 private static final String TOKEN_PATH = "/var/run/spiffe/openai.jwt";

627 

628 private SpiffeWorkloadIdentityExample() {}

629 

630 static final class SpiffeJwtSvidProvider implements SubjectTokenProvider {

631 private final Path tokenPath;

632 

633 SpiffeJwtSvidProvider(String tokenPath) {

634 this.tokenPath = Path.of(tokenPath);

635 }

636 

637 @Override

638 public SubjectTokenType tokenType() {

639 return SubjectTokenType.JWT;

640 }

641 

642 @Override

643 public String getToken(HttpClient httpClient, JsonMapper jsonMapper) {

644 String token;

645 try {

646 token = Files.readString(tokenPath).trim();

647 } catch (Exception e) {

648 throw new SubjectTokenProviderException("spiffe", "failed to read SPIFFE JWT-SVID", e);

649 }

650 

651 if (token.isEmpty()) {

652 throw new SubjectTokenProviderException("spiffe", "SPIFFE JWT-SVID file is empty", null);

653 }

654 

655 return token;

656 }

657 

658 @Override

659 public CompletableFuture<String> getTokenAsync(HttpClient httpClient, JsonMapper jsonMapper) {

660 return CompletableFuture.supplyAsync(() -> getToken(httpClient, jsonMapper));

661 }

662 }

663 

664 public static void main(String[] args) {

665 WorkloadIdentity workloadIdentity =

666 WorkloadIdentity.builder()

667 .identityProviderId(System.getenv("OPENAI_IDENTITY_PROVIDER_ID"))

668 .serviceAccountId(System.getenv("OPENAI_SERVICE_ACCOUNT_ID"))

669 .provider(new SpiffeJwtSvidProvider(TOKEN_PATH))

670 .build();

671 

672 OpenAIClient client = OpenAIOkHttpClient.builder().workloadIdentity(workloadIdentity).build();

673 

674 ResponseCreateParams params =

675 ResponseCreateParams.builder()

676 .model("gpt-5.6-terra")

677 .input("Say hello from SPIFFE workload identity federation.")

678 .build();

679 

680 client.responses().create(params).output().stream()

681 .flatMap(item -> item.message().stream())

682 .flatMap(message -> message.content().stream())

683 .flatMap(content -> content.outputText().stream())

684 .forEach(outputText -> System.out.println(outputText.text()));

685 }

686}

687```

688 

689```ruby

690require "openai"

691 

692TOKEN_PATH = "/var/run/spiffe/openai.jwt"

693 

694class SpiffeJWTSVIDProvider

695 include OpenAI::Auth::SubjectTokenProvider

696 

697 def initialize(token_path:)

698 @token_path = token_path

699 end

700 

701 def token_type

702 OpenAI::Auth::TokenType::JWT

703 end

704 

705 def get_token

706 token = File.read(@token_path).strip

707 if token.empty?

708 raise OpenAI::Errors::SubjectTokenProviderError.new(

709 message: "SPIFFE JWT-SVID file is empty",

710 provider: "spiffe"

711 )

712 end

713 token

714 rescue SystemCallError => e

715 raise OpenAI::Errors::SubjectTokenProviderError.new(

716 message: "Failed to read SPIFFE JWT-SVID: #{e.message}",

717 provider: "spiffe",

718 cause: e

719 )

720 end

721end

722 

723provider = SpiffeJWTSVIDProvider.new(token_path: TOKEN_PATH)

724 

725workload_identity = OpenAI::Auth::WorkloadIdentity.new(

726 identity_provider_id: ENV.fetch("OPENAI_IDENTITY_PROVIDER_ID"),

727 service_account_id: ENV.fetch("OPENAI_SERVICE_ACCOUNT_ID"),

728 provider: provider

729)

730 

731client = OpenAI::Client.new(workload_identity: workload_identity)

732 

733response = client.responses.create(

734 model: "gpt-5.6-terra",

735 input: "Say hello from SPIFFE workload identity federation."

736)

737 

738puts(response.output_text)

739```

740 

741 

742## Verify and troubleshoot

743 

744Run the example inside the attested workload with `OPENAI_API_KEY` unset. A successful request prints the model's reply. Confirm that requests continue to succeed after the helper replaces the JWT-SVID and the SDK refreshes its OpenAI access token.

745 

746| Symptom | What to check |

747| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |

748| The helper receives no identity | Check that the workload matches the SWA attestation policy and can access the agent socket. Run checks in the workload's context. |

749| The token file is missing or empty, or its token has expired | Check that the helper is running, the shared volume and file path match, and the workload can read the file. Keep host clocks synchronized. |

750| Token verification fails | Check `iss`, `aud`, `kid`, token expiry, and discovery/JWKS reachability. Apply the issuer comparison rule described earlier. |

751| No service account mapping matches | Check the provider ID, service account ID, enabled mapping, and exact `sub` value. |

752| More than one mapping matches | Narrow or disable overlapping mappings so exactly one enabled mapping matches. |

753| Exchange succeeds but the API rejects the request | Check the mapped service account's project, permissions, and applicable API access policies. |

754 

755For SWA deployment diagnostics, see [Idira's integration troubleshooting](https://docs.cyberark.com/secrets-manager-saas/latest/en/content/conjurcloud/ccl-swa-openai.htm).

Details

4 4 

5Use SPIFFE as a Workload Identity Provider by exchanging a SPIFFE JWT-SVID for a short-lived OpenAI access token. This lets workloads authenticated by SPIRE or another SPIFFE-compatible identity provider call the OpenAI API without storing long-lived API keys.5Use SPIFFE as a Workload Identity Provider by exchanging a SPIFFE JWT-SVID for a short-lived OpenAI access token. This lets workloads authenticated by SPIRE or another SPIFFE-compatible identity provider call the OpenAI API without storing long-lived API keys.

6 6 

7For Idira Secure Workload Access, follow the [Idira setup guide](https://developers.openai.com/api/docs/guides/workload-identity-federation/idira) for agent configuration and token delivery.

8 

7OpenAI supports SPIFFE JWT-SVIDs that can be validated as JWT subject tokens with an issuer, audience, expiration, issued-at timestamp, and JWKS-backed signature. OpenAI doesn't support SPIFFE X.509-SVIDs as workload identity federation subject tokens.9OpenAI supports SPIFFE JWT-SVIDs that can be validated as JWT subject tokens with an issuer, audience, expiration, issued-at timestamp, and JWKS-backed signature. OpenAI doesn't support SPIFFE X.509-SVIDs as workload identity federation subject tokens.

8 10 

9The JWT-SVID specification requires the `sub`, `aud`, and `exp` claims. To use a JWT-SVID with OpenAI, the token must also include `iss` and `iat` claims and a `kid` header so OpenAI can validate the token against the Workload Identity Provider configuration.11The JWT-SVID specification requires the `sub`, `aud`, and `exp` claims. To use a JWT-SVID with OpenAI, the token must also include `iss` and `iat` claims and a `kid` header so OpenAI can validate the token against the Workload Identity Provider configuration.

libraries.md +1 −1

Details

173<dependency>173<dependency>

174 <groupId>com.openai</groupId>174 <groupId>com.openai</groupId>

175 <artifactId>openai-java</artifactId>175 <artifactId>openai-java</artifactId>

176 <version>4.81.0</version>176 <version>4.82.0</version>

177</dependency>177</dependency>

178```178```

179 179 

quickstart.md +1 −1

Details

190<dependency>190<dependency>

191 <groupId>com.openai</groupId>191 <groupId>com.openai</groupId>

192 <artifactId>openai-java</artifactId>192 <artifactId>openai-java</artifactId>

193 <version>4.81.0</version>193 <version>4.82.0</version>

194</dependency>194</dependency>

195```195```

196 196