Official Python SDK for the Apertis AI API.
pip install apertisfrom apertis import Apertis
client = Apertis(api_key="your-api-key")
# Or set APERTIS_API_KEY environment variable
response = client.chat.completions.create(
model="gpt-5.4",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
]
)
print(response.choices[0].message.content)- Sync and Async Support: Both synchronous and asynchronous clients
- Streaming: Real-time streaming for chat completions
- Tool Calling: Function/tool calling support
- Embeddings: Text embedding generation
- Vision/Image: Analyze images with multimodal models
- Audio I/O: Audio input and output support
- Audio API: Text to speech, transcription and translation (
client.audio) - Video: Video content analysis
- Web Search: Real-time web search integration
- Context Compression: Compress long conversation history to save tokens
- Reasoning Mode: Chain-of-thought reasoning support
- Extended Thinking: Deep thinking for complex problems
- Messages API: Anthropic-native message format
- Responses API: OpenAI Responses API format
- Rerank API: Document reranking for RAG (not currently available, see below)
- Models API: List and retrieve available models
- Type Hints: Full type annotations for IDE support
- Automatic Retries: Built-in retry logic for transient errors
from apertis import Apertis
client = Apertis()
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "What is the capital of France?"}],
temperature=0.7,
max_tokens=100,
)
print(response.choices[0].message.content)
print(f"Tokens used: {response.usage.total_tokens}")stream = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Tell me a story"}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)Analyze images using multimodal models with the convenience method:
# Using image URL
response = client.chat.completions.create_with_image(
model="gpt-5.4",
prompt="What is in this image?",
image="https://example.com/photo.jpg",
)
print(response.choices[0].message.content)
# Using local file (automatically base64 encoded)
response = client.chat.completions.create_with_image(
model="gpt-5.4",
prompt="Describe this image",
image="/path/to/local/image.png",
)
# Multiple images
response = client.chat.completions.create_with_image(
model="gpt-5.4",
prompt="Compare these images",
image=["https://example.com/img1.jpg", "https://example.com/img2.jpg"],
system="Be detailed in your comparison.",
)Or use the standard API format:
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{"type": "image_url", "image_url": {"url": "https://example.com/cat.jpg"}}
]
}]
)# Audio input
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "What does this audio say?"},
{"type": "input_audio", "input_audio": {"data": "base64_audio_data", "format": "wav"}}
]
}]
)
# Audio output
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Say hello in a friendly voice"}],
modalities=["text", "audio"],
audio={"voice": "alloy", "format": "wav"},
)
print(response.choices[0].message.audio.transcript)# Text to speech
speech = client.audio.speech.create(
model="gpt-4o-mini-tts",
input="Hello from Apertis!",
voice="alloy",
)
speech.write_to_file("hello.mp3")
# Speech to text: pass a path, an open file, bytes, or (filename, bytes)
transcript = client.audio.transcriptions.create(file="meeting.mp3", model="whisper-1")
print(transcript.text)
# Subtitles come back as plain text
srt = client.audio.transcriptions.create(
file="meeting.mp3", model="whisper-1", response_format="srt"
)
# Translate speech into English text
translation = client.audio.translations.create(file="bonjour.m4a", model="whisper-1")
print(translation.text)Enable real-time web search for a non-free model by appending :web to its model ID.
Use the documented result-count and content-length fields, then read the returned sources:
response = client.chat.completions.create(
model="gpt-5.5:web",
messages=[{"role": "user", "content": "Latest Python releases?"}],
web_results_count=5,
web_content_length="medium",
)
for source in response.web_sources or []:
print(f"{source.title}: {source.url}")The legacy web_search_options argument and message-level annotations remain
available for existing clients. Do not combine the legacy options with
web_results_count or web_content_length in one request.
Enable chain-of-thought reasoning for complex problems:
response = client.chat.completions.create(
model="glm-5.1",
messages=[{"role": "user", "content": "How many r's are in strawberry?"}],
reasoning={"enabled": True, "effort": "high"},
)
# Or use shorthand
response = client.chat.completions.create(
model="glm-5.1",
messages=[{"role": "user", "content": "Solve this math problem..."}],
reasoning_effort="high",
)Use extended thinking for deep analysis:
response = client.chat.completions.create(
model="gemini-3-pro-preview",
messages=[{"role": "user", "content": "Analyze this complex problem..."}],
thinking={"type": "enabled"},
)
# With custom thinking budget
response = client.chat.completions.create(
model="gemini-3-pro-preview",
messages=[{"role": "user", "content": "Deep analysis needed..."}],
extra_body={
"google": {"thinking_config": {"thinking_budget": 10240}}
},
)Reduce token usage in long conversations by compressing older message history:
# Chat Completions with compression
response = client.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "user", "content": "Explain distributed systems"},
{"role": "assistant", "content": "Distributed systems are..."},
{"role": "user", "content": "Summarize the key points"},
],
compression={
"enabled": True,
"strategy": "aggressive", # "on", "conservative", or "aggressive"
"threshold": 8000, # Token threshold to trigger compression
"keep_turns": 6, # Recent turns to always preserve
"model": "auto", # Compression model ("auto" or specific model)
},
)
# Minimal config — just enable it
response = client.chat.completions.create(
model="gpt-4.1-mini",
messages=[{"role": "user", "content": "Hello!"}],
compression={"enabled": True},
)
# Also works with Messages API and Responses API
message = client.messages.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=1024,
compression={"enabled": True, "strategy": "conservative"},
)import asyncio
from apertis import AsyncApertis
async def main():
client = AsyncApertis()
response = await client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)
await client.close()
asyncio.run(main())async def stream_example():
client = AsyncApertis()
stream = await client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Tell me a joke"}],
stream=True,
)
async for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
await client.close()response = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city name"
}
},
"required": ["location"]
}
}
}],
tool_choice="auto",
parallel_tool_calls=True, # let the model call several tools in one turn
)
if response.choices[0].message.tool_calls:
for tool_call in response.choices[0].message.tool_calls:
print(f"Function: {tool_call.function.name}")
print(f"Arguments: {tool_call.function.arguments}")response = client.embeddings.create(
model="text-embedding-3-small",
input="Hello, world!",
)
embedding = response.data[0].embedding
print(f"Embedding dimension: {len(embedding)}")response = client.embeddings.create(
model="text-embedding-3-small",
input=["Hello", "World", "How are you?"],
)
for item in response.data:
print(f"Index {item.index}: {len(item.embedding)} dimensions")List and retrieve available models:
# List all models
models = client.models.list()
for model in models.data:
print(f"{model.id} - owned by {model.owned_by}")
# Retrieve specific model
model = client.models.retrieve("gpt-5.4")
print(f"Model: {model.id}, Created: {model.created}")Use Anthropic-native message format:
message = client.messages.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "Hello, Claude!"}],
max_tokens=1024,
system="You are a helpful assistant.",
)
print(message.content[0].text)
print(f"Stop reason: {message.stop_reason}")With tool use:
message = client.messages.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "What's the weather in Paris?"}],
max_tokens=1024,
tools=[{
"name": "get_weather",
"description": "Get weather for a location",
"input_schema": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"]
}
}],
)
if message.stop_reason == "tool_use":
for block in message.content:
if block.type == "tool_use":
print(f"Tool: {block.name}, Input: {block.input}")Streaming and extended thinking:
stream = client.messages.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "What is 27 * 453?"}],
max_tokens=4096,
thinking={"type": "enabled", "budget_tokens": 2048},
stream=True,
)
for event in stream:
if event.type == "content_block_delta":
if event.delta.type == "thinking_delta":
print(event.delta.thinking, end="")
elif event.delta.type == "text_delta":
print(event.delta.text, end="")Without stream, thinking arrives as thinking blocks in message.content, before
the text. extra_body={...} merges extra fields into the top level of the request.
Use OpenAI Responses API format for advanced use cases:
response = client.responses.create(
model="gpt-5.4",
input="Write a haiku about programming",
)
print(response.output_text)
# `output` also holds reasoning and function_call items, so iterate by type
for item in response.output:
print(item.type)
# Structured input uses the Responses API part types
response = client.responses.create(
model="gpt-5.4",
input=[{
"type": "message",
"role": "user",
"content": [
{"type": "input_text", "text": "What is in this image?"},
{"type": "input_image", "image_url": "https://example.com/cat.png"},
],
}],
)
# Streaming: events keep their Responses API type and fields
stream = client.responses.create(
model="gpt-5.4",
input="Write a haiku about programming",
stream=True,
)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
# With reasoning
response = client.responses.create(
model="gpt-5.4",
input="Solve this complex problem...",
reasoning={"effort": "high"},
)Not currently available. The Apertis API does not serve
POST /v1/rerankyet, soclient.rerank.create()returns a 404NotFoundErrorand emits aDeprecationWarning. The interface below is kept for when the endpoint ships.
results = client.rerank.create(
model="BAAI/bge-reranker-v2-m3",
query="What is machine learning?",
documents=[
"Machine learning is a subset of AI.",
"The weather is nice today.",
"Deep learning uses neural networks.",
],
top_n=2,
)
for result in results.results:
print(f"Index {result.index}: Score {result.relevance_score}")
# With document text in results
results = client.rerank.create(
model="BAAI/bge-reranker-v2-m3",
query="Python programming",
documents=["Python is great", "Java is popular"],
return_documents=True,
)
for result in results.results:
print(f"{result.document}: {result.relevance_score}")from apertis import (
Apertis,
ApertisError,
APIError,
AuthenticationError,
RateLimitError,
NotFoundError,
)
client = Apertis()
try:
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Hello!"}]
)
except AuthenticationError as e:
print(f"Invalid API key: {e.message}")
except RateLimitError as e:
print(f"Rate limited. Status: {e.status_code}")
except NotFoundError as e:
print(f"Resource not found: {e.message}")
except APIError as e:
print(f"API error {e.status_code}: {e.message}")
except ApertisError as e:
print(f"Error: {e.message}")client = Apertis(
api_key="your-api-key", # Or use APERTIS_API_KEY env var
base_url="https://api.apertis.ai/v1", # Custom base URL
timeout=60.0, # Request timeout in seconds
max_retries=2, # Number of retries for failed requests
default_headers={"X-Custom": "value"}, # Additional headers
)with Apertis() as client:
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Hello!"}]
)
# Client is automatically closed
# Async version
async with AsyncApertis() as client:
response = await client.chat.completions.create(...)Any model available on Apertis AI, including:
- OpenAI:
gpt-5.4,gpt-5.1,gpt-5.3-codex - Anthropic:
claude-opus-4-6,claude-sonnet-4-6,claude-haiku-4.5 - Google:
gemini-3-pro-preview,gemini-3-flash-preview,gemini-2.5-flash-preview - Other:
glm-5.1,minimax-m2.7, and 500+ more models
glm-5.1(with reasoning enabled)
gpt-5-search-api
text-embedding-3-small,text-embedding-3-large
Listed in the model catalog, but not callable until the rerank endpoint ships.
BAAI/bge-reranker-v2-m3Qwen/Qwen3-Reranker-0.6B,Qwen/Qwen3-Reranker-4B,Qwen/Qwen3-Reranker-8B
- Python 3.9+
- httpx
- pydantic
The repository publishes only from the manual Publish to PyPI workflow. Configure
PyPI Trusted Publishing for the apertis-ai/python-sdk repository, workflow
.github/workflows/publish.yml, and the pypi environment before dispatching it with
the publish confirmation value.
- Type Responses API input parts:
ResponseInputText(input_text),ResponseInputImage(input_image) andResponseInputFile(input_file). The oldtext/imagepart types still type-check. - Add
budget_tokenstoThinkingConfig. - Add
streamtoresponses.create():stream=TruereturnsResponseStreamEventevents, and anerrorevent raisesAPIError.
- Add
client.audio:speech.create()(binary audio withwrite_to_file()),transcriptions.create()andtranslations.create()(multipart uploads). - Add
stream,thinkingandextra_bodytomessages.create(), with typed stream events andthinking/redacted_thinkingcontent blocks. - Streamed requests that fail with an HTTP error now raise
APIError(previouslyhttpx.ResponseNotRead). - Parse real Responses API output:
output_textandrefusalmessage parts,reasoningandfunction_callitems, and unmodelled item types (kept asResponseUnknownOutputItem). AddResponse.output_textand thequeued/in_progressstatuses. - Add
parallel_tool_callsandmax_completion_tokenstochat.completions.create(). - Mark
rerank.create()as unavailable: it emits aDeprecationWarning, because the Apertis API does not servePOST /v1/rerank.
- Add the current Apertis Web Search request fields and typed top-level sources while retaining legacy Web Search options for compatibility.
- Updated model identifiers to latest versions (gpt-5.4, claude-opus-4-6, claude-sonnet-4-6, glm-5.1)
- Added support for gpt-5.3-codex and minimax-m2.7 models
- Removed deprecated model references (gpt-4o, o1-pro, gpt-5-pro)
- Fixed missing compression parameter in AsyncCompletions overloads
- Added Vision/Image support with
create_with_image()convenience method - Added Audio input/output support
- Added Video content support
- Added Web Search with
create_with_web_search()convenience method - Added Reasoning Mode support
- Added Extended Thinking (Gemini) support
- Added Models API (
client.models.list(),client.models.retrieve()) - Added Responses API (
client.responses.create()) - Added Messages API (
client.messages.create()) for Anthropic format - Added Rerank API (
client.rerank.create()) - Added helper utilities for base64 encoding
- Fixed API response compatibility issues
- Initial release with Chat Completions, Streaming, Tool Calling, and Embeddings
Apache 2.0 - see LICENSE for details.