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
https://madisonbratina--goodenough-gateway-serve.modal.run/v1note: 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:
Authorization: Bearer $GOODENOUGH_API_KEY03Models
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 id | name | source | input /M | output /M |
|---|---|---|---|---|
| llama-3.1-8b | Llama 3.1 8B | na | $0.10 | $0.30 |
| qwen-coder-7b | Qwen 2.5 Coder 7B | cn | $0.10 | $0.30 |
| qwen-vl-7b | Qwen 2.5 VL 7B | cn | $0.10 | $0.30 |
| gpt-oss-120b | GPT-OSS 120B | na | $0.15 | $0.60 |
| auto | Auto (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:
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:
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
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)
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
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 EnoughStreaming 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].