Limits & Errors
Session limits, upload limits, bandwidth budget, every cloud error code, and what is not in v1.
Session limits
| Limit | Value |
|---|---|
| Session duration | durationSec from the token; ceiling 180 s |
| Minimum useful duration | 20 s — shorter sessions cannot produce HRV |
| Idle with no packets | 10 s, then the connection closes |
| Frame rate | 32 / s; above it, frame_rate_exceeded |
| Message size | 8 KB |
| Connections per token | 1 |
| Session token TTL | 300 s by default |
| Unprocessed-frame queue | 15 frames (~500 ms at 30 fps). On overflow the oldest frames are dropped and a stats event reports it |
Dropping the oldest frames is the intended behaviour under overload: processing catches back up to real time, the inter-frame interval grows, and the pipeline absorbs it. The alternative — an unbounded queue — would drift further behind every second.
Upload limits
| Limit | Value |
|---|---|
frames file | 64 MB |
video file | 256 MB |
| Presigned URL validity | 15 min |
| Uploaded-object TTL | 1 hour from presign, then swept |
idempotencyKey length | 16–200 characters |
Bandwidth budget
| Channel | Rate |
|---|---|
Pulse crop, 36×36×3 uint8 at 30 fps | 3 900 B per frame ≈ 117 KB/s |
| Quality telemetry | ~2 KB/s |
| Auxiliary crop, optional module | 42–60 KB/s |
A 60-second streaming measurement is therefore roughly 7 MB, or about
10–11 MB with the optional auxiliary channel enabled. Budget accordingly on
mobile data, and prefer delivery: 'auto' so a session that cannot sustain the
rate degrades to a batch upload instead of failing.
Error codes
Streaming
| Code | Meaning |
|---|---|
token_invalid | Signature or payload of the session token did not verify |
token_expired | Token past its TTL — mint a new session |
session_already_used | A second connection for the same session id |
protocol_error | Malformed control message or binary packet |
frame_rate_exceeded | More than 32 frames per second |
duration_exceeded | Session ran past the duration in the token |
idle_timeout | No packets for 10 seconds |
insufficient_signal | Not enough signal for a meaningful result |
internal_error | Unexpected failure on our side |
insufficient_signal is not a transport failure. The session still closes with a
final, and that final contains quality.level: "poor", isComplete: false
and every metric null. neccessory does not fabricate numbers — a bad capture
returns "no measurement", not a guess. Handle it the same way you handle a poor
signal quality on-device.
Session minting
| HTTP | Code | Meaning |
|---|---|---|
| 401 | unauthorized | HMAC signature invalid, or unknown key |
| 403 | key_disabled | Key is not active |
| 400 | invalid_platform | platform outside web / ios / android |
| 400 | invalid_duration | durationSec outside 20…180 |
| 400 | consent_required | consent missing or not true |
| 403 | token_quota_exceeded | The key's monthly token cap is exhausted — see Pricing |
| 403 | insufficient_tokens | The organization token balance cannot cover the measurement |
| 503 | cloud_unavailable | Cloud inference is not configured or unreachable |
Batch
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_kind | kind is neither frames nor video |
| 400 | consent_required | consent missing or not true |
| 400 | validation_error | A malformed field — e.g. idempotencyKey outside 16–200 characters |
| 400 | invalid_content_type | contentType does not match the declared kind |
| 404 | object_not_found | objectKey was never uploaded, or already swept |
| 409 | idempotency_conflict | Same idempotency key, different sha256 |
| 413 | object_too_large | Over the size limit for that kind |
| 422 | sha256_mismatch | Uploaded bytes do not match the declared hash |
Video rejections
insufficient_frame_rate · too_short · face_not_detected ·
face_too_small · unsupported_media — see
Batch processing for the thresholds.
Rate limits
Most /sdk/v1 paths carry no blanket rate limit. Five scoped mechanisms exist,
and each one is deliberate:
- The streaming endpoints are limited per session.
POST /sdk/v1/cloud/sessions/:id/framesaccepts 6 uploads per 10 minutes (NC_CLOUD_FRAMES_RATE_LIMIT), andGET …/resultaccepts 60 polls per minute (NC_CLOUD_RESULT_RATE_LIMIT). Both count per session id, not per key. - The key owner can switch on a per-key limit —
rateLimitPerMinon the key in the console,0(the default) meaning off. With it on,429 rate_limitedcan appear on any NCS1-signed path. - A monthly quota of cloud seconds —
quotaSecondsMonthon the key, also set in the console. When it is exhausted, minting a session or a batch job answers403 quota_exceeded. - The usage log is limited on its own —
GET /sdk/v1/logsaccepts 60 requests per minute per key (NC_SDK_LOGS_RATE_LIMIT), independently of whether the key has arateLimitPerMin. See the usage log. - A monthly token cap —
tokenQuotaMonthon the key, shown in the console as the monthly spend ceiling and entered there in dollars. When the key's token spend for the month would exceed it, minting answers403 token_quota_exceeded; an empty organization balance answers403 insufficient_tokens. Both are explained in Pricing.
Limited responses carry the RateLimit-Limit, RateLimit-Remaining and
RateLimit-Reset headers. One honest caveat: the rate counters are
in-process — one instance, in memory — so they reset on every deploy. Treat
them as a guard, not an exact meter. The seconds quota is counted in Postgres
and survives restarts.
Either way, treat 429 rate_limited as a response your client already handles,
with exponential backoff. Usage itself is unchanged: cloud seconds and jobs are
metered from day one, converted into token charges (see
Pricing) and appear in your console.
Not in v1
Stated so you can plan around it rather than discover it. Billing is live —
cloud measurements are charged in tokens and packs are bought by card in the
console, see Pricing. Emotion and gaze are
computed in the cloud when ordered in features — see the
protocol.
- Android.
- Session resume. A dropped WebSocket ends the session;
delivery: 'auto'recovers the measurement through batch, not the connection. - Team invitations. One organization per account for now.
Next: Cloud privacy.