Inference
The OpenAI-compatible surface — list models and send chat completions over the /v1 routes.
The Inference module is the OpenAI-compatible surface of the Putty API. It exposes the standard GET /v1/models and POST /v1/chat/completions routes, with no Putty-specific concepts on the wire. Any OpenAI SDK or HTTP client can call it by pointing baseURL at https://api.putty.wtf/v1 and sending an sk- API key as the bearer.
Base URL and authentication
- Base URL.
https://api.putty.wtf/v1. All routes in this module sit at the root of the OpenAI path:/v1/modelsand/v1/chat/completions. - Auth.
Authorization: Bearer <PUTTY_API_KEY>. Mint a key from the dashboard at putty.wtf/app/api-keys if you do not have one yet.
1. List available models
GET /v1/models returns the allow-list of model ids the gateway will accept. Anything outside that list is rejected with 400 bad_model. Call it before showing a model picker to a user, and again whenever the picker is opened so a freshly rotated allow-list reaches the client.
curl https://api.putty.wtf/v1/models \
-H "Authorization: Bearer $PUTTY_API_KEY"The response is the standard OpenAI { object, data } shape, with a few Inference-specific fields (display_name, context_length, input_modalities, logo_path) on each row that the dashboard picker reads and the OpenAI SDK ignores.
{
"object": "list",
"data": [
{
"id": "newapi/MiniMax-M3",
"object": "model",
"created": 1767225600,
"owned_by": "newapi",
"display_name": "MiniMax M3",
"description": "MiniMax's next-generation flagship reasoning model, via the lotshot relay. Reads images. Default for new threads.",
"context_length": 200000,
"input_modalities": ["text", "image"],
"output_modalities": ["text"],
"tool_call": true,
"reasoning": false,
"logo_path": "/models/minimax.svg"
}
]
}The OpenAI SDK exposes the same call as client.models.list():
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://api.putty.wtf/v1",
apiKey: process.env.PUTTY_API_KEY,
})
const { data } = await client.models.list()
console.log(data.map((m) => m.id))from openai import OpenAI
client = OpenAI(
base_url="https://api.putty.wtf/v1",
api_key=os.environ["PUTTY_API_KEY"],
)
models = client.models.list()
print([m.id for m in models.data])2. Send a chat completion
The call is the OpenAI POST /v1/chat/completions body verbatim. The model id is one of the ids from step 1, the bearer is your API key, and the rest of the body is the OpenAI standard.
Non-streaming
curl https://api.putty.wtf/v1/chat/completions \
-H "Authorization: Bearer $PUTTY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "newapi/MiniMax-M3",
"messages": [{ "role": "user", "content": "Say hello in one short sentence." }],
"stream": false
}'The response is a JSON ChatCompletion object — the same shape openai-node returns. Read choices[0].message.content for the answer.
Streaming
curl https://api.putty.wtf/v1/chat/completions \
-H "Authorization: Bearer $PUTTY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "newapi/MiniMax-M3",
"messages": [{ "role": "user", "content": "Say hello in one short sentence." }],
"stream": true
}'With "stream": true the response is a text/event-stream stream of ChatCompletionChunk objects — the same wire format the OpenAI SDK already parses. The stream ends with data: [DONE]. stream defaults to false, as in the OpenAI API, so omit it for a blocking JSON response.
If the upstream model fails, a blocking call returns 502 with error.code: "upstream_error" and the provider's message. A streamed call ends with a chunk that carries an error object before data: [DONE].
Images
A user message can carry an image as an OpenAI image_url part. The URL is an https URL or a base64 data: URL; a data: image may be at most 3 MB decoded, and a larger one is rejected with 400. Only models whose input_modalities include "image" read it (today, newapi/MiniMax-M3).
{
"role": "user",
"content": [
{ "type": "text", "text": "What color is this?" },
{ "type": "image_url", "image_url": { "url": "data:image/png;base64,iVBORw0KGgo..." } }
]
}OpenAI SDK
Streaming, the data: [DONE] sentinel, usage on the final chunk, and the rest of the OpenAI surface are all the SDK's defaults — no Putty-specific client library is required.
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://api.putty.wtf/v1",
apiKey: process.env.PUTTY_API_KEY,
})
const stream = await client.chat.completions.create({
model: "newapi/MiniMax-M3",
messages: [{ role: "user", content: "Say hello in one short sentence." }],
stream: true,
})
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "")
}from openai import OpenAI
client = OpenAI(
base_url="https://api.putty.wtf/v1",
api_key=os.environ["PUTTY_API_KEY"],
)
stream = client.chat.completions.create(
model="newapi/MiniMax-M3",
messages=[{"role": "user", "content": "Say hello in one short sentence."}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")The full request/response schema and a live playground live on the generated reference at GET /v1/models and POST /v1/chat/completions.
3. The Putty Engine's MCP server
The same Putty Engine that serves this Inference module also runs a Model Context Protocol (MCP) server, reachable at https://mcp.putty.wtf/mcp. The dashboard chat surface wires the MCP server in automatically, so the tools listed under MCP → Overview are available to the model during a chat completion without any client-side configuration — the chat picks them up the same way the model picks up an OpenAI tools array.
A non-dashboard client that wants the same capability can point its own MCP client at https://mcp.putty.wtf/mcp with the same sk- key it uses for /v1/chat/completions. The transport is Streamable HTTP and the server is stateless, so an initialize round-trip is optional. The ten tool categories are described on the MCP overview; the authentication details, including which tools need more than an sk- key, are on Authenticating; and the full request/response shape for every tool lives on the generated reference at GET /mcp/tools.