Primary navigation

Legacy APIs

OpenAI CLI

Use the OpenAI API directly from your terminal.

Interact with the OpenAI API directly from your terminal with the openai command-line tool.

Installation

Install the CLI with Homebrew:

brew install openai/tools/openai

Or install it with Go 1.25 or later:

go install 'github.com/openai/openai-cli/cmd/openai@latest'

Older versions of the Python SDK also installed a legacy openai command. If you already had that package installed and the command you see does not match this guide, your shell may still be resolving the older binary. Fresh CLI installs are not affected.

Authentication

The CLI reads your API key from OPENAI_API_KEY:

Command:

export OPENAI_API_KEY="sk-..."

If you don’t have an API key yet, create one in the dashboard.

For Admin API endpoints, set OPENAI_ADMIN_KEY instead. The SDK layer selects the admin key or default API key based on the endpoint being called.

To point at a different API host, set OPENAI_BASE_URL.

Use cases

Use the CLI when the work belongs naturally in the terminal:

  • Generate local artifacts such as images or speech.
  • Extract structured data into JSONL for later shell steps.
  • Use Responses with files, computer use, and current web context in the cloud.
  • Create projects and API keys with Admin APIs.

Use it directly for one-off terminal requests, or from scripts when agents need repeatable batch work over files and generated artifacts.

CLI vs subagents for Codex

Use the CLI for repeatable API work you want to inspect and rerun, such as batch extraction, file transforms, artifact generation, or deliberate model selection. Use subagents when the work still needs judgment, such as exploring code, comparing hypotheses, debugging, or reviewing changes.

Global flags

These options work across commands:

FlagUse
--formatPrint responses as auto, json, jsonl, pretty, raw, yaml, or explore.
--transformExtract or reshape response data with a GJSON path before printing.
--debugPrint request and response details to stderr. Authorization is redacted; review headers before sharing logs.

This guide focuses on CLI patterns. For the latest arguments and response shapes for any API family, use the live API reference.

You can also change the base URL when you need to point the CLI at another compatible endpoint, such as a deployment that supports a different model set or only a subset of the API surface.

Responses

Use Responses for text generation, structured extraction, web search, file understanding, and repeatable Codex-authored batch scripts.

Send your first request

Command:

1
2
3
openai responses create \
  --model gpt-5.5 \
  --input "Say hello in one sentence."

Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "id": "resp_...",
  "object": "response",
  "status": "completed",
  "model": "gpt-5.5-...",
  "output": [
    {
      "type": "message",
      "role": "assistant",
      "content": [
        {
          "type": "output_text",
          "text": "Hello!"
        }
      ]
    }
  ],
  "usage": {
    "input_tokens": 12,
    "output_tokens": 6,
    "total_tokens": 18
  },
  "...": "additional response fields omitted"
}

The CLI prints the full API response object by default. Examples on this page keep representative fields such as id, status, model, output, and usage, and omit the rest.

Responses output can include non-message items, such as reasoning items, before the assistant message. When you need assistant text, select the message item by type instead of assuming it is always output[0]:

--transform 'output.#(type=="message").content.0.text'

Add a local file to the prompt

For a simple local file, build the prompt inline with command substitution:

1
2
3
4
5
6
7
8
9
openai responses create \
  --model gpt-5.5 \
  --input "Summarize this note in one sentence.

<note>
$(cat ./note.md)
</note>" \
  --format yaml \
  --transform 'output.#(type=="message").content.0.text'

Output:

The note says the launch checklist is ready except for final support ownership.

Passing request bodies

Use flags for short scalar inputs. Use a YAML heredoc for multiline prompts, tools, files, or nested request bodies. The heredoc can contain the same request fields you would otherwise pass as flags.

Be careful with string values that look like YAML, especially prompts that contain : or {}. On flags, the generated parser may interpret those values as structured YAML instead of plain text. If a prompt starts looking like configuration, put it under input: | in a YAML body instead:

Command:

1
2
3
4
5
6
7
8
9
10
11
12
13
openai responses create \
  --format yaml \
  --transform 'output.#(type=="message").content.0.text' <<'YAML'
model: gpt-5.5
instructions: Return exactly one sentence.
max_output_tokens: 120
input: |
  Summarize this release note in one sentence.

  <release_note>
  Fixed the image generation example and added CLI installation guidance.
  </release_note>
YAML

Output:

The release note updates the CLI docs with corrected image generation and installation guidance.

When the prompt itself needs shell assembly, build a YAML body and pipe it into the command:

1
2
3
4
5
6
7
8
9
10
{
  printf 'input: |\n'
  printf '  Summarize this note in one sentence.\n\n'
  printf '  <note>\n'
  sed 's/^/  /' ./note.md
  printf '  </note>\n'
} | openai responses create \
  --model gpt-5.5 \
  --format yaml \
  --transform 'output.#(type=="message").content.0.text'

Write structured data to JSON

Use structured outputs when downstream scripts need stable JSON. Save reusable schemas to disk:

Save as schema.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "type": "json_schema",
  "name": "fact",
  "strict": true,
  "schema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "person": { "type": "string" },
      "topic": { "type": "string" }
    },
    "required": ["person", "topic"]
  }
}

Command:

1
2
3
4
5
6
7
openai responses create \
  --model gpt-5.5 \
  --instructions "Extract the person and topic from the input." \
  --input "Ada Lovelace wrote notes about the Analytical Engine." \
  --text.format "$(cat ./schema.json)" \
  --format yaml \
  --transform 'output.#(type=="message").content.0.text'

Output:

{ "person": "Ada Lovelace", "topic": "notes about the Analytical Engine" }

Write structured records to JSONL

When one input may produce many records, ask the model for an array and flatten it into JSONL so later shell steps can process one record per line:

Save as records-schema.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
  "type": "json_schema",
  "name": "items",
  "strict": true,
  "schema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "items": {
        "type": "array",
        "items": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "title": { "type": "string" },
            "summary": { "type": "string" },
            "evidence": { "type": "string" }
          },
          "required": ["title", "summary", "evidence"]
        }
      }
    },
    "required": ["items"]
  }
}

Command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
: > records.jsonl

for file in notes/*.md; do
  extracted="$(
    openai responses create \
      --model gpt-5.5 \
      --text.format "$(cat ./records-schema.json)" \
      --raw-output \
      --transform 'output.#(type=="message").content.0.text' <<YAML
input: |
  <note path="$file">
$(sed 's/^/  /' "$file")
  </note>
YAML
  )"

  jq -r --arg source "$file" \
    '.items[]? + {source: $source} | @json' \
    <<<"$extracted" >> records.jsonl
done

This keeps the model response structured while producing one JSON object per line for later shell steps.

Responses can call hosted tools from the same YAML request body:

Command:

1
2
3
4
5
6
7
8
9
10
openai responses create \
  --model gpt-5.5 \
  --format yaml \
  --transform 'output.#(type=="message").content.0.text' <<'YAML'
tools:
  - type: web_search
input: |
  Research the latest material news for AAPL.
  Return three concise bullets and cite sources in the text.
YAML

Output:

- Apple announced ...
- Analysts highlighted ...
- The company said ...

File inputs

For uploaded files such as PDFs, create the file first, capture its ID, and pass it as input_file.file_id:

Command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FILE_ID=$(
  openai files create \
    --file ./brief.pdf \
    --purpose user_data \
    --format yaml \
    --transform id
)

openai responses create \
  --model gpt-5.5 \
  --format yaml \
  --transform 'output.#(type=="message").content.0.text' <<YAML
input:
  - role: user
    content:
      - type: input_text
        text: Summarize this brief and list three risks.
      - type: input_file
        file_id: ${FILE_ID}
YAML

Output:

- The brief proposes ...
- Risks: migration timing, unclear rollback criteria, and unresolved support ownership.

Recent generated builds send local file flags as multipart file parts with filename and content type metadata. If a local upload command fails with an UploadFile type error, update the CLI and retry.

Images

Generate an image

Generate an image, extract the base64 payload, and decode it into a normal asset file:

Command:

1
2
3
4
5
6
openai images generate \
  --model gpt-image-2 \
  --prompt "A simple product-style render of a translucent green cube on a neutral background." \
  --format yaml \
  --transform 'data.0.b64_json' | base64 --decode > hero.png
printf 'wrote hero.png\n'

Output:

wrote hero.png

Current limitation: image commands do not yet have native --output support, so image generation still requires extracting b64_json and decoding it yourself.

For gpt-image-2, omit --input-fidelity; image inputs are always processed at high fidelity. Do not use --background transparent with gpt-image-2. The model also supports broader --size values than earlier GPT Image models, as long as the requested resolution satisfies the Image API size constraints.

Edit an image

Image editing uses the same base64 extraction pattern after the edit request succeeds:

Command:

1
2
3
4
5
6
7
openai images edit \
  --model gpt-image-2 \
  --image ./hero.png \
  --prompt "Turn the cube bright green." \
  --format yaml \
  --transform 'data.0.b64_json' | base64 --decode > hero-edited.png
printf 'wrote hero-edited.png\n'

Output:

wrote hero-edited.png

If a local image edit upload fails with an UploadFile type error, update the CLI and retry.

Speech

Create an MP3 locally with the speech API:

Command:

1
2
3
4
5
openai audio:speech create \
  --model gpt-4o-mini-tts \
  --voice marin \
  --input "The OpenAI CLI can call the API from ordinary shell scripts." \
  --output speech.mp3

Output:

Wrote output to: speech.mp3

Play it with whatever local audio tool is available on your machine. On macOS:

afplay speech.mp3

Use --instructions to shape delivery and --input for the words that should be spoken. Instructions work well for cues such as pace, energy, warmth, formality, emphasis, or audience:

1
2
3
4
5
6
openai audio:speech create \
  --model gpt-4o-mini-tts \
  --voice marin \
  --instructions "Whisper very quickly, like a hurried stage cue, while staying clear and intelligible." \
  --input "The launch checklist is ready. Please send final feedback by Friday at noon." \
  --output reminder.mp3

Transcription

Print plain transcript text for shell pipelines:

Command:

1
2
3
4
5
openai audio:transcriptions create \
  --model gpt-4o-transcribe \
  --file ./speech.mp3 \
  --transform text \
  --raw-output

Output:

The OpenAI CLI can call the API from ordinary shell scripts.

Use the response format that matches the artifact you need:

NeedCommand shape
Plain transcript text--model gpt-4o-transcribe --transform text --raw-output
Subtitle files--model whisper-1 --response-format srt or --response-format vtt
Segment or word timestamps--model whisper-1 --response-format verbose_json
Speaker-labeled diarization--model gpt-4o-transcribe-diarize --response-format diarized_json

For word-level timing, request the verbose transcription shape:

Command:

1
2
3
4
5
6
openai audio:transcriptions create \
  --model whisper-1 \
  --file ./speech.mp3 \
  --response-format verbose_json \
  --timestamp-granularity word \
  --format json

Output:

1
2
3
4
5
6
7
8
9
10
11
{
  "task": "transcribe",
  "language": "english",
  "duration": 6,
  "text": "The OpenAI CLI can call the API from ordinary shell scripts.",
  "words": [
    { "word": "The", "start": 0, "end": 0.42 },
    { "word": "OpenAI", "start": 0.42, "end": 1.22 }
  ],
  "...": "additional response fields omitted"
}

For speaker-labeled output, use the diarization model and request diarized_json:

Command:

1
2
3
4
5
openai audio:transcriptions create \
  --model gpt-4o-transcribe-diarize \
  --file ./speech.mp3 \
  --response-format diarized_json \
  --format json

Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "text": "The OpenAI CLI can call the API from ordinary shell scripts.",
  "segments": [
    {
      "type": "transcript.text.segment",
      "id": "seg_0",
      "start": 0.05,
      "end": 5.25,
      "text": " The OpenAI CLI can call the API from ordinary shell scripts.",
      "speaker": "A"
    }
  ],
  "...": "additional response fields omitted"
}

whisper-1 supports json, text, srt, verbose_json, and vtt. diarized_json is the format that carries segments[].speaker; with the same diarization model and plain json, the response contains transcript text but not speaker labels.

Admin APIs

Use Admin APIs for organization management, credential provisioning, compliance, and usage-monitoring workflows. Set OPENAI_ADMIN_KEY, then call the generated admin:organization:* commands.

To provision a new machine credential, create a project, create a service account inside that project, and use the returned API key.

Create a project, service account, and API key

Creating a service account in that project returns an unredacted API key for the service account.

Command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Create the project that will own this app or agent and save the response.
openai admin:organization:projects create \
  --name "automation project" \
  --format json > project.json
PROJECT_ID="$(jq -r '.id' project.json)"

# Create a service account inside the project and save the full response.
openai admin:organization:projects:service-accounts create \
  --project-id "$PROJECT_ID" \
  --name "automation bot" \
  --format json > service-account.json

# Extract the returned API key into an env file for the workload to use.
jq -r '.api_key.value | "OPENAI_API_KEY=\(.)"' \
  service-account.json > .env

Output:

1
2
3
4
5
6
7
8
9
10
{
  "object": "organization.project.service_account",
  "id": "svc_acct_...",
  "name": "automation bot",
  "role": "member",
  "api_key": {
    "id": "key_...",
    "value": "sk-..."
  }
}

This writes the project response to project.json, parses its ID into the next command, writes the service-account response to service-account.json, and writes the returned credential to .env as OPENAI_API_KEY=.... Treat both JSON files as secrets, and add project.json, service-account.json, and .env to .gitignore before using this pattern in a repository.

For the rest of the surface, see the Admin APIs guide and the current Administration API reference. Be careful about giving unvetted actors access to admin keys.