API
Friendlai speaks the OpenAI Chat Completions API, so most SDKs, agents and tools work by changing the base URL. Create a key on the API keys page.
Base URL: https://friendlai.xyz/v1
Quick start
curl https://friendlai.xyz/v1/chat/completions \
-H "Authorization: Bearer $FRIENDLAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3:8b",
"messages": [{"role": "user", "content": "Write a haiku about GPUs"}]
}'Python
from openai import OpenAI
client = OpenAI(base_url="https://friendlai.xyz/v1", api_key="fai_...")
reply = client.chat.completions.create(
model="qwen3:8b",
messages=[{"role": "user", "content": "Hello!"}],
)
print(reply.choices[0].message.content)JavaScript
import OpenAI from 'openai';
const client = new OpenAI({ baseURL: 'https://friendlai.xyz/v1', apiKey: process.env.FRIENDLAI_API_KEY });
const stream = await client.chat.completions.create({
model: 'qwen3:8b',
messages: [{ role: 'user', content: 'Hello!' }],
stream: true,
});
for await (const chunk of stream) process.stdout.write(chunk.choices[0]?.delta?.content ?? '');Endpoints
GET /v1/models | Models that at least one node is serving right now. No API key needed. |
POST /v1/chat/completions | Chat completions, with or without stream: true. |
Supported parameters
model, messages (text content; system, developer, user, assistant and tool roles, with assistant tool_calls), tools (functions), tool_choice ("auto" or "none"), stream, stream_options.include_usage, temperature, top_p, max_tokens / max_completion_tokens, seed and stop. Images, n > 1 and forcing a tool call (tool_choice: "required" or a named function) aren't supported.
Tool calling
Pass tools as you would to OpenAI. When the model wants a tool, the reply has message.tool_calls and finish_reason: "tool_calls" (streamed as a delta.tool_calls chunk). Your code runs the tool and sends the result back as a tool message. Agent harnesses that speak the OpenAI API, such as opencode, work the same way. It needs a model that can call tools, such as qwen3:8b; others return 400 with code tools_not_supported. Tool definitions count as prompt tokens.
import json
# client as in the quick start; get_weather is your own function.
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current temperature in a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
messages = [{"role": "user", "content": "What's the weather in Paris?"}]
reply = client.chat.completions.create(model="qwen3:8b", messages=messages, tools=tools)
message = reply.choices[0].message
if message.tool_calls:
messages.append(message)
for call in message.tool_calls:
args = json.loads(call.function.arguments)
messages.append({"role": "tool", "tool_call_id": call.id, "content": get_weather(**args)})
reply = client.chat.completions.create(model="qwen3:8b", messages=messages, tools=tools)
print(reply.choices[0].message.content)Who you're trusting. Friendlai never runs tools; your harness does. But replies, tool calls included, come from computers run by community members, and a node's operator controls what it sends back. Treat a tool call like any untrusted input: keep your harness's permission prompts on, and don't let an agent run commands or edit files unattended. Operators also see what you send, including tool definitions and tool results, such as the contents of files an agent reads, just as they see prompts.
Pricing
Requests are billed in credits (1 credit = $0.001) per million tokens, with output priced higher than input. The price is locked in when a request is queued.
| Model | Input | Output | Nodes online |
|---|
Credits per million tokens. For example, at 48 in / 192 out, a request with 1,000 input and 500 output tokens costs 0.14 credits ($0.00014).
Limits and billing
- Every signed-in account gets free credits each day. After that, purchased credits are used.
- API keys are limited to 60 requests per minute and 20 requests in progress at once.
- Rate-limited requests get
429with aRetry-Afterheader; running out of credits returns402. - Requests wait in a queue for a node with the model, so the first token can take longer than with a hosted provider. Popular models are fastest.
- Prompts are processed by computers run by people like you. Don't send secrets or personal data. See the privacy policy.