OpenAI API Reference
The UCSB AI Commons exposes an OpenAI-compatible REST API that lets you use standard OpenAI client libraries (Python, Node.js, etc.) and third-party tools (AnythingLLM, Chatbox, Continue.dev, GitHub Copilot, etc.) backed by AWS Bedrock.
Base URL
Your API Base URL is unique to your deployment. To find it:
- Log in to the web app
- Click your profile icon (top-right) to open the Settings menu
- Select API Keys
- Your Base URL is displayed at the top of the page
It will look something like:
https://<your-api-id>.execute-api.us-east-1.amazonaws.com/v1
/v1/chat/completions and /chat/completions are accepted — set your client's base_url to the value shown on the API Keys page (it already includes /v1).
Authentication
All requests require an API key passed via one of two headers:
| Header | Format | Notes |
|---|---|---|
| Authorization | Bearer <api-key> |
OpenAI standard — recommended for SDK clients |
| x-api-key | <api-key> |
AWS-style alternative |
API keys are provisioned through the web UI. Navigate to Settings → API Keys to mint, view, and revoke keys.
POST /chat/completions
Create a chat completion. Supports synchronous and streaming (SSE) responses.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| model | string | Yes | Model identifier. See Model Resolution |
| messages | array | Yes | Conversation history |
| temperature | number | No | Sampling temperature (0.0–2.0) |
| max_tokens | number | No | Maximum output tokens |
| stream | boolean | No | If true, returns SSE token stream. Default: false |
| tools | array | No | Function/tool definitions for client-side tool calling |
tool_choice: "none". If provided, the platform silently falls back to "auto", meaning the model may still call a tool. Omit tools entirely to guarantee no tool calls.Response (Buffered)
{
"id": "chatcmpl-01JXYZ123456789ABCDEFGH",
"object": "chat.completion",
"created": 1718000000,
"model": "claude-v4.6-sonnet",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I help you today?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 9,
"total_tokens": 21
}
}
GET /models
List all models available to the authenticated user.
Response
{
"object": "list",
"data": [
{
"id": "claude-v4.6-sonnet",
"object": "model",
"created": 1718000000,
"owned_by": "bedrock"
},
{
"id": "gpt-4",
"object": "model",
"created": 1718000000,
"owned_by": "openai-alias"
}
]
}
Model Resolution
The model field accepts multiple forms:
When resolving the model field, the platform checks the following forms in order until one matches:
- Composite bot/model identifier
- OpenAI alias
- Raw Bedrock model
- Bot ULID
Raw Bedrock Models
Talk directly to a Bedrock model:
{
"model": "claude-v4.6-sonnet"
}
OpenAI Aliases
Drop-in compatibility for clients that hardcode OpenAI model names:
| Alias | Maps To |
|---|---|
| gpt-4 | claude-v4.6-sonnet |
| gpt-4o | claude-v4.6-sonnet |
| gpt-4-turbo | claude-v4.6-sonnet |
| gpt-3.5-turbo | claude-v4.5-haiku |
Python Examples
Basic Usage
from openai import OpenAI
client = OpenAI(
base_url="YOUR_BASE_URL",
api_key="your-api-key",
)
response = client.chat.completions.create(
model="claude-v4.6-sonnet",
messages=[
{"role": "user", "content": "Explain quantum computing in simple terms."}
],
)
print(response.choices[0].message.content)
Streaming
from openai import OpenAI
client = OpenAI(
base_url="YOUR_BASE_URL",
api_key="your-api-key",
)
stream = client.chat.completions.create(
model="claude-v4.6-sonnet",
messages=[
{"role": "user", "content": "Write a haiku about programming."}
],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
print()
With Images
import base64
from openai import OpenAI
client = OpenAI(
base_url="YOUR_BASE_URL",
api_key="your-api-key",
)
# Base64-encoded image
with open("chart.png", "rb") as f:
image_data = base64.b64encode(f.read()).decode("utf-8")
response = client.chat.completions.create(
model="claude-v4.6-sonnet",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What trends do you see in this chart?"},
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{image_data}"}
}
]
}
],
)
print(response.choices[0].message.content)
Using a Bot
from openai import OpenAI
client = OpenAI(
base_url="YOUR_BASE_URL",
api_key="your-api-key",
)
# Use a bot's system prompt, knowledge base, and guardrails
response = client.chat.completions.create(
model="01KQ07QSSST2TSZV5WT2YQ6BPM", # Your bot's ULID
messages=[
{"role": "user", "content": "What is the university's leave policy?"}
],
)
print(response.choices[0].message.content)
Bot with Model Override
from openai import OpenAI
client = OpenAI(
base_url="YOUR_BASE_URL",
api_key="your-api-key",
)
# Use the bot's config but force a specific underlying model
response = client.chat.completions.create(
model="01KQ07QSSST2TSZV5WT2YQ6BPM/claude-v4.5-haiku",
messages=[
{"role": "user", "content": "Summarize the key points."}
],
)
print(response.choices[0].message.content)
Custom Parameters
from openai import OpenAI
client = OpenAI(
base_url="YOUR_BASE_URL",
api_key="your-api-key",
)
response = client.chat.completions.create(
model="claude-v4.6-sonnet",
messages=[
{"role": "user", "content": "Generate a creative story opening."}
],
temperature=1.6, # 0.0–2.0 (mapped internally to 0.0–1.0 for Bedrock)
max_tokens=500,
top_p=0.95,
)
print(response.choices[0].message.content)
JavaScript/Node.js Examples
Basic Usage
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "YOUR_BASE_URL",
apiKey: "your-api-key",
});
const response = await client.chat.completions.create({
model: "claude-v4.6-sonnet",
messages: [{ role: "user", content: "Hello!" }],
});
console.log(response.choices[0].message.content);
Streaming
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "YOUR_BASE_URL",
apiKey: "your-api-key",
});
const stream = await client.chat.completions.create({
model: "claude-v4.6-sonnet",
messages: [{ role: "user", content: "Tell me a short story." }],
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) process.stdout.write(content);
}
console.log();
Multimodal (Image)
import OpenAI from "openai";
import fs from "fs";
const client = new OpenAI({
baseURL: "YOUR_BASE_URL",
apiKey: "your-api-key",
});
// Read image and convert to base64
const imageData = fs.readFileSync("chart.png", "base64");
const response = await client.chat.completions.create({
model: "claude-v4.6-sonnet",
messages: [
{
role: "user",
content: [
{ type: "text", text: "What trends do you see?" },
{
type: "image_url",
image_url: { url: `data:image/png;base64,${imageData}` },
},
],
},
],
});
console.log(response.choices[0].message.content);
Document Attachment
import OpenAI from "openai";
import fs from "fs";
const client = new OpenAI({
baseURL: "YOUR_BASE_URL",
apiKey: "your-api-key",
});
// Read PDF and convert to base64
const pdfData = fs.readFileSync("report.pdf", "base64");
const response = await client.chat.completions.create({
model: "claude-v4.6-sonnet",
messages: [
{
role: "user",
content: [
{ type: "text", text: "Summarize this report." },
{
type: "input_file",
filename: "report.pdf",
file_data: `data:application/pdf;base64,${pdfData}`,
},
],
},
],
});
console.log(response.choices[0].message.content);
cURL Examples
Basic Request
curl YOUR_BASE_URL/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-v4.6-sonnet",
"messages": [
{"role": "user", "content": "What is 2 + 2?"}
]
}'
Streaming Request
curl YOUR_BASE_URL/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-N \
-d '{
"model": "claude-v4.6-sonnet",
"messages": [
{"role": "user", "content": "Tell me a joke."}
],
"stream": true
}'
List Models
curl YOUR_BASE_URL/models \
-H "Authorization: Bearer your-api-key"
Multimodal (Image)
# First, encode your image to base64
IMAGE_B64=$(base64 -i chart.png)
curl YOUR_BASE_URL/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-v4.6-sonnet",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "Analyze this chart"},
{
"type": "image_url",
"image_url": {"url": "data:image/png;base64,'"$IMAGE_B64"'"}
}
]
}
]
}'
Persistent Conversations
Supply a conversation_id (26-character ULID) to continue a server-side conversation. Only the new user message is needed — prior history is stored in the database.
Python
from openai import OpenAI
from ulid import ULID
client = OpenAI(
base_url="YOUR_BASE_URL",
api_key="your-api-key",
)
# Generate a conversation ID once and reuse it
conv_id = str(ULID())
# Turn 1
response1 = client.chat.completions.create(
model="01KQ07QSSST2TSZV5WT2YQ6BPM", # bot ULID
messages=[{"role": "user", "content": "My name is Alice."}],
extra_body={"conversation_id": conv_id},
)
print(response1.choices[0].message.content)
# Turn 2 — server remembers Alice from turn 1
response2 = client.chat.completions.create(
model="01KQ07QSSST2TSZV5WT2YQ6BPM",
messages=[{"role": "user", "content": "What is my name?"}],
extra_body={"conversation_id": conv_id},
)
print(response2.choices[0].message.content) # "Your name is Alice."
Function Calling
The API supports the full OpenAI function-calling pattern. Both buffered (stream: false) and streaming (stream: true) modes are supported.
Example: Buffered Function Calling
from openai import OpenAI
import json
client = OpenAI(
base_url="YOUR_BASE_URL",
api_key="your-api-key",
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, e.g. Santa Barbara"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
}
]
response = client.chat.completions.create(
model="claude-v4.6-sonnet",
messages=[{"role": "user", "content": "What is the weather in Santa Barbara?"}],
tools=tools,
tool_choice="auto",
)
choice = response.choices[0]
print(f"finish_reason: {choice.finish_reason}") # "tool_calls"
if choice.finish_reason == "tool_calls":
for tc in choice.message.tool_calls:
print(f"Tool: {tc.function.name}")
print(f"Args: {tc.function.arguments}")
# Execute your actual tool logic
tool_result = {"temperature": "72°F", "condition": "Sunny"}
# Send back the full history with the tool result
response2 = client.chat.completions.create(
model="claude-v4.6-sonnet",
messages=[
{"role": "user", "content": "What is the weather in Santa Barbara?"},
{
"role": "assistant",
"content": None,
"tool_calls": [
{
"id": choice.message.tool_calls[0].id,
"type": "function",
"function": {
"name": "get_weather",
"arguments": choice.message.tool_calls[0].function.arguments,
},
}
],
},
{
"role": "tool",
"tool_call_id": choice.message.tool_calls[0].id,
"content": json.dumps(tool_result),
},
],
tools=tools,
)
print(response2.choices[0].message.content)
# "The weather in Santa Barbara is 72°F and sunny."
Example: Streaming Function Calling
stream = client.chat.completions.create(
model="claude-v4.6-sonnet",
messages=[{"role": "user", "content": "What is the weather in Santa Barbara?"}],
tools=tools,
tool_choice="auto",
stream=True,
)
collected_tool_calls = {}
for chunk in stream:
delta = chunk.choices[0].delta
finish = chunk.choices[0].finish_reason
# Accumulate streamed tool call fragments
if delta.tool_calls:
for tc in delta.tool_calls:
idx = tc.index
if idx not in collected_tool_calls:
collected_tool_calls[idx] = {"id": tc.id, "name": tc.function.name, "arguments": ""}
if tc.function.arguments:
collected_tool_calls[idx]["arguments"] += tc.function.arguments
if finish == "tool_calls":
for idx, tc in collected_tool_calls.items():
print(f"Tool: {tc['name']}, Args: {tc['arguments']}")
Tool Choice Options
| Value | Behavior |
|---|---|
"auto" |
Model decides whether to call a tool (default) |
"required" |
Model must call at least one tool |
"none" |
Model will not call any tools |
{"type": "function", "function": {"name": "get_weather"}} |
Force a specific tool |
Error Handling
All errors follow the OpenAI error envelope format:
{
"error": {
"message": "Invalid API key.",
"type": "authentication_error",
"param": null,
"code": null
}
}
Error Types
| HTTP Status | Type | Common Cause |
|---|---|---|
| 400 | invalid_request_error |
Missing/malformed fields, empty messages, invalid conversation_id |
| 401 | authentication_error |
Missing or invalid API key |
| 403 | permission_error |
Insufficient access rights |
| 404 | not_found_error |
Bot not found or no access |
| 429 | rate_limit_error |
Monthly token quota exceeded |
| 500 | api_error |
Internal server error |
Rate Limit Response
When your monthly token quota is exceeded, the 429 response includes quota details:
{
"error": {
"message": "You have exceeded your token limit. Please upgrade your plan or wait until your quota resets.",
"type": "rate_limit_error",
"code": "rate_limit_exceeded",
"limit": 1000000,
"remaining": 0,
"reset_at": "2026-07-01T00:00:01Z"
}
}
Error Handling Example
from openai import OpenAI, AuthenticationError, RateLimitError, BadRequestError
client = OpenAI(
base_url="YOUR_BASE_URL",
api_key="your-api-key",
)
try:
response = client.chat.completions.create(
model="claude-v4.6-sonnet",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)
except AuthenticationError as e:
print(f"Invalid API key: {e}")
except RateLimitError as e:
print(f"Quota exceeded: {e}")
except BadRequestError as e:
print(f"Bad request: {e}")
Limitations
| Constraint | Value/Note |
|---|---|
| Request timeout | ~30 seconds (API Gateway limit) |
| Token quota | Per-user monthly limit (configurable by admin) |
| Image formats | JPEG, PNG, GIF, WebP supported |
| Document formats | Any format supported by Bedrock Converse (PDF, DOCX, TXT, CSV, XLSX, etc.) |
| Audio content | Not supported — audio parts are silently ignored |
| System messages | Sent to the model's system prompt; a bot's instruction leads and the caller's system text follows. developer is a synonym; multiple are concatenated in order. Ignored in conversation_id mode. |
| conversation_id format | Must be a valid 26-character ULID (Crockford base32) |
| Bot access | Caller must own the bot or have shared access; 404 returned otherwise |
| CORS restrictions | Per-key origin allow-lists can be configured via the API keys UI. Keys without restrictions accept requests from any origin. |
| Prompt caching | Supported automatically on compatible models; token counts include cacheReadInputTokens and cacheWriteInputTokens from Bedrock |
Advanced Features
Per-Key CORS Allow-Lists
API keys can be configured with origin restrictions for browser-based clients. When configured:
- The OPTIONS preflight is handled permissively by the global CORS middleware
- The authenticated request checks the Origin header against the key's allow-list
- Wildcard patterns are supported (e.g.,
https://*.example.com) - A bare
*in the allow-list permits any origin - Requests without an Origin header (server-side SDKs) are not subject to CORS enforcement
Configure allow-lists via Settings → API Keys in the web UI.
Token Usage Accounting
Token counts in the usage response object include:
| Field | Description |
|---|---|
prompt_tokens |
Total input tokens including cache reads and writes |
completion_tokens |
Output tokens generated |
total_tokens |
Sum of prompt and completion tokens |
Prompt Caching: When Bedrock's prompt caching is active (automatically for supported models), prompt_tokens includes:
inputTokens— newly processed tokenscacheReadInputTokens— tokens retrieved from cachecacheWriteInputTokens— tokens written to cache
This ensures accurate billing and quota tracking even when cache hits significantly reduce processing.
Example Response with Caching
{
"id": "chatcmpl-01JXYZ123456789ABCDEFGH",
"object": "chat.completion",
"created": 1718000000,
"model": "claude-v4.6-sonnet",
"choices": [...],
"usage": {
"prompt_tokens": 2500,
"completion_tokens": 150,
"total_tokens": 2650
}
}
In this example, prompt_tokens (2500) is the sum of all input token types. On a subsequent cached request, the breakdown might be:
- 50 newly processed tokens (inputTokens)
- 2450 cache-read tokens (cacheReadInputTokens)
- Total prompt_tokens: 2500
The API abstracts these details — clients see only the aggregate values, while the server tracks the detailed breakdown for billing and performance optimization.