← Back to Blog

Python Quickstart: Call Claude, GPT and Gemini APIs

Zivv••27 min read
PythonSDKquickstart

Python is the default language for working with model APIs. The official SDKs are maintained there first, examples are everywhere, and it suits scripts, prototypes and data pipelines alike. This guide is for developers making their first serious API calls. We set up a clean environment, call Claude, GPT and Gemini using two different SDKs, then add multi-turn conversations, streaming, timeouts, retries and concurrency, ending with a small project layout you can reuse.

Every example points at Zivv, so one key reaches all three providers. If you don't have a key yet, register and add a small balance. If you are curious how a gateway routes your requests, read What Is an AI API Gateway first.

Set Up the Environment

Use Python 3.9 or newer and give the project its own virtual environment:

python3 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install openai anthropic python-dotenv

What each package does:

  • openai: the official OpenAI SDK. Pointed at an OpenAI-compatible endpoint, it can call every model
  • anthropic: the official Anthropic SDK, speaking Claude's native protocol
  • python-dotenv: loads variables from a .env file so keys stay out of your code

Keep Keys in Environment Variables

Hardcoding a key is the most common beginner mistake, and the most expensive. Public repositories are scanned constantly, and a leaked key can be abused within minutes. Put it in a .env file at the project root instead:

# .env - never commit this file
ZIVV_API_KEY=sk-your-key-here
OPENAI_BASE_URL=https://zivv.pro/v1
ANTHROPIC_BASE_URL=https://zivv.pro
echo ".env" >> .gitignore

Call load_dotenv() at startup. Both SDKs also read their own variables automatically: OPENAI_API_KEY and OPENAI_BASE_URL for openai, ANTHROPIC_API_KEY and ANTHROPIC_BASE_URL for anthropic. The examples below pass values explicitly so nothing is hidden.

Approach 1: The openai SDK for Every Model

Zivv's OpenAI-compatible endpoint is https://zivv.pro/v1. Switching providers is just a different model string:

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

client = OpenAI(
    api_key=os.environ["ZIVV_API_KEY"],
    base_url="https://zivv.pro/v1",
)

for model in ["claude-sonnet-5", "gpt-5.5", "gemini-3.5-flash"]:
    resp = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": "You are concise. Answer in two sentences max."},
            {"role": "user", "content": "What is a closure?"},
        ],
    )
    print(model, "=>", resp.choices[0].message.content)
    print("tokens:", resp.usage.prompt_tokens, resp.usage.completion_tokens)

One client, one response shape, three providers, and token counts for each call. Model ids change between releases, so check the Model Hub or GET /v1/models for current ones.

This approach shines when you compare models or route tasks between them. See multi-model routing for patterns. If you are moving an existing project off the official OpenAI API, the migration checklist covers the rest.

Approach 2: The anthropic SDK for Claude

If Claude is your main model and you want native features like extended thinking and tool use, use the Anthropic SDK directly:

import os
from dotenv import load_dotenv
import anthropic

load_dotenv()

client = anthropic.Anthropic(
    api_key=os.environ["ZIVV_API_KEY"],
    base_url="https://zivv.pro",  # bare domain; the SDK adds /v1/messages
)

message = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    system="You are concise. Answer in two sentences max.",
    messages=[{"role": "user", "content": "What is a closure?"}],
)
print(message.content[0].text)
print("tokens:", message.usage.input_tokens, message.usage.output_tokens)

Three differences trip people up:

  • No `/v1` in base_url. Adding it doubles the path and returns 404
  • `max_tokens` is required on the native API
  • `system` is a top-level parameter, not a message in the list

Which One Should You Use?

openai SDKanthropic SDK
Base URLhttps://zivv.pro/v1https://zivv.pro
ModelsClaude, GPT, GeminiClaude
System promptmessage with role systemtop-level system
max_tokensoptionalrequired
Response textresp.choices[0].message.contentmessage.content[0].text
Usage fieldsprompt_tokens / completion_tokensinput_tokens / output_tokens
Best forMixing providers, existing OpenAI codeDeep use of Claude-native features

You don't have to pick only one. Many projects use the openai SDK for general tasks and the anthropic SDK where Claude-native features matter, sharing one Zivv key and one balance.

Gemini also has a native surface at https://zivv.pro/v1beta for projects already using Google's SDK. Check that SDK's documentation and the endpoint reference for the exact settings. When you're starting out, calling Gemini through the openai SDK is the simplest route.

Multi-Turn Conversations

The API is stateless. "Memory" just means you resend the history every time:

history = [{"role": "system", "content": "You are a Python tutor"}]

def chat(user_input):
    history.append({"role": "user", "content": user_input})
    resp = client.chat.completions.create(model="claude-sonnet-5", messages=history)
    answer = resp.choices[0].message.content
    history.append({"role": "assistant", "content": answer})
    return answer

print(chat("What's the difference between a list and a tuple?"))
print(chat("So when should I use a tuple?"))

Longer history means more input tokens on every call, so cost grows with the conversation. For long sessions, keep only the last few turns, or have the model summarize older turns into a short recap.

Streaming

Chat interfaces need streaming, so users see text as it's generated instead of waiting for the whole reply. With the openai SDK, pass stream=True:

stream = client.chat.completions.create(
    model="gpt-5.5",
    messages=[{"role": "user", "content": "Write a short poem about autumn"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
print()

Keep the if chunk.choices guard. Some chunks, such as the final usage chunk when usage reporting is enabled, have an empty choices list, and indexing into it raises an error.

The anthropic SDK has a convenient context manager:

with client.messages.stream(
    model="claude-sonnet-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Write a short poem about autumn"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    final = stream.get_final_message()
print()
print("output tokens:", final.usage.output_tokens)

Timeouts and Retries

Production code will see network blips, rate limits and upstream overload. Both SDKs retry connection errors, 429s and 5xx responses twice by default, with backoff. Tune it per client:

client = OpenAI(
    api_key=os.environ["ZIVV_API_KEY"],
    base_url="https://zivv.pro/v1",
    timeout=120,      # seconds per request
    max_retries=3,
)

Then decide which errors deserve a retry and which should alert someone:

import openai

try:
    resp = client.chat.completions.create(
        model="claude-sonnet-5",
        messages=[{"role": "user", "content": "Hello"}],
    )
except openai.AuthenticationError:
    print("401: invalid key - is .env loaded?")
except openai.NotFoundError:
    print("404: wrong model id or base_url")
except openai.RateLimitError:
    print("429: slow down or retry later")
except openai.APIConnectionError:
    print("Network error - check proxy settings")
except openai.APIStatusError as e:
    print("Other status:", e.status_code, e.message)

The anthropic SDK uses the same class names (anthropic.RateLimitError, anthropic.APIStatusError and so on).

StatusMeaningRetry?What to do
401Invalid keyNoCheck key and header. See 401 fix
402Insufficient balanceNoTop up. See 402 fix
404Unknown model or pathNoCheck model id and base URL. See 404 fix
429Too many requestsYesExponential backoff. See 429 fix
500/502/503Temporary server errorYesShort wait, then retry
529Upstream overloadedYesLonger backoff. See 529 fix

The rule of thumb: client errors (4xx other than 429) won't fix themselves, so fail fast. Retry server and rate-limit errors, but cap it at two or three attempts so you don't amplify an outage.

Concurrent Calls

For batch jobs, serial calls are slow. Use AsyncOpenAI with asyncio and a semaphore to cap concurrency:

import asyncio
from openai import AsyncOpenAI

aclient = AsyncOpenAI(api_key=os.environ["ZIVV_API_KEY"], base_url="https://zivv.pro/v1")
sem = asyncio.Semaphore(5)  # at most 5 in flight

async def summarize(text):
    async with sem:
        resp = await aclient.chat.completions.create(
            model="gemini-3.5-flash",
            messages=[{"role": "user", "content": "Summarize in one sentence: " + text}],
        )
        return resp.choices[0].message.content

async def main(texts):
    return await asyncio.gather(*(summarize(t) for t in texts))

print(asyncio.run(main(["first text", "second text", "third text"])))

The semaphore is the simplest way to stay clear of 429s. For bulk work, prefer fast, low-cost models, and estimate spend with the token cost guide.

A Small Project Layout

  • config.py: loads .env and holds the key, base URLs and default model names
  • llm.py: builds the clients, sets timeouts and retries, and exposes a few call functions
  • Your app code: calls llm.py only and never touches an SDK directly

Swapping models, providers or retry policy then means editing one file. On a team, give each developer and environment its own key and set budgets in Teams, so usage shows up per member and per key.

FAQ

Q: Should I use the openai or anthropic library to call Claude from Python? A: Use anthropic if you only need Claude and want native features like extended thinking or tool use. Use openai with the compatible endpoint if you also call GPT or Gemini, or already have OpenAI code. One Zivv key works with both.

Q: Why am I getting 404 Not Found? A: Usually the base URL. The openai SDK needs /v1, the anthropic SDK must not have it. Next most likely is a misspelled model id, so check the Model Hub.

Q: Can the openai Python library call Gemini? A: Yes. Set base_url to https://zivv.pro/v1 and use a Gemini model id such as gemini-3.5-flash. The response looks exactly like a GPT response.

Q: Streaming fails with "list index out of range". Why? A: Some streamed chunks have no choices. Check if chunk.choices before reading the delta.

Q: Can I create embeddings with these SDKs through Zivv? A: No, Zivv has no embeddings endpoint. Use another provider for vectors and send the retrieved context to Claude, GPT or Gemini for generation.

Q: Where should I store my API key? A: A git-ignored .env file locally, and environment variables or a secrets manager on servers. Zivv records token usage for billing and does not store request or response bodies.

Next Steps

  1. Run the three-model example from Approach 1 to confirm your key and network
  2. Pick your main SDK and wrap it in llm.py
  3. Add streaming and error handling, then wire it into real features
  4. Check token usage in the dashboard and adjust model choices

SDK details are in the OpenAI SDK guide. To use the same models inside your editor or terminal, continue with Cursor custom API setup and the Claude API access guide.