← Blog

How to Use an LLM API in Python: A Working Guide

2026-09-04 · 4 min read · SubToAPI Team

Using an LLM API in Python comes down to four steps: get an API key, install an HTTP client (or the provider's SDK), send a POST request with your prompt as JSON, and parse the response. Everything else — streaming, retries, tool calls, token limits — builds on that basic loop.

This guide walks through the full pattern with working code, so you can go from zero to a functioning script in a few minutes, then extend it toward production use.

The Basic Pattern

Every major LLM API — OpenAI, Anthropic, or a proxy like SubToAPI — follows the same shape: you send a JSON payload with a model name, a list of messages, and a max token limit, and you get back a JSON response containing the generated text plus usage metadata.

Here's the minimal version using Python's built-in requests library, no SDK required:

import requests
import os

response = requests.post(
    "https://api.subtoapi.app/v1/messages",
    headers={
        "Authorization": f"Bearer {os.environ['SUBTOAPI_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "model": "claude-sonnet-4",
        "max_tokens": 1024,
        "messages": [
            {"role": "user", "content": "Summarize the plot of Hamlet in two sentences."}
        ],
    },
)

data = response.json()
print(data["content"][0]["text"])

That's the entire loop. If you're new to LLM APIs, start here before touching an SDK — it makes the request/response shape obvious, which helps a lot when debugging errors later.

Setting Up Your Environment

1. Get an API key. Every provider requires one, passed as a Bearer token in the Authorization header. Never hardcode it — use an environment variable:

export SUBTOAPI_KEY="sub_live_..."

2. Install what you need. For raw HTTP calls, requests is enough:

pip install requests

If you want streaming or async support without writing that logic yourself, an official SDK (like anthropic or openai) saves time. Both work fine with a provider that speaks the same message format.

3. Structure your messages correctly. Most LLM APIs use a messages array with role (user, assistant, or sometimes system) and content. Multi-turn conversations just mean appending to that list:

messages = [
    {"role": "user", "content": "What's the capital of Peru?"},
    {"role": "assistant", "content": "Lima."},
    {"role": "user", "content": "What's its population?"},
]

The API has no memory between calls — you resend the full conversation history every time.

Streaming Responses

For chat interfaces or anything user-facing, streaming avoids making people stare at a blank screen while a long response generates. Set "stream": true and read the response as Server-Sent Events:

import requests
import json
import os

response = requests.post(
    "https://api.subtoapi.app/v1/messages",
    headers={
        "Authorization": f"Bearer {os.environ['SUBTOAPI_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "model": "claude-sonnet-4",
        "max_tokens": 1024,
        "stream": True,
        "messages": [{"role": "user", "content": "Write a haiku about databases."}],
    },
    stream=True,
)

for line in response.iter_lines():
    if line and line.startswith(b"data:"):
        chunk = line[5:].strip()
        if chunk == b"[DONE]":
            break
        event = json.loads(chunk)
        delta = event.get("delta", {}).get("text", "")
        print(delta, end="", flush=True)

Streaming changes how you parse the response but not how you construct the request — the payload is nearly identical either way. SubToAPI's streaming docs cover the exact event format if you need to handle specific event types.

Handling Errors and Rate Limits

Production code needs to handle three failure modes: bad requests (4xx), rate limits (429), and transient server errors (5xx). A simple retry wrapper covers most of it:

import time
import requests

def call_llm(payload, headers, retries=3):
    for attempt in range(retries):
        response = requests.post(
            "https://api.subtoapi.app/v1/messages",
            headers=headers,
            json=payload,
        )
        if response.status_code == 200:
            return response.json()
        if response.status_code == 429:
            wait = 2 ** attempt
            time.sleep(wait)
            continue
        response.raise_for_status()
    raise RuntimeError("Max retries exceeded")

Always check max_tokens too — if a response gets cut off mid-sentence, the stop_reason field in the response tells you whether it hit that limit or finished naturally.

Giving the Model Tools

Many real applications need the model to call functions — look up a database, run a calculation, fetch live data — rather than just generate text. This works by describing tools in the request and checking whether the response asks to invoke one:

payload = {
    "model": "claude-sonnet-4",
    "max_tokens": 1024,
    "tools": [
        {
            "name": "get_weather",
            "description": "Get current weather for a city",
            "input_schema": {
                "type": "object",
                "properties": {"city": {"type": "string"}},
                "required": ["city"],
            },
        }
    ],
    "messages": [{"role": "user", "content": "What's the weather in Lisbon?"}],
}

If the model decides to use the tool, the response contains a tool_use block with the arguments it wants to call — your code executes the function and sends the result back in a follow-up message. Details and full examples are in the tool use docs.

Choosing How to Access the API

If you already have Claude access through a personal or team subscription, running a separate paid API account on top of it is redundant. SubToAPI turns that existing access into a standard HTTPS API — you get an sub_live_... key, the same /v1/messages endpoint shown above, streaming, tool support, and per-key usage tracking, all from one dashboard. Plans start at €9/month for solo use, with team pricing at €19/seat and Scale at €49/seat. Check the pricing page or start with the quickstart guide to get a key running in a few minutes — there's a free trial at signup.

Questions

Do I need an SDK to use an LLM API in Python? No. requests handles everything — an SDK just adds convenience methods for streaming, retries, and typed responses.

How do I keep conversation context across multiple calls? Resend the full messages array each time, appending new user and assistant turns. The API itself is stateless.

What's the difference between max_tokens and the model's context window? max_tokens limits the length of the generated response; the context window limits the total size of input plus output combined.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →