# Private direct uploads Render screenshots, thumbnails and demos on the coding client's computer. When private B2/S3 storage is active, finished media and HTML/ZIP builds can travel straight to private object staging. Hackemist verifies the complete file before attaching it to a draft. Native chunk uploads remain available without object storage, CORS, or an approved external transfer tool. ## MCP workflow 1. Read `hosting_info`, the current project and listing revision. Check the file locally and calculate its exact byte length and SHA-256. Preserve existing media unless the creator authorizes replacement. 2. Call `direct_upload_create` with `project_id`, `family`, `byte_length`, `sha256` and a unique `idempotency_key`. For `family: "bundle"`, provide an HTML/ZIP `filename` and current `base_version_id`. For `family: "media"`, provide `kind` (`thumbnail`, `screenshot`, or `demo`), current `base_revision`, and accurate `caption`, `alt_text`, and `description`. Use the returned `upload_id`, `part_bytes` and `part_count`. 3. For each part, call `direct_upload_part` with `upload_id` and one-based `part_number`. Send exactly its declared bytes using the returned **PUT URL**, with no Authorization header, cookies or referrer and no redirects. Content-Length is signed; a browser sets it automatically for a Blob body. Desktop HTTP tools must set the exact length. The last part can be smaller. Request a new part URL if its five-minute validity expires; retries overwrite the same numbered part. 4. Call `direct_upload_finalize` after all parts finish. It verifies provider metadata/version, complete size and SHA-256, then runs the normal source importer or media decoder, current credential/plan/ownership/consent/quota checks and revision guard. Use the returned version or listing revision for the next edit. Finalization stages a draft; publishing is a separate explicit `project_release` operation. 5. After an uncertain response, call `direct_upload_status`. A complete ticket returns its existing result. Retry finalization of the **same** ticket if needed. Never create another upload or fall back blindly after finalization starts. `direct_upload_abort` closes unfinished work; it does not delete attached media or published versions. Treat every signed URL as sensitive until expiry. It authorizes one exact-length part at a server-chosen random key. It cannot list/read objects, complete the multipart upload, choose another project/key, or grant account access. A SigV4 access-key **identifier** appears in the URL; the secret key and Hackemist bearer do not. Never export a connector bearer to a shell, recorder, browser page or external service. Use this route only when the client can transfer a local file with its approved network tools. Otherwise use `bundle_upload_create/chunk/finalize` or `media_upload_create/chunk/finalize` through the native connector. The upload is tied to its current account/project and, for MCP, its exact credential. Revoked credentials cannot finalize a previously signed upload. Media replacement still requires `media:write` and creator authorization. The server downloads the finished object once for mandatory validation; direct upload removes base64/control-ingress work, not validation. No server screenshot rendering or video encoding is introduced. ## Browser uploads The Studio and creation flow automatically try this route for HTML/ZIP files and listing media of at least 1 MiB. Smaller files retain the existing upload to avoid multipart setup overhead. SHA-256 runs locally in a worker with bounded chunks. Files transfer as sequential Blob parts without platform credentials. Before finalization, an unavailable provider or CORS policy falls back to the existing upload. Static build folders keep the existing folder importer. After an uncertain finalization, the UI reads ticket status instead of creating a duplicate attachment. ## Storage configuration Configure and verify private B2/S3 storage in Admin first. Enable **Settings → Performance → Allow direct uploads with private object storage**. No additional container is required. Credentials stay encrypted in the existing storage profiles. Bucket public-access/ACL checks must succeed before a ticket can issue upload capabilities. For browser transfers, configure a CORS rule on the private bucket for **only your site origin**, for example `https://hackemist.com`, permitting `PUT`. Browser-controlled Content-Length does not require a custom header. If your provider requires allowed headers, permit the upload headers it reports; do not add platform Authorization. A representative S3 rule is: ```json [ { "AllowedOrigins": ["https://hackemist.com"], "AllowedMethods": ["PUT"], "AllowedHeaders": ["content-type"], "MaxAgeSeconds": 300 } ] ``` CORS is browser permission, not public-read permission. Keep the bucket private. Hackemist allows the exact configured provider endpoint in its own upload UI's connection policy; uploaded apps do not inherit that permission. B2 uses its S3-compatible endpoint and bucket CORS configuration, not presigned POST. In addition to normal bundle/media access, the server key needs multipart create/upload/complete/abort, list parts, list multipart uploads, head/get/delete, and the private-bucket policy/ACL checks required by the provider. Scope object operations to the configured prefix. Staging uses `/incoming/`; final bundles/media use the existing immutable storage routes. Never grant creators a reusable storage key. Incomplete media tickets expire after one hour; build tickets after 24 hours. Completed, aborted, expired, deleted-account/project and revoked-token staging is removed by the operations worker. Defaults are eight tickets per cycle, two workers, exponential retry backoff and short provider timeouts. These are adjustable in Performance settings. Cleanup failures and queue age appear in Admin → Operations. Configure provider lifecycle rules to abort incomplete multipart uploads and expire **only** `incoming/` objects after at least two days as a second safety net, including old versions where applicable. Do not expire the final media/bundle prefixes. Portable backups include attached originals and responsive images. They exclude temporary staging and clear direct-upload tickets on restore; restored tickets must never clean the source installation's staging. The provider lifecycle remains the fallback for abandoned temporary objects. Required request validation and current authorization cannot be disabled by the Performance switches.