Docs / API

Chat completions

POST /v1/chat/completions — the OpenAI chat shape, served by Qwen3.8 27B as qwen3.8-27b-nvfp4. 262,144-token context. €0.29 / 1M input tokens, €1.77 / 1M output tokens (launch pricing). Streaming, tool calls, reasoning output and image input all work on this one endpoint.

Request & response

$ curl https://api.axforge.ai/v1/chat/completions \
  -H "Authorization: Bearer $AXFORGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3.8-27b-nvfp4",
    "messages": [
      {"role": "system", "content": "You answer in one sentence."},
      {"role": "user", "content": "What is an embedding?"}
    ],
    "max_tokens": 2048
  }'
Python
from openai import OpenAI

client = OpenAI(
    base_url="https://api.axforge.ai/v1",
    api_key="YOUR_AXFORGE_KEY",
)
r = client.chat.completions.create(
    model="qwen3.8-27b-nvfp4",
    messages=[{"role": "user", "content": "What is an embedding?"}],
)
print(r.choices[0].message.content)

The response is the standard OpenAI shape. Note reasoning_content — the model's thinking, separate from the answer (details below).

{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "model": "qwen3.8-27b-nvfp4",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "An embedding is a vector that encodes meaning...",
      "reasoning_content": "The user wants a one-line definition..."
    },
    "finish_reason": "stop"
  }],
  "usage": {"prompt_tokens": 21, "completion_tokens": 96, "total_tokens": 117}
}

Streaming

Set "stream": true and the response arrives as server-sent events, one JSON chunk per data: line. Usage is included in streaming mode too — the final chunk before [DONE] carries it.

$ curl -N https://api.axforge.ai/v1/chat/completions \
  -H "Authorization: Bearer $AXFORGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "qwen3.8-27b-nvfp4", "stream": true,
       "messages": [{"role": "user", "content": "Count to three."}]}'

# the wire format:
data: {"choices":[{"delta":{"content":"One"}}]}
data: {"choices":[{"delta":{"content":", two"}}]}
data: [DONE]

The wire format, whichever shell you used:

# the wire format:
data: {"choices":[{"delta":{"content":"One"}}]}
data: {"choices":[{"delta":{"content":", two"}}]}
data: {"choices":[{"delta":{},"finish_reason":"stop"}]}
data: {"choices":[],"usage":{"prompt_tokens":13,"completion_tokens":9,"total_tokens":22}}
data: [DONE]
Python
r = client.chat.completions.create(
    model="qwen3.8-27b-nvfp4",
    stream=True,
    messages=[{"role": "user", "content": "Count to three."}],
)
for chunk in r:
    if chunk.choices:
        print(chunk.choices[0].delta.content or "", end="")
    if chunk.usage:
        print("\ntokens:", chunk.usage.total_tokens)

Tool calls

The endpoint accepts the OpenAI tools schema. Declare functions, let the model decide when to call one, run it yourself, and send the result back as a tool message. One complete round trip:

Python
import json

tools = [{
    "type": "function",
    "function": {
        "name": "get_invoice",
        "description": "Look up an invoice by its number",
        "parameters": {
            "type": "object",
            "properties": {"number": {"type": "string"}},
            "required": ["number"],
        },
    },
}]

messages = [{"role": "user", "content": "What is the total on invoice 2041?"}]
r = client.chat.completions.create(
    model="qwen3.8-27b-nvfp4", messages=messages, tools=tools,
)

# the model asked for the tool instead of answering
call = r.choices[0].message.tool_calls[0]
args = json.loads(call.function.arguments)   # {"number": "2041"}

# run it yourself, then send the result back
messages.append(r.choices[0].message)
messages.append({
    "role": "tool",
    "tool_call_id": call.id,
    "content": json.dumps({"number": "2041", "total_eur": 1240.0}),
})
r = client.chat.completions.create(
    model="qwen3.8-27b-nvfp4", messages=messages, tools=tools,
)
print(r.choices[0].message.content)   # "Invoice 2041 totals EUR 1,240.00."

On the wire, the model's tool request looks like this — pass the same tools array in a curl body to get it:

"message": {
  "role": "assistant",
  "content": null,
  "tool_calls": [{
    "id": "call_...",
    "type": "function",
    "function": {"name": "get_invoice", "arguments": "{\"number\": \"2041\"}"}
  }]
},
"finish_reason": "tool_calls"

Reasoning output

The model thinks before it answers. The thinking arrives in reasoning_content on the message (and in the delta when streaming); content holds only the answer. Reasoning tokens are output tokens and are billed as such.

To disable thinking, pass chat_template_kwargs:

curl
$ curl https://api.axforge.ai/v1/chat/completions \
  -H "Authorization: Bearer $AXFORGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3.8-27b-nvfp4",
    "messages": [{"role": "user", "content": "Classify: \"refund please\""}],
    "chat_template_kwargs": {"enable_thinking": false}
  }'
Python
r = client.chat.completions.create(
    model="qwen3.8-27b-nvfp4",
    messages=[{"role": "user", "content": "Classify: \"refund please\""}],
    extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)

Tip — give thinking room. With thinking enabled, the model spends output tokens on reasoning_content before it writes the answer, so a small max_tokens can be used up by reasoning and return an empty content. Raise max_tokens, or disable thinking for short structured outputs.

Image input (vision)

Qwen3.8 27B reads images. Make content an array that mixes text and image_url parts. An image is either an https:// URL we can fetch, or a base64 data: URL you build from the file. You can send several images in one message — up to 8 per request — and the model reads them in the order you list them.

Send them the best way:

  • Resize big scans. Cost scales with pixels: a phone photo is about 500–1,500 tokens, a full A4 300-DPI page (2480×3508) about 8,500. Downscale documents to roughly 1,500–2,000 px on the long side unless the fine print matters — it is faster and cheaper, and just as accurate for invoices and forms.
  • Use JPEG for photos and scans (a fraction of a PNG's size). Keep PNG only for screenshots and line art.
  • One document per image. For a multi-page file, send one image per page, in order, and say so in the text.
  • Put the images first, then your question — the model answers what the text asks about the images above it.
curl · two images
$ curl https://api.axforge.ai/v1/chat/completions \
  -H "Authorization: Bearer $AXFORGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3.8-27b-nvfp4",
    "messages": [{
      "role": "user",
      "content": [
        {"type": "text", "text": "These are two invoice pages. What is the grand total?"},
        {"type": "image_url", "image_url": {"url": "https://example.com/page-1.jpg"}},
        {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."}}
      ]
    }]
  }'

The Python helper below resizes each file, encodes it as a JPEG data URL, and sends any number of images in one call.

Python · several files, resized
import base64, io
from openai import OpenAI
from PIL import Image  # pip install pillow openai

client = OpenAI(base_url="https://api.axforge.ai/v1", api_key="YOUR_AXFORGE_KEY")

def image_part(path, max_side=2000):
    # open, shrink the long side to max_side, re-encode as JPEG, return a data-URL part
    im = Image.open(path).convert("RGB")
    scale = min(1.0, max_side / max(im.size))
    if scale < 1.0:
        im = im.resize((round(im.width * scale), round(im.height * scale)))
    buf = io.BytesIO()
    im.save(buf, "JPEG", quality=85)
    b64 = base64.b64encode(buf.getvalue()).decode()
    return {"type": "image_url",
            "image_url": {"url": f"data:image/jpeg;base64,{b64}"}}

files = ["page-1.jpg", "page-2.jpg", "page-3.png"]  # up to 8
parts = [{"type": "text",
          "text": "These are the pages of one invoice, in order. "
                  "Return supplier, invoice number, date and total as JSON."}]
parts += [image_part(f) for f in files]

r = client.chat.completions.create(
    model="qwen3.8-27b-nvfp4",
    messages=[{"role": "user", "content": parts}],
)
print(r.choices[0].message.content)

Context & pricing

Context window262,144 tokens
Input€0.29 / 1M tokens (launch pricing)
Output€1.77 / 1M tokens (launch pricing)
Image input~500–9,000 prompt tokens per image by size (a full A4 scan ~8,500), billed as input
ReasoningBilled as output tokens

More on how prices are set: Models & pricing and the pricing page. Concurrency and error behavior: Errors & limits.

Anything unclear on this page?

Ask on the forum — the answer helps the next person too.

Ask about this page
© 2026 AxForge · EU-hosted AI infrastructure Docs Models For AI axforge.ai Pricing Trust Acceptable use Community guidelines