GOOD ENOUGH.AI

api reference

One endpoint. OpenAI-compatible. Cheap.

If your code speaks to the OpenAI API, it already speaks to ours. Point your client at our base URL and pick a model.

01Base URL

base url
https://madisonbratina--goodenough-gateway-serve.modal.run/v1

note: this is the live pilot URL. A branded api.goodenough.ai URL lands at GA — swapping it later is a one-line change on your side.

02Authentication

Send your API key as a standard bearer token on every request:

header
Authorization: Bearer $GOODENOUGH_API_KEY

03Models

gpt-oss-120b is the flagship — start there. It's a reasoning model: it spends tokens thinking (returned as reasoning_content) before the final answer, so give it generous max_tokens (500+) or the answer may arrive truncated or empty.

Prices are USD per million tokens. Send model: "auto" and the router picks the right backend per request — the response's model field tells you what it chose.

model idnamesourceinput /Moutput /M
llama-3.1-8bLlama 3.1 8Bna$0.10$0.30
qwen-coder-7bQwen 2.5 Coder 7Bcn$0.10$0.30
qwen-vl-7bQwen 2.5 VL 7Bcn$0.10$0.30
gpt-oss-120bGPT-OSS 120Bna$0.15$0.60
autoAuto (router)$0.10$0.30
typical frontier API (for reference)$3.00$15.00

Auto routing (IRL)

The router is a learned classifier (embedding + linear head) with deterministic constraint filters. With model: "auto" the request body may carry an optional preferences.exclude_sources field — a hard filter on model origin (values from the source column above) applied before the router picks:

bash
curl https://madisonbratina--goodenough-gateway-serve.modal.run/v1/chat/completions \
  -H "Authorization: Bearer $GOODENOUGH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "auto", "messages": [{"role": "user", "content": "refactor this function"}],
       "preferences": {"exclude_sources": ["cn"]}}'

The response's model field reveals which model the router picked — no custom headers. If the filter excludes every servable model, the request fails with a 400.

Attachments & vision

Messages may carry OpenAI-style content parts: an array mixing text and image_url parts (data: URLs work). With model: "auto", any request carrying an image routes to the vision pool (qwen-vl-7b) — text pools never see images, and the vision pool only serves image requests:

bash
curl https://madisonbratina--goodenough-gateway-serve.modal.run/v1/chat/completions \
  -H "Authorization: Bearer $GOODENOUGH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "messages": [{"role": "user", "content": [
      {"type": "text", "text": "What is in this image?"},
      {"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBOR..."}}
    ]}]
  }'

The vision pool is optional per deployment; when it isn't deployed (or the provenance filter excludes it), image requests fail with a 400 naming the constraint.

04curl

bash
curl https://madisonbratina--goodenough-gateway-serve.modal.run/v1/chat/completions \
  -H "Authorization: Bearer $GOODENOUGH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "messages": [{"role": "user", "content": "write a function that dedupes a list"}],
    "stream": true,
    "stream_options": {"include_usage": true}
  }'

05Python (openai client)

python
from openai import OpenAI

client = OpenAI(
    base_url="https://madisonbratina--goodenough-gateway-serve.modal.run/v1",
    api_key="YOUR_GOODENOUGH_KEY",
)

resp = client.chat.completions.create(
    model="auto",
    messages=[{"role": "user", "content": "Summarize this support ticket: ..."}],
)
print(resp.model)  # what the router picked, e.g. "llama-3.1-8b"
print(resp.choices[0].message.content)

06JavaScript — change your baseURL

js
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://madisonbratina--goodenough-gateway-serve.modal.run/v1", apiKey: process.env.GOODENOUGH_API_KEY });
// done — every existing chat.completions call now runs on Good Enough

Streaming works the standard way: set stream: true (and stream_options.include_usage to get a final usage chunk). SSE lines arrive as data: JSON, terminated by data: [DONE].