Docs · Codex SDK
Codex SDK on Selan
The Codex SDK runs a whole Codex session from your code — it reads the repository, runs commands and edits files. Pointing it at Selan is a base URL and a token. For running Codex at a terminal, start at the Codex page.
Install
npm install @openai/codex-sdk
Two different things share the name Codex. This SDK drives the agent. If you only want one answer from a model — a classification, a summary — you want the Responses format instead, which is the last section and needs no SDK of ours. And Anthropic-shaped code belongs on the Claude Agent SDK page; the gateway serves both formats, so there is nothing to gain by rewriting one into the other.
Point it at Selan
The constructor takes both values. The base URL carries the /openai/v1
namespace, which names the format the SDK speaks:
import { Codex } from "@openai/codex-sdk"
const codex = new Codex({
baseUrl: "https://gw.selan.ai/openai/v1",
apiKey: process.env.SELAN_TOKEN,
})
Mint the token in Selan under Settings → CLI tokens. It spends as
itself, under an address like nightly-triage@tokens.selan.ai, so what the
agent costs shows up as the agent in Usage rather than on whoever set it up.
The base URL has to end at /openai/v1. The SDK appends
/responses to whatever you give it. Drop the version segment and every
call lands on a path the gateway does not serve.
Your first session
A thread is one session; run is one turn in it. Call it again on the same
thread and the agent keeps its context:
const thread = codex.startThread({
model: "gpt-5.6-sol",
workingDirectory: "/tmp/scratch",
sandboxMode: "workspace-write",
approvalPolicy: "never",
skipGitRepoCheck: true,
})
const result = await thread.run("Fix the failing test in src/money.test.js")
console.log(result.finalResponse)
for (const item of result.items ?? []) {
if (item.type === "command_execution") console.log("ran:", item.command)
if (item.type === "file_change") console.log("changed:", item.changes.map((c) => c.path))
}
finalResponse is what the agent said. items is what it did —
a typed record of every command it ran and every file it patched, which is how you
check the work rather than trusting the reply.
Choosing what it may do
Two settings decide that, and they interact in a way worth knowing before the first unattended run.
sandboxMode is what the agent may touch.
read-only lets it look and nothing else; workspace-write
lets it edit inside workingDirectory.
approvalPolicy is what happens when the sandbox refuses
something. The default asks a human. With nobody there to answer,
never is the setting that works: the request is refused, the agent is
told, and it adapts.
Leave the approval policy at its default and edits are reported but never
written. The turn comes back with file_change items naming files
that do not exist on disk, because the write was waiting on an approval nobody gave.
Nothing errors, and the run looks like it worked.
skipGitRepoCheck is for a scratch directory that is not a repository. In
a real checkout, leave it off and let the agent see the git history.
Without writing any code
If a shell command is enough, selan codex exec runs the same kind of
session with nobody watching:
export SELAN_TOKEN=selanct_...
selan codex exec "update the changelog for the last ten commits"
It is the same launch as an interactive selan codex: same provider block,
same company models, same limits. No sign-in step, because the token is in the
environment. Setup is on the Codex page.
When you want one answer, not an agent
POST /openai/v1/responses against the gateway. The body is the one you
would send to OpenAI directly — the
Responses API reference
documents every field, and none of them mean anything different here:
curl https://gw.selan.ai/openai/v1/responses \
-H "Authorization: Bearer $SELAN_TOKEN" \
-H "content-type: application/json" \
-d '{
"model": "gpt-5.6-sol",
"input": "Summarise this changelog in one sentence."
}'
The official OpenAI SDKs take the same base URL and your Selan token as the key:
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://gw.selan.ai/openai/v1",
apiKey: process.env.SELAN_TOKEN,
})
const response = await client.responses.create({
model: "gpt-5.6-sol",
input: "Summarise this changelog in one sentence.",
})
import os
from openai import OpenAI
client = OpenAI(
base_url="https://gw.selan.ai/openai/v1",
api_key=os.environ["SELAN_TOKEN"],
)
response = client.responses.create(
model="gpt-5.6-sol",
input="Summarise this changelog in one sentence.",
)
No provider key anywhere in those. The gateway authenticates you, leases one of your company's connected credentials for the length of the request, streams the answer straight through, and releases it afterwards.
Load the token from wherever you keep secrets rather than committing it. Both examples read it from the environment for that reason.
Naming a model
Ask the gateway which ones this token may name. Under the OpenAI namespace the list comes back in the shape a Responses client expects:
curl https://gw.selan.ai/openai/v1/models \
-H "Authorization: Bearer $SELAN_TOKEN"
Your company might serve one of these through OpenAI directly or through another connected account, and which credential is spent is decided per request. The id you name is the same either way, so the list is the thing to trust rather than a guess about who is behind it.
What your company sees
The same as every other Selan client. Each request is metered against the token that made it, the company-wide per-user spend limit applies, and it appears in Logs under its own address.
One thread is many requests rather than one, so an agent that fails to terminate keeps spending until something stops it. Set a budget on the token before you leave one running unattended.