Twenty One Media
webAugust 7, 2026

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 through
  • maximumSizeInBytes: 500MB: a ceiling above what we ever actually upload
  • addRandomSuffix: false: the pathname the script passes is the exact pathname stored
  • allowOverwrite: 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.