OpenAI-Compatible API
Every FlyMy.AI LLM endpoint also speaks the OpenAI Chat Completions protocol.
Point the official OpenAI SDK at a per-model base_url and it works unchanged -
same client.chat.completions.create, same streaming iterator, same usage
object. This is the fastest way to try FlyMy.AI from an existing OpenAI
codebase: change two constructor arguments, keep the rest.
base_url = https://api.flymy.ai/api/v1/{endpoint_id}/openai/v1
The model is selected by the base_url, not by the model request field.
model is required by the SDK and echoed back in responses, but it does not
switch models - to use a different model, change the base_url.
This route is for LLM endpoints only. Image, video, and audio models have their own APIs - see Image Generation and Video Generation. Calling this route on a non-LLM endpoint is unsupported: if the model happens to accept a bare prompt, the request runs and is billed, and you get a raw file URL back instead of a chat reply.
Quickstart
from openai import OpenAI
MODEL = "flymyai/google-gemini-25-flash"
client = OpenAI(
base_url=f"https://api.flymy.ai/api/v1/{MODEL}/openai/v1",
api_key="fly-***",
)
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": "Answer briefly."},
{"role": "user", "content": "What is a vector database?"},
],
)
print(response.choices[0].message.content)
print(response.usage)
The API key is your regular FlyMy.AI key, passed the way the OpenAI SDK always
passes it: as a Bearer token in the Authorization header. There is no
X-API-KEY header on this route.
Unlike the native FlyMy.AI endpoint, the sync (non-streaming) call works here:
the gateway collects the model's stream internally and returns one complete
chat.completion response.
Streaming
stream = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": "Explain SSE in one paragraph."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.usage:
print("\nusage:", chunk.usage)
Streaming follows the OpenAI wire format: chat.completion.chunk events with
choices[0].delta.content, then a final chunk with empty choices and the
usage object, then data: [DONE]. Guard your handler against chunks with an
empty choices list - the usage chunk has one, and SDK examples that index
chunk.choices[0] unconditionally will crash on it.
Image inputs
Vision-capable models accept images as standard Chat Completions content
parts - public HTTP(S) URLs or base64 data: URLs, several per message if
needed:
response = client.chat.completions.create(
model=MODEL,
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "Describe both images."},
{"type": "image_url",
"image_url": {"url": "https://example.com/photo.jpg"}},
{"type": "image_url",
"image_url": {"url": "data:image/png;base64,iVBORw0KG..."}},
],
}],
)
Images are passed to the provider as-is and are billed as input tokens - the
image tokens appear in usage.prompt_tokens. Base64 data: URLs are the most
reliable way to send an image; remote URLs are fetched by the provider and add
its fetch latency to the request. Keep in mind that base64 inflates the request
body by about a third.
Token usage and billing
Responses carry the standard OpenAI usage object (prompt_tokens,
completion_tokens, total_tokens); in streaming it arrives in the final
chunk before [DONE]. These are the exact counts you are billed on, priced per
1M input and output tokens. Charges land asynchronously - see
token usage and billing for how the
wallet sync works.
Supported and unsupported parameters
| Parameter | Behavior |
|---|---|
messages | Supported: text content, image content parts, system role, full history |
stream, stream_options | Supported |
model | Required, echoed back; does not select the model |
tools, tool_choice | Rejected with 400 unsupported_parameter |
response_format | Rejected with 400 unsupported_parameter |
temperature, max_tokens, top_p, others | Silently ignored |
temperature, max_tokens and other tuning fields are accepted for SDK
compatibility but have no effect. Steer the model through the prompt instead.
Errors
Errors use the OpenAI envelope (error.message, error.type, error.code),
so SDK exception handling works as usual:
| Status | error.code | Meaning |
|---|---|---|
| 401 | missing_authorization / invalid_authorization | No or malformed Bearer header |
| 403 | invalid_api_key | Bad key, or no access to the model |
| 400 | unsupported_parameter | You sent tools, tool_choice, or response_format |
| 422 | - | Malformed request body (standard validation response) |
Which API should I use?
| OpenAI-compatible | Native FlyMy.AI | |
|---|---|---|
| Best for | Porting existing OpenAI code, LangChain and other OpenAI-protocol tooling | New integrations, the flymyai SDK, uniform access to all FlyMy.AI models |
| Auth | Authorization: Bearer fly-*** | X-API-KEY: fly-*** |
| Chat history | messages array | messages form field (JSON string) plus prompt |
| Non-streaming | Yes | No - streaming endpoint only |
| Usage in response | usage object | stream_details in the final SSE event |
Both routes hit the same models at the same prices. The native format is described in Using LLMs.