Skip to main content

Apertis Python SDK

PyPI version Python Versions License

Official Python SDK for the Apertis AI API.

Installation

pip install apertis

Quick Start

from 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)

Features

  • 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

Usage

Chat Completions

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

Streaming

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)

Vision / Image Analysis

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/Output

# 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)

Speech, Transcription and Translation

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

Reasoning Mode

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

Extended Thinking (Gemini)

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

Context Compression

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

Async Usage

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 Streaming

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()

Tool Calling

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

Embeddings

response = client.embeddings.create(
    model="text-embedding-3-small",
    input="Hello, world!",
)

embedding = response.data[0].embedding
print(f"Embedding dimension: {len(embedding)}")

Batch Embeddings

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

Models API

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

Messages API (Anthropic Format)

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.

Responses API

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

Rerank API

Not currently available. The Apertis API does not serve POST /v1/rerank yet, so client.rerank.create() returns a 404 NotFoundError and emits a DeprecationWarning. 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}")

Error Handling

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

Configuration

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
)

Context Manager

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(...)

Available Models

Any model available on Apertis AI, including:

Chat Models

  • 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

Reasoning Models

  • glm-5.1 (with reasoning enabled)

Search Models

  • gpt-5-search-api

Embedding Models

  • text-embedding-3-small, text-embedding-3-large

Rerank Models

Listed in the model catalog, but not callable until the rerank endpoint ships.

  • BAAI/bge-reranker-v2-m3
  • Qwen/Qwen3-Reranker-0.6B, Qwen/Qwen3-Reranker-4B, Qwen/Qwen3-Reranker-8B

Requirements

  • Python 3.9+
  • httpx
  • pydantic

Publishing

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.

Changelog

v0.4.1

  • Type Responses API input parts: ResponseInputText (input_text), ResponseInputImage (input_image) and ResponseInputFile (input_file). The old text / image part types still type-check.
  • Add budget_tokens to ThinkingConfig.
  • Add stream to responses.create(): stream=True returns ResponseStreamEvent events, and an error event raises APIError.

v0.4.0

  • Add client.audio: speech.create() (binary audio with write_to_file()), transcriptions.create() and translations.create() (multipart uploads).
  • Add stream, thinking and extra_body to messages.create(), with typed stream events and thinking / redacted_thinking content blocks.
  • Streamed requests that fail with an HTTP error now raise APIError (previously httpx.ResponseNotRead).
  • Parse real Responses API output: output_text and refusal message parts, reasoning and function_call items, and unmodelled item types (kept as ResponseUnknownOutputItem). Add Response.output_text and the queued / in_progress statuses.
  • Add parallel_tool_calls and max_completion_tokens to chat.completions.create().
  • Mark rerank.create() as unavailable: it emits a DeprecationWarning, because the Apertis API does not serve POST /v1/rerank.

v0.3.0

  • Add the current Apertis Web Search request fields and typed top-level sources while retaining legacy Web Search options for compatibility.

v0.2.2

  • 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)

v0.2.1

  • Fixed missing compression parameter in AsyncCompletions overloads

v0.2.0

  • 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

v0.1.1

  • Fixed API response compatibility issues

v0.1.0

  • Initial release with Chat Completions, Streaming, Tool Calling, and Embeddings

License

Apache 2.0 - see LICENSE for details.

Metadata

Release files for apertis 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for apertis 0.4.1
File Size Uploaded Provenance Actions
apertis-0.4.1.tar.gz 57.2 kB With provenance Details for apertis-0.4.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for apertis 0.4.1
File Interpreter ABI Platform Actions
apertis-0.4.1-py3-none-any.whl Python 3 none any Details for apertis-0.4.1-py3-none-any.whl

Total release size: 100.3 kB

Release files / apertis-0.4.1.tar.gz

Download URL apertis-0.4.1.tar.gz
Size 57.2 kB
Tags Source
SHA-256 checksum
How to use checksums
53f2194a457ab213e43753b07e0b3fe04735bc9aa9d51cb34fb685156c61c2ea
BLAKE2b-256 checksum
How to use checksums
34442576901b0b18dc03a6560b5ee1bf441a884d98e01cfd585878969c4d6220
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / apertis-0.4.1-py3-none-any.whl

Download URL apertis-0.4.1-py3-none-any.whl
Size 43.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d2ddf97f101917911dfaedc46707db4096041c832ed0318e7ab8f681c8ed5bdf
BLAKE2b-256 checksum
How to use checksums
7e3f10be80cde9f61b859bc9dc2a0a5fd64c782355b14db89f2b1731185790ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Security for apertis 0.4.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for apertis-0.4.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page