Chat Completions (OpenAI format)

POST /v1/chat/completions follows the OpenAI Chat Completions format. The official OpenAI SDKs work by changing only the base URL and API key.

Request

Body
{
  "model": "MODEL_ID",
  "messages": [
    {"role": "system", "content": "..."},
    {"role": "user", "content": "..."}
  ],
  "max_tokens": 500,
  "temperature": 0.7,
  "stream": false
}
ParameterRequiredNotes
modelyesAn ID from GET /v1/models.
messagesyes1 to 5,000 messages. Message content (text, images, tool calls) is passed to the model as is.
max_tokens or max_completion_tokensno, recommendedOutput token limit. When empty, the model limit is used. Values above the model's max_output_tokens are rejected with 400. This limit also sets the balance hold.
streamnotrue for an SSE response. See Streaming.
stream_options.include_usagenotrue to get usage in the last chunk.
nnoOnly 1.
temperature, top_p, stop, tools, tool_choice, response_format, and othersnoPassed through as is. Support depends on the model.
user, metadatanoRemoved before forwarding, so your identity is not sent to the model provider.

Examples

curl https://api.markas.ai.id/v1/chat/completions \
  -H "Authorization: Bearer $MARKASAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MODEL_ID",
    "messages": [{"role": "user", "content": "What is an API?"}],
    "max_tokens": 300
  }'
import os
from openai import OpenAI

client = OpenAI(base_url="https://api.markas.ai.id/v1", api_key=os.environ["MARKASAI_API_KEY"])

resp = client.chat.completions.create(
    model="MODEL_ID",
    messages=[{"role": "user", "content": "What is an API?"}],
    max_tokens=300,
)
print(resp.choices[0].message.content)
print(resp.usage)
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.markas.ai.id/v1", apiKey: process.env.MARKASAI_API_KEY });

const resp = await client.chat.completions.create({
  model: "MODEL_ID",
  messages: [{ role: "user", content: "What is an API?" }],
  max_tokens: 300,
});
console.log(resp.choices[0].message.content);

Response

200 OK
{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "model": "MODEL_ID",
  "choices": [
    {
      "index": 0,
      "message": {"role": "assistant", "content": "..."},
      "finish_reason": "stop"
    }
  ],
  "usage": {"prompt_tokens": 20, "completion_tokens": 120, "total_tokens": 140}
}

Cost is computed from usage. Tokens read from cache appear in usage.prompt_tokens_details.cached_tokens when the model reports them.

Streaming

With "stream": true the response is text/event-stream and ends with data: [DONE]. Disconnect behavior and time limits are covered in Streaming.

text/event-stream
data: {"id":"...","choices":[{"index":0,"delta":{"content":"Halo"}}]}

data: {"id":"...","choices":[{"index":0,"delta":{"content":" dunia"}}]}

data: {"id":"...","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":2,"total_tokens":14}}

data: [DONE]

Tool calling and image input

tools, tool_choice, and image content in messages are passed through as is. Pick a model that supports them. If the model rejects a parameter, the error comes back as 400 invalid_request with the model's message.

Body with tools
{
  "model": "MODEL_ID",
  "max_tokens": 500,
  "messages": [{"role": "user", "content": "..."}],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "parameters": {
          "type": "object",
          "properties": {"city": {"type": "string"}},
          "required": ["city"]
        }
      }
    }
  ]
}

Requests that define tools or functions are never changed by prompt optimization.