SpyBara
Go Premium

Documentation 2026-10-26 23:58 UTC to 2026-10-27 23:58 UTC

19 files changed +1,060 −87. View all changes and history on the product overview
2026
Sat 31 23:01 Fri 30 23:59 Thu 29 23:59 Wed 28 23:00 Tue 27 23:58 Mon 26 23:58 Sat 24 23:02 Fri 23 23:58 Thu 22 22:59 Wed 21 23:02 Tue 20 23:02 Mon 19 23:00 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 −9

Details

211| October 23, 2026 | `ft-o4-mini-2025-04-16` | `gpt-5.6-terra` |211| October 23, 2026 | `ft-o4-mini-2025-04-16` | `gpt-5.6-terra` |

212| October 23, 2026 | `o4-mini-2025-04-16` \| `o4-mini` | `gpt-5.6-terra` |212| October 23, 2026 | `o4-mini-2025-04-16` \| `o4-mini` | `gpt-5.6-terra` |

213 213 

214The following fine-tuned versions were also shut down on October 23, 2026:214Fine-tuned versions of these models follow the same shutdown schedule as their underlying base models.

215 

216| Shutdown date | Model snapshot | Recommended replacement base model |

217| ---------------- | ---------------------------- | ---------------------------------- |

218| October 23, 2026 | `ft-gpt-3.5-turbo` | `gpt-5.6-terra` |

219| October 23, 2026 | `ft-gpt-4` | `gpt-5.6-sol` |

220| October 23, 2026 | `ft-gpt-4.1-nano-2025-04-14` | `gpt-5.6-luna` |

221| October 23, 2026 | `ft-babbage-002` | `gpt-5.6-terra` |

222| October 23, 2026 | `ft-davinci-002` | `gpt-5.6-terra` |

223 215 

224### 2026-04-22: Legacy GPT model snapshots (July 2026 shutdown)216### 2026-04-22: Legacy GPT model snapshots (July 2026 shutdown)

225 217 

guides/asymmetric-keys.md +439 −0 created

Details

1# Set up asymmetric keys

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 

5Register an RSA-3072 asymmetric key pair from your cloud provider for use with OpenAI.

6 

7Your private key stays in AWS KMS, Azure Key Vault, or Google Cloud KMS. OpenAI stores a copy of the corresponding public key at registration.

8 

9Registering a key doesn't configure product features or storage. This guide

10 covers keys for [PSP

11 decryption](https://developers.openai.com/api/docs/guides/private-safety-processing#psp-decryption).

12 

13## Before you start

14 

15- Have your organization ID. Use the same organization for cloud trust, registration, and API access.

16- Create an [organization Admin API key](https://developers.openai.com/api/docs/guides/admin-apis) with external-key read/write access. Load it securely as `OPENAI_ADMIN_KEY`; project inference keys won't work.

17- Have permission to create an enabled RSA-3072 key and grant OpenAI the scoped public-key read access described in this guide.

18- Restrict private-key use to authorized users and retain old key versions while records depend on them.

19 

20## Set up a cloud key

21 

22Follow the steps for your provider before registering the key. Each setup uses RSA-3072 and grants OpenAI only public-key and metadata reads.

23 

24 

25 

26<a id="aws-kms"></a>

27 

28 

29 

30### AWS KMS

31 

32 

33 

34#### 1. Create an RSA key

35 

36Open **KMS** > **Customer managed keys** > **Create key** and choose your region. Use these settings:

37 

38- **Key type:** Asymmetric

39- **Key usage:** Encrypt and decrypt

40- **Key spec:** `RSA_3072`

41 

42Finish creating the key with your own key administrators. Keep it enabled and copy its full ARN, ending in `key/<CUSTOMER_KMS_KEY_ID>`. Don't use an alias ARN.

43 

44#### 2. Create a public-key read policy

45 

46Open **IAM** > **Policies** > **Create policy** > **JSON**. Replace `<CUSTOMER_KMS_KEY_ARN>` and save this policy as `OpenAI-PublicKeyRead`.

47 

48```json

49{

50 "Version": "2012-10-17",

51 "Statement": [

52 {

53 "Effect": "Allow",

54 "Action": ["kms:DescribeKey", "kms:GetPublicKey"],

55 "Resource": "<CUSTOMER_KMS_KEY_ARN>"

56 }

57 ]

58}

59```

60 

61The key supports decryption, but OpenAI only needs these read permissions. Don't grant OpenAI key-user or key-administrator access.

62 

63#### 3. Create the trusted IAM role

64 

65Open **IAM** > **Roles** > **Create role** > **Custom trust policy**. Replace `<OPENAI_ORG_ID>` with your OpenAI organization ID. Keep the OpenAI principal unchanged.

66 

67```json

68{

69 "Version": "2012-10-17",

70 "Statement": [

71 {

72 "Effect": "Allow",

73 "Principal": {

74 "AWS": "arn:aws:iam::790389265272:role/EnterpriseKeyManagement"

75 },

76 "Action": "sts:AssumeRole",

77 "Condition": {

78 "StringEquals": {

79 "sts:ExternalId": "<OPENAI_ORG_ID>"

80 }

81 }

82 }

83 ]

84}

85```

86 

87Attach `OpenAI-PublicKeyRead` and create the role. Copy its ARN. Use the same organization ID as `external_id` when registering the key.

88 

89#### 4. Check the KMS key policy

90 

91Confirm that the key policy authorizes the role. If it doesn't delegate authorization to IAM, add this statement. Replace `<CUSTOMER_ROLE_ARN>` and preserve existing administrator access.

92 

93In this KMS key policy, `"Resource": "*"` applies only to the key the policy is attached to. The IAM policy uses the exact key ARN.

94 

95```json

96{

97 "Sid": "AllowPublicKeyRead",

98 "Effect": "Allow",

99 "Principal": {

100 "AWS": "<CUSTOMER_ROLE_ARN>"

101 },

102 "Action": ["kms:DescribeKey", "kms:GetPublicKey"],

103 "Resource": "*"

104}

105```

106 

107Keep the role ARN and key ARN for registration.

108 

109 

110 

111 

112 

113 

114 

115<a id="azure-key-vault"></a>

116 

117 

118 

119### Azure Key Vault

120 

121 

122 

123#### 1. Add the OpenAI enterprise application

124 

125Ask a tenant administrator to add **EKM - OpenAI Azure** to your tenant. Its application ID is `20a14814-5ab7-4612-a671-1382b412bf93`.

126 

127Install Azure CLI, then sign in and check for the application:

128 

129```bash

130az login --tenant '<CUSTOMER_TENANT_ID>'

131az ad sp show --id '20a14814-5ab7-4612-a671-1382b412bf93' \

132 --query '{name:displayName,objectId:id}' -o table

133```

134 

135If the application is missing, add it:

136 

137```bash

138az ad sp create --id '20a14814-5ab7-4612-a671-1382b412bf93' \

139 --query '{name:displayName,objectId:id}' -o table

140```

141 

142Save the tenant-local object ID for the role assignment.

143 

144#### 2. Create the key

145 

146Open **Key vaults** > **Create**. Select your subscription, resource group, vault name, and region. Under **Access configuration**, choose **Azure role-based access control**.

147 

148Your administrator needs a role that permits key creation, such as **Key Vault Crypto Officer** on this vault. Don't grant that role to OpenAI.

149 

150In the vault, open **Objects** > **Keys** > **Generate/Import** and use:

151 

152- **Name:** `<OPENAI_ORG_ID>--asymmetric-key`, using your OpenAI organization ID

153- **Key type:** RSA

154- **RSA key size:** 3072

155- **Enabled:** Yes

156 

157If you set activation or expiration dates, make sure the key is currently valid. Include **Decrypt** in the key's permitted operations. OpenAI receives key-read access only.

158 

159RSA-HSM keys in a Key Vault are also supported. Standalone Managed HSM endpoints aren't supported.

160 

161#### 3. Grant key-read access

162 

163Create a custom role with the key-read `DataAction`. Save this as `public-key-reader.json` and replace the subscription and resource-group values.

164 

165```json

166{

167 "Name": "OpenAI Public Key Reader",

168 "IsCustom": true,

169 "Description": "Read key metadata and public keys.",

170 "Actions": [],

171 "NotActions": [],

172 "DataActions": ["Microsoft.KeyVault/vaults/keys/read"],

173 "NotDataActions": [],

174 "AssignableScopes": [

175 "/subscriptions/<CUSTOMER_SUBSCRIPTION_ID>/resourceGroups/<CUSTOMER_RESOURCE_GROUP>"

176 ]

177}

178```

179 

180Set `VAULT_RESOURCE_ID` to the vault's Azure resource ID from **Properties**, not its HTTPS URI. Set `OPENAI_SP_ID` to the application's tenant-local object ID, then assign the role on the key:

181 

182```bash

183az role definition create --role-definition @public-key-reader.json

184 

185OPENAI_SP_ID='<CUSTOMER_OPENAI_SP_OBJECT_ID>'

186VAULT_RESOURCE_ID='<CUSTOMER_VAULT_RESOURCE_ID>'

187KEY_NAME='<OPENAI_ORG_ID>--asymmetric-key'

188KEY_SCOPE="$VAULT_RESOURCE_ID/keys/$KEY_NAME"

189 

190az role assignment create \

191 --assignee-object-id "$OPENAI_SP_ID" \

192 --assignee-principal-type ServicePrincipal \

193 --role 'OpenAI Public Key Reader' \

194 --scope "$KEY_SCOPE"

195```

196 

197For a vault using legacy access policies, grant key **Get** instead. Keep its existing permission model.

198 

199#### 4. Record the exact version

200 

201Record your tenant ID, vault URI, and key name. Open the enabled key version and copy the 32-character hexadecimal version from the end of its **Key Identifier**.

202 

203 

204 

205 

206 

207 

208 

209<a id="google-cloud-kms"></a>

210 

211 

212 

213### Google Cloud KMS

214 

215 

216 

217#### 1. Prepare the project and key

218 

219Enable Cloud KMS in the key project and IAM and Security Token Service in the federation project. If these projects differ, enable Cloud KMS in both.

220 

221Record your federation project's 12-digit project number. The KMS project may be different.

222 

223Open **Security** > **Key Management**. Create a key ring, then create a key with:

224 

225- **Protection level:** Software

226- **Purpose:** Asymmetric decrypt

227- **Algorithm:** 3072 bit RSA - OAEP Padding - SHA256 Digest

228 

229Copy the KMS project ID, location, key ring, key name, and enabled version. Use a specific version such as `"1"`, not `"latest"` or `"0"`. Asymmetric keys don't have a primary version.

230 

231#### 2. Configure workload identity federation

232 

233Open **IAM & Admin** > **Workload Identity Federation**. Create a global pool, such as `openai-keys`, and an OIDC provider, such as `openai-google`, with these settings:

234 

235| Provider setting | Value |

236| ------------------- | ------------------------------------------ |

237| Issuer URL | `https://accounts.google.com` |

238| Allowed audience | Your exact OpenAI organization ID |

239| Attribute mapping | `google.subject = assertion.sub` |

240| Attribute condition | `assertion.sub == '105900137572174660365'` |

241 

242Use your OpenAI organization ID as the audience. Keep the issuer and OpenAI subject unchanged.

243 

244#### 3. Create and assign the key-reader role

245 

246In **IAM & Admin** > **Roles**, create a custom role in the KMS project with:

247 

248```text

249cloudkms.cryptoKeyVersions.get

250cloudkms.cryptoKeyVersions.viewPublicKey

251```

252 

253Both permissions are required; public-key-viewer alone is insufficient. No decrypt, sign, or service-account impersonation grant is needed.

254 

255On the key's **Permissions** tab, choose **Grant access**. Assign the role to this principal, replacing `<CUSTOMER_GCP_PROJECT_NUMBER>` and `<CUSTOMER_GCP_POOL_ID>`:

256 

257```text

258principal://iam.googleapis.com/projects/<CUSTOMER_GCP_PROJECT_NUMBER>/locations/global/workloadIdentityPools/<CUSTOMER_GCP_POOL_ID>/subject/105900137572174660365

259```

260 

261Use your pool's project number and scope the grant to this key. Key-level IAM covers its versions. Save the pool and provider IDs, organization audience, and KMS identifiers for registration.

262 

263 

264 

265 

266 

267## Register your key

268 

269The examples use `curl` and a Bash-compatible shell. Load `OPENAI_ADMIN_KEY` securely, then set your organization ID:

270 

271```bash

272API_BASE="https://api.openai.com/v1"

273ORG_ID="<OPENAI_ORG_ID>"

274 

275: "${OPENAI_ADMIN_KEY:?Load your organization admin key securely}"

276 

277KEYS_URL="$API_BASE/organization/external_keys"

278```

279 

280### Prepare the registration request

281 

282The `purpose` enum accepts `ekm` (the default) or `psp_decryption`. Use `psp_decryption` for the asymmetric keys in this guide; `ekm` is for Enterprise Key Management (EKM).

283 

284Save your provider's request as `registration.json`. Replace the example values; JSON doesn't expand shell variables.

285 

286#### AWS KMS request

287 

288```json

289{

290 "type": "aws",

291 "purpose": "psp_decryption",

292 "name": "asymmetric-key-aws",

293 "role_arn": "<CUSTOMER_ROLE_ARN>",

294 "external_id": "<OPENAI_ORG_ID>",

295 "kms_arn": "<CUSTOMER_KMS_KEY_ARN>"

296}

297```

298 

299#### Azure Key Vault request

300 

301```json

302{

303 "type": "azure",

304 "purpose": "psp_decryption",

305 "name": "asymmetric-key-azure",

306 "tenant_id": "<CUSTOMER_TENANT_ID>",

307 "vault_uri": "https://<CUSTOMER_VAULT_NAME>.vault.azure.net",

308 "key_name": "<OPENAI_ORG_ID>--asymmetric-key",

309 "key_version": "<CUSTOMER_KEY_VERSION>"

310}

311```

312 

313#### Google Cloud KMS request

314 

315```json

316{

317 "type": "gcp",

318 "purpose": "psp_decryption",

319 "name": "asymmetric-key-gcp",

320 "workload_identity_project_number": "<CUSTOMER_GCP_PROJECT_NUMBER>",

321 "workload_identity_pool_id": "openai-keys",

322 "workload_identity_provider_id": "openai-google",

323 "audience": "<OPENAI_ORG_ID>",

324 "kms_project_id": "<CUSTOMER_KMS_PROJECT_ID>",

325 "kms_key_ring_name": "asymmetric-keys",

326 "kms_key_name": "asymmetric-key",

327 "kms_key_location": "us-central1",

328 "kms_key_version": "1"

329}

330```

331 

332### 1. Register and validate the key

333 

334```bash

335curl --fail-with-body -sS "$KEYS_URL" \

336 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \

337 -H "OpenAI-Organization: $ORG_ID" \

338 -H "Content-Type: application/json" \

339 --data @registration.json

340```

341 

342Registration validates the key before saving it. Success returns HTTP 200 with an `extkey` ID and your provider configuration. Example response fields:

343 

344```json

345{

346 "id": "extkey_11111111111111111111111111111111",

347 "object": "organization.external_key",

348 "name": "asymmetric-key-gcp",

349 "type": "gcp",

350 "purpose": "psp_decryption",

351 "api_project_ids": []

352}

353```

354 

355Save the returned registration ID:

356 

357```bash

358KEY_ID="<EXTERNAL_KEY_ID>"

359```

360 

361### 2. Revalidate the key (optional)

362 

363`GET` returns stored details. `POST /validate` rechecks current access and key validity against the saved public key. It doesn't decrypt records or refresh the snapshot.

364 

365```bash

366curl --fail-with-body -sS "$KEYS_URL/$KEY_ID" \

367 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \

368 -H "OpenAI-Organization: $ORG_ID"

369```

370 

371```bash

372curl --fail-with-body -sS -X POST "$KEYS_URL/$KEY_ID/validate" \

373 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \

374 -H "OpenAI-Organization: $ORG_ID"

375```

376 

377Expected response (HTTP 200):

378 

379```json

380{ "status": "success" }

381```

382 

383Validate again after key, permission, or network changes. Allow IAM changes to propagate before retrying.

384 

385To use your registered key for PSP decryption, [select it for a project](https://developers.openai.com/api/docs/guides/private-safety-processing#select-a-key-for-psp-decryption).

386 

387## List keys

388 

389Use `purpose=psp_decryption` to list these asymmetric-key registrations. Omitting the filter returns all purposes; `purpose=ekm` returns EKM only.

390 

391```bash

392curl --fail-with-body -sS --get "$KEYS_URL" \

393 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \

394 -H "OpenAI-Organization: $ORG_ID" \

395 --data-urlencode "purpose=psp_decryption" \

396 --data-urlencode "order=asc" \

397 --data-urlencode "limit=20"

398```

399 

400While `has_more` is `true`, request the next page with `after` set to `last_id`. Keep `purpose`, `order`, and `limit` unchanged.

401 

402## Replace a key

403 

4041. Create a replacement AWS key or Azure/GCP version and grant read access.

4052. Register the replacement.

4063. [Select the new `extkey` ID on the project](https://developers.openai.com/api/docs/guides/private-safety-processing#select-a-key-for-psp-decryption) and verify the selection.

407 

408Registrations are fixed snapshots; cloud rotation doesn't update them. Keep old private keys for records that depend on them. Changing the selection doesn't migrate existing records.

409 

410## Delete a key

411 

412[Clear](https://developers.openai.com/api/docs/guides/private-safety-processing#clear-the-key-selection) or [replace](https://developers.openai.com/api/docs/guides/private-safety-processing#select-a-key-for-psp-decryption) the selection on every active project before deleting the registration:

413 

414```bash

415curl --fail-with-body -sS -X DELETE "$KEYS_URL/$KEY_ID" \

416 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \

417 -H "OpenAI-Organization: $ORG_ID"

418```

419 

420Deletion returns HTTP 200 with `object: "organization.external_key.deleted"`. It doesn't delete your cloud key.

421 

422## Troubleshooting

423 

424| Error | What to check |

425| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |

426| 401 or 403 | Use an organization Admin API key and confirm access to external-key registration. |

427| 400 `external_key_provider_error` | Check cloud trust, read permissions, network access, and the key's enabled state and algorithm. |

428| 400 request-validation errors | Check the purpose, concrete AWS key ARN, and exact Azure/GCP version. |

429| 404 | Check the registration ID and organization. Deleted registrations and registrations from another organization can't be used. |

430| `external_key_used_by_active_projects` | Clear or replace all active project selections. |

431| Key no longer matches | Register the replacement and select its new ID. |

432 

433For support, share the error and `x-request-id`. Never share credentials.

434 

435## Related guides

436 

437- [Decrypt PSP records](https://developers.openai.com/api/docs/guides/psp-decryption)

438- [ZDR with Private Safety Processing](https://developers.openai.com/api/docs/guides/private-safety-processing)

439- [Admin APIs](https://developers.openai.com/api/docs/guides/admin-apis)

Details

12 12 

13 13 

14 14 

15 <table>

16 <thead>

17 <tr>

18 <th scope="col">Model</th>

19 <th scope="col">Approval required</th>

20 <th scope="col">

21 API value: `access_programs.cyber`

22 </th>

23 </tr>

24 </thead>

25 <tbody>

26 <tr>

27 <td>GPT-6 Sol</td>

28 <td>Blue or Red</td>

29 <td>

30 `daybreak_blue`

31 </td>

32 </tr>

33 <tr>

34 <td className="align-top">

15 35

16| Model option | Model ID | Access program | When to use |

17| ---------------------------------------- | ------------------------------ | --------------- | ----------------------------------------------------------------------------------------------------------------------------- |

18| Mainline model with standard safeguards | `gpt-6-sol` | `standard` | General-purpose or security tasks with standard safeguards, even if you have Daybreak access. |

19| Mainline model with Daybreak Blue | `gpt-6-sol` | `daybreak_blue` | Approved defensive security work with a specific mainline model. |

20| GPT-6.1 Sol or GPT-6 Astra with Daybreak | `gpt-6.1-sol` or `gpt-6-astra` | `daybreak_blue` | Reduced refusals with either model. Requires Daybreak Red approval for your organization and access enabled for your project. |

21| Cyber model with Daybreak Red | `gpt-5.6-cyber` | `daybreak_red` | Advanced, authorized security work with a specific cyber model. Requires Daybreak Red approval. |

22 36 

23 37

38GPT-6.1 Sol

39 

40

41GPT-6 Astra

42 

43

44GPT-5.5-Cyber

45 

46

47 

48 </td>

49 <td className="align-top">Red</td>

50 <td className="align-top">

51

52 

53

54 

55 `daybreak_blue`

56

57 

58

59 

60 `daybreak_blue`

61

62 

63

64 

65 `daybreak_red`

66

67 

68

69 

70 </td>

71 </tr>

72 <tr>

73 <td>GPT-5.6-Cyber</td>

74 <td>Red + additional model approval</td>

75 <td>

76 `daybreak_red`

77 </td>

78 </tr>

79 </tbody>

80 </table>

81 

24 82 

25 83 

26Match the request value to the model, not your organization's approval level. For example, when using `gpt-6-sol` with Daybreak, send `daybreak_blue` even if your organization has Daybreak Red approval. Sending `daybreak_red` with this model returns `invalid_access_program`.84Match the request value to the model, not your organization's approval level. For example, when using `gpt-6-sol` with Daybreak, send `daybreak_blue` even if your organization has Daybreak Red approval. Sending `daybreak_red` with this model returns `invalid_access_program`.


69 127 

70## Understand defaults when omitted128## Understand defaults when omitted

71 129 

72If you omit `access_programs.cyber`, the API selects a compatible program based on the model and your organization and project access:130Starting with `gpt-6.1-terra`, omitting the `access_programs.cyber` parameter results in `standard` request treatment instead of defaulting to the highest available Daybreak tier.

131 

132For the models listed below, omitting `access_programs.cyber` uses the following defaults:

73 133 

74- **Mainline models such as `gpt-6-sol`:** Daybreak Blue treatment when your organization and project have the required access; otherwise, standard safeguards.134- **Mainline models such as `gpt-6-sol`:** Daybreak Blue treatment when your organization and project have the required access; otherwise, standard safeguards.

75- **Red models such as `gpt-5.6-cyber`:** The API selects `daybreak_red`. The request fails if the required access is missing.135- **Red models such as `gpt-5.6-cyber`:** The API selects `daybreak_red`. The request fails if the required access is missing.

76- **`gpt-6-astra` and `gpt-6.1-sol`:** Reduced refusals for eligible callers with Daybreak Red access enabled for their project; otherwise, standard safeguards.136- **`gpt-6-astra` and `gpt-6.1-sol`:** Reduced refusals for eligible callers with Daybreak Red access enabled for their project; otherwise, standard safeguards.

77 137 

78Model permissions still apply. To explicitly request standard safeguards on a compatible model, send `standard`. An explicit Daybreak selection fails if it's incompatible with the model or you don't have the required access.138Model permissions still apply. To turn Daybreak off and request standard safeguards on a compatible model, set `access_programs.cyber` to `standard`, even if you have Daybreak access. An explicit Daybreak selection fails if it's incompatible with the model or you don't have the required access.

79 139 

80## Check the response140## Check the response

81 141 

Details

78Include prior text messages in `session.input` when you create the session. For example, add this `input` field to your [session creation configuration](https://developers.openai.com/api/docs/guides/live#connect-your-first-session):78Include prior text messages in `session.input` when you create the session. For example, add this `input` field to your [session creation configuration](https://developers.openai.com/api/docs/guides/live#connect-your-first-session):

79 79 

80```javascript80```javascript

81export const session = {

82 model: "gpt-live-1",

83 input: [

84 {

85 type: "message",

86 role: "user",

87 content: [

88 {

89 type: "input_text",

90 text: "I need help with my recent order.",

91 },

92 ],

93 },

94 {

95 type: "message",

96 role: "assistant",

97 content: [

98 {

99 type: "output_text",

100 text: "What is the order number?",

101 },

102 ],

103 },

104 ],

105};

81```106```

82 107 

83```python108```python

Details

47Add this delegation configuration when [creating your Live session](https://developers.openai.com/api/docs/guides/live). Choose the Responses model independently of the voice model:47Add this delegation configuration when [creating your Live session](https://developers.openai.com/api/docs/guides/live). Choose the Responses model independently of the voice model:

48 48 

49```javascript49```javascript

50export const session = {

51 model: "gpt-live-1",

52 delegation: {

53 type: "responses",

54 responses: {

55 model: "gpt-6-luna",

56 instructions: "[Your backend prompt]",

57 },

58 },

59};

50```60```

51 61 

52```python62```python


240Set `delegation` when [creating your Live session](https://developers.openai.com/api/docs/guides/live):250Set `delegation` when [creating your Live session](https://developers.openai.com/api/docs/guides/live):

241 251 

242```javascript252```javascript

253export const session = {

254 model: "gpt-live-1",

255 delegation: {

256 type: "client",

257 },

258};

243```259```

244 260 

245```python261```python

Details

52 52 

53### Asynchronous Safety Pipeline53### Asynchronous Safety Pipeline

54 54 

55ZDR with PSP retrieves encrypted records from your storage and checks their ability to be decrypted. The Safety Review Runtime, a hardware-attested computing environment that disables human access, is designed to be the only workload that can decrypt customer content. It performs automated safety review using an approved reviewer prompt and output schema that does not expose customer content.55ZDR with PSP retrieves encrypted records from your storage and checks their ability to be decrypted. The Safety Review Runtime, a hardware-attested computing environment that disables human access, is designed to be the only OpenAI workload that can decrypt customer content. It performs automated safety review using an approved reviewer prompt and output schema that does not expose customer content.

56 56 

57Only predefined, bounded safety signals and approved operational metadata may leave the review in plaintext. Detailed results are encrypted before leaving the runtime and stored in your cloud storage with the original record’s expiration. ZDR with PSP encrypts the records and writes them to your regional cloud storage with a TTL of 30 days.57Only predefined, bounded safety signals and approved operational metadata may leave the review in plaintext. Detailed results are encrypted before leaving the runtime and stored in your cloud storage with the original record’s expiration. ZDR with PSP encrypts the records and writes them to your regional cloud storage with a TTL of 30 days.

58 58 


85 85 

86 86 

87 87 

88<a id="psp-decryption"></a>

89 

90 

91 

92### PSP decryption

93 

94 

95 

96PSP decryption lets you decrypt customer content in PSP records stored in your connected cloud storage using an asymmetric key that you control. OpenAI uses the public key, while the private key stays in your cloud key-management service. This is separate from EKM and doesn't grant OpenAI personnel access to your content.

97 

98See [Set up asymmetric keys](https://developers.openai.com/api/docs/guides/asymmetric-keys) for AWS KMS, Azure Key Vault, and Google Cloud KMS setup and key registration. Confirm PSP decryption availability and activation with your OpenAI contact. Registering a key doesn't enable PSP or configure storage, and only records written with PSP decryption enabled for your key can be decrypted with that key. Existing records aren't converted.

99 

100For the decryption steps, record format, and key derivation, see [Decrypt PSP records](https://developers.openai.com/api/docs/guides/psp-decryption).

101 

102 

103 

104<a id="select-a-key-for-psp-decryption"></a>

105 

106 

107 

108#### Select a key for PSP decryption

109 

110 

111 

112Use an [organization Admin API key](https://developers.openai.com/api/docs/guides/admin-apis) with project read/write access. Load it securely as `OPENAI_ADMIN_KEY`; project inference keys won't work. The examples use `curl` and a Bash-compatible shell.

113 

114Set your organization ID, project ID, and the `extkey` ID returned when you registered the key:

115 

116```bash

117API_BASE="https://api.openai.com/v1"

118ORG_ID="<OPENAI_ORG_ID>"

119PROJECT_ID="<CUSTOMER_PROJECT_ID>"

120KEY_ID="<EXTERNAL_KEY_ID>"

121 

122: "${OPENAI_ADMIN_KEY:?Load your organization admin key securely}"

123 

124PROJECTS_URL="$API_BASE/organization/projects"

125```

126 

127Set `psp_decryption_key_id` to the returned `extkey` ID from the same organization. Leave `external_key_id`, which selects the project's EKM key, unchanged.

128 

129```bash

130curl --fail-with-body -sS "$PROJECTS_URL/$PROJECT_ID" \

131 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \

132 -H "OpenAI-Organization: $ORG_ID" \

133 -H "Content-Type: application/json" \

134 --data "{\"psp_decryption_key_id\":\"$KEY_ID\"}"

135```

136 

137Success returns HTTP 200. Read the project to confirm the selection:

138 

139```bash

140curl --fail-with-body -sS "$PROJECTS_URL/$PROJECT_ID" \

141 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \

142 -H "OpenAI-Organization: $ORG_ID"

143```

144 

145Project lists also return `psp_decryption_key_id`. The registration's `api_project_ids` shows its projects. Omitting `psp_decryption_key_id` preserves the selection; selecting the same ID again is supported.

146 

147 

148 

149 

150 

151 

152 

153<a id="clear-the-key-selection"></a>

154 

155 

156 

157#### Clear the key selection

158 

159 

160 

161First set the API variables shown in [Select a key for PSP decryption](#select-a-key-for-psp-decryption).

162 

163Send `null` to clear the selection. Omitting the field leaves it unchanged. Repeating a clear is supported.

164 

165```bash

166curl --fail-with-body -sS "$PROJECTS_URL/$PROJECT_ID" \

167 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \

168 -H "OpenAI-Organization: $ORG_ID" \

169 -H "Content-Type: application/json" \

170 --data '{"psp_decryption_key_id":null}'

171```

172 

173 

174 

175 

176 

177 

178 

179 

180 

181 

182 

88<a id="customer-storage-setup-steps"></a>183<a id="customer-storage-setup-steps"></a>

89 184 

90 185 


296 391 

297If the application already exists, use `az ad sp show` with the same `--id` and query. Record the application name and its tenant-local object ID.392If the application already exists, use `az ad sp show` with the same `--id` and query. Record the application name and its tenant-local object ID.

298 393 

299Open **Storage Account > Access control (IAM) > Add role assignment** on your storage account. Select the **Reader** role, set **Assign access to** to **User, group, or service principal**, then search for and select **CSG - Azure Blob Storage Prod**.394Open **Storage Account > Access control (IAM) > Add role assignment** on your storage account. Select the **Reader** role, set **Assign access to** **User, group, or service principal**, then search for and select **CSG - Azure Blob Storage Prod**.

300 395 

301![Azure role-assignment Members tab showing the Storage Blob Data Reader role and User, group, or service principal selected.](https://developers.openai.com/images/platform/guides/private-safety-processing/setup-14-azure-role-members.webp)396![Azure role-assignment Members tab showing the Storage Blob Data Reader role and User, group, or service principal selected.](https://developers.openai.com/images/platform/guides/private-safety-processing/setup-14-azure-role-members.webp)

302 397 

303![Azure Select members pane with CSG - Azure Blob Storage Prod listed as an application.](https://developers.openai.com/images/platform/guides/private-safety-processing/setup-15-azure-select-application.webp)398![Azure Select members pane with CSG - Azure Blob Storage Prod listed as an application.](https://developers.openai.com/images/platform/guides/private-safety-processing/setup-15-azure-select-application.webp)

304 399 

305Then select the **Storage Blob Data Contributor** role, set **Assign access to** to **User, group, or service principal**, and search for and select **CSG - Azure Blob Storage Prod**.400Then select the **Storage Blob Data Contributor** role, set **Assign access to** **User, group, or service principal**, and search for and select **CSG - Azure Blob Storage Prod**.

306 401 

307OpenAI manages the application credentials. Don't create or share a storage key, SAS token, or client secret.402OpenAI manages the application credentials. Don't create or share a storage key, SAS token, or client secret.

308 403 

guides/psp-decryption.md +266 −0 created

Details

1# Decrypt PSP records

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 

5PSP decryption lets you read the content of PSP records stored in your connected cloud storage using an asymmetric key that you control.

6 

7Once you have the record and its original key metadata, decryption uses your cloud key-management service and local code. You don't need to call an OpenAI API.

8 

9This guide applies to ZDR with PSP. It does not apply to Private Retention

10 with PSP, which stores records in OpenAI-managed infrastructure.

11 

12Your cloud key-management service decrypts a small recovery capsule. Your application uses the recovered secret to decrypt the record locally. Your private key stays in your cloud key-management service.

13 

14The [appendix](#appendix-format-and-decryption-reference) shows the record bundle and a Python example.

15 

16## Before you start

17 

18- Enable PSP decryption, connect storage, and select a recovery key. See [PSP decryption](https://developers.openai.com/api/docs/guides/private-safety-processing#psp-decryption) and [Set up asymmetric keys](https://developers.openai.com/api/docs/guides/asymmetric-keys).

19- Give your application permission to read the stored object and decrypt using the original RSA-3072 recovery key for the record.

20- Keep the original registration, public key, and exact cloud key/version while records depend on them.

21 

22Only records written with PSP decryption enabled for your key can be decrypted

23 with it. Existing records aren't converted.

24 

25## Decrypt a record

26 

27### 1. Read the record

28 

29Read the record from your cloud storage. If it has an EKM storage envelope, decrypt that layer with its original EKM configuration first.

30 

31Parse the resulting JSON bundle containing `protected_record` and `customer_access`, as shown in [Bundle format](#bundle-format). If `customer_access` is `null`, PSP decryption is unavailable for that record.

32 

33### 2. Select the original key

34 

35Use `customer_access.customer_key_id` to find the original cloud key and version in your saved registration metadata. You can also retrieve the registration through the Admin API with `GET /v1/organization/external_keys/{customer_key_id}` using an organization Admin API key. See [Retrieve the key registration](https://developers.openai.com/api/docs/guides/asymmetric-keys#2-revalidate-the-key-optional).

36 

37Check the public-key fingerprint and use the exact cloud key/version. Keep the private key in your cloud key-management service. Never share it with OpenAI.

38 

39### 3. Decrypt the recovery capsule

40 

41Send the decoded `customer_access.recovery_secret` to your cloud key-management service for RSA-OAEP-SHA256 decryption.

42 

43### 4. Decrypt and verify the content

44 

45Use the [Python example](#python-example) to check the capsule's binding to the record and decrypt the content locally.

46 

47Return content only after all checks and AES-GCM authentication succeed.

48 

49## Check the result

50 

51A successful operation returns authenticated UTF-8 content. Request records contain `request_text` and metadata telling you whether truncation occurred. See [Recovered content](#recovered-content).

52 

53## Troubleshooting

54 

55| Result | What to check |

56| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |

57| `customer_access` is `null` | The record wasn't written with PSP decryption enabled for a recovery key. Selecting a key doesn't enable PSP decryption for that record. |

58| KMS access denied or key unavailable | Check your decrypt permission and the availability of the original cloud key and version. |

59| Fingerprint or binding mismatch | Check the intended record and original registration. Don't modify authenticated fields or substitute another key. |

60| AES-GCM authentication fails | Check the inputs to the [Python example](#python-example). Don't return partial plaintext. |

61 

62## Related guides

63 

64- [Set up asymmetric keys](https://developers.openai.com/api/docs/guides/asymmetric-keys)

65- [ZDR with Private Safety Processing](https://developers.openai.com/api/docs/guides/private-safety-processing)

66 

67## Appendix: Format and decryption reference

68 

69This appendix covers **bundle version 1, protected-record version `v4`**. Use the Python example below to implement local decryption without OpenAI libraries.

70 

71### Bundle format

72 

73The object is UTF-8 JSON with this shape. Angle brackets are placeholders.

74 

75```json

76{

77 "bundle_version": 1,

78 "protected_record": {

79 "version": "v4",

80 "algorithm": "HPKE-v1-DHKEM_P256_HKDF_SHA256-EXPORT_ONLY-AES256_GCM",

81 "scope": "global",

82 "key_thumbprint": "<runtime public-key thumbprint>",

83 "expires_at": "2026-11-20",

84 "content_id": "<content ID>",

85 "encrypted_dek": "<E: base64url P-256 public point>",

86 "safety_review_runtime_public_key": "<R: base64url P-256 public point>",

87 "nonce": "<base64url 12-byte nonce>",

88 "encrypted_cot": ["<opaque encrypted CoT string>"],

89 "ciphertext": "<base64url ciphertext followed by GCM tag>"

90 },

91 "customer_access": {

92 "version": 1,

93 "algorithm": "RSA-OAEP-256",

94 "customer_key_id": "<original registration ID>",

95 "customer_public_key_fingerprint": "<RSA public-key fingerprint>",

96 "recovery_secret": "<base64url RSA ciphertext>"

97 }

98}

99```

100 

101`encrypted_cot` contains encrypted chain-of-thought content. This content cannot be decrypted with your customer recovery key. The array may be empty.

102 

103### Python example

104 

105Use the example below after decrypting any separate EKM storage envelope and the recovery capsule with the original cloud key. It checks the key fingerprint, capsule binding, recovered scalar, and runtime thumbprint before authenticating and decrypting the payload.

106 

107Install `pyhpke`, `cryptography`, and `rfc8785` in your Python environment. Pass these inputs to `recover_record`:

108 

109- `bundle_json`: The inner record bundle as UTF-8 JSON bytes.

110- `capsule`: The bytes returned by your cloud key-management service.

111- `original_key_id`: The original key registration ID from step 2.

112- `original_public_key_pem`: That cloud key version's RSA public key in PEM format, as bytes.

113 

114The function returns the decrypted content as a UTF-8 string. A failed check stops recovery.

115 

116Decrypt a PSP record

117 

118```python

119import base64

120import hashlib

121import hmac

122import json

123 

124import rfc8785

125from cryptography.hazmat.primitives import serialization

126from cryptography.hazmat.primitives.asymmetric import ec, rsa

127from cryptography.hazmat.primitives.ciphers.aead import AESGCM

128from pyhpke import AEADId, CipherSuite, KDFId, KEMId, KEMKey, KEMKeyPair

129 

130 

131def recover_record(bundle_json, capsule, original_key_id, original_public_key_pem):

132 """Return plaintext from exact inner bundle bytes and a decrypted capsule.

133 

134 Use the documented schema and trusted original key ID/RSA public PEM.

135 No cloud calls occur here. The RSA private key stays in the cloud.

136 """

137 record, access, runtime, nonce, ciphertext = validate_bundle(

138 bundle_json, original_key_id, original_public_key_pem

139 )

140 e = recover_scalar(record, access, capsule)

141 

142 ephemeral = ec.derive_private_key(e, ec.SECP256R1())

143 pair = KEMKeyPair(

144 KEMKey.from_pyca_cryptography_key(ephemeral),

145 KEMKey.from_pyca_cryptography_key(ephemeral.public_key()),

146 )

147 suite = CipherSuite.new(

148 KEMId.DHKEM_P256_HKDF_SHA256, KDFId.HKDF_SHA256, AEADId.EXPORT_ONLY

149 )

150 enc, sender = suite.create_sender_context(

151 KEMKey.from_pyca_cryptography_key(runtime),

152 info=b"OpenAI-PSP-v4/HPKE-export",

153 eks=pair,

154 )

155 require(enc == decode64(record["encrypted_dek"]), "Wrong recovered scalar")

156 aad = rfc8785.dumps({k: v for k, v in record.items() if k != "ciphertext"})

157 key = sender.export(b"OpenAI-PSP-v4/payload-key" + hashlib.sha256(aad).digest(), 32)

158 return AESGCM(key).decrypt(nonce, ciphertext, aad).decode("utf-8")

159 

160 

161def validate_bundle(bundle_json, original_key_id, original_public_key_pem):

162 bundle = json.loads(bundle_json.decode("utf-8"), object_pairs_hook=unique_object)

163 require(

164 type(bundle["bundle_version"]) is int and bundle["bundle_version"] == 1,

165 "Unsupported bundle version",

166 )

167 record, access = bundle["protected_record"], bundle["customer_access"]

168 require(access is not None, "Customer recovery is unavailable")

169 require(

170 record["version"] == "v4"

171 and record["algorithm"]

172 == "HPKE-v1-DHKEM_P256_HKDF_SHA256-EXPORT_ONLY-AES256_GCM",

173 "Unsupported record",

174 )

175 require(

176 type(access["version"]) is int

177 and access["version"] == 1

178 and access["algorithm"] == "RSA-OAEP-256",

179 "Unsupported recovery capsule",

180 )

181 require(access["customer_key_id"] == original_key_id, "Wrong key registration")

182 require(len(decode64(access["recovery_secret"])) == 384, "Wrong RSA length")

183 

184 public_key = serialization.load_pem_public_key(original_public_key_pem)

185 require(isinstance(public_key, rsa.RSAPublicKey), "Expected an RSA public key")

186 require(public_key.key_size == 3072, "Expected RSA-3072")

187 spki = public_key.public_bytes(

188 serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo

189 )

190 fingerprint = decode64(access["customer_public_key_fingerprint"])

191 require(

192 hmac.compare_digest(fingerprint, hashlib.sha256(spki).digest()), "Wrong key"

193 )

194 runtime = point(record["safety_review_runtime_public_key"])

195 coordinates = runtime.public_numbers()

196 jwk = {

197 "crv": "P-256",

198 "kty": "EC",

199 "x": b64url(coordinates.x.to_bytes(32, "big")),

200 "y": b64url(coordinates.y.to_bytes(32, "big")),

201 }

202 thumbprint = b64url(hashlib.sha256(rfc8785.dumps(jwk)).digest())

203 require(record["key_thumbprint"] == thumbprint, "Wrong runtime key")

204 nonce, ciphertext = decode64(record["nonce"]), decode64(record["ciphertext"])

205 require(len(nonce) == 12 and len(ciphertext) >= 16, "Invalid AES-GCM encoding")

206 return record, access, runtime, nonce, ciphertext

207 

208 

209def recover_scalar(record, access, capsule):

210 header = {k: v for k, v in access.items() if k != "recovery_secret"}

211 binding_input = {

212 "purpose": "OpenAI-PSP-v4/customer-recovery-binding",

213 "bundle_version": 1,

214 "record": record,

215 "recovery_header": header,

216 }

217 binding = hashlib.sha256(rfc8785.dumps(binding_input)).digest()

218 require(

219 isinstance(capsule, bytes)

220 and len(capsule) == 72

221 and capsule[:8] == b"PSPv4\x00\x00\x01"

222 and hmac.compare_digest(capsule[40:], binding),

223 "Invalid recovery capsule binding",

224 )

225 e = int.from_bytes(capsule[8:40], "big")

226 order = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551

227 require(1 <= e < order, "Invalid P-256 private scalar")

228 return e

229 

230 

231def require(condition, message):

232 if not condition:

233 raise ValueError(message)

234 

235 

236def unique_object(pairs):

237 require(len(pairs) == len(dict(pairs)), "Duplicate JSON field")

238 return dict(pairs)

239 

240 

241def b64url(data):

242 return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")

243 

244 

245def decode64(value):

246 # Restore omitted "=" padding: -len(value) % 4 is how many characters

247 # are needed to reach a multiple of four (e.g. length 10 needs "==").

248 data = base64.b64decode(

249 value + "=" * (-len(value) % 4), altchars=b"-_", validate=True

250 )

251 require(b64url(data) == value, "Noncanonical base64url")

252 return data

253 

254 

255def point(value):

256 encoded = decode64(value)

257 require(len(encoded) == 65 and encoded[0] == 4, "Invalid P-256 encoding")

258 return ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), encoded)

259```

260 

261 

262### Recovered content

263 

264For request records whose recovered JSON has `format: "psp_request_v1"`, `request_text` contains the retained request text. The other fields are `reasoning_summaries` (string array), `request_text_truncated` (boolean), and `reasoning_summary_parts_omitted` (nonnegative integer). Check the truncation fields when interpreting the result. Other protected content types may use different plaintext formats.

265 

266Customer recovery can continue after the OpenAI runtime key expires, provided your original customer recovery key and snapshot remain available. Leave the authenticated `expires_at` value unchanged. The separately encrypted `encrypted_cot` blobs remain opaque to customer recovery.

Details

88 "session": {88 "session": {

89 "type": "realtime",89 "type": "realtime",

90 "model": "gpt-realtime-2.1",90 "model": "gpt-realtime-2.1",

91 # Lock the output to audio (add "text" if you also want text).91 # Choose ["audio"] or ["text"]; do not combine them.

92 "output_modalities": ["audio"],92 "output_modalities": ["audio"],

93 "audio": {93 "audio": {

94 "input": {94 "input": {


131// Replace pmpt_123 with your stored prompt ID.131// Replace pmpt_123 with your stored prompt ID.

132var pcm =132var pcm =

133 RealtimeAudioFormats.AudioPcm.builder()133 RealtimeAudioFormats.AudioPcm.builder()

134 .type(RealtimeAudioFormats.AudioPcm.Type.AUDIO_PCM)

134 .rate(RealtimeAudioFormats.AudioPcm.Rate._24000)135 .rate(RealtimeAudioFormats.AudioPcm.Rate._24000)

135 .build();136 .build();

136connection.send(137connection.send(


311```312```

312 313 

313 314 

314After adding the user message to the conversation, send the [`response.create`](https://developers.openai.com/api/reference/resources/realtime) event to initiate a response from the model. If both audio and text are enabled for the current session, the model will respond with both audio and text content. If you'd like to generate text only, you can specify that when sending the `response.create` client event, as shown below.315After adding the user message to the conversation, send the [`response.create`](https://developers.openai.com/api/reference/resources/realtime) event to initiate a response from the model. Choose one output modality: `audio` produces audio with its transcript, and `text` produces text only. If you'd like to generate text only, you can specify that when sending the `response.create` client event, as shown below.

315 316 

316Generate a text-only response317Generate a text-only response

317 318 


544 545 

545The snippet above enables interaction with the Realtime API, but there's much more that can be done. For more examples of different kinds of user interfaces, check out the [WebRTC samples](https://github.com/webrtc/samples) repository. Live demos of these samples can also be [found here](https://webrtc.github.io/samples/).546The snippet above enables interaction with the Realtime API, but there's much more that can be done. For more examples of different kinds of user interfaces, check out the [WebRTC samples](https://github.com/webrtc/samples) repository. Live demos of these samples can also be [found here](https://webrtc.github.io/samples/).

546 547 

547Using [media captures and streams](https://developer.mozilla.org/en-US/docs/Web/API/Media_Capture_and_Streams_API) in the browser enables you to do things like mute and unmute microphones, select which device to collect input from, and more.548Using [media captures and streams](https://developer.mozilla.org/en-US/docs/Web/API/Media_Capture_and_Streams_API) in the browser enables you to do things like turn microphones off and on, select which device to collect input from, and more.

548 549 

549### Client and server events for audio in WebRTC550### Client and server events for audio in WebRTC

550 551 


679 680 

680To stream audio input to the server, you can use the [`input_audio_buffer.append`](https://developers.openai.com/api/reference/resources/realtime) client event. This event requires you to send chunks of **Base64-encoded audio bytes** to the Realtime API over the socket. Each chunk cannot exceed 15 MB in size.681To stream audio input to the server, you can use the [`input_audio_buffer.append`](https://developers.openai.com/api/reference/resources/realtime) client event. This event requires you to send chunks of **Base64-encoded audio bytes** to the Realtime API over the socket. Each chunk cannot exceed 15 MB in size.

681 682 

682The format of the input chunks can be configured either for the entire session, or per response.683Set `session.audio.input.format` in [`session.update`](https://developers.openai.com/api/reference/resources/realtime). For PCM, use `{"type": "audio/pcm", "rate": 24000}` and send 24 kHz mono audio.

683 

684- Session: `session.input_audio_format` in [`session.update`](https://developers.openai.com/api/reference/resources/realtime)

685- Response: `response.input_audio_format` in [`response.create`](https://developers.openai.com/api/reference/resources/realtime)

686 684 

687Append audio input bytes to the conversation685Append audio input bytes to the conversation

688 686 


726for (const filename of files) {724for (const filename of files) {

727 const audioFile = fs.readFileSync(filename);725 const audioFile = fs.readFileSync(filename);

728 const audioBuffer = await decodeAudio(audioFile);726 const audioBuffer = await decodeAudio(audioFile);

727 if (

728 audioBuffer.sampleRate !== 24000 ||

729 audioBuffer.channelData.length !== 1

730 ) {

731 throw new Error("Expected 24 kHz mono audio: " + filename);

732 }

729 const channelData = audioBuffer.channelData[0];733 const channelData = audioBuffer.channelData[0];

730 const base64Chunk = base64EncodeAudio(channelData);734 const base64Chunk = base64EncodeAudio(channelData);

731 ws.send(735 ws.send(


766 770 

767for filename in files:771for filename in files:

768 data, samplerate = sf.read(filename, dtype="float32")772 data, samplerate = sf.read(filename, dtype="float32")

769 channel_data = data[:, 0] if data.ndim > 1 else data773 if samplerate != 24000 or data.ndim != 1:

770 base64_chunk = base64_encode_audio(channel_data)774 raise ValueError(f"Expected 24 kHz mono audio: {filename}")

775 base64_chunk = base64_encode_audio(data)

771 776 

772 # Send the client event777 # Send the client event

773 event = {"type": "input_audio_buffer.append", "audio": base64_chunk}778 event = {"type": "input_audio_buffer.append", "audio": base64_chunk}


948 953 

949**To play output audio back on a client device like a web browser, we recommend using WebRTC rather than WebSockets**. WebRTC will be more robust sending media to client devices over uncertain network conditions.954**To play output audio back on a client device like a web browser, we recommend using WebRTC rather than WebSockets**. WebRTC will be more robust sending media to client devices over uncertain network conditions.

950 955 

951But to work with audio output in server-to-server applications using a WebSocket, you will need to listen for [`response.output_audio.delta`](https://developers.openai.com/api/reference/resources/realtime) events containing the Base64-encoded chunks of audio data from the model. You will either need to buffer these chunks and write them out to a file, or maybe immediately stream them to another source like [a phone call with Twilio](https://www.twilio.com/en-us/blog/twilio-openai-realtime-api-launch-integration).956To work with audio output in server-to-server applications using a WebSocket, you will need to listen for [`response.output_audio.delta`](https://developers.openai.com/api/reference/resources/realtime) events containing the Base64-encoded chunks of audio data from the model. You will either need to buffer these chunks and write them out to a file, or maybe immediately stream them to another source like [a phone call with Twilio](https://www.twilio.com/en-us/blog/twilio-openai-realtime-api-launch-integration).

952 957 

953Note that the [`response.output_audio.done`](https://developers.openai.com/api/reference/resources/realtime) and [`response.done`](https://developers.openai.com/api/reference/resources/realtime) events won't actually contain audio data in them - just audio content transcriptions. To get the actual bytes, you'll need to listen for the [`response.output_audio.delta`](https://developers.openai.com/api/reference/resources/realtime) events.958Note that the [`response.output_audio.done`](https://developers.openai.com/api/reference/resources/realtime) and [`response.done`](https://developers.openai.com/api/reference/resources/realtime) events won't actually contain audio data in them - just audio content transcriptions. To get the actual bytes, you'll need to listen for the [`response.output_audio.delta`](https://developers.openai.com/api/reference/resources/realtime) events.

954 959 


1216 serverEvent.type === "response.done" &&1221 serverEvent.type === "response.done" &&

1217 serverEvent.response.metadata?.topic === "classification"1222 serverEvent.response.metadata?.topic === "classification"

1218 ) {1223 ) {

1219 // this server event pertained to our OOB model response1224 const { response } = serverEvent;

1220 console.log(serverEvent.response.output[0]);1225 if (response.status !== "completed" || response.output.length === 0) {

1226 throw new Error(

1227 "Classification response ended with status " + response.status

1228 );

1229 }

1230 console.log(response.output[0]);

1221 }1231 }

1222}1232}

1223 1233 


1231```python1241```python

1232def on_message(ws, message):1242def on_message(ws, message):

1233 server_event = json.loads(message)1243 server_event = json.loads(message)

1234 topic = ""1244 if server_event["type"] != "response.done":

1235 1245 return

1236 # See if metadata is present1246 response = server_event["response"]

1237 try:1247 if (response.get("metadata") or {}).get("topic") != "classification":

1238 topic = server_event["response"]["metadata"]["topic"]1248 return

1239 except KeyError:1249 if response["status"] != "completed" or not response["output"]:

1240 print("topic not set")1250 raise RuntimeError(

1241 1251 f"Classification response ended with status {response['status']}"

1242 if server_event["type"] == "response.done" and topic == "classification":1252 )

1243 # this server event pertained to our OOB model response1253 print(response["output"][0])

1244 print(server_event["response"]["output"][0])

1245```1254```

1246 1255 

1247```java1256```java

Details

390 390 

391```javascript391```javascript

392let translationSessionClosing = false;392let translationSessionClosing = false;

393let gotTranslationClosed = false;

394let drainDeadline;

395 

396ws.on("close", () => {

397 clearTimeout(drainDeadline);

398 if (!gotTranslationClosed) {

399 console.error("Translation disconnected before final session.closed");

400 process.exitCode = 1;

401 }

402});

393 403 

394function closeTranslationSession() {404function closeTranslationSession() {

395 if (translationSessionClosing) {405 if (translationSessionClosing) {


397 }407 }

398 408 

399 translationSessionClosing = true;409 translationSessionClosing = true;

400 ws.send(410 // Adjust this deadline for your application's finalization requirements.

401 JSON.stringify({411 drainDeadline = setTimeout(() => {

402 type: "session.close",412 console.error("Translation finalization timed out");

403 })413 process.exitCode = 1;

404 );414 ws.close();

415 }, 15_000);

416 ws.send(JSON.stringify({ type: "session.close" }));

405}417}

406 418 

419// Use this in place of the earlier output handler, so audio plays only once.

407ws.on("message", (data) => {420ws.on("message", (data) => {

408 const event = JSON.parse(data.toString());421 const event = JSON.parse(data.toString());

409 422 


419 updateSourceTranscript(event.delta);432 updateSourceTranscript(event.delta);

420 }433 }

421 434 

435 if (event.type === "error") {

436 clearTimeout(drainDeadline);

437 console.error("Translation error", event.error?.code);

438 process.exitCode = 1;

439 ws.close();

440 }

441 

422 if (event.type === "session.closed") {442 if (event.type === "session.closed") {

443 clearTimeout(drainDeadline);

444 gotTranslationClosed = true;

423 ws.close();445 ws.close();

424 }446 }

425});447});


429```451```

430 452 

431```python453```python

454import time

455 

432translation_session_closing = False456translation_session_closing = False

433 457 

434 458 


443 467 

444# Call this when the source stream ends.468# Call this when the source stream ends.

445close_translation_session()469close_translation_session()

446 470# Adjust this deadline for your application's finalization requirements.

447while True:471drain_deadline = time.monotonic() + 15.0

448 event = json.loads(ws.recv())472 

473try:

474 while True:

475 remaining = drain_deadline - time.monotonic()

476 if remaining <= 0:

477 raise TimeoutError("Translation finalization timed out")

478 ws.settimeout(remaining)

479 data = ws.recv()

480 if not data:

481 raise RuntimeError("Translation disconnected before session.closed")

482 event = json.loads(data)

483 

484 if event["type"] == "error":

485 raise RuntimeError(f"Translation error: {event['error']['code']}")

449 486 

450 if event["type"] == "session.output_audio.delta":487 if event["type"] == "session.output_audio.delta":

451 play_pcm16(event["delta"])488 play_pcm16(event["delta"])


457 update_source_transcript(event["delta"])494 update_source_transcript(event["delta"])

458 495 

459 if event["type"] == "session.closed":496 if event["type"] == "session.closed":

460 ws.close()

461 break497 break

498finally:

499 ws.close()

462```500```

463 501 

464 502 

Details

180 });180 });

181 // Send back the SDP we received from the OpenAI REST API181 // Send back the SDP we received from the OpenAI REST API

182 const sdp = await r.text();182 const sdp = await r.text();

183 res.send(sdp);183 res.status(r.status).send(sdp);

184 } catch (error) {184 } catch (error) {

185 console.error("Token generation error:", error);185 console.error("Token generation error:", error);

186 res.status(500).json({ error: "Failed to generate token" });186 res.status(500).json({ error: "Failed to generate token" });

Details

10 10 

11Steering does not rewrite output already sent to your application, undo earlier actions, or cancel tools that have already started.11Steering does not rewrite output already sent to your application, undo earlier actions, or cancel tools that have already started.

12 12 

13For connection setup and general transport behavior, see [WebSocket mode](https://developers.openai.com/api/docs/guides/websocket-mode). For exact event definitions, see the [Responses WebSocket events reference](https://developers.openai.com/api/reference/resources/responses/websocket-events).13The Java example requires Java 17 or later. For connection setup and general transport behavior, see [WebSocket mode](https://developers.openai.com/api/docs/guides/websocket-mode). For exact event definitions, see the [Responses WebSocket events reference](https://developers.openai.com/api/reference/resources/responses/websocket-events).

14 14 

15## Send a steering message15## Send a steering message

16 16 


151 initial_response_id = None151 initial_response_id = None

152 successor_response_id = None152 successor_response_id = None

153 153 

154 async with client.responses.connect() as connection, asyncio.timeout(120):154 async with client.responses.connect() as connection:

155 await connection.response.create(155 await connection.response.create(

156 model="gpt-6-astra",156 model="gpt-6-astra",

157 reasoning={"effort": "medium"},157 reasoning={"effort": "medium"},


188 raise RuntimeError("Connection closed before the steered response finished.")188 raise RuntimeError("Connection closed before the steered response finished.")

189 189 

190 190 

191asyncio.run(main())191async def run():

192 await asyncio.wait_for(main(), timeout=120)

193 

194 

195asyncio.run(run())

192```196```

193 197 

194```go198```go


260import com.openai.models.responses.*;264import com.openai.models.responses.*;

261import java.util.*;265import java.util.*;

262 266 

267var client = OpenAIOkHttpClient.fromEnv();

268 

263long deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(120);269long deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(120);

264try (var conn =270try (var conn =

265 client.async().responses().connect().get(10, java.util.concurrent.TimeUnit.SECONDS)) {271 client.async().responses().connect().get(10, java.util.concurrent.TimeUnit.SECONDS)) {

Details

1126 1127 

1127## Supported models1128## Supported models

1128 1129 

1129The following models support the image generation tool:1130The following non-deprecated models have documented support for the image generation tool in the Responses API. Use these model IDs in the top-level `model` field. See each model's page for availability and access requirements.

1130 1131 

1131- `gpt-5.5`1132- [`chat-latest`](https://developers.openai.com/api/docs/models/chat-latest)

1132- `gpt-5.4-mini`1133- [`gpt-5.2`](https://developers.openai.com/api/docs/models/gpt-5.2)

1133- `gpt-5.4-nano`1134- [`gpt-5.2-pro`](https://developers.openai.com/api/docs/models/gpt-5.2-pro)

1134- `gpt-5.2`1135- [`gpt-5.4`](https://developers.openai.com/api/docs/models/gpt-5.4)

1135- `gpt-5`1136- [`gpt-5.4-mini`](https://developers.openai.com/api/docs/models/gpt-5.4-mini)

1136- `gpt-5-nano`1137- [`gpt-5.4-pro`](https://developers.openai.com/api/docs/models/gpt-5.4-pro)

1137- `o3`1138- [`gpt-5.5`](https://developers.openai.com/api/docs/models/gpt-5.5)

1138- `gpt-4.1`1139- [`gpt-5.5-pro`](https://developers.openai.com/api/docs/models/gpt-5.5-pro)

1139- `gpt-4.1-mini`1140- [`gpt-5.6-cyber`](https://developers.openai.com/api/docs/models/gpt-5.6-cyber)

1140- `gpt-4.1-nano`1141- [`gpt-5.6-luna`](https://developers.openai.com/api/docs/models/gpt-5.6-luna)

1141- `gpt-4o`1142- [`gpt-5.6-sol`](https://developers.openai.com/api/docs/models/gpt-5.6-sol)

1142- `gpt-4o-mini`1143- [`gpt-5.6-terra`](https://developers.openai.com/api/docs/models/gpt-5.6-terra)

1144- [`gpt-6-astra`](https://developers.openai.com/api/docs/models/gpt-6-astra)

1145- [`gpt-6-luna`](https://developers.openai.com/api/docs/models/gpt-6-luna)

1146- [`gpt-6-sol`](https://developers.openai.com/api/docs/models/gpt-6-sol)

1147- [`gpt-6.1-sol`](https://developers.openai.com/api/docs/models/gpt-6.1-sol)

1148- [`gpt-6.1-terra`](https://developers.openai.com/api/docs/models/gpt-6.1-terra)

1149 

1150For model availability and shutdown dates, see [Deprecations](https://developers.openai.com/api/docs/deprecations).

Details

75 result = await pipeline.run(audio_input)75 result = await pipeline.run(audio_input)

76 async for event in result.stream():76 async for event in result.stream():

77 if event.type == "voice_stream_event_audio":77 if event.type == "voice_stream_event_audio":

78 print("Received audio bytes", len(event.data))78 print("Received audio bytes", event.data.nbytes)

79 79 

80 80 

81if __name__ == "__main__":81if __name__ == "__main__":

Details

325import WebSocket from "ws";325import WebSocket from "ws";

326 326 

327const callId = "rtc_u1_9c6574da8b8a41a18da9308f4ad974ce";327const callId = "rtc_u1_9c6574da8b8a41a18da9308f4ad974ce";

328const ws = new WebSocket(`wss://api.openai.com/v1/realtime?call_id=rtc_u1_9c6574da8b8a41a18da9308f4ad974ce`, {328const ws = new WebSocket(`wss://api.openai.com/v1/realtime?call_id=${callId}`, {

329 headers: {329 headers: {

330 Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,330 Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,

331 },331 },


455def webhook():455def webhook():

456 try:456 try:

457 event = client.webhooks.unwrap(request.data, request.headers)457 event = client.webhooks.unwrap(request.data, request.headers)

458 except (InvalidWebhookSignatureError, ValueError) as e:

459 print("Invalid signature", e)

460 return Response("Invalid signature", status=400)

458 461 

459 if event.type == "realtime.call.incoming":462 if event.type == "realtime.call.incoming":

460 requests.post(463 requests.post(


463 + "/accept",466 + "/accept",

464 headers={**AUTH_HEADER, "Content-Type": "application/json"},467 headers={**AUTH_HEADER, "Content-Type": "application/json"},

465 json=call_accept,468 json=call_accept,

466 )469 ).raise_for_status()

467 threading.Thread(470 threading.Thread(

468 target=lambda: asyncio.run(websocket_task(event.data.call_id)),471 target=lambda: asyncio.run(websocket_task(event.data.call_id)),

469 daemon=True,472 daemon=True,

470 ).start()473 ).start()

471 return Response(status=200)474 return Response(status=200)

472 except InvalidWebhookSignatureError as e:

473 print("Invalid signature", e)

474 return Response("Invalid signature", status=400)

475 475 

476 476 

477if __name__ == "__main__":477if __name__ == "__main__":

Details

242function cleanup() {242function cleanup() {

243 clearTimeout(closeTimeout);243 clearTimeout(closeTimeout);

244 microphone?.getTracks().forEach((track) => track.stop());244 microphone?.getTracks().forEach((track) => track.stop());

245 events?.close();245 const closingEvents = events;

246 events = undefined;

247 closingEvents?.close();

246 peer?.close();248 peer?.close();

247 audio.srcObject = null;249 audio.srcObject = null;

248 ready = false;250 ready = false;


440 });442 });

441 // Send back the SDP we received from the OpenAI REST API443 // Send back the SDP we received from the OpenAI REST API

442 const sdp = await r.text();444 const sdp = await r.text();

443 res.send(sdp);445 res.status(r.status).send(sdp);

444 } catch (error) {446 } catch (error) {

445 console.error("Token generation error:", error);447 console.error("Token generation error:", error);

446 res.status(500).json({ error: "Failed to generate token" });448 res.status(500).json({ error: "Failed to generate token" });


541 {543 {

542 method: "POST",544 method: "POST",

543 headers: {545 headers: {

544 Authorization: `Bearer ${apiKey}`,546 Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,

545 "Content-Type": "application/json",547 "Content-Type": "application/json",

546 "OpenAI-Safety-Identifier": "hashed-user-id",548 "OpenAI-Safety-Identifier": "hashed-user-id",

547 },549 },


550 );552 );

551 553 

552 const data = await response.json();554 const data = await response.json();

553 res.json(data);555 res.status(response.status).json(data);

554 } catch (error) {556 } catch (error) {

555 console.error("Token generation error:", error);557 console.error("Token generation error:", error);

556 res.status(500).json({ error: "Failed to generate token" });558 res.status(500).json({ error: "Failed to generate token" });


631To send and receive other client and server events, you can use the WebRTC peer connection's [data channel](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Using_data_channels).633To send and receive other client and server events, you can use the WebRTC peer connection's [data channel](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Using_data_channels).

632 634 

633```javascript635```javascript

634// This is the data channel set up in the browser code above...636// Reuse dc from the preceding browser connection example.

635const dc = pc.createDataChannel("oai-events");

636 637 

637// Listen for server events638// Listen for server events

638dc.addEventListener("message", (e) => {639dc.addEventListener("message", (e) => {


654 ],655 ],

655 },656 },

656};657};

657dc.send(JSON.stringify(event));658const sendGreeting = () => dc.send(JSON.stringify(event));

659if (dc.readyState === "open") {

660 sendGreeting();

661} else if (dc.readyState === "connecting") {

662 dc.addEventListener("open", sendGreeting, { once: true });

663} else {

664 throw new Error("The data channel has closed before greeting was sent.");

665}

658```666```

659 667 

660 668 

Details

192 await connection.session.close()192 await connection.session.close()

193 try:193 try:

194 await asyncio.wait_for(finalized.wait(), timeout=15)194 await asyncio.wait_for(finalized.wait(), timeout=15)

195 except TimeoutError:195 except asyncio.TimeoutError:

196 print(196 print(

197 "Incomplete finalization: no session.closed event",197 "Incomplete finalization: no session.closed event",

198 file=sys.stderr,198 file=sys.stderr,

Details

17Install the WebSocket dependencies with `pip install "openai[realtime]>=3.8.0"` for Python, `npm install openai@^7.10.0 ws` for JavaScript, or `gem install openai async-websocket` for Ruby.17Install the WebSocket dependencies with `pip install "openai[realtime]>=3.8.0"` for Python, `npm install openai@^7.10.0 ws` for JavaScript, or `gem install openai async-websocket` for Ruby.

18 18 

19For Go, run `go get github.com/openai/openai-go/v3@v3.73.0`.19For Go, run `go get github.com/openai/openai-go/v3@v3.73.0`.

20For Java, add the Maven dependency `com.openai:openai-java:4.79.0`.20For Java, add the Maven dependency `com.openai:openai-java:4.79.0` and use Java 17 or later for these examples.

21These Go and Java SDK versions provide native Responses WebSocket support.21These Go and Java SDK versions provide native Responses WebSocket support.

22 22 

23In WebSocket mode, start each turn by sending a `response.create` event from the client. The payload mirrors the normal [Responses create body](https://developers.openai.com/api/reference/resources/responses/methods/create), except that transport-specific fields like `stream` and `background` are not used.23In WebSocket mode, start each turn by sending a `response.create` event from the client. The payload mirrors the normal [Responses create body](https://developers.openai.com/api/reference/resources/responses/methods/create), except that transport-specific fields like `stream` and `background` are not used.


93 break93 break

94 if event.type in {"response.failed", "response.incomplete", "error"}:94 if event.type in {"response.failed", "response.incomplete", "error"}:

95 raise RuntimeError(event.to_json())95 raise RuntimeError(event.to_json())

96 else:

97 raise RuntimeError("Connection closed before the response finished.")

96```98```

97 99 

98```go100```go


130import com.openai.models.responses.*;132import com.openai.models.responses.*;

131import java.util.*;133import java.util.*;

132 134 

135var client = OpenAIOkHttpClient.fromEnv();

136 

133try (var conn = client.responses().connect()) {137try (var conn = client.responses().connect()) {

134 conn.send(138 conn.send(

135 ResponsesClientEvent.ofResponseCreate(139 ResponsesClientEvent.ofResponseCreate(


149 .flatMap(message -> message.content().stream())153 .flatMap(message -> message.content().stream())

150 .flatMap(content -> content.outputText().stream())154 .flatMap(content -> content.outputText().stream())

151 .forEach(text -> System.out.println(text.text()));155 .forEach(text -> System.out.println(text.text()));

156} finally {

157 client.close();

152}158}

153```159```

154 160 


457import com.openai.models.responses.*;463import com.openai.models.responses.*;

458import java.util.*;464import java.util.*;

459 465 

466var client = OpenAIOkHttpClient.fromEnv();

467 

460var tool =468var tool =

461 FunctionTool.builder()469 FunctionTool.builder()

462 .name("get_test_results")470 .name("get_test_results")


525 .flatMap(message -> message.content().stream())533 .flatMap(message -> message.content().stream())

526 .flatMap(content -> content.outputText().stream())534 .flatMap(content -> content.outputText().stream())

527 .forEach(text -> System.out.println(text.text()));535 .forEach(text -> System.out.println(text.text()));

536} finally {

537 client.close();

528}538}

529```539```

530 540 


720 break730 break

721 if event.type in {"response.failed", "response.incomplete", "error"}:731 if event.type in {"response.failed", "response.incomplete", "error"}:

722 raise RuntimeError(event.to_json())732 raise RuntimeError(event.to_json())

733 else:

734 raise RuntimeError("Connection closed before the response finished.")

723```735```

724 736 

725```ruby737```ruby

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.93.0</version>176 <version>4.94.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.93.0</version>193 <version>4.94.0</version>

194</dependency>194</dependency>

195```195```

196 196