Vizen Shop

File storage, Studio folders and static site folders

/docs/file-storagecurrentEN· проверено 2026-10-05

Резюме. Три разных механизма: медиатека Studio хранит файлы UUID и коллекции-папки; папка-сайт хранит дерево путей в версионируемом релизе; /site-files хранит текстовые шаблоны фидов и служебных файлов. Их операции и права различаются. Ниже — действующий контракт, проверенный по коду.

Status: current · Verified: 2026-10-05 · Serves: GET /docs/file-storage

Choose the right system

NeedAPIIdentity
Studio/media assets and reusable folder organization/v1/storages/files, /v1/storages/collectionsUUID file; collection has parent_id, mode:folder
Whole static HTML/CSS/JS website inside a category/site-folders/{id}category type:site; files have relative paths in a release
Feed, ads.txt, verification, small template-generated page/site-filescategory + filename; body ≤64 KiB; owner/admin

Studio collections may contain the same file in multiple folders. Removing a collection-file link does not delete the file. POST /v1/storages/files/{id}/rename changes original_name, preserving its URL and bytes. It does not rename a site-folder path. Site folders have no separate persisted directory entity: empty folders cannot be stored; create a file such as pages/index.html.

Static site folder: create, upload, publish, restore

  1. Create a category with POST /categories, body {"item":{"name":"Demo","type":"site","is_published":false,"seo":{"slug":"demo"}}}. Use the numeric result.id. Set a unique SEO slug; nested category slugs determine the public prefix. site cannot contain products.
  2. GET /site-folders/{id} returns section_id, document_id, active_release_id, contour, path, files:[{path,size,mime,sha256}], limits. Before the first release this is 200 with empty files, not a missing category.
  3. For individual files, POST /site-folders/{id}/releases: {"base":"active","files":[{"path":"index.html","sha256":"…","size":123}],"note":"…"}. base is a STRING: active, none, or a release id as a string. New paths overlay the base; unmentioned files remain. delete:["old/path"] removes paths.
  4. PUT bytes to each returned upload[].upload_url, without admin auth headers. Already-known hashes may be reused. Once ALL uploads finish, call POST /site-folders/{id}/releases/{rid}/publish. validate on the same release returns findings without publication. A failed publish does not change the active pointer. Publishing a release does not publish its category: enable it separately with PUT /categories/{id} {"item":{"is_published":true}}; the shop must also be visible.
  5. ZIP alternative: POST /site-folders/{id}/releases/zip?base=active&publish=1, multipart field file (or raw ZIP). base=none REPLACES the whole site; base=active merges, replacing matching paths. One common outer directory is stripped, hidden metadata is ignored. Ask before replacing existing work.
  6. Editing, renaming or removing files creates another release. Rename with the same SHA/size under the new path plus delete:["old/path"]. Update references in all affected HTML/CSS/JS yourself. A folder deletion enumerates its descendants.
  7. GET …/releases gives the latest 50 versions. Read a manifest with GET …/releases/{rid}; source text with GET …/releases/{rid}/file?path=…. POST …/rollback {"release_id":123} reactivates a published version. A prod rollback rejects a draft-only version (was_live:false). DELETE …/releases/{rid} rejects versions active in either contour (in_use:true).

Admin tree and history cleanup

Folder directories start collapsed and open on demand. Search finds nested files without expanding the entire tree. Newly created or recreated directories also start collapsed; refresh preserves manually opened directories.

The category Structure tab initially shows five releases. Use the version's menu for a single activation or deletion, or Manage history to select inactive versions and delete them after one confirmation. Activations remain individual. Versions active in PROD or DEV cannot be selected; the server also rejects their deletion. Bulk cleanup sends sequential DELETE requests with one captured shop/user/contour context, including authentication retries. It stops at the first failure and refreshes history and tariff usage. Already deleted versions are not restored; the remaining selection survives an error and retry. No automatic retention policy deletes existing history.

Deleting an HTML draft release is different from POST /v1/dev/discard. Discard removes the DEV overlay and unreferenced draft uploads, but preserves files referenced by retained HTML releases, including inactive DEV history. Their bytes remain billed until those releases and other references are removed. PROD hash reuse excludes files uploaded only in a DEV contour; DEV may reuse its own draft uploads or shared PROD files. Reference creation, repointing and orphan cleanup synchronize per shop, and activation cannot revive a concurrently deleted release. Snapshot loading/restoration refuses stale concurrent release references rather than committing a dangling active pointer.

Public URLs and React exports

The shop and category must be published. /prefix/ serves index.html; /prefix/docs/ serves docs/index.html; /prefix/docs/topic.html serves that file. A directory without a trailing slash redirects to its canonical directory URL. Relative links resolve against the HTML page's location. A missing path uses 404.html with HTTP 404 when supplied; otherwise it stays 404.

Upload a built static export of React/Vite, not source src/, node_modules or a Node server. Scripts run and assets keep their relative paths. There is no SPA fallback to root index.html for arbitrary history routes. Use hash routing, or export an HTML entry at each known route. Draft site-folder preview is not implemented: a draft-contour release does not change the live pointer. A site folder runs as its own document, without the shop's block-builder header/menu; include internal navigation in the uploaded website.

Project size and tariff storage

GET /site-folders/{id} also returns storage_usage: {active_bytes,retained_bytes,retained_files,release_count}. active_bytes sums the paths in the selected contour's current manifest. retained_bytes sums the actual sizes of distinct live file_id objects referenced by every retained release of this document, including drafts; retained_files counts those objects. release_count includes the entire history, even when no release is active. Do not add the sizes of the latest 50 releases, or deduplicate by SHA alone: multiple objects can have the same SHA. Older servers may omit this field; missing means unavailable, not zero.

GET /tariff/usage is the existing authoritative total for the whole shop and reconciles live uploaded shop files, including Studio/media, HTML/site folders, history and DEV. A shared file is counted once globally. Project totals overlap when projects share a file; completed files from partial uploads can occupy space without being attached to a release. Personal files and external CDN links are excluded. Removing a path creates history and does not free bytes still referenced by another version or entity. Remove unused inactive history and files through their normal operations to free space.

Folder JSON/ZIP imports use the same CreateFile/DoneFile storage_bytes gate. An over-quota publish/validate returns 429 STORAGE_LIMIT_REACHED with limit_code:storage_bytes; it does not switch the active release. Successfully completed earlier files in a failed batch can remain stored and billed. Refresh usage after success or failure. Quota enforcement uses actual final file size, including any storage image conversion; it is not a promise about HTTP traffic, database size, CDN resize cache or browser demo storage.

Images and cache

Images may stay inside the uploaded folder or use an existing public CDN URL. There is no automatic rewrite of <img>/CSS URLs or generation of srcset. For PNG/JPEG/WebP/AVIF, /prefix/assets/photo.webp?w=640&q=80 redirects to the common image service; SVG/GIF are served as originals. Generate appropriate srcset/sizes in the website if responsive variants are required. HTML is no-cache with a hash ETag; folder assets are public, max-age=60 with an ETag. The resize service caches an immutable storage-key/width/quality variant separately. A new release changes bytes/hash; URLs retain their paths.

Browser isolation, demo state and public forms

The folder response uses a CSP sandbox without `allow-same-origin`. It has opaque origin null: scripts/modules/dialogs work, but cookies, localStorage, sessionStorage, IndexedDB and service workers are unavailable. Wrap optional storage access in try/catch. A memory-only demo cart works within a page but does not survive reloads or navigation to another HTML document. For demo state use a single document/hash navigation, or explicitly carry non-sensitive ids and quantities in the URL; never put contacts, drafts, PATs or sessions there. Persistent browser storage needs a separately isolated hosting origin; it is not enabled by this contract. Do not add allow-same-origin on the shop origin.

The standalone document does not install the storefront's VzRuntime, native cart/account session, form popup host or widget iframe bridge automatically. Use ordinary absolute links for technical checkout/payment pages; those pages must validate products/prices themselves. The demo cart is not a real order.

For a custom form, use the existing anonymous GET /forms/{id}/public and POST /form-leads contract in /docs/webcoding §13.5. The opaque origin lane accepts exactly these methods/paths with credentials:"omit"; POST requires Content-Type: application/json. Do not send Authorization, Cookie, X-Vizen-Contour or X-Vizen-Draft: their presence, including empty values, is rejected. Preflight permits only Accept and Content-Type. Other endpoints retain their ordinary CORS policy. Origin:null is not proof of shop ownership; visibility, tenant-from-form, required fields, consent, spam, quotas and intake are still enforced by the same public form handler. Forms requiring a resource access grant cookie cannot be used through this anonymous lane.

Create the form and configure its sandbox/webhook in admin or a server-side tool. A browser needs only the public form id and API origin, never a PAT or webhook signing secret. Declare attribution fields (source_page/widget_id/ button_id/button_text and optional product/options) in the form's schema; unknown keys are discarded. Collect consent explicitly and preserve inputs on failure. Webhook consumers verify the raw-body HMAC and deduplicate by event_id: delivery is at least once. Sequential identical submissions within a minute are normally deduplicated; concurrent submissions do not have an atomic exactly-once guarantee.

Actual limits

  • Site: 2000 files, 20 MiB per HTML file, 100 MiB per other file. limits.html_bytes is per file, not total HTML bytes. Raster uploads also pass Storage's image ceiling (API_IMG_MAX_UPLOAD_MB, default 10 MiB): effective limit is the smaller cap. Do not promise 100 MiB images merely from folder limits.
  • Source editor reads at most 2 MiB, although larger HTML can be uploaded.
  • ZIP: 60 MiB compressed, 512 MiB expanded, 2000 archive entries.
  • Relative paths ≤512 UTF-8 bytes; no traversal/control characters. Executables and server scripts are forbidden. Incoming paths differing only by case conflict.
  • index.html is required. Zero-byte files are ignored by the release manifest; an empty release is rejected. Storage done rejects an empty file.

Auth and contours

With a PAT, first call GET /v1/account/token. Check its actual scopes, contour, writes_to_live, warnings and capabilities (catalog_read, catalog_write, storage_write, html_level3_allowed). A preset name or publish_allowed alone does not prove permission to upload/publish a folder.

Tenant comes only from the authenticated user's MainCompanyID, not supplied company_id. JWT uses the admin session/contour. PAT must belong to a shop owner/admin for site folders: read requires catalog:read; create/upload/validate/ publish/delete require both catalog:write and storage:write; rollback requires catalog:write. Normal JWT callers also require owner/admin. Dev PAT cannot delete even an inactive release: 403 PAT_CONTOUR_MISMATCH. An owner/admin cabinet session may remove inactive history through its usual contour; versions active in either contour remain protected.

For Studio Storage, PAT storage:read permits file/collection reads, collection search and reverse lookup. storage:write permits file create/done/replace/delete/ rename/prefer-original, and collection create/add file only. Collection edit, delete, remove-file and reorder are NOT in the PAT allowlist and return PAT_METHOD_NOT_ALLOWED. Live operational collection writes and file rename/delete reject dev PAT; overlay-safe upload/done/replace remain contour-aware. /site-files instead uses site-files:read/write, owner/admin, prod-only mutations. MCP currently exposes scene tools; no site-folder or Studio collection tools.

Source anchors: api/storage/storage.proto; internal/api/htmlrelease/{site_folder, releasemap,paths,filetext,zip,publish,release_delete}.go; internal/core/domain/ site_folder.go; internal/api/storage/{done_file,rename_file}.go; auth guard PAT registry.

Исходник: https://api.vizen.shop/docs/file-storage

Работает в демо-режиме