Docs
AISIX AI GatewayBuildProvider UpstreamsOpenAI

OpenAI

Connect OpenAI chat models and Sora video generation to an AISIX gateway: a provider key that holds the credential, a model alias callers send, and a caller API key scoped to it.

Applies to:AISIX AI Gateway 1.4.0

Roles:Platform engineerTime:10 minutesImpact:No restart, no traffic change

Goal

One OpenAI-backed model alias that callers reach through the gateway with a caller API key, while the OpenAI credential stays on the control plane and never enters client code.

OpenAI serves hosted GPT models through its own API, and the AISIX integration uses the openai adapter — the only provider value that lets AISIX fall back to OpenAI's own base URL, https://api.openai.com/v1. An Azure-hosted deployment is a different provider — see Azure OpenAI.

Before you begin

  • The AISIX Enterprise Admin API answers with your token. It needs write permission. Check it with:

    curl -s -o /dev/null -w '%{http_code}\n' "${AISIX_URL}/api/environments" \
      -H "Authorization: Bearer ${AISIX_API_KEY}"

    Expected: 200.

  • At least one gateway is attached to the environment and reporting healthy. Check it with:

    curl -s "${AISIX_URL}/api/environments/${ENV_ID}/dp_nodes" \
      -H "Authorization: Bearer ${AISIX_API_KEY}" \
      | jq '[.data[] | select(.status == "healthy")] | length'

    Expected: 1 or more — the number of data plane nodes reporting healthy.

  • An environment with an attached gateway, from AISIX Enterprise Quickstart for AISIX Enterprise or On-Premises. To request Hybrid Cloud access, contact API7.

  • An OpenAI API key from the OpenAI platform, and curl and jq on your path.

Set the variables this guide uses:

export AISIX_URL="http://127.0.0.1:8080"       # the control plane origin, no trailing slash; the local On-Premises default, where cp-api serves the Admin API under /api
export GATEWAY_URL="http://127.0.0.1:3000"     # the gateway's proxy listener, proxy.addr
read -rsp "AISIX admin token: " AISIX_API_KEY && export AISIX_API_KEY && echo
export ENV_ID=$(curl -s "${AISIX_URL}/api/environments" -H "Authorization: Bearer ${AISIX_API_KEY}" | jq -r '.data[0].id')

AISIX_URL is an origin, so every Admin API path below carries the /api prefix cp-api serves it under. The port numbers are the product's own: 8080 is cp-api, the browser-facing dashboard entry point and the AISIX Enterprise Admin API, and 3000 is the gateway's proxy.addr — both in Ports.

The last line takes the first environment the token can see. With more than one, list them with jq -r '.data[] | "\(.id) \(.display_name)"' and export the id you mean to configure. The two checks above use these variables, so set them first and then run the checks.

Steps

Three steps, each one a POST whose id the next one references: the credential, then the alias callers send, then the key that may use the alias.

Step 1: Create the provider key

The provider key is where the OpenAI credential lives. api_key is encrypted before it is stored and no read endpoint ever returns it, which is why it is read here rather than exported.

read -rsp "OpenAI API key: " OPENAI_API_KEY && export OPENAI_API_KEY && echo

export PROVIDER_KEY_ID=$(curl -sS "${AISIX_URL}/api/provider_keys" -X POST \
  -H "Authorization: Bearer ${AISIX_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "openai",
    "display_name": "openai-prod",
    "api_key": "'"${OPENAI_API_KEY}"'",
    "allowed_environments": ["'"${ENV_ID}"'"]
  }' | jq -r '.provider_key.id')

unset OPENAI_API_KEY
echo "${PROVIDER_KEY_ID}"
31f302a2-d055-4f9f-a662-1dc4b84de9f7
  • provider is openai, and it is the only value that falls back to OpenAI's own base URL. Leave api_base out for the public API; set it to reach an OpenAI-compatible proxy that keeps bearer authentication and the OpenAI route shape.
  • allowed_environments lists the environments that may reference this key when a model is created. Empty or null means none may.
  • adapter is only accepted on a bring-your-own provider key; for a catalog provider such as OpenAI, AISIX derives it. The apis declaration is filled in from AISIX's curated catalog entry when it is left out, and the create response echoes neither it nor api_base — GET /provider_keys/{provider_key_id} returns them, with apis_source reading catalog when AISIX supplied the declaration.

Credential handling in full is in Provider Keys.

What changed: the control plane holds an encrypted OpenAI credential that one environment is allowed to use, and nothing can call OpenAI through it yet.

Step 2: Create the model alias

The alias is the string callers put in model. Keeping it separate from the upstream model id is what lets you move a workload to another model without touching a client.

export MODEL_ID=$(curl -sS "${AISIX_URL}/api/environments/${ENV_ID}/models" -X POST \
  -H "Authorization: Bearer ${AISIX_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "gpt-5.6-sol-prod",
    "model_name": "gpt-5.6-sol",
    "provider_key_id": "'"${PROVIDER_KEY_ID}"'"
  }' | jq -r '.model.id')

echo "${MODEL_ID}"
d7a49c58-ae2d-4dcb-a600-3237dc7b04d1
  • display_name is the alias callers send in model.
  • model_name is the OpenAI model id. This guide uses gpt-5.6-sol, the current flagship model in the OpenAI model catalog; choose another current model when its cost, latency or modality suits the workload better.
  • provider_key_id attaches the alias to the provider key from step 1.

What changed: the environment has one callable alias, and every write projects to the attached gateways on its own.

Step 3: Create a caller API key scoped to the alias

The control plane generates the key value and returns the plaintext exactly once, on this response. It stores only a hash, so this is the one moment the value can be saved.

response=$(curl -sS "${AISIX_URL}/api/environments/${ENV_ID}/api_keys" -X POST \
  -H "Authorization: Bearer ${AISIX_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "openai-caller",
    "allowed_models": ["'"${MODEL_ID}"'"]
  }')

printf '%s' "${response}" | jq -r '.plaintext' > caller-key.txt && chmod 600 caller-key.txt
export CALLER_KEY_ID=$(printf '%s' "${response}" | jq -r '.api_key.id')
printf '%s' "${response}" | jq -r '.api_key.status'
active

The plaintext went to caller-key.txt rather than to your terminal or your shell history; the commands below read it from there. Move it into whatever your applications already use for secrets — there is no recovery path on the server, and a lost key has to be replaced.

What changed: one bearer token can now call one model alias through the gateway, and the OpenAI credential is reachable by nothing else.

Publication to the gateway is asynchronous. A request sent immediately after this step can get a gateway-side 401 ("type": "invalid_api_key") even with a correct caller key, because the new key has not reached the gateway's applied configuration yet. Retry after a few seconds, or confirm propagation first with Resource Projection.

The same three objects on an open-source AISIX gateway

An open-source gateway has no control plane to call: the same provider key, model alias and caller key are three entries in the declarative resources file it loads at startup. provider_keys takes provider: "openai" and an api_key written as a ${VAR} reference the gateway resolves from its own process environment; models names the alias and its model_name and references the provider key by display_name; api_keys names the caller key with key_env, which is a bare variable name and not a ${VAR} reference. For a new gateway write a complete file; for an existing one merge the entries into its current file, keeping its other resources.

Validate the file before loading it:

aisix validate --resources resources.yaml

Then start the gateway with every variable the file references in its process environment. Reload an existing gateway only when those variables are already available to the process; otherwise restart it with the updated environment. With Docker, pass each one with -e and mount the file, as in the Open-Source AISIX Gateway Quickstart.

Every field of all three entries, the interpolation rules and the loading, validation and reload pipeline are in Resources File. The ## Validate section below works against either gateway: the caller key is the bearer either way.

Add a Sora video alias

Optional, and it reuses the same provider key: a second alias pointing at a Sora model drives the gateway's modeled video routes.

OpenAI deprecated the Videos API and the Sora 2 models on March 24, 2026 and will remove them from the API on September 24, 2026. Aliases backed by sora-2 or sora-2-pro stop working on that date, and OpenAI lists no replacement model on this API.

export VIDEO_MODEL_ID=$(curl -sS "${AISIX_URL}/api/environments/${ENV_ID}/models" -X POST \
  -H "Authorization: Bearer ${AISIX_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "sora-video-prod",
    "model_name": "sora-2",
    "provider_key_id": "'"${PROVIDER_KEY_ID}"'"
  }' | jq -r '.model.id')

curl -sS "${AISIX_URL}/api/environments/${ENV_ID}/api_keys/${CALLER_KEY_ID}" -X PATCH \
  -H "Authorization: Bearer ${AISIX_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "allowed_models": ["'"${MODEL_ID}"'","'"${VIDEO_MODEL_ID}"'"]
  }' | jq -r '.api_key.allowed_models | length'
2

Submit a task against the new alias with the same caller key:

curl -si "${GATEWAY_URL}/v1/videos" -X POST \
  -H "Authorization: Bearer $(cat caller-key.txt)" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "sora-video-prod",
    "prompt": "A paper boat drifts down a rain-soaked street at dusk.",
    "seconds": 4,
    "size": "1280x720"
  }' | head -1
HTTP/1.1 200 OK

Five things about this route are specific to OpenAI and worth knowing before scripting around it:

  • OpenAI is the only video provider with a default base URL. An OpenAI-backed video alias works with api_base unset, exactly like the chat alias above. Every other video provider requires api_base on the provider key.
  • Sora validates seconds and size itself. OpenAI's create-video schema accepts 4, 8 or 12 for seconds and lists 720x1280, 1280x720, 1024x1792 and 1792x1024 for size, but the per-model pages publish narrower resolution sets — check the page for the model your alias names. AISIX forwards seconds as a string and checks that size has the WIDTHxHEIGHT shape before forwarding it verbatim, so a well-formed value the model does not accept is rejected by the provider, not by the gateway.
  • Finished videos stream through the gateway. Sora serves the finished file from an authenticated content endpoint rather than a signed URL, so GET /v1/videos/{id}/content returns 200 with the MP4 bytes instead of a 302. AISIX fetches the file with the provider credential and relays it chunk by chunk, so the credential never reaches the caller and a large file does not grow the gateway's memory use. Size the gateway's egress for this traffic: those bytes transit the gateway and do not count against the model's rate limits.
  • progress is a real percentage. Sora reports task progress, so poll responses carry the provider's own completion percentage. Providers that report none stay at 0 until the task completes.
  • Image-to-video requires a passthrough route. The normalized request models only prompt, seconds and size; extra fields, including input_reference, are ignored rather than rejected, so a remix or image-guided request would silently generate from the prompt alone. Call /passthrough/openai/v1/videos with the native multipart body for those, and use the native remix route the same way. An OpenAI SDK client reaches this path too, but it needs its own client: point its base URL at ${GATEWAY_URL}/passthrough/openai/v1 rather than the ${GATEWAY_URL}/v1 client used elsewhere on this page, and grant the route name in the caller key's allowed_routes. The video create helper always sends multipart/form-data, which the modeled route does not accept. Passthrough does not return the unified AISIX video object or gateway-encoded task id.

The full submit, poll and download workflow, including status semantics and rate-limit behaviour on polling, is in Video Generation.

Choose a route for each OpenAI API family

OpenAI exposes several API families that use different model types, so configure one AISIX alias per upstream model an application needs. What each route does with an OpenAI-backed alias:

RouteBehavior with an OpenAI-backed alias
/v1/chat/completionsSupported with the OpenAI request and response shape. Model-specific feature and parameter limits still apply.
/v1/responsesForwarded to OpenAI's native Responses endpoint after model-alias rewriting. Stateful fields, hosted tools, reasoning controls and upstream SSE remain on the native path.
/v1/completionsForwarded to OpenAI, but this is a legacy endpoint and the selected model must support it.
/v1/embeddingsSupported with an alias for an OpenAI embedding model, such as text-embedding-3-large.
/v1/images/generationsSupported with an alias for a current OpenAI image model.
/v1/images/editsSupported with an alias for an OpenAI image-editing model such as gpt-image-2. Requests are multipart/form-data; see Image Editing.
/v1/videos and its status and content routesSupported with an alias for a Sora model, sora-2 or sora-2-pro. The content route streams the MP4 through the gateway instead of redirecting, because OpenAI serves finished files from an authenticated endpoint. See Add a Sora video alias.
/v1/audio/*Supported with an appropriate speech, transcription or translation model alias. To send recorded audio to a chat model, or save its generated audio response, see Audio Input and Output with Chat Completions.
/v1/realtimeSupported as a WebSocket relay with a direct Realtime model alias. See Realtime API.
/v1/files, /v1/batches, /v1/fine_tuning/jobsSupported through the OpenAI adapter. These job-style routes select credentials differently from ordinary inference calls; see Files, Batch, and Fine-tuning.
/v1/messagesTranslated through OpenAI chat; OpenAI does not expose a native Anthropic Messages endpoint.
/v1/messages/count_tokensRejected because token counting on this route requires an Anthropic-backed model.
/v1/modelsReturns caller-accessible AISIX model aliases, not the OpenAI account's model inventory. Use /passthrough/openai/v1/models when the native list is required.
/v1/rerankAISIX accepts the openai provider value, but the public OpenAI API does not expose /v1/rerank, so the upstream rejects the call.

Use a passthrough route for an OpenAI route AISIX does not model, such as moderation or image variations. The /passthrough/openai paths on this page assume a route claiming that prefix with OpenAI's API root as its target_url; grant the route on the caller key's allowed_routes. Passthrough uses OpenAI model ids rather than AISIX aliases, and its streaming and usage accounting differ. To drive any of this from an OpenAI SDK, point the client's base URL at ${GATEWAY_URL}/v1 and use the caller key as the API key — see the OpenAI SDK guide.

Validate

Send one chat-completions request through the gateway with the caller key. The response echoes the caller-facing alias rather than the upstream model id, which is what proves the alias resolved rather than that the request merely reached OpenAI. If this request gets a gateway-side 401 right after step 3, see the propagation note there before troubleshooting further.

curl -sS "${GATEWAY_URL}/v1/chat/completions" -X POST \
  -H "Authorization: Bearer $(cat caller-key.txt)" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-sol-prod",
    "messages": [
      { "role": "user", "content": "Say hello from OpenAI." }
    ]
  }' | jq -r '.model'
gpt-5.6-sol-prod

The same alias serves OpenAI's Responses API, which OpenAI recommends for reasoning, tool-calling and multi-turn work. Because the model's provider is openai, AISIX rewrites the alias and forwards to OpenAI's native Responses endpoint rather than through the cross-provider Responses bridge:

curl -si "${GATEWAY_URL}/v1/responses" -X POST \
  -H "Authorization: Bearer $(cat caller-key.txt)" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-sol-prod",
    "input": "Say hello from the OpenAI Responses API."
  }' | head -1
HTTP/1.1 200 OK

Both requests appear on the OpenAI usage dashboard, which is the confirmation that the provider key's credential — and not some other one — paid for them.

Roll back / Clean up

Remove what this guide created, in reverse dependency order: the caller key, then the aliases that key allowed, then the credential they shared.

curl -sS -o /dev/null -w '%{http_code}\n' -X DELETE \
  "${AISIX_URL}/api/environments/${ENV_ID}/api_keys/${CALLER_KEY_ID}" \
  -H "Authorization: Bearer ${AISIX_API_KEY}"

curl -sS -o /dev/null -w '%{http_code}\n' -X DELETE \
  "${AISIX_URL}/api/environments/${ENV_ID}/models/${MODEL_ID}" \
  -H "Authorization: Bearer ${AISIX_API_KEY}"

curl -sS -o /dev/null -w '%{http_code}\n' -X DELETE \
  "${AISIX_URL}/api/provider_keys/${PROVIDER_KEY_ID}" \
  -H "Authorization: Bearer ${AISIX_API_KEY}"

rm -f caller-key.txt
200
200
200

Deleting the caller key closes the gateway to that bearer immediately, so every application still holding it starts getting 401; deleting the alias makes the model name itself unknown, which is a 404. Delete a Sora alias created above the same way before the provider key. To retire a credential without taking the aliases down, PATCH /provider_keys/{provider_key_id} with a new api_key instead.

Troubleshoot

SymptomCauseFix
The gateway rejects the request. 401 with "type": "invalid_api_key" means the bearer is not a caller key of this environment, or is expired or disabled; 403 with "type": "permission_denied" means the key is valid but the alias is not one it may use.401: the bearer is wrong — the plaintext is returned once and cannot be recovered, so a lost key is a new key. A 401 on the very first request right after step 3 can also mean the key has not yet reached the gateway's applied configuration — see the propagation note in Step 3. 403: allowed_models on the caller key does not contain this alias.401: re-read caller-key.txt, retry after a few seconds, or create a replacement key as in step 3. 403: PATCH the caller key with allowed_models containing the id from step 2.
The gateway returns 404 with "type": "model_not_found".The model field carries something that is not a configured alias — usually the upstream model id (gpt-5.6-sol) instead of the alias (gpt-5.6-sol-prod).Send the alias's display_name. GET /environments/${ENV_ID}/models lists the aliases this environment holds.
The request reaches OpenAI and comes back as an authentication failure: upstream 4xx responses keep their client-visible status class, and native OpenAI errors keep their own fields.The api_key stored on the provider key is not a valid OpenAI key. No read endpoint returns it, so it cannot be inspected — only replaced.PATCH /provider_keys/${PROVIDER_KEY_ID} with a new api_key, then retry. Every AISIX error type and status is in Headers and Error Codes.
  • Model Aliases — routing, retry behaviour and cost metadata on the alias created here.
  • Provider Keys — how the credential is stored, rotated and shared between environments.
  • Resources File — the declarative provider_keys, models and api_keys entries an open-source AISIX gateway loads instead of these Admin API calls.
  • Provider Compatibility — which proxy endpoints every provider serves, and the provider-specific boundaries.
  • Azure OpenAI — the Azure-hosted deployment, which is a different provider and a different credential shape.