For coding agents
This page gives a coding agent what it needs to build with the Expression Measurement API: what the API does, one complete request and response, the realtime session flow and the rules that code most often gets wrong. Point your agent to this page before it writes code that calls the API.
Set up your agent
Add this to your project’s agent instructions, such as CLAUDE.md or AGENTS.md:
What the API does
- The API measures emotional expression in speech and in faces. The audio endpoints detect utterances in speech and measure each window of speech. The video endpoints detect faces in JPEG images and measure the largest faces in each one.
- Each measurement is lists of scores, not text. A score pairs a
name, such asamusement, with aprobabilityfrom 0 to 1. Each array includes only the scores that pass its own cutoff. - Upload endpoints measure media you already have, and realtime endpoints measure media as it streams. An upload is one
multipart/form-datarequest that returns every measurement in its response. A realtime session is a WebSocket that returns each measurement as soon as it is produced. For the same audio, both produce the same measurements. - Every upload request and realtime session is recorded as a run. The run endpoints return a run’s status, configuration and event log, but not its measurements.
Make a request
An audio upload is a multipart/form-data POST to https://api.cloud.hume.ai/v1/expression/audio/file, with the API key in the X-Hume-Api-Key header and up to two parts:
file: one audio file, as a 16 kHz mono 16-bit PCM WAV file or as headerless samples in that format.config: an optional JSON object of settings, such asmeasurement_timer_ms, the interval between measurements.
The response lists every utterance with its span, and every measurement with the utterance_id it belongs to. Times are in milliseconds from the start of the audio. This response is for 5 seconds of audio containing one utterance:
An image upload to https://api.cloud.hume.ai/v1/expression/video/file takes one or more JPEG images as file parts, and returns one result per image with the faces found in it. See Image upload.
Stream in realtime
The realtime endpoints are wss://api.cloud.hume.ai/v1/expression/audio/realtime and wss://api.cloud.hume.ai/v1/expression/video/realtime. Both use the same session protocol:
- Open the WebSocket with the API key in the
X-Hume-Api-Keyheader. The server sendssession.createdwith thesession_id, which is also the session’s run ID. - To change the defaults, send
session.updatebefore the first media frame. The server confirms withsession.updated. - Send media as binary frames. On the audio endpoint, send headerless 16 kHz mono 16-bit PCM in frames of about 100 ms. On the video endpoint, send one JPEG image per frame.
- Read each
measurement.result. The audio endpoint also sendsutterance.startandutterance.endaround each stretch of speech. The video endpoint replies to every frame with ameasurement.resultor anerror. - Send
session.close, then read untilsession.closedarrives. Results still in progress are delivered before it, and it totals what the server received and produced.
See Sessions for the full protocol.
Score arrays
Each array’s probability denotes a different quantity, and each array has its own cutoff.
Rules for calling code
Scores
- Treat each score as independent. Each
probabilityis its own estimate for one name. Scores do not sum to 1, several names can score highly at once and they are not meant to be compared with each other. - Handle empty arrays. An empty array means that no score passed the cutoff, so a measurement may list fewer scores than expected, or none.
- Do not hardcode the list of names. New names may be added, so treat a name you do not recognize as a new prediction. The audio and video endpoints use different expression names.
- Read a face’s
confidenceas detection, not expression. It is the detector’s confidence that the bounding box contains a face. - Store the measurements you need. The run endpoints return a run’s status, configuration and event log, but not its measurements.
Audio
- Send 16 kHz mono 16-bit PCM. Audio is not resampled or downmixed. A WAV file in another format is rejected with
audio_decode_failed, and MP3, FLAC and other containers withunsupported_media_type. Convert withffmpeg -i recording.mp3 -acodec pcm_s16le -ac 1 -ar 16000 speech.wav. - Keep an audio upload within 25 MB, a little over 13 minutes of audio. A larger request is rejected with
audio_too_long. - Find where an utterance ends from
utterancesorutterance.end, not fromtrigger. The final measurement of an utterance can be skipped, so its last measurement can have atriggerofinterval. - Stream audio no faster than 1 second of audio per second. A faster frame can be rejected with
rate_limited; wait briefly and send the same frame again. To measure a recording faster than it plays, upload it instead.
Video
- Send JPEG images of up to 2 MB and 3840x2160 pixels. Other formats are rejected.
- Handle unmeasured faces. Only the largest faces are measured, 2 by default, and up to 32 are listed. A face that was not measured has
face_id,expressionsanddescriptionsset tonull. - Match image upload results to images by position.
measurementsholds one result per image, in the order thefileparts were sent. - Match realtime replies to frames by position, not by
frame_id. Keep a queue of sent frames and remove the oldest for eachmeasurement.result, and for eacherrorwhose code isinvalid_image_frame,rate_limitedorinternal_error.frame_idadvances only when a frame produces a result. - Stream at most 3 frames per second. A faster frame can be rejected with
rate_limited. From a live camera, drop it and send the next frame. From a recording, wait briefly and send the same frame again. - Do not use
face_idto identify a person. It links a face across the images of one request or session and restarts at 0 in each. To continue numbering across image uploads, passface_statefrom one response to the next request.
Sessions
- Include every setting you want in each
session.update. It replaces the configuration rather than merging into it. The configuration locks at the first media frame, and a latersession.updateor an unrecognized field ends the session withconfig_invalid. - Close with
session.closeand read untilsession.closed. Closing the WebSocket instead loses the results in progress. - Keep media flowing. A session closes after 30 seconds without a media frame the server can use. To pause for longer, end the session and open a new one.
- Open a new connection after any session ends. A session cannot be resumed. A close that was not preceded by
session.closedmeans the session was lost. The Node.js SDK’s socket connects again after a server restart or a dropped connection, and so does the Python SDK’sreconnectinghelper. Each reconnect starts a new session whose IDs and times start again from 0.
Requests and errors
- Keep the API key on a server you control. Browsers cannot set the header on a WebSocket handshake, so relay media from a browser through your server.
- Send only documented fields. A
configpart orsession.updatewith an unrecognized field is rejected withconfig_invalid. In responses and messages, ignore fields you do not recognize. - Branch on the error’s
code, and on the status when there is nocode. A401is answered before the request reaches the service and carries nocode. A realtime handshake rejected with401or403sends noerrormessage, so read the status from the handshake result. - Fix a
4xxbefore resending, retry a500and wait for the seconds inRetry-Afterbefore retrying a503. A realtimeerrorcarriesretryable, which isfalsefor every error that ends the session. See Errors.