# OpenAI Administration API — atlas **Status:** `DOCUMENTED` for all 124 operations (OpenAPI spec v2.3.0); every GET we could probe is `ACCOUNT_RESTRICTED` (our project key gets HTTP 403 — 401 for audit logs — `Missing scopes: api.management.read` etc.). No Admin key is available, so nothing below is `LIVE_VERIFIED` except the exact error shapes. No mutating admin call was ever issued. **Sources:** [Administration overview](https://developers.openai.com/api/reference/administration/overview) · [Admin APIs guide](https://developers.openai.com/api/docs/guides/admin-apis) · [Production best practices → organization](https://developers.openai.com/api/docs/guides/production-best-practices#setting-up-your-organization) · [Spend limits](https://developers.openai.com/api/docs/guides/spend-limits) · [Mutual TLS](https://developers.openai.com/api/docs/guides/mutual-tls) · [IP allowlist](https://developers.openai.com/api/docs/guides/ip-allowlist) · [Your data (retention / residency)](https://developers.openai.com/api/docs/guides/your-data) · OpenAPI `sources/openai/openapi/openapi-master.yaml` (all `/organization/*` + `/projects/*` paths) · SDK surfaces `sources/openai/openapi/{python,node}-sdk-api.md`. **Last verified:** 2026-09-18 (live probes: 14 admin GETs, all 401/403; details in `tmp-live/openai-admin/` and `reports/live-requests.jsonl`). Machine-readable twins: `generated/fragments/endpoints/openai-admin.json` (124 endpoints), `generated/fragments/parameters/openai-admin.json` (431 parameters), `generated/fragments/audit-log-events/openai-audit-log-events.json` (149 event types). ## 1. Model of the Admin API | Concept | What it is | Where | |---|---|---| | Organization | Billing + policy boundary; has users (owner/reader), groups, custom roles, spend limit, data retention, certificates, external storage, audit log | `/v1/organization/*` | | Project | Isolation unit inside an org: own API keys, service accounts, users (owner/member), groups, per-model rate limits, model/hosted-tool permissions, spend limit & alerts, data retention, certificates, webhooks | `/v1/organization/projects/{project_id}/*`, project roles at `/v1/projects/{project_id}/*` | | Admin API key | `sk-admin-…` created by org owners at *Settings → Organization → Admin keys*; the only credential accepted by `/v1/organization/*` and `/v1/projects/*`; refused by non-admin endpoints. Workload-identity tokens **cannot** call Admin endpoints. | `GET/POST/DELETE /v1/organization/admin_api_keys` | | Scopes | Fine-grained permissions carried by restricted keys / roles. Read scopes we observed in error messages: `api.management.read` (users, invites, projects, admin keys, spend limit/alerts, data retention), `api.usage.read` (usage + costs), `api.audit_logs.read`, `api.roles.read`, `api.groups.read`, `api.mtls.read` (certificates, message adds "(Owner)"), `api.external_storage.read`. Documented write scopes: `api.groups.write`, `api.mtls.write`. Model-side scopes seen in the spec/guides: `api.model.request`, `api.model.read`, `api.responses.write`, `api.files.read/write`, `api.batch.read/write`, `api.vector_store.read`, `api.videos.read/write`, `api.voices.read/write`, `api.agents.read/write`, `api.vaults.read/write`, `api.webhooks.read`, `api.traces.read`, `api.safety.read`, `api.safety.alerts.read`, `api.apps.read/write`. | error bodies, Role.permissions[] | | Roles | Predefined + custom roles (`object: role`, `permissions[]`, `resource_type: api.organization | api.project`, `predefined_role`). Bound to users or groups at org level (`/organization/users/{id}/roles`, `/organization/groups/{id}/roles`) or project level (`/projects/{id}/users/{uid}/roles`, `/projects/{id}/groups/{gid}/roles`). Legacy simple roles: org `owner` / `reader`; project `owner` / `member`. | `/v1/organization/roles`, `/v1/projects/{id}/roles` | | Pagination | Cursor style: `limit` (1–100, default 20) + `after` → `{object:"list", data, first_id, last_id, has_more}`. Usage/costs use `page` → `{object:"page", data:[buckets], has_more, next_page}`. Roles/groups lists use `after`/`limit` and return `has_more` + `next` cursor (SDK `SyncNextCursorPage`). | — | | Mutations | Always `POST` (no PUT/PATCH), deletes are `DELETE` → `{object:"…deleted", id, deleted:true}`. No Idempotency-Key header. | — | ## 2. Endpoint catalogue Legend: `*` = required parameter; SDK column = Python method (Node is the camelCase twin, e.g. `client.admin.organization.auditLogs.list`). ### Organization admin api keys | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/admin_api_keys` | List all organization and project API keys. | `after`, `order` (asc|desc), `limit` | `client.admin.organization.admin_api_keys.list` | ACCOUNT_RESTRICTED (403/401 with project key) | | `POST` | `/v1/organization/admin_api_keys` | Create admin API key | `name`*, `expires_in_seconds` | `client.admin.organization.admin_api_keys.create` | DOCUMENTED | | `GET` | `/v1/organization/admin_api_keys/{key_id}` | Retrieve admin API key | — | `client.admin.organization.admin_api_keys.retrieve` | DOCUMENTED | | `DELETE` | `/v1/organization/admin_api_keys/{key_id}` | Delete admin API key | — | `client.admin.organization.admin_api_keys.delete` | DOCUMENTED | ### Organization audit logs | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/audit_logs` | List audit logs | `effective_at`, `tenant_only`, `limit`, `after`, `before` | `client.admin.organization.audit_logs.list` | ACCOUNT_RESTRICTED (403/401 with project key) | ### Certificates (Mutual TLS) | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/certificates` | List organization certificates | `limit`, `after`, `order` (asc|desc) | `client.admin.organization.certificates.list` | ACCOUNT_RESTRICTED (403/401 with project key) | | `POST` | `/v1/organization/certificates` | Upload certificate | `name`, `certificate`* | `client.admin.organization.certificates.create` | DOCUMENTED | | `POST` | `/v1/organization/certificates/activate` | Activate certificates for organization | `certificate_ids`* | `client.admin.organization.certificates.activate` | DOCUMENTED | | `POST` | `/v1/organization/certificates/deactivate` | Deactivate certificates for organization | `certificate_ids`* | `client.admin.organization.certificates.deactivate` | DOCUMENTED | | `GET` | `/v1/organization/certificates/{certificate_id}` | Get certificate | `include` (content) | `client.admin.organization.certificates.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/certificates/{certificate_id}` | Modify certificate | `name` | `client.admin.organization.certificates.update` | DOCUMENTED | | `DELETE` | `/v1/organization/certificates/{certificate_id}` | Delete certificate | — | `client.admin.organization.certificates.delete` | DOCUMENTED | | `GET` | `/v1/organization/projects/{project_id}/certificates` | List project certificates | `limit`, `after`, `order` (asc|desc) | `client.admin.organization.projects.certificates.list` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/certificates/activate` | Activate certificates for project | `certificate_ids`* | `client.admin.organization.projects.certificates.activate` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/certificates/deactivate` | Deactivate certificates for project | `certificate_ids`* | `client.admin.organization.projects.certificates.deactivate` | DOCUMENTED | ### Usage & costs | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/costs` | Costs | `start_time`*, `end_time`, `bucket_width` (1d), `project_ids`, `api_key_ids`, `line_items`, `group_by` (project_id|line_item|api_key_id), `limit`, `page` | `client.admin.organization.usage.costs` | ACCOUNT_RESTRICTED (403/401 with project key) | | `GET` | `/v1/organization/usage/audio_speeches` | Audio speeches | `start_time`*, `end_time`, `bucket_width` (1m|1h|1d), `project_ids`, `user_ids`, `api_key_ids`, `models`, `group_by` (project_id|user_id|api_key_id|model), `limit`, `page` | `client.admin.organization.usage.audio_speeches` | DOCUMENTED | | `GET` | `/v1/organization/usage/audio_transcriptions` | Audio transcriptions | `start_time`*, `end_time`, `bucket_width` (1m|1h|1d), `project_ids`, `user_ids`, `api_key_ids`, `models`, `group_by` (project_id|user_id|api_key_id|model), `limit`, `page` | `client.admin.organization.usage.audio_transcriptions` | DOCUMENTED | | `GET` | `/v1/organization/usage/code_interpreter_sessions` | Code interpreter sessions | `start_time`*, `end_time`, `bucket_width` (1m|1h|1d), `project_ids`, `group_by` (project_id), `limit`, `page` | `client.admin.organization.usage.code_interpreter_sessions` | DOCUMENTED | | `GET` | `/v1/organization/usage/completions` | Completions | `start_time`*, `end_time`, `bucket_width` (1m|1h|1d), `project_ids`, `user_ids`, `api_key_ids`, `models`, `batch`, `group_by` (project_id|user_id|api_key_id|model|batch|service_tier), `limit`, `page` | `client.admin.organization.usage.completions` | ACCOUNT_RESTRICTED (403/401 with project key) | | `GET` | `/v1/organization/usage/embeddings` | Embeddings | `start_time`*, `end_time`, `bucket_width` (1m|1h|1d), `project_ids`, `user_ids`, `api_key_ids`, `models`, `group_by` (project_id|user_id|api_key_id|model), `limit`, `page` | `client.admin.organization.usage.embeddings` | DOCUMENTED | | `GET` | `/v1/organization/usage/file_search_calls` | File search calls | `start_time`*, `end_time`, `bucket_width` (1m|1h|1d), `project_ids`, `user_ids`, `api_key_ids`, `vector_store_ids`, `group_by` (project_id|user_id|api_key_id|vector_store_id), `limit`, `page` | `client.admin.organization.usage.file_search_calls` | DOCUMENTED | | `GET` | `/v1/organization/usage/images` | Images | `start_time`*, `end_time`, `bucket_width` (1m|1h|1d), `sources` (image.generation|image.edit|image.variation), `sizes` (256x256|512x512|1024x1024|1792x1792|1024x1792), `project_ids`, `user_ids`, `api_key_ids`, `models`, `group_by` (project_id|user_id|api_key_id|model|size|source), `limit`, `page` | `client.admin.organization.usage.images` | DOCUMENTED | | `GET` | `/v1/organization/usage/moderations` | Moderations | `start_time`*, `end_time`, `bucket_width` (1m|1h|1d), `project_ids`, `user_ids`, `api_key_ids`, `models`, `group_by` (project_id|user_id|api_key_id|model), `limit`, `page` | `client.admin.organization.usage.moderations` | DOCUMENTED | | `GET` | `/v1/organization/usage/vector_stores` | Vector stores | `start_time`*, `end_time`, `bucket_width` (1m|1h|1d), `project_ids`, `group_by` (project_id), `limit`, `page` | `client.admin.organization.usage.vector_stores` | DOCUMENTED | | `GET` | `/v1/organization/usage/web_search_calls` | Web search calls | `start_time`*, `end_time`, `bucket_width` (1m|1h|1d), `project_ids`, `user_ids`, `api_key_ids`, `models`, `context_levels` (low|medium|high), `group_by` (project_id|user_id|api_key_id|model|context_level), `limit`, `page` | `client.admin.organization.usage.web_search_calls` | DOCUMENTED | ### Organization data retention | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/data_retention` | Retrieve organization data retention | — | `client.admin.organization.data_retention.retrieve` | ACCOUNT_RESTRICTED (403/401 with project key) | | `POST` | `/v1/organization/data_retention` | Update organization data retention | `retention_type`* (zero_data_retention|modified_abuse_monitoring|enhanced_zero_data_retention|enhanced_modified_abuse_monitoring) | `client.admin.organization.data_retention.update` | DOCUMENTED | ### Organization groups | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/groups` | List groups | `limit`, `after`, `order` (asc|desc) | `client.admin.organization.groups.list` | ACCOUNT_RESTRICTED (403/401 with project key) | | `POST` | `/v1/organization/groups` | Create group | `name`* | `client.admin.organization.groups.create` | DOCUMENTED | | `GET` | `/v1/organization/groups/{group_id}` | Retrieve group | — | `client.admin.organization.groups.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/groups/{group_id}` | Update group | `name`* | `client.admin.organization.groups.update` | DOCUMENTED | | `DELETE` | `/v1/organization/groups/{group_id}` | Delete group | — | `client.admin.organization.groups.delete` | DOCUMENTED | ### Organization role bindings | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/groups/{group_id}/roles` | List group organization role assignments | `limit`, `after`, `order` (asc|desc) | `client.admin.organization.groups.roles.list` | DOCUMENTED | | `POST` | `/v1/organization/groups/{group_id}/roles` | Assign organization role to group | `role_id`* | `client.admin.organization.groups.roles.create` | DOCUMENTED | | `GET` | `/v1/organization/groups/{group_id}/roles/{role_id}` | Retrieve group organization role | — | `client.admin.organization.groups.roles.retrieve` | DOCUMENTED | | `DELETE` | `/v1/organization/groups/{group_id}/roles/{role_id}` | Unassign organization role from group | — | `client.admin.organization.groups.roles.delete` | DOCUMENTED | | `GET` | `/v1/organization/users/{user_id}/roles` | List user organization role assignments | `limit`, `after`, `order` (asc|desc) | `client.admin.organization.users.roles.list` | DOCUMENTED | | `POST` | `/v1/organization/users/{user_id}/roles` | Assign organization role to user | `role_id`* | `client.admin.organization.users.roles.create` | DOCUMENTED | | `GET` | `/v1/organization/users/{user_id}/roles/{role_id}` | Retrieve user organization role | — | `client.admin.organization.users.roles.retrieve` | DOCUMENTED | | `DELETE` | `/v1/organization/users/{user_id}/roles/{role_id}` | Unassign organization role from user | — | `client.admin.organization.users.roles.delete` | DOCUMENTED | ### Group membership | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/groups/{group_id}/users` | List group users | `limit`, `after`, `order` (asc|desc) | `client.admin.organization.groups.users.list` | DOCUMENTED | | `POST` | `/v1/organization/groups/{group_id}/users` | Add group user | `user_id`* | `client.admin.organization.groups.users.create` | DOCUMENTED | | `GET` | `/v1/organization/groups/{group_id}/users/{user_id}` | Retrieve group user | — | `client.admin.organization.groups.users.retrieve` | DOCUMENTED | | `DELETE` | `/v1/organization/groups/{group_id}/users/{user_id}` | Remove group user | — | `client.admin.organization.groups.users.delete` | DOCUMENTED | ### Organization invites | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/invites` | List invites | `limit`, `after` | `client.admin.organization.invites.list` | ACCOUNT_RESTRICTED (403/401 with project key) | | `POST` | `/v1/organization/invites` | Create invite | `email`*, `role`* (reader|owner), `projects` | `client.admin.organization.invites.create` | DOCUMENTED | | `GET` | `/v1/organization/invites/{invite_id}` | Retrieve invite | — | `client.admin.organization.invites.retrieve` | DOCUMENTED | | `DELETE` | `/v1/organization/invites/{invite_id}` | Delete invite | — | `client.admin.organization.invites.delete` | DOCUMENTED | ### Organization projects | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/projects` | List projects | `limit`, `after`, `include_archived` | `client.admin.organization.projects.list` | ACCOUNT_RESTRICTED (403/401 with project key) | | `POST` | `/v1/organization/projects` | Create project | `name`*, `geography`, `residency`, `external_key_id` | `client.admin.organization.projects.create` | DOCUMENTED | | `GET` | `/v1/organization/projects/{project_id}` | Retrieve project | — | `client.admin.organization.projects.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}` | Modify project | `name`, `external_key_id`, `geography` | `client.admin.organization.projects.update` | DOCUMENTED | ### Project api keys | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/projects/{project_id}/api_keys` | List project API keys | `limit`, `after`, `owner_project_access` (active|inactive|any) | `client.admin.organization.projects.api_keys.list` | DOCUMENTED | | `GET` | `/v1/organization/projects/{project_id}/api_keys/{api_key_id}` | Retrieve project API key | — | `client.admin.organization.projects.api_keys.retrieve` | DOCUMENTED | | `DELETE` | `/v1/organization/projects/{project_id}/api_keys/{api_key_id}` | Delete project API key | — | `client.admin.organization.projects.api_keys.delete` | DOCUMENTED | ### Project archive | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `POST` | `/v1/organization/projects/{project_id}/archive` | Archive project | — | `client.admin.organization.projects.archive` | DOCUMENTED | ### Project data retention | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/projects/{project_id}/data_retention` | Retrieve project data retention | — | `client.admin.organization.projects.data_retention.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/data_retention` | Update project data retention | `retention_type`* (organization_default|none|zero_data_retention|modified_abuse_monitoring|enhanced_zero_data_retention|enhanced_modified_abuse_monitoring) | `client.admin.organization.projects.data_retention.update` | DOCUMENTED | ### Project groups | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/projects/{project_id}/groups` | List project groups | `limit`, `after`, `order` (asc|desc) | `client.admin.organization.projects.groups.list` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/groups` | Add project group | `group_id`*, `role`* | `client.admin.organization.projects.groups.create` | DOCUMENTED | | `GET` | `/v1/organization/projects/{project_id}/groups/{group_id}` | Retrieve project group | `group_type` (group|tenant_group) | `client.admin.organization.projects.groups.retrieve` | DOCUMENTED | | `DELETE` | `/v1/organization/projects/{project_id}/groups/{group_id}` | Remove project group | — | `client.admin.organization.projects.groups.delete` | DOCUMENTED | ### Project hosted tool permissions | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/projects/{project_id}/hosted_tool_permissions` | Retrieve project hosted tool permissions | — | `client.admin.organization.projects.hosted_tool_permissions.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/hosted_tool_permissions` | Modify project hosted tool permissions | `file_search`, `web_search`, `image_generation`, `mcp`, `code_interpreter` | `client.admin.organization.projects.hosted_tool_permissions.update` | DOCUMENTED | ### Project model permissions | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/projects/{project_id}/model_permissions` | Retrieve project model permissions | — | `client.admin.organization.projects.model_permissions.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/model_permissions` | Modify project model permissions | `mode`* (allow_list|deny_list), `model_ids`* | `client.admin.organization.projects.model_permissions.update` | DOCUMENTED | | `DELETE` | `/v1/organization/projects/{project_id}/model_permissions` | Delete project model permissions | — | `client.admin.organization.projects.model_permissions.delete` | DOCUMENTED | ### Project rate limits | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/projects/{project_id}/rate_limits` | List project rate limits | `limit`, `after`, `before` | `client.admin.organization.projects.rate_limits.list_rate_limits` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/rate_limits/{rate_limit_id}` | Modify project rate limit | `max_requests_per_1_minute`, `max_tokens_per_1_minute`, `max_images_per_1_minute`, `max_audio_megabytes_per_1_minute`, `max_requests_per_1_day`, `batch_1_day_max_input_tokens` | `client.admin.organization.projects.rate_limits.update_rate_limit` | DOCUMENTED | ### Project service accounts | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/projects/{project_id}/service_accounts` | List project service accounts | `limit`, `after` | `client.admin.organization.projects.service_accounts.list` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/service_accounts` | Create project service account | `name`*, `create_service_account_only`, `expires_in_seconds` | `client.admin.organization.projects.service_accounts.create` | DOCUMENTED | | `GET` | `/v1/organization/projects/{project_id}/service_accounts/{service_account_id}` | Retrieve project service account | — | `client.admin.organization.projects.service_accounts.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/service_accounts/{service_account_id}` | Update project service account | `name`, `role` (member|owner) | `client.admin.organization.projects.service_accounts.update` | DOCUMENTED | | `DELETE` | `/v1/organization/projects/{project_id}/service_accounts/{service_account_id}` | Delete project service account | — | `client.admin.organization.projects.service_accounts.delete` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/service_accounts/{service_account_id}/api_keys` | Create an API key for a service account | `name`, `scopes`, `expires_in_seconds` | `client.admin.organization.projects.service_accounts.api_keys.create` | DOCUMENTED | ### Project spend alerts | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/projects/{project_id}/spend_alerts` | List project spend alerts | `limit`, `order` (asc|desc), `after`, `before` | `client.admin.organization.projects.spend_alerts.list` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/spend_alerts` | Create project spend alert | `threshold_amount`*, `currency`* (USD), `interval`* (month), `notification_channel`* | `client.admin.organization.projects.spend_alerts.create` | DOCUMENTED | | `GET` | `/v1/organization/projects/{project_id}/spend_alerts/{alert_id}` | Retrieve project spend alert | — | `client.admin.organization.projects.spend_alerts.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/spend_alerts/{alert_id}` | Update project spend alert | `threshold_amount`*, `currency`* (USD), `interval`* (month), `notification_channel`* | `client.admin.organization.projects.spend_alerts.update` | DOCUMENTED | | `DELETE` | `/v1/organization/projects/{project_id}/spend_alerts/{alert_id}` | Delete project spend alert | — | `client.admin.organization.projects.spend_alerts.delete` | DOCUMENTED | ### Project users | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/projects/{project_id}/users` | List project users | `limit`, `after` | `client.admin.organization.projects.users.list` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/users` | Create project user | `user_id`, `email`, `role`* | `client.admin.organization.projects.users.create` | DOCUMENTED | | `GET` | `/v1/organization/projects/{project_id}/users/{user_id}` | Retrieve project user | — | `client.admin.organization.projects.users.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/users/{user_id}` | Modify project user | `role` | `client.admin.organization.projects.users.update` | DOCUMENTED | | `DELETE` | `/v1/organization/projects/{project_id}/users/{user_id}` | Delete project user | — | `client.admin.organization.projects.users.delete` | DOCUMENTED | ### Organization roles | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/roles` | List organization roles | `limit`, `after`, `order` (asc|desc) | `client.admin.organization.roles.list` | ACCOUNT_RESTRICTED (403/401 with project key) | | `POST` | `/v1/organization/roles` | Create organization role | `role_name`*, `permissions`*, `description` | `client.admin.organization.roles.create` | DOCUMENTED | | `GET` | `/v1/organization/roles/{role_id}` | Retrieve organization role | — | `client.admin.organization.roles.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/roles/{role_id}` | Update organization role | `permissions`, `description`, `role_name` | `client.admin.organization.roles.update` | DOCUMENTED | | `DELETE` | `/v1/organization/roles/{role_id}` | Delete organization role | — | `client.admin.organization.roles.delete` | DOCUMENTED | ### Organization spend alerts | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/spend_alerts` | List organization spend alerts | `limit`, `order` (asc|desc), `after`, `before` | `client.admin.organization.spend_alerts.list` | ACCOUNT_RESTRICTED (403/401 with project key) | | `POST` | `/v1/organization/spend_alerts` | Create organization spend alert | `threshold_amount`*, `currency`* (USD), `interval`* (month), `notification_channel`* | `client.admin.organization.spend_alerts.create` | DOCUMENTED | | `GET` | `/v1/organization/spend_alerts/{alert_id}` | Retrieve organization spend alert | — | `client.admin.organization.spend_alerts.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/spend_alerts/{alert_id}` | Update organization spend alert | `threshold_amount`*, `currency`* (USD), `interval`* (month), `notification_channel`* | `client.admin.organization.spend_alerts.update` | DOCUMENTED | | `DELETE` | `/v1/organization/spend_alerts/{alert_id}` | Delete organization spend alert | — | `client.admin.organization.spend_alerts.delete` | DOCUMENTED | ### Organization users | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/users` | List users | `limit`, `after`, `emails` | `client.admin.organization.users.list` | ACCOUNT_RESTRICTED (403/401 with project key) | | `GET` | `/v1/organization/users/{user_id}` | Retrieve user | — | `client.admin.organization.users.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/users/{user_id}` | Modify user | `role`, `role_id`, `technical_level`, `developer_persona` | `client.admin.organization.users.update` | DOCUMENTED | | `DELETE` | `/v1/organization/users/{user_id}` | Delete user | — | `client.admin.organization.users.delete` | DOCUMENTED | ### Project roles & role bindings | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/projects/{project_id}/groups/{group_id}/roles` | List project group role assignments | `limit`, `after`, `order` (asc|desc) | `client.admin.organization.projects.groups.roles.list` | DOCUMENTED | | `POST` | `/v1/projects/{project_id}/groups/{group_id}/roles` | Assign project role to group | `role_id`* | `client.admin.organization.projects.groups.roles.create` | DOCUMENTED | | `GET` | `/v1/projects/{project_id}/groups/{group_id}/roles/{role_id}` | Retrieve project group role | — | `client.admin.organization.projects.groups.roles.retrieve` | DOCUMENTED | | `DELETE` | `/v1/projects/{project_id}/groups/{group_id}/roles/{role_id}` | Unassign project role from group | — | `client.admin.organization.projects.groups.roles.delete` | DOCUMENTED | | `GET` | `/v1/projects/{project_id}/roles` | List project roles | `limit`, `after`, `order` (asc|desc) | `client.admin.organization.projects.roles.list` | DOCUMENTED | | `POST` | `/v1/projects/{project_id}/roles` | Create project role | `role_name`*, `permissions`*, `description` | `client.admin.organization.projects.roles.create` | DOCUMENTED | | `GET` | `/v1/projects/{project_id}/roles/{role_id}` | Retrieve project role | — | `client.admin.organization.projects.roles.retrieve` | DOCUMENTED | | `POST` | `/v1/projects/{project_id}/roles/{role_id}` | Update project role | `permissions`, `description`, `role_name` | `client.admin.organization.projects.roles.update` | DOCUMENTED | | `DELETE` | `/v1/projects/{project_id}/roles/{role_id}` | Delete project role | — | `client.admin.organization.projects.roles.delete` | DOCUMENTED | | `GET` | `/v1/projects/{project_id}/users/{user_id}/roles` | List project user role assignments | `limit`, `after`, `order` (asc|desc) | `client.admin.organization.projects.users.roles.list` | DOCUMENTED | | `POST` | `/v1/projects/{project_id}/users/{user_id}/roles` | Assign project role to user | `role_id`* | `client.admin.organization.projects.users.roles.create` | DOCUMENTED | | `GET` | `/v1/projects/{project_id}/users/{user_id}/roles/{role_id}` | Retrieve project user role | — | `client.admin.organization.projects.users.roles.retrieve` | DOCUMENTED | | `DELETE` | `/v1/projects/{project_id}/users/{user_id}/roles/{role_id}` | Unassign project role from user | — | `client.admin.organization.projects.users.roles.delete` | DOCUMENTED | ### Organization external storage | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/external_storage` | List external storage configurations | `project_id`, `after`, `order` (asc|desc), `limit` | `—` | ACCOUNT_RESTRICTED (403/401 with project key) | | `POST` | `/v1/organization/external_storage` | Create an external storage configuration | `project_id`*, `provider`* | `—` | DOCUMENTED | | `GET` | `/v1/organization/external_storage/{external_storage_id}` | Get an external storage configuration | — | `—` | DOCUMENTED | | `DELETE` | `/v1/organization/external_storage/{external_storage_id}` | Delete an external storage configuration | — | `—` | DOCUMENTED | | `POST` | `/v1/organization/external_storage/{external_storage_id}/validate` | Validate an external storage configuration | — | `—` | DOCUMENTED | ### Organization spend limit | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/spend_limit` | Get organization spend limit | — | `client.admin.organization.spend_limit.retrieve` | ACCOUNT_RESTRICTED (403/401 with project key) | | `POST` | `/v1/organization/spend_limit` | Update organization spend limit | `threshold_amount`*, `currency`* (USD), `interval`* (month) | `client.admin.organization.spend_limit.update` | DOCUMENTED | | `DELETE` | `/v1/organization/spend_limit` | Delete organization spend limit | — | `client.admin.organization.spend_limit.delete` | DOCUMENTED | ### Project spend limit | Method | Path | Name | Key params (query/body) | Python SDK | Status | |---|---|---|---|---|---| | `GET` | `/v1/organization/projects/{project_id}/spend_limit` | Get project spend limit | — | `client.admin.organization.projects.spend_limit.retrieve` | DOCUMENTED | | `POST` | `/v1/organization/projects/{project_id}/spend_limit` | Update project spend limit | `threshold_amount`*, `currency`* (USD), `interval`* (month) | `client.admin.organization.projects.spend_limit.update` | DOCUMENTED | | `DELETE` | `/v1/organization/projects/{project_id}/spend_limit` | Delete project spend limit | — | `client.admin.organization.projects.spend_limit.delete` | DOCUMENTED | ## 3. Roles & scopes catalogue | Level | Predefined roles (legacy strings) | Where they appear | |---|---|---| | Organization | `owner` (billing, members, all reader rights), `reader` (make API requests, view org info, manage resources) | `Invite.role`, `OrganizationUser.role`, `POST /organization/users/{id}` body `role` | | Project | `owner`, `member` | `ProjectUser.role`, `Invite.projects[].role`, `POST /organization/projects/{id}/users` | | Custom roles | `Role{id, name, description, permissions[], resource_type, predefined_role}` created with `POST /organization/roles` (org-wide) or `POST /projects/{id}/roles`; assigned with the `…/roles` binding endpoints (`role_id`) | Roles API | | API key permission levels (dashboard) | *All*, *Restricted* (per-resource read/write), *Read-only*; restricted keys carry scopes like `api.model.request`; audit log `api_key.created.data.scopes[]` records them | dashboard + audit logs | | Key types | project key `sk-proj-…` (owned by a user or a **service account**, `owner.type: user|service_account`, `owner_project_access: active|inactive`), legacy user key, service-account key (`POST /organization/projects/{p}/service_accounts` returns `api_key.value` once, or `POST …/service_accounts/{id}/api_keys` to mint another with optional `expires_at`), Admin key `sk-admin-…`, WIF short-lived bearer | see `docs/openai/authentication-and-keys.md` | Governance knobs documented in production-best-practices: max API-key lifetime (org and project, project ≤ org), *API Key Governance* (allow only service-account keys / only user keys / block new keys; org restrictions win), key usage tracking (keys created before 2023-12-20 are untracked by default). ## 4. Usage & costs query cookbook (`ACCOUNT_RESTRICTED` for us — verified 403 `api.usage.read`) All usage endpoints share the same query grammar (from the OpenAPI spec): | Param | Type | Notes | |---|---|---| | `start_time`* | int (unix s) | inclusive | | `end_time` | int | exclusive | | `bucket_width` | `1m` / `1h` / `1d` (default `1d`) | costs: only `1d` | | `limit` | int | buckets per page: `1d` ≤ 31 (default 7), `1h` ≤ 168 (default 24), `1m` ≤ 1440 (default 60) | | `page` | string | cursor from `next_page` | | `project_ids`, `user_ids`, `api_key_ids`, `models` | string[] | filters (repeat the query key) | | `batch` | bool | completions/embeddings/…: true = Batch API traffic only | | `group_by` | string[] | completions: `project_id, user_id, api_key_id, model, batch, service_tier`; costs: `project_id, line_item`; images add `size, source`; audio speeches/transcriptions add `model`; code_interpreter_sessions/vector_stores: `project_id` | | Endpoint | Result object | Key metrics | |---|---|---| | `GET /v1/organization/usage/completions` | `organization.usage.completions.result` | `input_tokens, input_cached_tokens, input_cache_write_tokens, input_uncached_tokens, output_tokens, input/output_text|audio|image_tokens, num_model_requests`, dims `project_id, user_id, api_key_id, model, batch, service_tier` | | `…/usage/embeddings` | `organization.usage.embeddings.result` | `input_tokens, num_model_requests` | | `…/usage/moderations` | `organization.usage.moderations.result` | `input_tokens, num_model_requests` | | `…/usage/images` | `organization.usage.images.result` | `images, num_model_requests`, dims `size` (`256x256`…`1536x1024`), `source` (`image.generation, image.edit, image.variation`) | | `…/usage/audio_speeches` | `organization.usage.audio_speeches.result` | `characters, num_model_requests` | | `…/usage/audio_transcriptions` | `organization.usage.audio_transcriptions.result` | `seconds, num_model_requests` | | `…/usage/vector_stores` | `organization.usage.vector_stores.result` | `usage_bytes` | | `…/usage/code_interpreter_sessions` | `organization.usage.code_interpreter_sessions.result` | `num_sessions` | | `…/usage/file_search_calls`, `…/usage/web_search_calls` | `organization.usage.*_calls.result` | `num_calls` (web search adds `search_context_size`) | | `GET /v1/organization/costs` | `organization.costs.result` | `amount{value, currency:"usd"}, line_item, project_id, organization_id` | Recipes (curl; `-G` turns `--data-urlencode` into query params): ```bash # daily completions tokens per model for the last 7 days curl -G https://api.openai.com/v1/organization/usage/completions -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \ --data-urlencode "start_time=$(( $(date +%s) - 7*86400 ))" --data-urlencode bucket_width=1d \ --data-urlencode group_by=model --data-urlencode group_by=service_tier --data-urlencode limit=7 # hourly usage of one project, Batch only, paginated curl -G https://api.openai.com/v1/organization/usage/completions -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \ --data-urlencode start_time=1789000000 --data-urlencode bucket_width=1h --data-urlencode project_ids=proj_XXX \ --data-urlencode batch=true --data-urlencode limit=168 --data-urlencode page=page_AAAA… # spend in USD per day per line item and project curl -G https://api.openai.com/v1/organization/costs -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \ --data-urlencode start_time=$(( $(date +%s) - 30*86400 )) --data-urlencode bucket_width=1d \ --data-urlencode group_by=line_item --data-urlencode group_by=project_id --data-urlencode limit=31 ``` Python: `client.admin.organization.usage.completions(start_time=…, bucket_width="1d", group_by=["model"], limit=7)`; costs: `client.admin.organization.usage.costs(...)`. Runnable examples: `examples/openai/admin/usage_costs.sh`, `admin_readonly.py`, `admin_readonly.ts` (all return 403 on our account). ## 5. Spend limits, alerts, data retention, permissions | Resource | Endpoints | Body / semantics | |---|---|---| | Org spend limit | `GET/POST/DELETE /v1/organization/spend_limit` | `threshold_amount` (cents), `currency: USD`, `interval: month`; hitting it → `429 organization_spend_limit_exceeded` on API traffic | | Project spend limit | `GET/POST/DELETE /v1/organization/projects/{p}/spend_limit` | same body; → `429 project_spend_limit_exceeded` | | Spend alerts | `/v1/organization/spend_alerts[/{id}]`, `/v1/organization/projects/{p}/spend_alerts[/{id}]` | `threshold_amount`, `currency`, `interval`, `notification_channel{type:"email", recipients[], subject_prefix}` | | Data retention | `GET/POST /v1/organization/data_retention`, `GET/POST /v1/organization/projects/{p}/data_retention` | project `retention_type: organization_default` inherits; org sets the policy (ZDR / modified retention need approval) | | Model permissions | `GET/POST/DELETE /v1/organization/projects/{p}/model_permissions` | `mode: allow_list | deny_list`, `model_ids[]` (must be visible to the org, fine-tuned snapshots allowed); denied models return `404 model_not_found` | | Hosted tool permissions | `GET/POST /v1/organization/projects/{p}/hosted_tool_permissions` | allow/deny hosted tools (web search, code interpreter, file search, MCP, …) per project | | Rate limits | `GET /v1/organization/projects/{p}/rate_limits`, `POST …/rate_limits/{rate_limit_id}` | `project.rate_limit{model, max_requests_per_1_minute, max_tokens_per_1_minute, max_images_per_1_minute, max_audio_megabytes_per_1_minute, max_requests_per_1_day, batch_1_day_max_input_tokens}`; a project limit can only lower the org limit | | Certificates (mTLS) | org: `GET/POST /v1/organization/certificates`, `…/{id}` GET/POST/DELETE, `…/activate`, `…/deactivate`; project: `GET …/projects/{p}/certificates`, `…/activate`, `…/deactivate` | upload PEM trust anchor (`content`, `name`), activate = enforce; API mTLS host `mtls.api.openai.com`; scopes `api.mtls.read/write` | | External storage | `GET/POST /v1/organization/external_storage`, `GET/DELETE …/{id}`, `POST …/{id}/validate` | bring-your-own bucket for stored artifacts; audit events `external_storage.registered/removed` | | Archive project | `POST /v1/organization/projects/{p}/archive` | irreversible; `status: archived` | ## 6. Audit logs `GET /v1/organization/audit_logs` (scope `api.audit_logs.read`; **HTTP 401** not 403 without it). Filters: `effective_at[gt|gte|lt|lte]`, `project_ids[]`, `event_types[]`, `actor_ids[]`, `actor_emails[]`, `resource_ids[]`, `limit`, `after`, `before`. Each entry: `{object:"organization.audit_log", id, type, effective_at, project{id,name}, actor{type: session|api_key, session{user, ip_address, user_agent, ja3, ja4, ip_address_details}, api_key{type: user|service_account, id, user|service_account}}, : {payload}}`. | Category | Event types (149 total) | |---|---| | api_key | `api_key.created`, `api_key.updated`, `api_key.deleted` | | certificate | `certificate.created`, `certificate.updated`, `certificate.deleted` | | certificates | `certificates.activated`, `certificates.deactivated` | | checkpoint | `checkpoint.permission.created`, `checkpoint.permission.deleted` | | external_key | `external_key.registered`, `external_key.removed` | | external_storage | `external_storage.registered`, `external_storage.removed` | | group | `group.created`, `group.updated`, `group.deleted` | | invite | `invite.sent`, `invite.accepted`, `invite.deleted` | | ip_allowlist | `ip_allowlist.created`, `ip_allowlist.updated`, `ip_allowlist.deleted`, `ip_allowlist.config.activated`, `ip_allowlist.config.deactivated` | | login | `login.succeeded`, `login.failed` | | logout | `logout.succeeded`, `logout.failed` | | organization | `organization.updated` | | project | `project.created`, `project.updated`, `project.archived`, `project.deleted` | | rate_limit | `rate_limit.updated`, `rate_limit.deleted` | | resource | `resource.deleted` | | tunnel | `tunnel.created`, `tunnel.updated`, `tunnel.deleted` | | workload_identity_provider | `workload_identity_provider.created`, `workload_identity_provider.updated`, `workload_identity_provider.deleted` | | workload_identity_provider_mapping | `workload_identity_provider_mapping.created`, `workload_identity_provider_mapping.updated`, `workload_identity_provider_mapping.deleted` | | role | `role.created`, `role.updated`, `role.deleted`, `role.assignment.created`, `role.assignment.deleted`, `role.bound_to_resource`, `role.unbound_from_resource` | | scim | `scim.enabled`, `scim.disabled` | | service_account | `service_account.created`, `service_account.updated`, `service_account.deleted` | | user | `user.added`, `user.updated`, `user.deleted` | | tenant (enterprise workspace / ChatGPT-side) | `tenant.metadata.updated`, `tenant.microsoft_entra_mapping.upserted`, `tenant.microsoft_entra_mapping.deleted`, `tenant.workload_identity.provider.created`, `tenant.workload_identity.provider.updated`, `tenant.workload_identity.provider.archived`, `tenant.workload_identity.mapping.created`, `tenant.workload_identity.mapping.updated`, `tenant.workload_identity.mapping.archived`, `tenant.workload_identity.binding.created`, `tenant.workload_identity.principal.provisioned`, `tenant.workload_identity.access_token.issued`, `tenant.admin_api_key.created`, `tenant.admin_api_key.updated`, `tenant.admin_api_key.deleted`, `tenant.project_api_key.created`, `tenant.trusted_access.business_verification.started`, `tenant.trusted_access.application.submitted`, `tenant.chatgpt_access_token.revoked`, `tenant.migration.completed`, `tenant.sso.migrated`, `tenant.domains.migrated`, `tenant.sso_connection.created`, `tenant.sso_connection.updated`, `tenant.sso_connection.deleted`, `tenant.sso_connection.setup.started`, `tenant.policy.created`, `tenant.policy.updated`, `tenant.policy.deleted`, `tenant.policy.attached`, `tenant.policy.detached`, `tenant.principal_authentication_policy.resolved`, `tenant.scim.setup.started`, `tenant.scim.deletion.requested`, `tenant.scim.directory.created`, `tenant.product_access_policy.updated`, `tenant.resource_share_grant.created`, `tenant.resource_share_grant.updated`, `tenant.resource_share_grant.accepted`, `tenant.resource_share_grant.declined`, `tenant.resource_share_grant.revoked`, `tenant.resource_share_grant.deleted`, `tenant.service_account.updated`, `tenant.service_account.deleted`, `tenant.service_account.token.revoked`, `tenant.billing.overage_limit.updated`, `tenant.billing.alerts.updated`, `tenant.billing.info.updated`, `tenant.usage_limit.workspace.updated`, `tenant.usage_limit.group.updated`, `tenant.usage_limit.user.updated`, `tenant.usage_limit.increase_request.updated`, `tenant.usage_limit.increase_request.resolved`, `tenant.group.created`, `tenant.group.updated`, `tenant.group.deleted`, `tenant.group.member.added`, `tenant.group.member.removed`, `tenant.migration_rollout.status.updated`, `tenant.migration_rollout.tier.updated`, `tenant.role.metadata.updated`, `tenant.custom_role.created`, `tenant.custom_role.updated`, `tenant.custom_role.deleted`, `tenant.role_assignment.created`, `tenant.role_assignment.deleted`, `tenant.resource_role_assignment.created`, `tenant.resource_role_assignment.deleted`, `tenant.resource_access.updated`, `tenant.resource_access.deleted`, `tenant.ads_account.onboarding.redemption`, `tenant.session_policy.created`, `tenant.session_policy.updated`, `tenant.session_policy.deleted`, `tenant.session_revocation.started`, `tenant.third_party_app_policy.updated`, `tenant.user.added`, `tenant.user.updated`, `tenant.user.removed`, `tenant.user.looked_up`, `tenant.user.invited`, `tenant.membership.revoked`, `tenant.api_organization_invite.upserted`, `tenant.api_organization_invite.deleted`, `tenant.chatgpt_workspace_invite.upserted`, `tenant.membership.accepted`, `tenant.membership.declined`, `tenant.workspace_invite_email_settings.updated` | Payload fields per event are in `generated/fragments/audit-log-events/openai-audit-log-events.json` (57 events carry a typed payload in the spec; the `tenant.*` enterprise-workspace events are enum-only). ## 7. Verified behaviour with a non-admin key (2026-09-18) | Call | HTTP | Body | |---|---|---| | `GET /v1/organization/{users,invites,projects,admin_api_keys,spend_limit,spend_alerts,data_retention}` | 403 | `{"error": "You have insufficient permissions for this operation. Missing scopes: api.management.read. Check that you have the correct role in your organization, and if you're using a restricted API key, that it has the necessary scopes."}` — note `error` is a **bare string** | | `GET /v1/organization/usage/completions`, `GET /v1/organization/costs` | 403 | same shape, `api.usage.read` | | `GET /v1/organization/roles` / `groups` | 403 | `api.roles.read` / `api.groups.read` | | `GET /v1/organization/certificates` | 403 | `api.mtls.read … correct role in your organization (Owner)` | | `GET /v1/organization/external_storage` | 403 | structured: `{"error":{"message":"… api.external_storage.read …","type":"invalid_request_error","param":null,"code":"insufficient_permissions"}}` | | `GET /v1/organization/audit_logs` | **401** | structured, `api.audit_logs.read`, `code: null` | Response headers on these errors still include `x-request-id`, `openai-version: 2020-10-01`, `openai-processing-ms`, and (except certificates/audit logs) `openai-organization` / `openai-project`. ## 8. SDK access ```python from openai import OpenAI client = OpenAI(admin_api_key=os.environ["OPENAI_ADMIN_KEY"]) # Python >= 2.34.0 users = client.admin.organization.users.list(limit=20) # SyncConversationCursorPage -> iterate for u in users: print(u.id, u.email, u.role) client.admin.organization.projects.model_permissions.update("proj_abc", mode="allow_list", model_ids=["gpt-4.1"]) ``` ```ts const client = new OpenAI({ adminAPIKey: process.env.OPENAI_ADMIN_KEY }); // Node >= 6.36.0 for await (const log of client.admin.organization.auditLogs.list({ limit: 50 })) console.log(log.type); ``` Go `option.WithAdminAPIKey` (≥ 3.34.0), Java `OpenAIOkHttpClient.builder().adminApiKey(...)` (≥ 4.34.0), Ruby `OpenAI::Client.new(admin_api_key:)` (≥ 0.61.0).