Local API
Connect a coding agent to this Mac
The app serves two protocols over loopback: an OpenAI-compatible API (Chat Completions and Responses) and an Anthropic-compatible one (Messages). Four values stand between a coding agent and your local models: the address, the model identifier, the credential field, and the request.
Everything on this page was checked against a running app, and the values below are the ones the app serves today.
The fastest path: the launcher
One command does the configuration below for a single session, without writing your settings files: it finds the running app, picks a chat model that speaks the agent's protocol (or takes --model), and starts the agent pointed at this Mac.
astronomical launch opencode
astronomical launch codex
astronomical launch claude
Arguments after -- go to the agent unchanged, so astronomical launch claude -- -p "Say hello in one short sentence." runs one headless Claude Code turn. A Pi launcher is planned; until then, the Pi configuration below is the working path. The rest of this page is the manual route, for any client the launcher does not cover.
The address
- Stable.
http://127.0.0.1:6732/v1 - Development.
http://127.0.0.1:6733/v1
OpenAI-protocol clients take the address with /v1, as above. Anthropic-protocol clients take it without, because they append /v1/messages themselves: http://127.0.0.1:6732.
Each installed channel owns its port, so Stable and Development can run side by side and a client pointed at one never reaches the other. The address is loopback only: no other machine can reach it, and the agent or client must be on this Mac.
The server runs while the app runs. The menu popover shows the port of the installed channel, and Observatory, the local console, is served by that same server — open it and choose Connect to see the full base URL of the instance you are running, generated from the instance itself rather than copied from this page.
There is no authorization check, which also means no isolation: any process on this Mac can reach the endpoint. Treat the port as this machine's own business, not as a shared service.
The model identifier
Model identifiers name artifacts, not families, so the correct value comes from the app rather than from a list. Install a model from the Observatory Library first, then read the identifiers the app currently serves:
curl -s http://127.0.0.1:6732/v1/models
Each entry in the returned list carries an id. That id is the identifier to paste into the samples below, and the same identifier appears in the Library and in the menu. A request naming an identifier the app does not serve is rejected with a JSON error instead of being guessed:
{"error":{"message":"model is not loaded by the local worker","type":"invalid_request_error","param":"model","code":"model_not_found"}}
Each chat model entry also carries context_window, the model's real prompt-plus-output capacity, and supported_endpoints, the paths it serves. The same list answers in both protocols' shapes, so Anthropic-protocol clients read type, display_name, and created_at from the very same entries.
Where a sample below shows <identifier from /v1/models>, paste one of those identifiers.
The credential
The endpoint performs no authorization check, so no credential is required and none can be wrong. Many clients nonetheless refuse to use a provider whose credential field is empty. Give them an inert placeholder such as astronomical: it is not a secret, it is never validated, and it never leaves this Mac.
opencode
Save this to ~/.config/opencode/opencode.json. OpenCode merges configuration sources, so an existing file only needs the provider entry added. Settings are taken from the published OpenCode configuration format, and the recipe below was run against the app before it was written down.
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"astronomical": {
"npm": "@ai-sdk/openai-compatible",
"name": "Astronomical (local)",
"options": {
"baseURL": "http://127.0.0.1:6732/v1",
"apiKey": "astronomical"
},
"models": {
"<identifier from /v1/models>": { "name": "<identifier from /v1/models>" }
}
}
}
}
OpenCode lists the model from the /models picker. To send one prompt without the picker:
opencode run -m "astronomical/<identifier from /v1/models>" "Say hello in one short sentence."
Pi
Save this to ~/.pi/agent/models.json. Pi expects a placeholder key for a keyless local server, which is what the apiKey field carries here.
{
"providers": {
"astronomical": {
"baseUrl": "http://127.0.0.1:6732/v1",
"api": "openai-completions",
"apiKey": "astronomical",
"compat": {
"supportsStore": false,
"supportsStrictMode": false,
"supportsDeveloperRole": false
},
"models": [
{ "id": "<identifier from /v1/models>", "name": "<identifier from /v1/models>" }
]
}
}
}
Pi lists the model from this file, and with these three compatibility values a Pi session works. Each has a measured reason: this endpoint's chat contract does not accept the store request option, the strict field inside a tool definition, or the developer role that reasoning-capable clients lead their system prompt with — a request carrying any of them is answered with a JSON error, not corrected. Setting them false keeps Pi on the shapes this Mac serves.
A model that answers with thinking wants reasoning: true on its entry; with that declared, a session picks a level like -m "astronomical/<identifier from /v1/models>:medium". Both shapes were run against the app before being published.
Claude Code
Claude Code speaks the Anthropic Messages protocol, which the app serves at /v1/messages. Set these in the shell that starts claude; they apply to that session only and leave your saved Claude Code settings alone. ANTHROPIC_AUTH_TOKEN carries the inert placeholder, and CLAUDE_CODE_MAX_CONTEXT_TOKENS gives Claude Code the model's real context window, the context_window value /v1/models reports, because it cannot recognize a local model name and would otherwise assume 200K tokens. This recipe was run against the app before it was written down.
ANTHROPIC_BASE_URL=http://127.0.0.1:6732 \
ANTHROPIC_AUTH_TOKEN=astronomical \
ANTHROPIC_MODEL="<identifier from /v1/models>" \
ANTHROPIC_DEFAULT_HAIKU_MODEL="<identifier from /v1/models>" \
CLAUDE_CODE_MAX_CONTEXT_TOKENS=<context_window from /v1/models> \
claude
Claude Code runs background work on its haiku model, so naming the local model there keeps that work on this Mac as well. A subagent or command that asks for opus or sonnet by name needs ANTHROPIC_DEFAULT_OPUS_MODEL, ANTHROPIC_DEFAULT_SONNET_MODEL, and CLAUDE_CODE_SUBAGENT_MODEL set the same way; astronomical launch claude sets all of them for you.
Verify the endpoint
This is the shortest request that proves a client path works end to end. It asks for a JSON answer, so it succeeds whether the model replies with prose or thinks first.
curl -s http://127.0.0.1:6732/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"<identifier from /v1/models>","messages":[{"role":"user","content":"Say hello in one short sentence."}],"max_tokens":1024}'
A working endpoint answers with a completion object: "object": "chat.completion" and an entry under choices. The first request for a model also loads it, which takes longer than later ones. A reasoning model may fill reasoning_content before content; either way, a JSON reply is the proof that the endpoint answered.
The Anthropic-protocol twin of that check asks for one Messages answer with thinking switched off, so even a thinking model replies directly:
curl -s http://127.0.0.1:6732/v1/messages \
-H 'Content-Type: application/json' \
-d '{"model":"<identifier from /v1/models>","max_tokens":1024,"thinking":{"type":"disabled"},"messages":[{"role":"user","content":"Say hello in one short sentence."}]}'
A working endpoint answers with "type": "message", the reply under content, and a stop_reason. /v1/messages/count_tokens takes the same conversation without max_tokens and answers {"input_tokens": N}, counted by the model's own tokenizer, so the number matches what the Messages request would read.
The served paths, in full: /v1/chat/completions, /v1/responses, /v1/messages, /v1/messages/count_tokens, /v1/models, /v1/embeddings, and /v1/images/generations.