Skip to main content
FortAPI supports two API protocols simultaneously: classic OpenAI-compatible and native Anthropic Claude-compatible. The same API key works with both.

OpenAI protocol

POST /v1/chat/completions — industry standard, supported by virtually all SDKs and frameworks.

Claude protocol

POST /v1/messages — native Anthropic format with thinking blocks and MCP support.

Base URL

https://www.fortapi.com/v1
All endpoints live under /v1. Auth via the Authorization: Bearer sk-... header.
With the OpenAI protocol, base_url includes /v1 (https://www.fortapi.com/v1). With the Anthropic SDK, set ANTHROPIC_BASE_URL to the bare domain without /v1 (https://www.fortapi.com) — the SDK appends /v1/messages itself, and adding /v1 yourself causes a 404.

OpenAI protocol

Use this protocol if your code already uses the official OpenAI SDK or a compatible framework (LangChain, LlamaIndex, Vercel AI SDK, etc.).

Endpoint

POST https://www.fortapi.com/v1/chat/completions

Compatibility

The request/response format matches OpenAI’s Chat Completions API. Supported:
  • messages (with roles system / user / assistant / tool)
  • model — name of any model in the catalog (Claude/Gemini/Grok included — we convert on the fly)
  • stream: true for streaming responses (Server-Sent Events)
  • tools / tool_choice for function calling
  • temperature, top_p, max_tokens and other parameters

Example: Python (OpenAI SDK)

from openai import OpenAI

client = OpenAI(
    api_key="sk-YOUR_KEY",
    base_url="https://www.fortapi.com/v1",
)

response = client.chat.completions.create(
    model="your-model-name",  # see the pricing page on the main site for available model names
    messages=[
        {"role": "system", "content": "You are a friendly assistant."},
        {"role": "user", "content": "Hello! What can you do?"},
    ],
    temperature=0.7,
)

print(response.choices[0].message.content)
print(f"Tokens used: {response.usage.total_tokens}")

Example: Node.js (OpenAI SDK)

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "sk-YOUR_KEY",
  baseURL: "https://www.fortapi.com/v1",
});

const response = await client.chat.completions.create({
  model: "your-model-name", // call Claude through OpenAI protocol; see the pricing page on the main site for available model names
  messages: [
    { role: "user", content: "Explain quantum entanglement simply." },
  ],
});

console.log(response.choices[0].message.content);

Example: curl + streaming

Replace your-model-name with any model name from the pricing page on the main site.
# replace "your-model-name" with any model name from the pricing page
curl https://www.fortapi.com/v1/chat/completions \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "your-model-name",
    "messages": [{"role": "user", "content": "Write a haiku about code."}],
    "stream": true
  }'

Claude protocol

Use this protocol if your code is built on the Anthropic SDK or you need Claude-specific features (thinking blocks, native tool calls).

Endpoint

POST https://www.fortapi.com/v1/messages

Compatibility

The request/response format matches Anthropic’s Messages API. Supported:
  • messages array (native Claude format)
  • system as a separate field
  • model — Claude model name or any other (we convert into Claude protocol)
  • max_tokens (required for Claude)
  • stream: true
  • tools / tool_choice
  • thinking for reasoning models

Example: Python (Anthropic SDK)

from anthropic import Anthropic

client = Anthropic(
    api_key="sk-YOUR_KEY",
    base_url="https://www.fortapi.com",
)

response = client.messages.create(
    model="your-model-name",  # see the pricing page on the main site for available model names
    max_tokens=1024,
    system="You are a technical expert.",
    messages=[
        {"role": "user", "content": "What is the CAP theorem?"},
    ],
)

print(response.content[0].text)
print(f"Used: input={response.usage.input_tokens}, output={response.usage.output_tokens}")

Example: Node.js (Anthropic SDK)

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  apiKey: "sk-YOUR_KEY",
  baseURL: "https://www.fortapi.com",
});

const response = await client.messages.create({
  model: "your-model-name", // see the pricing page on the main site for available model names
  max_tokens: 1024,
  messages: [
    { role: "user", content: "Explain Rust's borrow checker." },
  ],
});

console.log(response.content[0].text);

Example: curl

Replace your-model-name with any model name from the pricing page on the main site.
# replace "your-model-name" with any model name from the pricing page
curl https://www.fortapi.com/v1/messages \
  -H "x-api-key: sk-YOUR_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "your-model-name",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Write a haiku about code."}]
  }'
Anthropic SDK uses the x-api-key header instead of Authorization: Bearer. FortAPI accepts both on the /v1/messages endpoint.

Which protocol should I pick?

ScenarioUse
Already on OpenAI SDK / LangChain / LlamaIndexOpenAI protocol
Writing native Anthropic code, need thinking blocksClaude protocol
Want to call Claude from existing OpenAI codeOpenAI protocol (we convert)
Want to call a GPT model from Anthropic SDKClaude protocol (we convert)
In every case — same API key, same per-token price, shared balance.

More usage

The examples below use the OpenAI protocol; for the Claude protocol see the equivalent calls in the respective SDK docs.

Streaming (consuming SSE)

With stream: true, the response is a series of Server-Sent Events: each line starts with data: and ends with a final data: [DONE]. The official SDKs parse this for you — just iterate.
stream = client.chat.completions.create(
    model="your-model-name",  # see the pricing page on the main site for available model names
    messages=[{"role": "user", "content": "Count from 1 to 5"}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)
const stream = await client.chat.completions.create({
  model: "your-model-name", // see the pricing page on the main site for available model names
  messages: [{ role: "user", content: "Count from 1 to 5" }],
  stream: true,
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || "");
}

Tool calling (function calling)

The model decides which function to call and returns the arguments; you run the function locally, then send the result back as a role: "tool" message, and the model produces the final answer.
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get the current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

messages = [{"role": "user", "content": "What's the weather in Beijing right now?"}]
resp = client.chat.completions.create(
    model="your-model-name",  # use a tool-calling capable model; see the pricing page
    messages=messages,
    tools=tools,
    tool_choice="auto",
)

msg = resp.choices[0].message
if msg.tool_calls:
    call = msg.tool_calls[0]
    # run the function yourself (mock data here) and send the result back
    messages.append(msg)
    messages.append({
        "role": "tool",
        "tool_call_id": call.id,
        "content": '{"temp": "26°C", "weather": "sunny"}',
    })
    final = client.chat.completions.create(model="your-model-name", messages=messages)
    print(final.choices[0].message.content)

Vision / multimodal

Write a user message’s content as an array and mix in image_url. The image can be a URL or a data: base64 string:
response = client.chat.completions.create(
    model="your-model-name",  # use a vision-capable model; see the pricing page
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "What's in this image?"},
            {"type": "image_url", "image_url": {"url": "https://example.com/cat.jpg"}},
            # base64: {"url": "data:image/jpeg;base64,<BASE64_DATA>"}
        ],
    }],
)
print(response.choices[0].message.content)

Embeddings / images / audio

emb = client.embeddings.create(
    model="your-model-name",  # use an embedding model; see the pricing page
    input="text to embed",
)
print(len(emb.data[0].embedding))
img = client.images.generate(
    model="your-model-name",  # use an image model; see the pricing page
    prompt="a shiba inu coding at a laptop, flat illustration",
    size="1024x1024",
)
print(img.data[0].url)
curl https://www.fortapi.com/v1/audio/transcriptions \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -F file=@audio.mp3 \
  -F model="your-model-name"
curl https://www.fortapi.com/v1/audio/speech \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"your-model-name","input":"Hello, world","voice":"alloy"}' \
  --output speech.mp3

All endpoints

MethodPathPurpose
POST/v1/chat/completionsOpenAI Chat Completions
POST/v1/messagesClaude Messages (Anthropic native)
POST/v1/responsesOpenAI Responses API
POST/v1/embeddingsEmbeddings (OpenAI compatible)
POST/v1/images/generationsImage generation (OpenAI compatible)
POST/v1/audio/transcriptionsSpeech to text
POST/v1/audio/speechText to speech (TTS)
POST/v1/rerankReranking
POST/v1/moderationsContent moderation
POST/v1/video/generationsVideo generation
GET/v1/modelsList of available models
POST/v1beta/models/*Gemini native protocol
POST/mj/*Midjourney image generation
POST/suno/*Suno music generation
The full list with current models is on the Pricing page on the main site. Which models each endpoint supports is defined by the Pricing page and the Models list in the console.