The Upload Token Has to Know Overwrite Is Allowed
Training videos for our client portal run 50 to 100 MB. A Next.js serverless function tops out at 4.5 MB for the request body. Those two facts mean video bytes can never travel through the API.
The solution is Vercel's client-side upload API. The browser (or in our case, a local upload script) never talks to our server to move bytes. It makes one short request to prove it's authorized, gets a scoped short-lived token back, and then sends the file directly to Vercel Blob. The token is the entire server-side contribution to the upload.
What Goes Into the Token
The token comes from a POST /api/training-upload route that only our upload script knows about. It checks a shared secret, validates the target pathname against a regex, and then returns a token with constraints baked in:
allowedContentTypes: ["video/mp4"]: nothing but MP4 gets throughmaximumSizeInBytes: 500MB: a ceiling above what we ever actually uploadaddRandomSuffix: false: the pathname the script passes is the exact pathname storedallowOverwrite: this one bit us
The path validation deserves a note. Anything that doesn't match ^[a-z0-9][a-z0-9._/-]{0,120}\.mp4$ is rejected before a token is ever issued. That keeps the upload endpoint from being used to overwrite something unrelated elsewhere in the bucket.
When You Re-Record a Module
We re-shot module 2 after module 1 had shipped to the client. The content changed, the runtime changed, the poster frame changed. The filename stayed the same: claude-connectors-schedules.mp4.
The upload script pushed to the same pathname. Without allowOverwrite: true on the token, that upload fails. Vercel Blob treats an existing object as protected by default.
If it fails, you have two options and neither is clean. Delete the old blob manually through the Vercel dashboard, then upload. Or upload under a new filename and update the reference in the tenant JSON. The tenant config stores each module's video as a plain filename: "videoPath": "claude-connectors-schedules.mp4". Changing that filename means a JSON edit, a commit, and a deploy, which is not what you want when you're pushing a corrected take at the end of a recording session.
With allowOverwrite: true, the re-upload replaces the bytes at the same path. The token still validates the filename format and the shared secret. The tenant JSON doesn't change. The module serves the corrected video as soon as the CDN propagates.
Why the Permission Lives on the Token
The route doesn't handle the upload. It mints a token, and Vercel validates that token when the client actually transfers the file. allowOverwrite has to live on the token because the upload bypasses our infrastructure entirely.
That's the right model. The route is the policy enforcement point: check the secret, check the path, decide whether overwrite is appropriate, issue the token. Once the token exists, the client has exactly the permissions it encodes, nothing more.
Design for Corrections Before the First Upload
The first version of any module is not the final one. You find a mistake on camera, you re-record the segment, you want to push the fix. If the upload infrastructure has no answer for "replace this file cleanly," correcting a module becomes a manual operation.
We didn't think about this until we actually needed it. The fix was one line in the token generation. But it's worth deciding how corrections work before you ship the first video, because once clients are watching, you don't want the infrastructure slowing down a content fix.