NeoAI API · v1

API reference

Everything you need to call the NeoAI API: one OpenAI-compatible endpoint for every model, and Neo.

Introduction

The NeoAI API gives your product the leading models from every lab through one endpoint, one key and one bill. It follows the OpenAI Chat Completions format, so the official OpenAI SDKs work as they are: change the base URL and the key.

v1
Base URL   https://neoolympus.ai/v1
Auth       Authorization: Bearer neo_live_…

Send "model": "neo" to use Neo, the layer that unifies every model, or any model id from the model list. Every answer says what it cost.

Keep keys on your server. Never put a key in a web page or a mobile app. The API sends no CORS headers, so browsers refuse it anyway.

Authentication

Every request carries your key in the Authorization header.

Authorization: Bearer neo_live_3f9c…
  • Make keys in the console. A key is shown once: NeoAI keeps only a fingerprint of it, so it cannot show it again.
  • Rotate a key without downtime: the new key works at once and the old one keeps working for 24 hours.
  • Revoke a key and it stops at once. An account can have up to 20 working keys.
  • Live keys start with neo_live_. Accounts on a monthly invoice also get test keys (neo_test_) with a small budget of their own.
  • In the console you can limit an account to a list of IP addresses.

Quickstart

Create your account in the console, add credits, make a key and export it as NEO_API_KEY. Then:

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://neoolympus.ai/v1",
    api_key=os.environ["NEO_API_KEY"],
)

r = client.chat.completions.create(
    model="neo-terra",  # neo-luna, neo-sol or any model id from /v1/models
    messages=[{"role": "user", "content": "Explain quantum computing in two sentences."}],
)
print(r.choices[0].message.content)

Models

GET/v1/models

The models your account may use: neo first, then every chat model, newest first. Prices are what you pay, per million tokens.

{
  "object": "list",
  "data": [
    {
      "id": "neo-terra", "object": "model", "owned_by": "neoai", "name": "Neo Terra",
      "neo": {"level": "terra", "price_tier": "$$", "pricing": {"input_usd_per_mtok": 0.91, "output_usd_per_mtok": 4.55}, "tool_calling": true, "zdr": true}
    },
    {
      "id": "anthropic/claude-sonnet-5.5", "object": "model", "owned_by": "anthropic", "context_length": 1000000,
      "neo": {
        "input_modalities": ["text", "image"], "output_modalities": ["text"], "price_tier": "$$",
        "pricing": {"input_usd_per_mtok": 3.6455, "output_usd_per_mtok": 18.2275},
        "max_output_tokens": 64000, "tool_calling": true, "zdr": true
      }
    }
  ]
}
FieldWhat it is
neo.kindtext, image, video, speech or transcription; any for Neo, which does them all.
neo.price_tier$, $$ or $$$: how expensive the model is, from its real price.
neo.pricingUS dollars: per million input and output tokens; for pictures usd_per_image, video usd_per_second, voice usd_per_mchar (per million characters), transcription usd_per_minute. Neo models have them all.
neo.tool_callingtrue: passed NeoAI's tool-calling test. false: failed it or has no tools. null: not tested yet.
neo.zdrtrue if a provider with zero data retention serves it.

Chat completions

POST/v1/chat/completions

The OpenAI request body, with a few optional fields of NeoAI's.

Request

FieldTypeWhat it does
modelstring, required"neo-luna", "neo-terra" or "neo-sol" (Neo), "neo" (Neo at your account's default level), or a model id from GET /v1/models.
messagesarray, requiredThe conversation, in the OpenAI format: roles system, user, assistant, tool (or developer).
streambooleanSend the answer as server-sent events. See Streaming.
toolsarrayFunctions the model may call. See Tool calling.
tool_choicestring or objectAs in OpenAI: auto, none, required or one function.
max_tokensintegerMost tokens the answer may use (max_completion_tokens works too). Without it, the model's own limit applies.
userstringYour own customer's id, up to 200 characters. Optional but recommended; required for accounts on a monthly invoice. Also accepted as the X-Neo-End-User header.
temperature, top_p, stop, seedSent to the model as they are.
response_formatobjectJSON mode or a JSON schema, for the models that support it.
presence_penalty, frequency_penalty, logit_bias, parallel_tool_callsSent to the model as they are.
reasoningobjectReasoning effort for the models that think, e.g. {"effort": "high"}.

NeoAI fields

FieldTypeWhat it does
neo_level"luna" · "terra" · "sol"With model neo: which level. Default: your account's (terra unless you change it in the console). Not needed with neo-luna, neo-terra or neo-sol.
neo_zdrbooleanOnly providers with zero data retention answer. See Zero data retention.
neo_max_cost_usdnumberThe most this one request may cost, in US dollars. A request that would go over is refused before it runs.
neo_fallbackbooleanDefault true. With false, only the first model is tried.

With the OpenAI SDKs, send NeoAI's fields in extra_body (Python) or in the request object (JavaScript).

Response

A chat.completion object as in OpenAI, plus a neo object.

{
  "id": "chatcmpl-7f3a…",
  "object": "chat.completion",
  "created": 1793145600,
  "model": "neo-terra",
  "choices": [{"index": 0, "message": {"role": "assistant", "content": "…"}, "finish_reason": "stop"}],
  "usage": {"prompt_tokens": 412, "completion_tokens": 96, "total_tokens": 508},
  "neo": {
    "request_id": "req_7f3a…",
    "model": "neo-terra",
    "requested": "neo-terra",
    "level": "terra",
    "end_user": "customer-123",
    "cost_usd": 0.000812,
    "estimated": false
  }
}
neo.What it is
request_idThe id of this request in your usage and in the console. Quote it when you write to us.
modelThe model that answered, or neo when you asked for neo.
requestedThe model you asked for.
levelWith neo: the level used.
fallbackWith a model id: true if another model answered because yours failed.
zdrtrue when zero data retention was on.
end_userThe user you sent.
cost_usdWhat this request cost you, in US dollars.
estimatedtrue if the provider had not reported the exact cost yet: it is settled within minutes, and usage shows the final figure.

Usage

GET/v1/usage?from=YYYY-MM-DD&to=YYYY-MM-DD&user=…

What your requests cost in a period, in total, by day, by model and by your customers. Dates are UTC; both are optional (the default is this month). The console has the same figures, charts and a CSV of every request.

curl "https://neoolympus.ai/v1/usage?from=2026-10-01&to=2026-10-31" -H "Authorization: Bearer $NEO_API_KEY"
{
  "object": "usage", "env": "live",
  "requests": 1284, "cost_usd": 3.41827, "tokens_in": 2210431, "tokens_out": 391204,
  "by_day": [{"day": "2026-10-01", "requests": 41, "cost_usd": 0.10233}, …],
  "by_model": [{"model": "neo-terra", "requests": 1102, "cost_usd": 2.90311, …}, …],
  "by_end_user": [{"end_user": "customer-123", "requests": 77, "cost_usd": 0.21455, …}, …],
  "balance": {"available_usd": 6.5817, "held_usd": 0.0}
}

Neo and levels

Neo is one layer over every model: send it text, code or images, with or without tools, at the level you choose. If a model fails or is too slow, another one answers. Neo comes in three models, each with one fixed price per million tokens:

  • neo-luna, Neo Luna: fast and cheap. Short answers, extraction, classification.
  • neo-terra, Neo Terra (the default): writing, analysis and everyday code.
  • neo-sol, Neo Sol: the strongest models, for hard reasoning, long documents and complex code.

"model": "neo" is Neo at your account's default level (Neo Terra unless you change it in the console); neo_level picks another.

The response shows the Neo model you asked for and its level, not the model behind it. You pay the level's fixed price, whichever model answered; the API page and GET /v1/models show it. To pin one model, use its id instead.

Streaming

With "stream": true the answer arrives as server-sent events, in the OpenAI format. The last chunk has no choices and carries usage and neo (with the cost). While a model thinks, the stream sends a comment line every 10 seconds so no proxy closes it.

stream = client.chat.completions.create(
    model="neo-terra",
    messages=[{"role": "user", "content": "Write a haiku about the sea."}],
    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 not chunk.choices:  # the last chunk: usage and cost
        print("\n", chunk.usage, chunk.model_extra["neo"]["cost_usd"])
text/event-stream
data: {"object": "chat.completion.chunk", "model": "neo-terra", "choices": [{"index": 0, "delta": {"role": "assistant", "content": ""}}]}

data: {"object": "chat.completion.chunk", "model": "neo-terra", "choices": [{"index": 0, "delta": {"content": "Salt wind"}}]}

: ping

data: {"object": "chat.completion.chunk", "model": "neo-terra", "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}]}

data: {"object": "chat.completion.chunk", "model": "neo-terra", "choices": [], "usage": {…}, "neo": {"cost_usd": 0.00002, …}}

data: [DONE]

Tool calling

Send tools in the OpenAI format. With neo, Neo only routes to models that passed NeoAI's tool-calling test; with a model id, the model must support tools.

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Current weather for a city",
        "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
    },
}]
r = client.chat.completions.create(model="neo-terra", tools=tools,
                                   messages=[{"role": "user", "content": "Is it raining in Barcelona?"}])
call = r.choices[0].message.tool_calls[0]
print(call.function.name, call.function.arguments)  # get_weather {"city": "Barcelona"}

Send the result back as a message with role tool and the same tool_call_id, as in OpenAI.

Pictures and audio in

Send pictures as content parts, by URL or as base64 data. With Neo, a model that reads pictures answers; a model id must read them too (see input_modalities in the model list). Audio goes in input_audio parts ({"data": base64, "format": "mp3"}): when the model can't hear it, and always with Neo, it is transcribed first and the transcript takes its place (its price is in neo.transcribed_usd). PDFs and other files go in file parts, as in OpenAI's format.

{
  "model": "neo-terra",
  "messages": [{
    "role": "user",
    "content": [
      {"type": "text", "text": "What does this receipt total?"},
      {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQ…"}}
    ]
  }]
}

Every kind in the chat

Neo answers with whatever the message asks for, in /v1/chat/completions:

  • A picture ("draw…", "make an image of…", or modalities: ["image"]): message.images, a list of {"type": "image_url", "image_url": {"url": "data:image/png;base64,…"}}. A picture in the message is edited or used as the start.
  • Something read aloud ("read this aloud: …"): message.audio with data (base64), format and transcript.
  • A video: the job in neo.video; the message says where to fetch it. A picture in the message becomes the first frame.
  • modalities: ["text", "audio"]: the text answer, also read aloud in message.audio (audio: {"voice", "format"} as in OpenAI).
  • With a model id that draws (image in its output_modalities), modalities: ["image", "text"] brings its pictures in message.images.

Streamed, the pictures or the audio come in one chunk before the finish. The price is in neo.cost_usd and neo.kind says what was made.

Pictures

POST /v1/images/generations, in OpenAI's format. With a Neo model it costs that level's price per picture; with the id of a picture model (kind image in the model list), what that model costs.

curl https://neoolympus.ai/v1/images/generations \
  -H "Authorization: Bearer $NEO_API_KEY" -H "Content-Type: application/json" \
  -d '{"model": "neo-terra", "prompt": "A red bicycle by the sea, morning light", "size": "1024x1536"}'

{
  "created": 1793145600,
  "data": [{"b64_json": "iVBORw0KGgo…"}],
  "size": "1024x1536",
  "neo": {"model": "neo-terra", "level": "terra", "cost_usd": 0.056}
}
FieldTypeWhat it does
modelstring, requiredneo-luna, neo-terra, neo-sol, neo, or a picture model's id.
promptstring, requiredWhat the picture shows, or how to change it.
ninteger1 to 4 pictures. Default 1. You pay for the ones made.
sizestringExact pixels, such as 1024x1536 (the picture is cut to them), or auto.
aspect_ratiostringSuch as 3:4 or 16:9, instead of a size.
response_formatstringb64_json (default), or url to also get a data URL.
userstringYour own customer's id, as in chat completions.

POST /v1/images/edits takes the same fields plus image: the picture to change or start from. Send it as a file (multipart, as the OpenAI SDKs do), a data URL or an https URL; up to 16.

Voice

POST /v1/audio/speech reads text aloud. The answer is the audio itself; the headers X-Neo-Request-Id, X-Neo-Model, X-Neo-Level and X-Neo-Cost-Usd say what it was and what it cost. With Neo, the price is per million characters.

r = client.audio.speech.create(model="neo-terra", voice="alloy", input="Your order has shipped.")
r.write_to_file("order.mp3")
FieldTypeWhat it does
inputstring, requiredThe text, up to 20,000 characters.
voicestringA voice the model has (its voices in the model list), one of OpenAI's names (alloy, nova, onyx…) or plain words such as deep male. Otherwise the model's first voice.
response_formatstringmp3 (default), wav, opus, aac, flac or pcm.

Transcription

POST /v1/audio/transcriptions turns a recording into text: a multipart file (mp3, wav, m4a, mp4, ogg, webm, flac or aac, up to 25 MB) or, in JSON, file as a data URL. With Neo, the price is per minute of audio.

r = client.audio.transcriptions.create(model="neo-luna", file=open("call.m4a", "rb"), response_format="srt")
FieldTypeWhat it does
languagestringThe language spoken (ISO 639-1, such as ca), when you know it.
response_formatstringjson (default: text, seconds and price), text, verbose_json (with segments), srt or vtt.

Video

A video takes minutes, so POST /v1/videos starts a job and answers at once. Ask GET /v1/videos/{id} for its status (queued, in_progress, completed, failed) and download it from GET /v1/videos/{id}/content (an MP4, kept 7 days). GET /v1/videos lists your latest. It is charged when it is made, per second; a failed job costs nothing. Neo makes video up to 720p.

{"model": "neo-terra", "prompt": "A drone shot over a vineyard at dawn", "seconds": 5, "size": "1280x720"}

{"id": "video_8c1e…", "object": "video", "status": "queued", "model": "neo-terra", "seconds": "5"}
FieldTypeWhat it does
secondsinteger1 to 20. Default 5; a model that can't make that length makes the nearest one.
size / resolutionstring1280x720 or 720p. The orientation of a size sets the aspect ratio.
aspect_ratiostring16:9 (default), 9:16, 1:1…
input_referencepictureThe first frame: a file (multipart), a data URL or an https URL.

Zero data retention

Send "neo_zdr": true and only providers that keep nothing of the request (no logging, no training) answer it. With neo, only those models answer; a model id without such a provider is refused with zdr_not_available. The console can turn it on for every request of your account.

NeoAI itself never keeps the content of API requests: it records only what billing needs (time, model, tokens and cost) and never trains on your data.

Your customers

Send your own customer's id in user (or the X-Neo-End-User header) and your usage is broken down by customer, in the console and in GET /v1/usage. Use an id of your own, not an email. Providers receive only a one-way fingerprint of it, never the id itself.

Credits and billing

  • The API is prepaid: add credits in the console, from $10, by card. Credits never expire.
  • Each request costs the tokens it uses at the model's price, as in the model list. Prices exclude tax; VAT or sales tax is added at checkout where it applies.
  • When your credits run out, requests are refused with insufficient_credits (HTTP 402). Nothing is charged automatically.
  • Set a monthly spend cap in the console; neo_max_cost_usd caps a single request.
  • Every top-up has an invoice in the console. Accounts with a monthly invoice are set up with us: info@neoolympus.ai.

Limits

LimitDefault
Requests per minute, per key120
Requests at a time, per account4
Request body32 MB
Messages per request4,000

Over a limit, the API answers 429 with a Retry-After header. Need more? Write to info@neoolympus.ai.

Errors

Errors use the OpenAI format, with an HTTP status and a code you can match on.

{"error": {"message": "This account has no credits left for this request. Add credits in the console: https://neoolympus.ai/console/billing", "type": "insufficient_credits", "code": "insufficient_credits"}}
StatusCodeWhat it means
400invalid_request_error, invalid_jsonThe body is not valid: the message says what to fix.
400tools_not_supported, images_not_supportedThe model you asked for cannot do this. Use neo, or another model.
400zdr_not_availableThe model has no zero-data-retention provider (pictures, voice, transcription and video have none yet).
400no_model_availableNo model can do this request with your account's settings.
400missing_end_userThis account must send user with every request.
400content_policyEvery model refused the picture or video (often real, recognisable people).
400resolution_not_supportedNeo makes video up to 720p; for more, pick a model id.
409video_not_ready, video_failedThe video is still being made, or could not be made (nothing was charged).
410video_expiredThe file was deleted 7 days after it was made.
401invalid_api_keyThe key is missing, wrong, revoked or expired.
402insufficient_creditsNot enough credits for this request. Add credits in the console.
402spend_limit_reachedYour monthly spend cap, or neo_max_cost_usd, would be exceeded.
403model_not_allowed, ip_not_allowed, account_inactiveThe account may not use this model, from this address, or is switched off.
404model_not_found, not_foundUnknown model or endpoint.
413request_too_largeThe body is over 32 MB.
429rate_limited, concurrency_limitToo many requests. Wait the seconds in Retry-After and try again.
502upstream_errorEvery model failed. You pay only for any part of an answer already produced; try again.

Versions

This is version 1. Within /v1 we only add things (new fields, new models, new endpoints); anything that would break your code would come as /v2, announced in advance. Model ids follow the providers: when a provider retires a model, it leaves the model list.

The OpenAPI description of this API: openapi-v1.yaml.