# Common Drift and Breaks

## 1. Auth fails even though credentials look correct

Likely causes:
- wrong credential pair from the portal
- expired JWT
- mixing Build-platform credentials with non-Build Zoom app credentials
- valid-looking key/secret pair that is not authorized for AI Services Scribe

Check:
- `iss` value
- `exp` window
- current credential labels in portal
- if the API responds with `{"code":124,"message":"Invalid Access token"}`, treat that as a real upstream auth failure, not a transport problem

## 2. Fast mode request shape mismatch

Docs show JSON with `file` URL, but the official quickstart also proxies multipart upload and forwards a `FormData` request.

Use one clear model per service boundary:
- client upload -> your backend multipart
- backend upload proxy -> `multipart/form-data` to `/aiservices/scribe/transcribe`
- backend URL-based submit -> JSON body with `file` URL

Symptoms:
- browser request stays pending for a long time
- backend eventually returns timeout or empty upstream reply

Preferred fix:
- treat uploaded files and URL-based files as two separate request paths instead of forcing both through one JSON shape

## 3. Fast mode returns `413 Request Entity Too Large`

Likely cause:
- reverse proxy rejected the upload before the request reached your app

Known deployment check:
- if nginx fronts the app, raise `client_max_body_size` to match or exceed your server-side upload limit

Current Scribe fast-mode API limits:
- maximum file size: `100 MB`
- maximum duration: `2 hours`

## 4. Fast mode returns `504 Gateway time-out`

Likely cause:
- the request reached your backend, but synchronous processing took too long for the edge/proxy path

Observed deployment behavior:
- public HTTPS can time out even when the same request path is valid and the backend is healthy
- observed hosted sample timings:
  - ~17.2 MB MP4: ~26s
  - ~38.6 MB MP4: ~26-37s
  - ~59.2 MB MP4: ~32-34s backend completion, but some browser requests still timed out first

Guardrail:
- use fast mode for smaller, interactive files
- use batch mode for large uploads or longer media where waiting synchronously through the web UI is brittle
- add request-level logging for:
  - file name
  - file size
  - mime type
  - upstream elapsed time
  - response payload size and top-level keys
  so you can tell whether the origin completed successfully while the browser/edge timed out
- for hosted UIs, wrap fast mode in an async request/polling flow instead of holding the browser open for the entire upstream response
- if nginx access logs show `499` while app logs later show `zoom_request_finished status: 200`, the transcription succeeded and only the browser-side request path was lost

## 5. Batch job accepted but outputs never appear

Likely causes:
- S3 URI/auth mismatch
- expired STS credentials
- output layout/URI mismatch
- webhook endpoint unreachable if you rely on callbacks

Check:
- `/jobs/{jobId}` summary
- `/jobs/{jobId}/files`
- cloud storage permissions

## 6. Webhook verification fails

Current sample pattern uses:
- `x-zm-signature`
- `x-zm-request-timestamp`
- HMAC-SHA256 with `sha256=` prefix

If verification fails:
- confirm raw body capture before JSON parsing
- confirm timestamp header was included in the signed string
- confirm the shared secret matches the job notification config

## 7. Health check says credentials exist, but API calls still fail

Likely cause:
- environment file contains literal placeholders such as `${ZOOM_API_KEY}` or `${ZOOM_API_SECRET}`

Guardrail:
- only treat credentials as present if they are real values, not unresolved shell placeholders
- fail fast with a clear credential error before attempting Zoom calls

## 8. Wrong product chosen

Symptoms:
- trying to use Scribe for live in-meeting media
- trying to use RTMS for offline archive transcription

Guardrail:
- file/storage transcription -> `scribe`
- live meeting media -> `rtms`

## 9. Browser microphone chunk 1 works but later chunks are empty

Likely cause:
- the browser emitted a valid first container chunk, but later `MediaRecorder` timeslice blobs were partial WebM/Opus clusters without fresh container headers

Symptoms:
- chunk 1 transcribes normally
- chunk 2 onward returns empty transcript text or much weaker results
- auth and request flow still look healthy

Preferred fix:
- do not rely on one long `MediaRecorder.start(timeslice)` session for standalone chunk uploads
- rotate the recorder per chunk instead:
  - start recorder
  - record one chunk window
  - stop recorder
  - upload that blob
  - start a new recorder for the next chunk

Guardrail:
- treat browser microphone pseudo-streaming as a file-container problem first, not a Scribe-language-model problem
