Platform API
Platform API is Nairon’s data plane, hosted at https://api.testing.nairon.cloud. Its /v1 surface is
intentionally OpenAI-compatible — point a compatible client SDK’s base_url at it and existing
client code mostly works unchanged.
Authenticate with a secret API key (nrn_sk_...) minted on platform.testing.nairon.cloud, either as
Authorization: Bearer nrn_sk_... or an x-api-key header.
Chat completions
Section titled “Chat completions”POST /v1/chat/completions — the standard chat-completions shape; unknown fields pass through to
the model. Use model: "auto" and let the platform pick the model. With "auto", an optional Nairon-specific reasoning field (turbo | fast | balanced | smart) steers automatic
model selection — it is not passed through to the model.
from openai import OpenAI
client = OpenAI(api_key="NAIRON_API_KEY", base_url="https://api.testing.nairon.cloud/v1")
resp = client.chat.completions.create( model="auto", messages=[{"role": "user", "content": "Summarize this call in one sentence."}], extra_body={"reasoning": "balanced"}, stream=True,)for chunk in resp: print(chunk.choices[0].delta.content or "", end="")Speech (text-to-speech)
Section titled “Speech (text-to-speech)”POST /v1/audio/speech — the standard speech shape; voice is a Nairon voice id or name. Streams chunked audio by default (stream: true).
curl -X POST https://api.testing.nairon.cloud/v1/audio/speech \ -H "Authorization: Bearer NAIRON_API_KEY" \ -H "Content-Type: application/json" \ -d '{"input": "Hello from Nairon.", "voice": "your-agent-voice-id", "response_format": "wav"}' \ --output speech.wavTranscriptions
Section titled “Transcriptions”POST /v1/audio/transcriptions — multipart upload, standard transcription shape.
audio_file = open("call.wav", "rb")transcript = client.audio.transcriptions.create(model="auto", file=audio_file)print(transcript.text)Every request — chat, speech, transcription, and realtime — can select a usage tier:
standard (default), premium, or eco. A tier is a policy, not a different endpoint: it
controls which models/voices a request may resolve to, a target response-time ceiling, whether the
request gets priority capacity, and (for realtime) a concurrency cap — not price directly (see
usage & billing).
standard— the standard model, standard and premium voices, speech recognition with automatic fallback, no concurrency cap.premium— everything instandard, plus a higher-reasoning model and high-fidelity voices.eco— a cost-optimised model, standard voices only, and a capped number of concurrent realtime sessions per project; requests beyond the cap are rejected rather than queued.
Every tier also accepts model: "auto", which lets Nairon pick the best available model for the
request within that tier’s policy — auto never resolves outside the tier you asked for.
Pass the tier as a tier field in the JSON body (POST /v1/chat/completions, POST /v1/audio/speech), a tier multipart form field (POST /v1/audio/transcriptions), or a ?tier=
query parameter on the realtime WebSocket connection. Omit it to get standard.
resp = client.chat.completions.create( model="auto", messages=[{"role": "user", "content": "Summarize this call in one sentence."}], extra_body={"tier": "eco"},)If a request pins a specific model/voice that the chosen tier doesn’t allow, or the tier isn’t
enabled for the project’s plan, the call fails with a 4xx error (see Errors below)
rather than silently falling back to a different model.
Realtime (speech-to-speech)
Section titled “Realtime (speech-to-speech)”A WebSocket endpoint speaking the server side of the standard Realtime event protocol — the
session.update, input_audio_buffer.append, response.create, response.output_audio.delta,
and response.done events that compatible realtime clients send/expect — with built-in
voice-activity and turn detection. Use your client SDK’s realtime support with a
websocket_base_url override pointed at this host; the model, voice, stt, tier, and
reasoning selectors are passed the same way as the HTTP endpoints, as connection query
parameters. Realtime sessions take a capacity lease for the duration of the connection — under
sustained load a session may be briefly queued rather than failing outright.
Audio in and out is fixed 24 kHz mono PCM16, matching the common client default — no resampling needed on the client side.
A handful of protocol corners are intentionally out of scope for this release, since compatible realtime clients don’t exercise them in normal use:
- Automatic (server-driven) turn detection only. The connection always behaves as if
turn_detection: server_vadis set — speech start/stop is detected for you. Manualinput_audio_buffer.commit/.clearcalls are accepted but have no effect. session.updateonly changes the model’s system instruction mid-session. Changingvoiceor the selected model after the connection is already open is not supported — pick them via the connection’s query parameters instead.conversation.item.truncate/.delete/.retrieveandresponse.cancelare not implemented (accepted and ignored). A response always plays out to completion.- If a request selects a voice or model that isn’t available for realtime, the
connection sends a structured
errorevent naming the unsupported selection rather than silently falling back — the corresponding batch (/v1/audio/speech) endpoint still works for that voice.
Errors
Section titled “Errors”Every non-2xx response — HTTP or a realtime error event — uses one envelope:
{"error": {"code": "model_not_in_tier", "message": "Model is not available in tier 'eco'"}}details may carry extra structured context (for example, field-level validation errors) and is
omitted when there’s nothing to add. The application code values:
| Code | Meaning |
|---|---|
unauthorized | The API key is missing, invalid, revoked, or not authorized for this API. |
invalid_request | Request body failed validation. |
payload_too_large | The request body exceeds the maximum accepted size. |
not_found | The referenced resource (batch, file, store, or custom voice) doesn’t exist. |
unknown_model / unknown_voice | The referenced model/voice tag doesn’t exist in the catalogue. |
unknown_tier | The tier value isn’t one of standard/premium/eco. |
tier_disabled | The tier is currently unavailable. |
tier_not_in_plan | The project’s plan doesn’t include this tier. |
model_not_in_tier / voice_not_in_tier | The pinned model/voice isn’t part of the selected tier. |
concurrency_limit_exceeded | Too many concurrent realtime sessions for this project+tier. |
unsupported_provider / unsupported_format | The requested voice/model or output format isn’t supported on this route. |
upstream_error / upstream_unavailable | The model or voice backend returned an error or couldn’t be reached. |
provider_unavailable | The selected model or voice couldn’t be reached. |
provider_misconfigured | The selected model or voice isn’t configured and can’t be used. |
catalogue_unavailable | No model/voice candidates are currently available for the request. |
retrieval_unavailable | Knowledge-base retrieval is unavailable. |
storage_unavailable | A storage operation (such as a reference-audio upload) failed. |
service_unavailable | A required backing service is temporarily unavailable. |
internal_error | An unexpected server-side failure. |
Framework-level HTTP errors — an unknown route or an unsupported method, for example — use the
same envelope with the code http_error. Switch on code defensively and fall back to the HTTP
status for any code you don’t recognize.
Batch API
Section titled “Batch API”Standard batch shape: upload a JSONL file, create a batch job against /v1/chat/completions (or
another supported endpoint), poll for completion, then fetch the output file.
file = client.files.create(file=open("requests.jsonl", "rb"), purpose="batch")batch = client.batches.create( input_file_id=file.id, endpoint="/v1/chat/completions", completion_window="24h",)# poll client.batches.retrieve(batch.id) until status == "completed", then:# client.files.content(batch.output_file_id)Knowledge base stores
Section titled “Knowledge base stores”/v1/stores (create/list/get/delete) and /v1/stores/{store_id}/information (add/list/remove
content) let you manage a knowledge base programmatically — the same underlying entity as a
dashboard-created knowledge base. POST /v1/stores/{store_id}/query
runs a similarity search and returns scored chunks.
curl -X POST https://api.testing.nairon.cloud/v1/stores \ -H "Authorization: Bearer NAIRON_API_KEY" -H "Content-Type: application/json" \ -d '{"name": "Product Docs"}'
curl -X POST https://api.testing.nairon.cloud/v1/stores/STORE_ID/information \ -H "Authorization: Bearer NAIRON_API_KEY" -H "Content-Type: application/json" \ -d '{"name": "Pricing page", "sourceType": "website", "source": "https://example.com/pricing"}'
curl -X POST https://api.testing.nairon.cloud/v1/stores/STORE_ID/query \ -H "Authorization: Bearer NAIRON_API_KEY" -H "Content-Type: application/json" \ -d '{"query": "What does the starter plan include?", "top_k": 6}'Who am I
Section titled “Who am I”GET /v1/me returns which API key/project/organization resolved — useful for verifying a key
before wiring it into an integration.
See the full Platform API reference for every endpoint and schema.
