Skip to content

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.

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="")

POST /v1/audio/speech — the standard speech shape; voice is a Nairon voice id or name. Streams chunked audio by default (stream: true).

Terminal window
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.wav

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 in standard, 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.

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_vad is set — speech start/stop is detected for you. Manual input_audio_buffer.commit/.clear calls are accepted but have no effect.
  • session.update only changes the model’s system instruction mid-session. Changing voice or 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/.retrieve and response.cancel are 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 error event naming the unsupported selection rather than silently falling back — the corresponding batch (/v1/audio/speech) endpoint still works for that voice.

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:

CodeMeaning
unauthorizedThe API key is missing, invalid, revoked, or not authorized for this API.
invalid_requestRequest body failed validation.
payload_too_largeThe request body exceeds the maximum accepted size.
not_foundThe referenced resource (batch, file, store, or custom voice) doesn’t exist.
unknown_model / unknown_voiceThe referenced model/voice tag doesn’t exist in the catalogue.
unknown_tierThe tier value isn’t one of standard/premium/eco.
tier_disabledThe tier is currently unavailable.
tier_not_in_planThe project’s plan doesn’t include this tier.
model_not_in_tier / voice_not_in_tierThe pinned model/voice isn’t part of the selected tier.
concurrency_limit_exceededToo many concurrent realtime sessions for this project+tier.
unsupported_provider / unsupported_formatThe requested voice/model or output format isn’t supported on this route.
upstream_error / upstream_unavailableThe model or voice backend returned an error or couldn’t be reached.
provider_unavailableThe selected model or voice couldn’t be reached.
provider_misconfiguredThe selected model or voice isn’t configured and can’t be used.
catalogue_unavailableNo model/voice candidates are currently available for the request.
retrieval_unavailableKnowledge-base retrieval is unavailable.
storage_unavailableA storage operation (such as a reference-audio upload) failed.
service_unavailableA required backing service is temporarily unavailable.
internal_errorAn 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.

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)

/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.

Terminal window
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}'

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.