OpenAI
The OpenAI integration monitors your OpenAI platform organization — members and their organization roles, projects and project membership, service accounts, groups, custom roles, project and admin API keys, mTLS certificates and pending invitations — plus each project's declared model and hosted-tool policy. When an Enterprise plan exposes them it also reads audit logs (for last-activity enrichment and the OpenAI event stream) and cost reporting, and when a ChatGPT Enterprise Compliance API key is supplied it reads the ChatGPT workspace and its members.
It can also capture AI invocations from OpenAI Codex (the CLI and the ChatGPT desktop app) — each model response, the files it carried and the tools it used — and block prompts and tool calls that read files you have tagged sensitive or that violate your SlashID policies. That part runs on your developers' machines as Codex managed hooks; see step 5.
Before starting
- You need the owner role in your OpenAI organization to provision an Admin API key with full read access.
- Optional: a ChatGPT Enterprise plan and a Compliance API key if you want ChatGPT workspace visibility.
- Audit logs, cost reporting, groups, custom roles and mTLS certificates depend on your plan. Each is read on a best-effort basis and skipped when unavailable — the rest of the sync is unaffected.
1. Create an Admin API key
- In the OpenAI platform, go to Settings →
Organization → Admin keys and create an Admin API key (it starts with
sk-admin-...). Only organization owners can provision one. - Copy the key — it is shown once.
SlashID only reads with this key. It never retrieves a usable API key secret:
OpenAI returns a key's secret exactly once, at creation, and the sync reads only
the redacted form (sk-...abcd) that the listing endpoints return.
2. Find your organization ID
Your organization ID starts with org-. Copy it from
Settings → Organization → General
in the OpenAI platform. It is also echoed on every Admin API response:
curl https://api.openai.com/v1/organization/projects?limit=1 \
--header "Authorization: Bearer $OPENAI_ADMIN_KEY"
If the key does not belong to the organization you entered, the connection's
admin_key_valid preflight check fails with a message naming the organization.
3. (Optional) Create a Compliance API key — ChatGPT Enterprise only
If your organization uses ChatGPT Enterprise, create a Compliance API key in
the ChatGPT admin settings with the workspace read scopes. This unlocks the
ChatGPT workspace and its members as a separate identity type
(openai_workspace_user), which is distinct from the platform organization's
members.
Without it, SlashID syncs the platform organization only. The compliance endpoints are never dialled with the Admin API key.
4. Create your OpenAI ↔ SlashID integration
Go to the SlashID Console integrations page and create an OpenAI connection:
| SlashID Console field | Description | Example |
|---|---|---|
| Name of the connection | Arbitrary name for this connection | OpenAI Production |
| Organization ID | Your OpenAI organization ID (step 2) | org-a1B2c3D4e5F6 |
| Admin API key | The key you created in step 1 | sk-admin-... |
| Compliance API key | Optional — the ChatGPT Enterprise key from step 3 | sk-... |
| Authoritative status | Whether OpenAI identities are the primary source of truth when reconciling across sources | Primary or Secondary |
5. (Optional) Capture Codex AI invocations
The connection above reads your organization's inventory. Capturing what people actually do in Codex takes slashid-codex, a small program you install on each developer machine with your device-management (MDM) tooling. It registers as a Codex managed hook, so Codex runs it before every prompt and tool call and after every response. It does two things:
| What | How it works | What it adds |
|---|---|---|
| Preflight | Before each prompt and each tool call, the hook asks SlashID for a verdict, and Codex blocks the prompt or call if SlashID denies it. | Blocking prompts and tool calls that read files tagged sensitive, or that violate a policy (model and time-window rules). |
| Invocation events | After each response, the hook reads Codex's local session log and pushes one invocation per model response to SlashID. | Every response: the model, token usage, the tools requested and used, and the files read, with conversation linking. |
The hook itself is a thin client. It talks to a small background process on the
user's machine, reachable only on 127.0.0.1, which starts with the first hook
and exits after ten minutes without activity. Responses written while it was not
running (a machine that was off, or a crash) are sent the next time it starts,
up to seven days back. The connection's Event streaming token is read from a
file on the device; it is never passed in hook arguments.
What leaves the machine depends on the hook and on the configuration:
- Preflight, always: the full paths and hashes of the files a prompt attaches or a tool call is about to read, the model, tool and MCP server names, and the conversation and turn IDs. Never prompt text, tool arguments, file contents or the working directory, and no setting changes that.
- Invocation events, by default: the same metadata plus token counts. For the round's input and output they carry only hashes, content types and sizes, not text.
- Invocation events with
include_raw_content = true: also the text of the round (the prompts, the model's answers and the tool outputs), and the contents of files that tool calls returned (for example the output ofcat), each cut tomax_content_sizecharacters (default 100000). Turn this on only if your policy allows that data to leave the device.
Files larger than max_file_bytes are sent without a hash in either case.
Before you begin
Codex on the machines. The Codex CLI or the ChatGPT desktop app, a recent release that supports managed hooks (tested with Codex CLI 0.158 and the ChatGPT desktop app 26.924).
Administrator access to install files as root or Administrator through your MDM tooling, and uv on the machines (the MDM job can install it).
The package from SlashID support.
slashid-codexis not public yet; support gives you the wheel (slashid_codex-<version>-py3-none-any.whl).The connection's Event streaming token, from its details page in the SlashID Console. It is how the hook authenticates to SlashID.
Each user's ChatGPT user ID (
user-...) — the same ID SlashID syncs for that member in step 4. OpenAI returns it as theidin the Admin API's list of organization users; find the member byemail:curl https://api.openai.com/v1/organization/users \
--header "Authorization: Bearer $OPENAI_ADMIN_KEY"
The installation below has been exercised on Linux. macOS and Windows use the same layout, with the Windows locations shown where they differ, but validate the install on a pilot machine on each platform first.
Deploy to a device
Run these steps as an administrator, through your MDM tooling. Everything is installed read-only for users, so a user cannot change what the hook does.
Install the package. Put the tool in a directory users cannot write to (on Windows,
C:\ProgramData\SlashID\Codex\toolsand…\bin):UV_TOOL_DIR=/opt/slashid/codex/tools UV_TOOL_BIN_DIR=/opt/slashid/codex/bin \
uv tool install ./slashid_codex-<version>-py3-none-any.whlWrite the token file. Save the Event streaming token to
/opt/slashid/codex/token(on Windows,C:\ProgramData\SlashID\Codex\token), readable but not writable by users. It must hold at least 32 characters.Write the configuration to
/opt/slashid/codex/config.toml(on Windows,C:\ProgramData\SlashID\Codex\config.toml), also read-only to users. Setuser_idto the ChatGPT user ID of the person who uses the machine — one configuration per device:push_token_file = "/opt/slashid/codex/token"
user_id = "user-XXXXXXXXXXXXXXXXXXXXXXXX"
verdict_fail_mode = "deny"Install the managed requirements as Codex's system requirements file,
/etc/codex/requirements.tomlon Linux and macOS, or deliver the same content through the managed-configuration channel your Codex deployment uses (an MDM profile or the Codex admin console). Each hook runsslashid-codex hookfor one event:allow_managed_hooks_only = true
[features]
hooks = true
[hooks]
managed_dir = "/opt/slashid/codex"
windows_managed_dir = 'C:\ProgramData\SlashID\Codex'
[[hooks.UserPromptSubmit]]
[[hooks.UserPromptSubmit.hooks]]
type = "command"
command = "/opt/slashid/codex/bin/slashid-codex hook --config /opt/slashid/codex/config.toml --event UserPromptSubmit"
command_windows = 'C:\ProgramData\SlashID\Codex\bin\slashid-codex.exe hook --config C:\ProgramData\SlashID\Codex\config.toml --event UserPromptSubmit'
timeout = 10
[[hooks.PreToolUse]]
matcher = ".*"
[[hooks.PreToolUse.hooks]]
type = "command"
command = "/opt/slashid/codex/bin/slashid-codex hook --config /opt/slashid/codex/config.toml --event PreToolUse"
command_windows = 'C:\ProgramData\SlashID\Codex\bin\slashid-codex.exe hook --config C:\ProgramData\SlashID\Codex\config.toml --event PreToolUse'
timeout = 10
[[hooks.Stop]]
[[hooks.Stop.hooks]]
type = "command"
command = "/opt/slashid/codex/bin/slashid-codex hook --config /opt/slashid/codex/config.toml --event Stop"
command_windows = 'C:\ProgramData\SlashID\Codex\bin\slashid-codex.exe hook --config C:\ProgramData\SlashID\Codex\config.toml --event Stop'
timeout = 5
[[hooks.SessionStart]]
[[hooks.SessionStart.hooks]]
type = "command"
command = "/opt/slashid/codex/bin/slashid-codex hook --config /opt/slashid/codex/config.toml --event SessionStart"
command_windows = 'C:\ProgramData\SlashID\Codex\bin\slashid-codex.exe hook --config C:\ProgramData\SlashID\Codex\config.toml --event SessionStart'
timeout = 5
[[hooks.SessionEnd]]
[[hooks.SessionEnd.hooks]]
type = "command"
command = "/opt/slashid/codex/bin/slashid-codex hook --config /opt/slashid/codex/config.toml --event SessionEnd"
command_windows = 'C:\ProgramData\SlashID\Codex\bin\slashid-codex.exe hook --config C:\ProgramData\SlashID\Codex\config.toml --event SessionEnd'
timeout = 3allow_managed_hooks_onlymust be a top-level key, as above; Codex ignores it under[hooks]. With it set, Codex does not load users' own~/.codex/hooks.json, and managed hooks run without asking the user for approval. Leave the line out if users should keep their own hooks.Open a new Codex session on the machine and send a prompt. The first hook starts the background process; nothing else needs to be started.
Configuration
Only push_token_file and user_id are required.
| Field | Default | Description |
|---|---|---|
endpoint | https://api.slashid.com | The SlashID API origin, https:// with no path. Set it only for another environment. |
push_token_file | Path to the file holding the connection's Event streaming token. A relative path resolves against the configuration file's directory. | |
user_id | The ChatGPT user ID (user-...) that prompts and events are attributed to. | |
verdict_fail_mode | deny | What happens when no verdict can be obtained (SlashID or the local process unavailable): deny blocks the prompt or tool call, allow lets it through. |
preflight_timeout_seconds | 4.0 | How long a verdict may take, counted from when the hook starts, capped at 8 seconds. |
max_file_bytes | 52428800 | Files larger than this are sent to preflight without a hash. |
include_raw_content | false | Send the round's text and the contents of files tool calls returned, in events. Off, only hashes are sent. |
daemon_idle_seconds | 600 | The background process exits after this long without hooks or events. |
codex_home | ~/.codex | Where Codex keeps its session logs. CODEX_HOME is not read, so set this if Codex uses another location. |
codex_bin | on PATH | The codex executable, used to list MCP servers. Defaults to codex on PATH, then the desktop app's bundled copy. |
Environment variables are never read, so a user cannot redirect events or swap the token by changing their environment. A change to the configuration or token file takes effect within about five seconds: the background process notices, exits, and the next hook starts a new one.
Roll out enforcement
The hooks ask SlashID about every prompt and tool call, but SlashID blocks only what you have configured. With no AI policy on the connection and no files tagged sensitive, every verdict is allow and only invocation events flow, so a fresh deployment observes without blocking.
To enforce:
- Save an AI policy for the connection in SlashID, and tag the files that are sensitive. Each prompt and tool call is then denied if it reads a tagged file or violates a policy.
- Choose
verdict_fail_mode:deny(the default) blocks when no verdict can be obtained,allowlets the prompt or call through. Start withallowon a pilot group, and switch todenyonce you are confident the deployment is healthy.
A time-window rule on invoke_model also blocks tool calls of a turn that is
already running when the window closes.
Read checks cover a file the user attaches, a simple shell read of one file
(cat, sed, head, tail or nl) and view_image. Other commands that
touch files are not checked before they run; the files they read are reported in
the invocation event afterwards.
The PreToolUse hook adds one SlashID round trip to every tool call. Narrowing
its matcher removes that cost for the tools you leave out, and with it the
read checks on them.
user_id is asserted by the device's configuration; it is not proven by the
user. The Event streaming token sits in a file users can read, so a user who
extracts it can send events under any ID. Treat the events as attributed by the
device, not authenticated per person.
Upgrade
Before replacing the package, stop the background processes — the
slashid-codex Python processes running -m slashid_codex daemon under the
tool directory; on Windows a running .exe cannot be replaced. Do not kill the
hook processes themselves: a hook that is killed lets its prompt or tool call
through. A background process whose version differs from the hook's is replaced
on the next hook.
Verification
After the first sync, SlashID shows:
- Users — organization members with their organization role, SCIM-managed flag, enabled/banned state and, with audit logs available, last activity and last IP,
- Service accounts — project-scoped non-human identities, with the project role they hold,
- Projects — with owner counts, rate limits, and the declared model policy (allow list or deny list) and hosted-tool enablement,
- Groups and custom roles, with their memberships and grants,
- API keys — project keys and organization admin keys, with the redacted key hint, creation and last-used timestamps, and the owner they belong to,
- mTLS certificates and pending invitations,
- Models and tools each project declares access to, coalesced with the same nodes that observed AI traffic produces, so declared and actual usage line up on one graph.
Connection preflight checks validate the credential and each permission. Required checks mirror what the sync hard-fails on; optional slices degrade gracefully.
If you deployed the Codex hooks (step 5), open a new Codex session on a pilot
machine and send a prompt. The invocation appears in SlashID shortly after the
response ends. The background process keeps a log, daemon.log, in the user's
data directory (~/.local/share/slashid-ai-forwarder-codex on Linux,
~/Library/Application Support/slashid-ai-forwarder-codex on macOS,
%LOCALAPPDATA%\slashid\slashid-ai-forwarder-codex on Windows); it records each
start, stop and failed request without prompt content.
Troubleshooting
admin_key_validfails with "was rejected" — the Admin API key is invalid or revoked. Create a new one (step 1). Admin keys start withsk-admin-.admin_key_validfails with "does not have access to organization …" — the key was accepted but is not permitted to read the organization you entered. Check the organization ID (step 2), and confirm the key was provisioned by an organization owner.list_usersorlist_projectsfails — these are required. The sync aborts rather than importing an empty list, because an empty list would delete every OpenAI identity on the next reconcile.list_project_users,list_project_service_accountsorlist_project_api_keysfails — the key cannot read one of the per-project collections. The sync reads every project and aborts on the first denial, so these are required too. All three are reported as skipped when the organization has no projects.list_groupsorlist_rolesfails — groups and custom roles require a plan that exposes them. Group memberships and role grants are skipped; the rest of the sync continues.list_admin_api_keysfails — reading organization admin keys requires an owner-provisioned key. Without it, admin keys are not inventoried, including any that never expire.list_invitesorlist_certificatesfails — pending invitations or mTLS certificates are skipped. Certificates require the certificates feature on your plan.read_audit_logsfails — audit logs are available on Enterprise plans only. Last-activity enrichment and the OpenAI event stream are skipped.read_costsfails — cost reporting requires billing access on the Admin API key. OpenAI spend is not attributed to projects or keys.compliance_workspacefails — the Compliance API key lacks the workspace read scopes. The ChatGPT workspace and its members are skipped. This check is reported as skipped, not failed, when no compliance key is configured.
Codex hooks
- Every prompt is blocked with "Failed to start the SlashID Codex daemon." —
the background process could not start: the configuration is missing or
invalid, or the token file is missing or shorter than 32 characters. Read
daemon.loganddaemon.stderrin the user's data directory (see Verification). After a failed start the hook does not retry for five minutes; fixing the configuration file applies at once, but fixing only the token file can take up to the five minutes. Withverdict_fail_mode = "allow"the prompt goes through instead of being blocked. - Prompts are blocked with "SlashID preflight failed: ..." — the background
process is running but could not get a verdict in time or SlashID answered
with an error. Check the machine can reach the
endpoint, and that the token is the connection's current Event streaming token. - Prompts are denied with an identity message —
user_idin the configuration is not a user this connection has synced. Check the ID against the member'sidin OpenAI's organization users list, and that the member appears in the connection's users after a sync (step 4). - No invocations appear in SlashID — the token is not the connection's
current Event streaming token (recreating the connection issues a new one;
update the token file and the change applies within about five seconds), or
Codex keeps its session logs somewhere other than
~/.codex.CODEX_HOMEis not read; setcodex_homein the configuration. - A user's own hooks do not run —
allow_managed_hooks_onlyis set, which makes Codex ignore~/.codex/hooks.json. Remove the line from the managed requirements to allow them. - The hooks do not run at all — the managed requirements file is not where
Codex reads it, or
hooks = trueunder[features]is missing. Confirm the file is installed and thatallow_managed_hooks_onlyis a top-level key, not under[hooks]. - A rotated token or edited configuration does not take effect — the
background process re-reads both files every five seconds and exits when they
change; the next hook starts a new one. If it does not, stop the process
(
-m slashid_codex daemonunder the tool directory) and send another prompt.