Credentials and runtime

Register or sign in, then create a group-specific key under API keys. Keep credentials in the script process environment, outside source code, browser applications and commits. Running them sends requests and may incur charges.

For Python 3, install python3 -m pip install openai anthropic. For Node.js, use an LTS release supported by the current SDKs and install npm install openai @anthropic-ai/sdk. Use the official SDK packages.

bash
export MONKEYAI_API_KEY="YOUR_API_KEY"
powershell
$env:MONKEYAI_API_KEY="YOUR_API_KEY"

Protocol and group selection

ProtocolStable pathModel used here
ResponsesPOST /v1/responsesgpt-6.1-sol
Chat CompletionsPOST /v1/chat/completionsgpt-6.1-sol
MessagesPOST /v1/messagesclaude-opus-4-8

Use https://monkeytheme.com/v1 as the OpenAI SDK base and https://monkeytheme.com as the Anthropic SDK base. The latter appends /v1/messages. Avoid duplicating /v1.

Both model IDs are listed for the relevant groups. Scripts need Codex Pro external access, GPT direct access or a Claude group that permits external clients. Groups restricted to Codex or CC are not general script credentials. A listing does not establish access for every key or verify every vision, tool or protocol feature. Gemini native routing uses /v1beta/; swapping a model name does not translate its payload into these three protocols.

Python: text, vision and streaming across three protocols

Save as api_demo.py. Arguments select protocol, input and delivery, for example python3 api_demo.py responses text plain. For vision, provide your own static input.png in the current directory. Select stream instead of plain for streaming. All three protocols include text and vision branches. They demonstrate request structures; vision compatibility for the selected group still requires verification.

python
import base64
import os
import sys
from pathlib import Path
from openai import OpenAI, APIError
from anthropic import Anthropic, APIError as AnthropicError

# python api_demo.py responses|chat|messages text|vision plain|stream
protocol, mode, delivery = sys.argv[1:]
if protocol not in {"responses", "chat", "messages"}:
    raise ValueError("Unknown protocol")
if mode not in {"text", "vision"} or delivery not in {"plain", "stream"}:
    raise ValueError("Invalid mode or delivery")
streaming = delivery == "stream"
key = os.environ["MONKEYAI_API_KEY"]
image = None
if mode == "vision":
    image = base64.b64encode(Path("input.png").read_bytes()).decode("ascii")
try:
    if protocol == "messages":
        client = Anthropic(api_key=key, base_url="https://monkeytheme.com",
                           timeout=120.0, max_retries=0)
        content = [{"type": "text", "text": "Describe the visible evidence."}]
        if image:
            content.append({"type": "image", "source": {"type": "base64",
                "media_type": "image/png", "data": image}})
        args = dict(model="claude-opus-4-8", max_tokens=600,
                    messages=[{"role": "user", "content": content}])
        if streaming:
            with client.messages.stream(**args) as events:
                for text in events.text_stream:
                    print(text, end="", flush=True)
                final = events.get_final_message()
                print("\nusage:", final.usage)
        else:
            result = client.messages.create(**args)
            for block in result.content:
                if block.type == "text":
                    print(block.text)
            print("usage:", result.usage)
    else:
        client = OpenAI(api_key=key, base_url="https://monkeytheme.com/v1",
                        timeout=120.0, max_retries=0)
        if protocol == "responses":
            content = [{"type": "input_text", "text": "Describe the visible evidence."}]
            if image:
                content.append({"type": "input_image",
                    "image_url": "data:image/png;base64," + image})
            result = client.responses.create(model="gpt-6.1-sol",
                input=[{"role": "user", "content": content}], stream=streaming)
            if streaming:
                with result as events:
                    for event in events:
                        if event.type == "response.output_text.delta":
                            print(event.delta, end="", flush=True)
                        elif event.type in {"error", "response.failed", "response.incomplete"}:
                            raise RuntimeError(str(event))
                        elif event.type == "response.completed":
                            print("\nusage:", event.response.usage)
            else:
                print(result.output_text)
                print("usage:", result.usage)
        else:
            content = [{"type": "text", "text": "Describe the visible evidence."}]
            if image:
                content.append({"type": "image_url", "image_url": {
                    "url": "data:image/png;base64," + image}})
            result = client.chat.completions.create(model="gpt-6.1-sol",
                messages=[{"role": "user", "content": content}], stream=streaming)
            if streaming:
                with result as chunks:
                    for chunk in chunks:
                        if chunk.choices:
                            print(chunk.choices[0].delta.content or "", end="", flush=True)
            else:
                print(result.choices[0].message.content)
                print("usage:", result.usage)
except (APIError, AnthropicError, OSError, RuntimeError) as error:
    print("Request failed:", error, file=sys.stderr)
    sys.exit(1)

Node.js: the same input and delivery combinations

Save as api_demo.mjs, then use arguments such as node api_demo.mjs messages vision stream. SSE is not one JSON document. Let the SDK parse event boundaries and append only text deltas.

javascript
import fs from "node:fs/promises";
import OpenAI from "openai";
import Anthropic from "@anthropic-ai/sdk";

// node api_demo.mjs responses|chat|messages text|vision plain|stream
try {
  const [protocol, mode, delivery] = process.argv.slice(2);
  if (!["responses", "chat", "messages"].includes(protocol) ||
      !["text", "vision"].includes(mode) || !["plain", "stream"].includes(delivery)) {
    throw new Error("Invalid protocol, mode or delivery");
  }
  const apiKey = process.env.MONKEYAI_API_KEY;
  if (!apiKey) throw new Error("Set MONKEYAI_API_KEY");
  const stream = delivery === "stream";
  const image = mode === "vision" ? (await fs.readFile("input.png")).toString("base64") : null;
  if (protocol === "messages") {
    const client = new Anthropic({apiKey, baseURL: "https://monkeytheme.com",
      timeout: 120000, maxRetries: 0});
    const content = [{type: "text", text: "Describe the visible evidence."}];
    if (image) content.push({type: "image", source: {
      type: "base64", media_type: "image/png", data: image}});
    const args = {model: "claude-opus-4-8", max_tokens: 600,
      messages: [{role: "user", content}]};
    if (stream) {
      const events = client.messages.stream(args);
      for await (const event of events) {
        if (event.type === "content_block_delta" && event.delta.type === "text_delta")
          process.stdout.write(event.delta.text);
      }
      console.log("\nusage:", (await events.finalMessage()).usage);
    } else {
      const result = await client.messages.create(args);
      for (const block of result.content) if (block.type === "text") console.log(block.text);
      console.log("usage:", result.usage);
    }
  } else {
    const client = new OpenAI({apiKey, baseURL: "https://monkeytheme.com/v1",
      timeout: 120000, maxRetries: 0});
    if (protocol === "responses") {
      const content = [{type: "input_text", text: "Describe the visible evidence."}];
      if (image) content.push({type: "input_image", image_url: `data:image/png;base64,${image}`});
      const result = await client.responses.create({model: "gpt-6.1-sol",
        input: [{role: "user", content}], stream});
      if (stream) {
        for await (const event of result) {
          if (event.type === "response.output_text.delta") process.stdout.write(event.delta);
          if (["error", "response.failed", "response.incomplete"].includes(event.type))
            throw new Error(JSON.stringify(event));
          if (event.type === "response.completed") console.log("\nusage:", event.response.usage);
        }
      } else { console.log(result.output_text); console.log("usage:", result.usage); }
    } else {
      const content = [{type: "text", text: "Describe the visible evidence."}];
      if (image) content.push({type: "image_url", image_url: {url: `data:image/png;base64,${image}`}});
      const result = await client.chat.completions.create({model: "gpt-6.1-sol",
        messages: [{role: "user", content}], stream});
      if (stream) {
        for await (const chunk of result) process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
      } else { console.log(result.choices[0].message.content); console.log("usage:", result.usage); }
    }
  }
} catch (error) {
  console.error("Request failed:", error.status ?? "", error.message);
  process.exitCode = 1;
}

Completion and usage in streams

For Responses, append response.output_text.delta and inspect completion usage; failed and incomplete events must remain failures. For Messages, collect text blocks and retrieve the final message. Chat chunks can have no choices or empty deltas; the example handles both. Chat streams do not always include usage. Once your group supports it, request stream_options: {include_usage: true} and read the final usage chunk; otherwise reconcile against account usage records.

Treat interrupted output as partial. Production applications should track completion, stop reasons and available request IDs, and close streams when cancelling. Tool calls need separate argument accumulation and validation; these examples do not invoke tools. Retrying after receiving partial output can duplicate work and charges.

Image generation and editing

The model listing lists gpt-image-2 in Codex Pro external access and the ChatGPT image group; Codex Lite does not list it. These examples use the stable /v1/images/generations and /v1/images/edits paths. The SDK sends edit input as multipart. The listing alone does not verify editing, masks, dimensions or response formats on the selected channel; those remain unverified. The code requires Base64 output and fails explicitly for URL-only output rather than downloading an unknown address.

Save the Python program as image_demo.py and the Node.js program as image_demo.mjs. Pass generate or edit; edits need your own input.png. The output overwrites output.png in the current directory, so preserve any existing file first. No video availability is guaranteed by the available evidence.

python
import base64
import os
import sys
from pathlib import Path
from openai import OpenAI, APIError

# python image_demo.py generate|edit
try:
    client = OpenAI(api_key=os.environ["MONKEYAI_API_KEY"],
        base_url="https://monkeytheme.com/v1", timeout=180.0, max_retries=0)
    mode = sys.argv[1]
    if mode == "generate":
        result = client.images.generate(model="gpt-image-2",
            prompt="A paper sculpture of a mountain, plain background", n=1)
    elif mode == "edit":
        with open("input.png", "rb") as image:
            result = client.images.edit(model="gpt-image-2", image=image,
                prompt="Change the background to pale blue; preserve the subject", n=1)
    else:
        raise ValueError("Use generate or edit")
    if not result.data or not result.data[0].b64_json:
        raise RuntimeError("No Base64 image returned; inspect the response format safely")
    Path("output.png").write_bytes(base64.b64decode(result.data[0].b64_json, validate=True))
    print("Saved output.png")
except (APIError, OSError, ValueError, RuntimeError) as error:
    print("Image request failed:", error, file=sys.stderr)
    sys.exit(1)
javascript
import fs from "node:fs";
import OpenAI from "openai";

try {
  if (!process.env.MONKEYAI_API_KEY) throw new Error("Set MONKEYAI_API_KEY");
  const client = new OpenAI({apiKey: process.env.MONKEYAI_API_KEY,
    baseURL: "https://monkeytheme.com/v1", timeout: 180000, maxRetries: 0});
  const mode = process.argv[2];
  let result;
  if (mode === "generate") {
    result = await client.images.generate({model: "gpt-image-2",
      prompt: "A paper sculpture of a mountain, plain background", n: 1});
  } else if (mode === "edit") {
    result = await client.images.edit({model: "gpt-image-2", image: fs.createReadStream("input.png"),
      prompt: "Change the background to pale blue; preserve the subject", n: 1});
  } else throw new Error("Use generate or edit");
  if (!result.data?.[0]?.b64_json) throw new Error("No Base64 image returned; inspect response format safely");
  fs.writeFileSync("output.png", Buffer.from(result.data[0].b64_json, "base64"));
  console.log("Saved output.png");
} catch (error) {
  console.error("Image request failed:", error.status ?? "", error.message);
  process.exitCode = 1;
}

Large file input

A local path is not file content. Extract text from PDF or office documents locally; scanned pages require OCR. Read UTF-8 text with Python Path("document.txt").read_text(encoding="utf-8") or Node.js await fs.readFile("document.txt", "utf8"), then place it in the example's text block. Do not put arbitrary binary bytes in text. Uploads, file IDs, automatic URL retrieval and native PDF input remain unverified for these groups; do not assume /v1/files is enabled. Split large documents at meaningful boundaries, keep segment IDs and aggregate results later. Base64 images increase body size as well as the input budget. Confirm exact file, image and context limits for the selected channel.

Billing and troubleshooting

Use live pricing and account usage records. Input, output, caching and images may have different billing rules; group multipliers and long context rules can also matter. A per-image rate is not a text token rate, and missing stream usage does not mean free usage.

SymptomAction
401 / 403Check environment, key validity, balance and group client restrictions; never log Authorization
404 / model_not_foundCheck exact model ID, path and group; avoid duplicate /v1
400 / 422Check protocol-specific content blocks, image MIME type and optional fields; remove unsupported options
413 / context exceededResize images, extract text or split input; characters are not tokens
429 / 503Reduce concurrency, respect Retry-After and use bounded exponential backoff
Timeout / interruptionCheck connectivity and proxies; mark partial output and inspect billing before retrying
Empty image outputCheck image group permissions and return format; never fabricate an image or success result

Redact user content before logging errors. SDK automatic retries are disabled so examples do not silently repeat requests. Handle HTTP, transport and in-stream failures. Exact SDK compatibility, live channel behavior and end-to-end vision/editing results depend on the selected channel and client version.