← Blog

How to Call an LLM API: The Complete Mechanics

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

Calling an LLM API means sending an HTTP request with your prompt and configuration to a provider's endpoint, then handling the response — either as a single JSON payload or as a stream of tokens. The mechanics are the same across providers: authenticate with an API key, POST a JSON body describing the conversation, and parse whatever comes back.

This article walks through that request/response cycle in detail: what goes in the headers, what the body needs to contain, how streaming changes the flow, and the errors you should expect to handle. The examples use curl and JavaScript, but the pattern applies whether you're calling OpenAI, Anthropic, or a proxy like SubToAPI.

The Anatomy of an LLM API Call

Every LLM API call has four parts:

  1. Endpoint URL — where the request goes (usually a /messages or /chat/completions path)
  2. Headers — authentication and content type
  3. Request body — model name, messages, and parameters like max_tokens or temperature
  4. Response handling — parsing JSON or reading a stream

Here's a minimal curl call against SubToAPI's Messages endpoint, which follows Anthropic's message format:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4",
    "max_tokens": 1024,
    "messages": [
      {"role": "user", "content": "Explain what a race condition is."}
    ]
  }'

The response comes back as JSON with the generated text, a stop reason, and token usage:

{
  "id": "msg_01abc...",
  "content": [{"type": "text", "text": "A race condition occurs when..."}],
  "stop_reason": "end_turn",
  "usage": {"input_tokens": 12, "output_tokens": 84}
}

Full request/response field details are in the Messages docs.

Calling the API from Code

In practice you'll call the API from your backend, not from a terminal. Here's a Node.js example using fetch:

async function askClaude(prompt) {
  const res = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "claude-sonnet-4",
      max_tokens: 512,
      messages: [{ role: "user", content: prompt }]
    })
  });

  if (!res.ok) {
    throw new Error(`API error ${res.status}: ${await res.text()}`);
  }

  const data = await res.json();
  return data.content[0].text;
}

Three things matter here that beginners often skip:

If you're just getting started, walk through the quickstart guide — it covers key generation and your first successful call end to end.

Streaming Responses

For anything user-facing — chat UIs, CLI tools — you don't want to wait for the full response before showing anything. Streaming sends the response as a series of chunks (usually Server-Sent Events) as tokens are generated.

const res = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model: "claude-sonnet-4",
    max_tokens: 512,
    stream: true,
    messages: [{ role: "user", content: "Write a haiku about databases." }]
  })
});

const reader = res.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  process.stdout.write(decoder.decode(value));
}

Streaming does not change the endpoint or the authentication — only the stream flag and how you read the response body. Details on event types and parsing are in the streaming docs.

Calling Tools from the LLM

Most modern LLM APIs support tool use (also called function calling): you describe available functions in the request, and the model responds with a structured request to call one, which your code executes and feeds back.

{
  "model": "claude-sonnet-4",
  "max_tokens": 512,
  "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?" }]
}

The model's response will include a tool_use block instead of (or alongside) text. Your code runs the actual function, then sends the result back as a follow-up message so the model can incorporate it into a final answer. This is a multi-turn call pattern, not a single request — plan for the extra round trip in your latency budget. See the tools docs for the full schema and flow.

Handling Errors and Rate Limits

Production code calling an LLM API needs to handle, at minimum:

async function callWithRetry(fn, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === retries - 1) throw err;
      await new Promise(r => setTimeout(r, 2 ** i * 500));
    }
  }
}

If you're calling the API on behalf of multiple users or products, using distinct application keys (like SubToAPI's sub_live_... keys) instead of one shared key makes it much easier to trace which caller triggered which error, and to check per-key usage without digging through logs. You can generate and manage keys from the dashboard after signing up.

Wrapping Up

Calling an LLM API is a standard HTTP POST with a JSON body — the complexity is in message formatting, streaming, tool-call round trips, and retry logic, not the transport itself. Get the basic request working first, then layer in streaming and tools once the core call is solid. Check pricing if you're comparing hosted options for turning existing Claude access into an API you can call from your own code.

Questions

Do I need a special SDK to call an LLM API? No. Any HTTP client works — curl, fetch, axios, Python's requests. SDKs just wrap the same JSON request/response pattern with typed helpers.

Why did my API call return a 429 error? You've hit a rate or usage limit. Implement exponential backoff and retry, and check your plan's limits if it happens consistently.

What's the difference between calling with streaming on vs off? Non-streaming waits for the full response then returns one JSON object. Streaming sends the response in chunks as it's generated, letting you display output incrementally.

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 →