Nur holds a conversation. You stream audio in, Nur streams audio back, and the server decides when you have finished speaking, lets you interrupt, and keeps the conversation’s history. The model is nur-v1. There are two ways to connect, and both speak the OpenAI Realtime protocol: Events, session settings and replies are the same on both.

WebRTC in the browser

The browser captures the microphone and plays Nur through a normal audio element. Your server only relays two requests, so the key stays private.
server.js
browser.js
To hang up, close the peer connection. The OpenAI SDKs can create the call too: client.realtime.calls.create(sdp=..., session=...) posts the same request.

Hold to talk

A product with a talk button can keep what the person said before the call connected and end their turn the moment they let go. Add held_audio=1 to the /realtime/calls URL. Once the session is up, send what you held as input_audio_buffer.append events on the data channel (16 kHz, 16-bit mono PCM, base64, up to 20 seconds): the first second at once and the rest at no more than three times real time, since Nur keeps two seconds of input. Then send {"type": "eesi.input.track"} and add the microphone’s track; its audio waits behind the marker. When the button comes up, send {"type": "eesi.input.release"} once the last of the speech has gone out, and {"type": "eesi.input.hold"} if it goes down again within 700 ms. A call that takes them says hold_to_talk: true in session.created’s capabilities.events.

WebSocket from a server

A browser can also use the WebSocket by connecting with a single-use ticket instead of a key.

Configure the session

Send session.update at any time. Later updates merge with earlier ones, so a frame that changes only the voice keeps your instructions.
A field it cannot take, such as a replace phrase with punctuation in it, refuses the whole update with an invalid_session_config error and changes nothing. Put today’s date and anything else the assistant should know in instructions.

Audio on a WebSocket

WebRTC handles audio formats for you. On a WebSocket, audio is 16-bit mono PCM, base64 encoded:
  • Input defaults to 16 kHz. Send about 20 ms per message (640 bytes) at the pace it was recorded. Turn detection listens to the timing, so sending a file all at once makes for poor turn-taking, not a faster reply.
  • Output defaults to 16 kHz. For 24 kHz, set "output": {"format": {"type": "audio/pcm", "rate": 24000}}.
  • For telephony, {"type": "audio/pcmu"} and {"type": "audio/pcma"} select G.711 at 8 kHz.
To keep 16 kHz, leave format out. Don’t write "rate": 16000, which the schema rejects.
You can type into the same conversation. Text does not end a turn by itself, so follow it with response.create:
input_audio_buffer.clear drops audio that turn detection has not picked up yet, answered with input_audio_buffer.cleared; speech it already heard finishes as a turn. conversation.item.delete with an item_id takes an item out of what Nur reads next: one you added, one of Nur’s replies by its item_id, or a spoken turn by the item_id its transcript came with. It is answered with conversation.item.deleted, or an item_not_found error.

Binary audio

Set audio.input.transport or audio.output.transport to "binary" to send or receive audio as binary WebSocket frames that hold the audio’s bytes and nothing else, in place of input_audio_buffer.append and response.output_audio.delta. Every other event stays JSON, and a reply’s audio frames belong to the response.created before them. Input is accepted either way. A change to the output transport applies from the next reply on, so a reply never arrives half in one form and half in the other.
With {"type": "audio/opus"}, each binary frame (or each base64 audio and delta) is one raw Opus packet, 24 kHz mono. Nur’s packets are 20 ms each. Over JSON, each packet is its own response.output_audio.delta.

Scripted lines

To have Nur say something word for word, such as a disclosure or a greeting your product wrote, send a force_message:
The line arrives as an ordinary reply (response.created, its transcript and audio, response.done) and joins the conversation as something Nur said, under the item id you give or one the server picks. Don’t follow it with response.create: the line is the turn. A line sent while Nur is speaking waits for the reply to end, and lines are said in the order you send them. interruptible defaults to true, so people can talk over the line like any reply. With false, what they say is not heard until the line has played, so nothing cuts it off. response.cancel still stops it. Lines are up to 4,096 characters. Add "language": "es" (any language code) when you know what language the line is in, so the voice says it as a speaker of that language would; without it, the server tells from the line itself.

Interruptions

People talk over each other, and Nur is built for it. When someone starts speaking over a reply, the server stops generating and tells you with input_audio_buffer.speech_started. On WebRTC the gateway also stops the audio it already sent. On a WebSocket, stop your own playback when that event arrives, or Nur will seem to keep talking.

Events you will use

The server also sends a few eesi.* events. eesi.session carries the session_id and resume_token for reconnecting, eesi.recording_disclosure has wording to show people when sessions are recorded, and eesi.synthetic_audio declares the audio machine-generated. You can ignore them if you don’t need them.

Reconnecting

If a connection drops, reconnect within 15 minutes with ?resume=<session_id>&resume_token=<resume_token> from the eesi.session event, and the conversation continues where it left off. The new eesi.session event then includes resumed; if it doesn’t, you got a fresh session.

Languages

Nur understands and speaks more than a hundred languages and follows the person when they switch. To keep it in one language, say so in instructions. For interpreting rather than conversing, use Live translation. When the person speaks first, as in a push-to-talk app, name the languages they speak, most used first and three at most, so that a word or two at the start is heard in one of them: "eesi_languages": ["ar", "en"] in session.update. Once they have said more, Nur follows whatever language they speak.

Limits

  • A session can last up to three hours. A WebSocket with no client traffic for five minutes closes; send {"type": "eesi.keepalive"} while a person is reading or thinking.
  • Usage is billed per minute of session time.
  • If your organization runs out of credits during a call, the session ends with a message saying why before it closes.