Guide

OpenAI-Compatible API on Android: Endpoints, Base URL and Code Examples

Your Android phone can answer the same requests you send to a hosted chat API, so existing scripts and tools work against it by changing a base URL. This guide covers the endpoints, the URL format, working code, and how to keep the server on your own network.

Updated 7 min readBy FreeRouter Team

If you build with AI tools, you have probably written the same few lines many times: set a base URL, set a key, name a model, send a chat request. An OpenAI-compatible API on Android means your phone answers those same requests, so code you already have keeps working with a different address. The server is part of the Ollama Local AI app, available on Google Play. Start it, read the endpoint the app shows, and every example below follows from that one address.

What the phone does when a request arrives

The phone acts as a gateway. A request arrives over your local network, the app chooses a backend, and the answer comes back in the standard response shape that OpenAI client libraries expect. The backend can be a quantized GGUF model running on the device through embedded llama.cpp, a self-hosted Ollama instance or llama.cpp server, or a cloud provider such as OpenAI, Anthropic Claude, NVIDIA NIM, Hugging Face, OpenRouter, Gemini, or any custom OpenAI-compatible endpoint.

Your client does not need to know which backend answered. On-device models work without internet. Cloud requests go directly from the phone to that provider, with no relay server operated by the app developer.

Ollama Local AI is an independent app by FreeRouter Team. It is not affiliated with, sponsored by, or endorsed by OpenAI or any other provider named here. "OpenAI-compatible" describes the request format only.

The three endpoints

The app exposes three routes to devices on the same network.

MethodPathWhat it is for
GET/healthQuick check that the server is up and reachable. Run it first when anything misbehaves.
GET/v1/modelsLists the model ids you can put in the model field.
POST/v1/chat/completionsThe chat endpoint. Send a list of messages, receive a completion, or set stream to true for incremental output when the selected provider and model support streaming.

Only these three routes are listed for the app. Do not assume other OpenAI routes such as embeddings, images, or audio are available; test them before building on them.

Building the base URL

The base URL has four parts: the scheme, the phone's address on your Wi-Fi, the port, and the /v1 prefix.

http://<PHONE_IP>:<PORT>/v1
http://192.168.1.50:8080/v1   (example only)

Copy the real value from the app rather than typing it from memory. Client libraries append the route themselves, so the base URL stops at /v1. The health check sits at the root, for example http://192.168.1.50:8080/health.

Four mistakes cause most first-run failures:

  • Using https. On a home LAN the endpoint is plain http.
  • Leaving out /v1, or adding it twice.
  • Pasting a full route such as /chat/completions into a field that expects a base URL.
  • Using an old IP address. Routers can hand out a new address after a reconnect, so consider reserving one for the phone in your router settings.

curl examples

curl is the fastest way to confirm that the phone, the network, and the server agree. Replace the address with yours.

Check that the server is up

curl -i http://192.168.1.50:8080/health

List models

curl http://192.168.1.50:8080/v1/models

Pick an id from the response. That string is your <MODEL_ID> in the next requests.

Send a chat completion

curl http://192.168.1.50:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer local-lan" \
  -d '{
    "model": "<MODEL_ID>",
    "messages": [
      {"role": "system", "content": "You are a concise assistant."},
      {"role": "user", "content": "Explain DHCP in two sentences."}
    ]
  }'

The local-lan value is a placeholder, like the one the quick start uses for Cursor.

Stream the response

curl -N http://192.168.1.50:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer local-lan" \
  -d '{
    "model": "<MODEL_ID>",
    "stream": true,
    "messages": [{"role": "user", "content": "Count from one to five."}]
  }'

The -N flag turns off curl's output buffering so chunks appear as they arrive. In the standard format each event is a line starting with data:, and the stream ends with data: [DONE]. Streaming depends on the provider and model; the app has a per-provider Request streaming switch that you can turn off if a provider rejects it.

Python with the official openai package

Point the official package at the phone by passing base_url. The package requires a non-empty api_key, so supply a placeholder.

from openai import OpenAI

client = OpenAI(
    base_url="http://192.168.1.50:8080/v1",
    api_key="local-lan",  # placeholder, not a real key
)

models = client.models.list()
model_id = models.data[0].id
print("Using:", model_id)

reply = client.chat.completions.create(
    model=model_id,
    messages=[{"role": "user", "content": "Give me one tip for debugging Wi-Fi."}],
)
print(reply.choices[0].message.content)

stream = client.chat.completions.create(
    model=model_id,
    messages=[{"role": "user", "content": "Count from one to five."}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

Frameworks such as LangChain, LlamaIndex, LiteLLM, AutoGen, and CrewAI have an OpenAI-compatible mode that accepts a base URL and a key. Check each framework's documentation for the exact class or model-name prefix it expects.

JavaScript with fetch

No SDK is required. Save this as an .mjs file, or wrap it in an async function, and run it with Node.js 18 or later, Deno, or Bun.

const BASE_URL = "http://192.168.1.50:8080/v1";

const res = await fetch(`${BASE_URL}/chat/completions`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer local-lan",
  },
  body: JSON.stringify({
    model: "<MODEL_ID>",
    messages: [{ role: "user", content: "Say hello in five words." }],
  }),
});

if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data.choices[0].message.content);

Calling the phone from a web page in a browser adds two browser rules that have nothing to do with the phone: a page served over https cannot call a plain http address, and cross-origin requests are subject to CORS.

How model ids work

The model field in a chat request must match an id returned by GET /v1/models. Treat ids as opaque strings. Copy them exactly, including case, punctuation, and any prefix, and do not assume they match the names a vendor uses on its own API.

When you load a different on-device model or add or change a provider in the app, request the model list again instead of reusing an old id. The app also offers Auto Route, which can pick a free model; check the app for how it is presented to clients.

Rate limits, failover, and round-robin

The phone is more than a pass-through. Its smart proxy includes request pools, provider timeouts, rate limiting, automatic failover, and round-robin distribution. Conceptually:

  • Rate limiting caps how fast requests are accepted. Check the app for the available settings.
  • Timeouts stop a request from waiting forever on a provider that has stalled.
  • Failover means that when one configured provider fails, the gateway can try another one instead of returning an error straight away.
  • Round-robin spreads requests across several providers in turn, which helps when you have more than one option for the same kind of work.

Exact limits and settings live in the app, so this guide quotes no numbers. On the client side, set your HTTP timeout longer than the slowest model you use, keep concurrency modest, and retry with a short backoff on timeouts, HTTP 429, and 5xx responses, as you would with any shared API.

Keep the server on your private network

The endpoint is built for devices on the same local Wi-Fi or LAN. Treat it that way.

  • Never expose it to the public internet. Do not port-forward the phone, and do not publish it through a public tunnel.
  • Remember it is plain http. Traffic between your computer and the phone on the LAN is not encrypted by TLS, so avoid shared or public Wi-Fi for this.
  • Assume anyone who can reach the port could send requests. If you added cloud provider keys, those requests are made with your accounts, so only use networks you control.
  • Turn the server off when you are done instead of leaving it running on a network you do not fully trust.

Troubleshooting connection errors

  1. Confirm the server is running in the app, then try /health from the same computer as your script.
  2. Make sure the phone and the computer are on the same Wi-Fi network. Guest networks often isolate devices from each other.
  3. Check the IP and port against the endpoint the app displays right now.
  4. Verify the URL uses http and includes /v1, then rule out VPNs and firewalls blocking the port.
  5. A model not found style error usually means the id was typed from memory; fetch /v1/models and copy it.

Once requests succeed, connect your editor: see connect Cursor to your phone or use your phone with VS Code. For a wider overview of running models on the phone itself, read run an LLM on Android.

Frequently asked questions

What is the base URL for the OpenAI-compatible API on my phone?

It is http, then the phone's LAN IP address, the port, and /v1, for example http://192.168.1.50:8080/v1. Use the exact endpoint the app shows after you tap Start Server; the address in this guide is only an example.

Which endpoints does the phone expose?

Ollama Local AI exposes /v1/chat/completions for chat, /v1/models to list available model ids, and /health as a quick check that the server is up. Do not assume other OpenAI routes are available without testing them.

Do I need a real API key to call the phone?

The quick start uses a placeholder such as local-lan in tools like Cursor, and the official openai packages simply need a non-empty string. Your real cloud provider keys stay in the app's encrypted on-device storage and are not part of your client code.

Where do I find the model id to put in my request?

Send a GET request to /v1/models and copy the id value exactly as returned. Do not guess ids or reuse names from another provider's documentation.

Can I reach the phone's API from outside my home network?

The server is meant for devices on the same local Wi-Fi or LAN. Do not port-forward it or publish it to the public internet; keep access limited to networks you trust.

Keep reading

Ollama Local AIAndroid · iPhone coming soon
Install