AI Commons

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.

Table of Contents

Base URL

Your API Base URL is unique to your deployment. To find it:

  1. Log in to the web app
  2. Click your profile icon (top-right) to open the Settings menu
  3. Select API Keys
  4. 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
Both /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. See the Authentication guide for key handling, security practices, and authentication errors.

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 string | object No "auto", "required", a named function, or "none" — see the warning below
top_p number No Nucleus sampling, 0.0–1.0. Not honoured by every model; see X-Parameter-Notice
top_k integer No ≥ 0. Not honoured by every model; see X-Parameter-Notice
reasoning_effort string No "low", "medium" or "high". Any other value, including OpenAI's "minimal", is rejected with 422 rather than silently reshaped. What the request actually did with it is reported in X-Reasoning-Notice
stream_options object No Send {"include_usage": true} to receive a final usage chunk on a streamed response. Only meaningful with "stream": true
conversation_id string No 26-character ULID naming a server-side conversation to continue. See Persistent Conversations. A malformed value returns 400
user string No Accepted for OpenAI client compatibility and currently ignored
Warning: tool_choice: "none" behaves differently depending on the turn. On a first turn it is honoured — no tool specifications are shown to the model, so no tool can be called. On a follow-up turn whose messages already contain tool calls or tool results, it cannot be honoured and the request is refused with 400 and an error.code of tool_choice_none_unsupported; Bedrock requires a tool configuration once tool history is present, and its choice union has no "none". Omitting tools on such a turn does not avoid the error. Send "auto", or omit tool_choice entirely, to continue a conversation that has already used tools.

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 direct Bedrock model IDs and OpenAI-compatible aliases available to the authenticated user. Any returned id can be supplied in the model field of a chat completion request.

Authoritative catalog: Use this endpoint at runtime to discover the model IDs currently enabled for your deployment. Entries owned by bedrock are direct model IDs; entries owned by openai-alias are compatibility aliases resolved by the service.

Response

{
  "object": "list",
  "data": [
    {
      "id": "amazon-nova-lite",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "amazon-nova-micro",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "amazon-nova-pro",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "claude-v4.5-haiku",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "claude-v4.6-opus",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "claude-v4.6-sonnet",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "claude-v5-opus",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "claude-v5-sonnet",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "claude-v5.5-haiku",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "claude-v5.5-opus",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "claude-v5.5-sonnet",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "llama-4-maverick-17b-instruct",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "llama-4-scout-17b-instruct",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "moonshotai-kimi-k3",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "nvidia-nemotron-super-3-120b",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "openai-gpt-6-luna",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "openai-gpt-oss-120b",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "openai-gpt-oss-20b",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "qwen3-32b",
      "object": "model",
      "created": 1789051979,
      "owned_by": "bedrock"
    },
    {
      "id": "gpt-3.5-turbo",
      "object": "model",
      "created": 1789051979,
      "owned_by": "openai-alias"
    },
    {
      "id": "gpt-3.5-turbo-16k",
      "object": "model",
      "created": 1789051979,
      "owned_by": "openai-alias"
    },
    {
      "id": "gpt-4",
      "object": "model",
      "created": 1789051979,
      "owned_by": "openai-alias"
    },
    {
      "id": "gpt-4-turbo",
      "object": "model",
      "created": 1789051979,
      "owned_by": "openai-alias"
    },
    {
      "id": "gpt-4o",
      "object": "model",
      "created": 1789051979,
      "owned_by": "openai-alias"
    }
  ]
}

GET /token-usage

Retrieve the authenticated user's current token quota and usage statistics. This endpoint returns the same authoritative counter that triggers 429 rate limit errors.

Note: Token quotas are tracked per-user, not per-key. All API keys belonging to the same user share the same monthly quota. This endpoint call itself is not metered against your quota.

Response Schema

Field Type Description
limit integer Total monthly token quota (prompt + completion tokens)
used integer Tokens consumed in the current billing period
remaining integer Tokens available (floors at 0 when over-limit)
reset_at string ISO 8601 UTC timestamp when quota resets (first of next month)

Example Response

{
  "limit": 1000000,
  "used": 245680,
  "remaining": 754320,
  "reset_at": "2026-09-01T00:00:00Z"
}

Use Cases

  • Dashboard displays: Show quota usage in your application UI
  • Pre-flight checks: Verify sufficient quota before making expensive requests
  • Alerting: Trigger notifications when approaching quota limits
  • Budget management: Track token consumption across your organization

Important Notes

  • This is the authoritative counter - the same one that triggers 429 errors when exceeded
  • remaining will never be negative; it floors at 0 when you're over-limit
  • Quota resets automatically at midnight UTC on the first day of each month
  • All API keys for a user share the same quota (user-level, not key-level)

Python Example

from openai import OpenAI

client = OpenAI(
    base_url="YOUR_BASE_URL",
    api_key="your-api-key",
)

# Using the client's built-in get method
response = client.get("/token-usage")
print(f"Used: {response['used']:,} / {response['limit']:,}")
print(f"Remaining: {response['remaining']:,}")
print(f"Resets: {response['reset_at']}")

# Calculate percentage used
percent_used = (response['used'] / response['limit']) * 100
print(f"Quota usage: {percent_used:.1f}%")

cURL Example

curl YOUR_BASE_URL/token-usage \
  -H "Authorization: Bearer your-api-key"

Node.js Example

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "YOUR_BASE_URL",
  apiKey: "your-api-key",
});

// Fetch token usage
const response = await fetch(`${client.baseURL}/token-usage`, {
  headers: {
    Authorization: `Bearer ${client.apiKey}`,
  },
});

const usage = await response.json();
console.log(`Used: ${usage.used.toLocaleString()} / ${usage.limit.toLocaleString()}`);
console.log(`Remaining: ${usage.remaining.toLocaleString()}`);
console.log(`Resets: ${usage.reset_at}`);

Error Responses

HTTP Status Type Cause
401 authentication_error Missing or invalid API key
403 permission_error API key lacks required permissions
500 api_error Internal server error

Model Resolution

The model field accepts multiple forms:

When resolving the model field, the platform checks the following forms in order until one matches:

  1. Composite bot/model identifier
  2. OpenAI alias
  3. Raw Bedrock model
  4. 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 Owner Resolution
gpt-4 openai-alias claude-v4.6-sonnet
gpt-4o openai-alias claude-v4.6-sonnet
gpt-4-turbo openai-alias claude-v4.6-sonnet
gpt-3.5-turbo openai-alias claude-v4.5-haiku
gpt-3.5-turbo-16k openai-alias Resolved server-side; the target is not exposed by GET /models
Alias availability: Treat GET /models as authoritative. Alias targets may change without changing the client-facing alias.

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."

Reading Conversations

Three read-only endpoints return what the server stored for a conversation created with conversation_id. A conversation that does not exist, or belongs to another account, returns 404 — the two are indistinguishable, so no existence is leaked.

Endpoint Returns
GET /conversations/{conversation_id} The conversation itself
GET /conversations/{conversation_id}/items A page of items, newest thread order preserved
GET /conversations/{conversation_id}/items/{item_id} A single item

Paging

Query parameter Notes
limit 1–100, default 100. Out of range is refused with 422
after The id of the last item you already have, exclusive. An id that is not on this thread returns 400 with param of after

The envelope carries object, data, first_id, last_id and has_more. A page is bounded by limit and by 1 MiB of response body, whichever binds first; either way the page reports has_more: true so you resume from last_id and lose nothing.

What these endpoints do not return: a stored input_image or input_file part carries no body — the bytes are not served over this API. thinking_log is never returned. Tool calls, tool results and reasoning are omitted from an item's content; OpenAI models those as sibling items, which this API does not emit yet. An API key reads every conversation on its owner's account, including ones created in the web app — there is no per-key scoping.

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}")

Response Headers

Each of these carries a report you cannot get from the response body. All are exposed for cross-origin reads, so a browser client can read them too.

Header When it is sent Meaning
X-Conversation-Id Streamed responses, and errors that had a conversation The conversation the request wrote. The completion id is not a conversation handle, so on a streamed response this is the only way to learn the id the server used when you did not supply one
X-Parameter-Notice When a sampling parameter was not honoured Which of temperature, top_p and top_k the model did not apply, and whether it was dropped or adjusted. Carries the code parameters_dropped. Models differ in what they accept, so a request can succeed with a parameter silently unused — this is how you find out
X-Reasoning-Notice When a reasoning request was changed What the request did with reasoning_effort — including that reasoning was not asked of the model at all, and why
x-should-retry On the buffered 504 false. The 290-second buffered ceiling was exceeded; retrying the same request will not help. See Limitations

Limitations

Constraint Value/Note
Request timeout (streaming) 900 seconds for a streamed response ("stream": true) — the API Gateway integration timeout, with the handler Lambda configured to the same 900 seconds
Request timeout (buffered) 290 seconds for a buffered response ("stream": false). That fork emits nothing until the answer is whole, so this shorter application deadline binds and the 900-second streaming ceiling above is unreachable. Exceeding it returns 504 with an error.code of request_ceiling_exceeded and x-should-retry: false; a conversation the request stored is left incomplete, and the next request naming it continues from its last finished turn
Token quota Per-user monthly limit (configurable by admin)
Image formats JPEG, PNG, GIF, WebP supported
Document formats PDF, CSV, DOC, DOCX, XLS, XLSX, HTML, TXT, MD — sent as input_file parts
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
Warning: A file whose extension is not one of the document formats listed above is still accepted, but it is sent to the model as plain text instead of as a document, silently and with no error. That is useful for text-based files such as .json, .py, .ts or .yaml, but an unlisted binary format will be read as text and produce unusable results. Convert it to a listed format first.

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 tokens
  • cacheReadInputTokens — tokens retrieved from cache
  • cacheWriteInputTokens — 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.