NeccessoryNeccessory
Cloud

Limits & Errors

Session limits, upload limits, bandwidth budget, every cloud error code, and what is not in v1.

Session limits

LimitValue
Session durationdurationSec from the token; ceiling 180 s
Minimum useful duration20 s — shorter sessions cannot produce HRV
Idle with no packets10 s, then the connection closes
Frame rate32 / s; above it, frame_rate_exceeded
Message size8 KB
Connections per token1
Session token TTL300 s by default
Unprocessed-frame queue15 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

LimitValue
frames file64 MB
video file256 MB
Presigned URL validity15 min
Uploaded-object TTL1 hour from presign, then swept
idempotencyKey length16–200 characters

Bandwidth budget

ChannelRate
Pulse crop, 36×36×3 uint8 at 30 fps3 900 B per frame ≈ 117 KB/s
Quality telemetry~2 KB/s
Auxiliary crop, optional module42–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

CodeMeaning
token_invalidSignature or payload of the session token did not verify
token_expiredToken past its TTL — mint a new session
session_already_usedA second connection for the same session id
protocol_errorMalformed control message or binary packet
frame_rate_exceededMore than 32 frames per second
duration_exceededSession ran past the duration in the token
idle_timeoutNo packets for 10 seconds
insufficient_signalNot enough signal for a meaningful result
internal_errorUnexpected 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

HTTPCodeMeaning
401unauthorizedHMAC signature invalid, or unknown key
403key_disabledKey is not active
400invalid_platformplatform outside web / ios / android
400invalid_durationdurationSec outside 20…180
400consent_requiredconsent missing or not true
403token_quota_exceededThe key's monthly token cap is exhausted — see Pricing
403insufficient_tokensThe organization token balance cannot cover the measurement
503cloud_unavailableCloud inference is not configured or unreachable

Batch

HTTPCodeMeaning
400invalid_kindkind is neither frames nor video
400consent_requiredconsent missing or not true
400validation_errorA malformed field — e.g. idempotencyKey outside 16–200 characters
400invalid_content_typecontentType does not match the declared kind
404object_not_foundobjectKey was never uploaded, or already swept
409idempotency_conflictSame idempotency key, different sha256
413object_too_largeOver the size limit for that kind
422sha256_mismatchUploaded 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/frames accepts 6 uploads per 10 minutes (NC_CLOUD_FRAMES_RATE_LIMIT), and GET …/result accepts 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 — rateLimitPerMin on the key in the console, 0 (the default) meaning off. With it on, 429 rate_limited can appear on any NCS1-signed path.
  • A monthly quota of cloud seconds — quotaSecondsMonth on the key, also set in the console. When it is exhausted, minting a session or a batch job answers 403 quota_exceeded.
  • The usage log is limited on its own — GET /sdk/v1/logs accepts 60 requests per minute per key (NC_SDK_LOGS_RATE_LIMIT), independently of whether the key has a rateLimitPerMin. See the usage log.
  • A monthly token cap — tokenQuotaMonth on 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 answers 403 token_quota_exceeded; an empty organization balance answers 403 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.