Use Outlier as the engine behind your tools
Outlier exposes a standard /v1/chat/completions API on your own Mac. Point Cursor, LangChain, LiteLLM, Codex, or anything built on the OpenAI SDK at it — same code you already have, but the model runs locally and nothing leaves your machine.
1 · Turn it on
In Outlier: Settings → Advanced → Developer → OpenAI-compatible API. Flip the toggle and copy your endpoint and API key. The endpoint is loopback-only (http://127.0.0.1:8766/v1) — nothing is exposed to your network.
2 · OpenAI SDK — drop-in
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8766/v1",
api_key="YOUR-OUTLIER-KEY", # Settings → Developer
)
r = client.chat.completions.create(
model="outlier-nano", # outlier-lite / outlier-quick / outlier-compact (Core)
# outlier-plus
# Vision 3.8 answers text here too, but this
# endpoint takes no images — see below.
messages=[{"role": "user", "content": "Three taglines for a coffee truck"}],
)
print(r.choices[0].message.content)
Streaming, max_tokens, temperature, and finish_reason behave like the reference API.
Images do not go through this endpoint. Sending
OpenAI's multimodal form — a content array of
{"type": "image_url", ...} parts — returns
400 messages.0.content: Input should be a valid string, because
/v1/chat/completions takes text only. Vision 3.8 will answer a
text prompt here like any other tier; it just cannot see anything you send
it this way. Pictures go to Outlier's own endpoint instead:
POST http://127.0.0.1:8766/chat/vision
Authorization: Bearer YOUR-OUTLIER-KEY
Verified against 1.11.808 rather than assumed, which is why the error text above is quoted rather than described.
3 · Function calling (tools)
r = client.chat.completions.create(
model="outlier-nano",
messages=[{"role": "user", "content": "What's the weather in Paris?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}],
)
tc = r.choices[0].message.tool_calls[0]
print(tc.function.name, tc.function.arguments)
# → get_weather {"city": "Paris"}
Run your function, then send the result back as a role:"tool" message — multi-turn tool loops work the way the OpenAI docs describe, including streaming (delta.tool_calls, finish_reason:"tool_calls").
4 · LangChain
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
base_url="http://127.0.0.1:8766/v1",
api_key="YOUR-OUTLIER-KEY",
model="outlier-nano",
).bind_tools(my_tools)
msg = llm.invoke("What's the weather in Berlin?")
print(msg.tool_calls)
5 · Cursor / Continue
In the model settings, add a custom OpenAI-compatible model: base URL http://127.0.0.1:8766/v1, your Outlier key, model outlier-compact (the Core tier, the strongest one that fits most Macs) or outlier-nano for speed.
6 · Codex CLI
# ~/.codex/config.toml
model = "outlier-compact"
model_provider = "outlier"
[model_providers.outlier]
name = "Outlier (local)"
base_url = "http://127.0.0.1:8766/v1"
env_key = "OUTLIER_API_KEY"
wire_api = "chat" # Outlier serves chat completions, not the Responses API
export OUTLIER_API_KEY="YOUR-OUTLIER-KEY"
codex
7 · LiteLLM
import litellm
r = litellm.completion(
model="openai/outlier-nano",
api_base="http://127.0.0.1:8766/v1",
api_key="YOUR-OUTLIER-KEY",
messages=[{"role": "user", "content": "hi"}],
)
8 · What the app does that this endpoint does not
The endpoint runs the same models as the app, and deliberately not the same product around them. If you compare an answer here against the same question typed into Outlier and they differ, this is usually why:
- No web search. The app can search and cite sources. A request to this endpoint is answered from the model alone.
- No memory. Nothing you send here is read from or written to the app’s saved facts. Each request stands alone.
- No project or codebase context. The app can attach the files you are working on. Here, the messages you send are the whole input.
- No history trimming. The app shortens a long conversation to fit the context window. This endpoint sends what you sent — dropping messages a client deliberately included would be the wrong default for an API, so a long conversation stays intact and simply takes longer.
None of this is a limitation of the model; it is the line between an application and an API. It is listed because the difference is invisible until an answer surprises you.
9 · Which parameters actually do something
A drop-in endpoint that accepts a parameter and then ignores it is worse than one that rejects it, because your code carries on believing the constraint applied. Every field below was sent to a running 1.11.808 engine and the reply read back on 1 September 2026, so this is what was observed rather than what the schema permits.
Honoured — the effect was measured, not assumed.
temperature,max_tokens,max_completion_tokensstop— truncates the reply and comes back withfinish_reason: "stop"stream—text/event-stream, a stable id across chunks,rolein the first delta,finish_reasonon the last, terminated by[DONE]stream_options: {"include_usage": true}— adds a final chunk carrying real token countsresponse_format: {"type": "json_object"}— genuinely constrains the reply. Without it the same prompt came back as prose with markdown headings, which is how we know the flag is doing the work and not the model's habittoolsandtool_choice, both left to the model and forced to a named function
Refused on purpose. n greater than 1
returns 400 and says why: this endpoint returns a single
choice. Silently handing back one choice would have been the easier
behaviour and the wrong one.
Images do not travel through this endpoint. A message
whose content is a list containing an image_url
part is refused with an error naming POST /chat/vision, which
is where Outlier Vision 3.8 reads pictures. The model list says the same
thing in the tier's own description, so a client that reads the catalogue
before choosing is not misled.
Accepted, effect not measured here.
top_p, seed, presence_penalty,
frequency_penalty, logit_bias,
logprobs, user, metadata and
parallel_tool_calls are all accepted without error. They are
listed separately because accepting a field is not the same as acting on
it, and only the ones above were checked end to end.