vibizStack
Console
Products

Files

A drive per project. Every image, video and audio the AI generates is saved there by itself with a permanent link, next to the files your app and agents upload.

Open as .md

Every project has Files, a drive with a permanent link for each file. Every image, video and audio you generate is saved in the project's Files. Use the url in the response (it is permanent), or vibiz files latest / vibiz files search "<prompt words>" later. Nothing to turn on: generated media lands there by itself, with the model, the prompt and the cost. Apps and agents upload there too: avatars, product images, downloads.

Terminal
vibiz files                      # newest first: date, name, prompt, link
vibiz files latest --type image  # just the newest image's link
vibiz files search "cold email cover"
vibiz files upload logo.png hero.jpg --folder brand

In the console: the project's Files page, with previews, filters and search (/projects/<id>/files?type=image&source=ai).

Environments

Like the rest of a project, Files has a production and a development side, kept apart. AI calls made with a development key (the development app key or AI key) save to development; every other project call saves to production. The API's routes take ?env=development; the default is production. Owners and agents can both read, upload and delete in either environment: files are content, not secrets.

The file

JSON
{
  "object": "file",
  "id": "file_01k…",
  "environment": "production",
  "name": "2026-10-28-cold-email-cover.png",
  "folder": null,
  "content_type": "image/png",
  "kind": "image",
  "size": 482113,
  "width": 1024,
  "height": 1024,
  "duration_seconds": null,
  "source": "ai",
  "ai": { "model": "vibiz/image-pro", "prompt": "A cover image for a cold email…", "cost_usd": 0.034, "call_id": "spd_…" },
  "tags": [],
  "note": null,
  "private": false,
  "url": "https://app.vibiz.app/f/Qm9x…/2026-10-28-cold-email-cover.png",
  "url_expires_at": null,
  "created_at": "2026-10-28T10:00:00.000Z",
  "console_url": "https://app.vibiz.app/projects/prj_…/files?env=production&file=file_01k…"
}

Generated files are named by date and prompt; a second one with the same name gets -2, -3… kind is image, video, audio or other. width and height are set for images, duration_seconds for video and audio when known.

Routes

RouteDoes
GET /v1/projects/:id/filesnewest first: type, source (upload, ai), model, tag, since, until (ISO, or 30m, 4h, 2d, 1w), q (name, prompt, tags, note), folder, limit, cursor; also total_files and total_bytes for the environment
GET /v1/projects/:id/files/latestthe newest file matching type, source, model, tag (404 when none)
POST /v1/projects/:id/filesupload: the bytes as the body with their Content-Type and ?name= (and folder, private=1), or multipart/form-data with file
POST /v1/projects/:id/files/import{ "url": "https://…", "name"?, "folder"? }: save a file from a public URL
GET /v1/projects/:id/files/:fileIdone file
GET /v1/projects/:id/files/:fileId/contentits bytes (private files too)
PATCH /v1/projects/:id/files/:fileIdname, folder (null: none), private, tags (up to 20), note
DELETE /v1/projects/:id/files/:fileIddelete it: the bytes are removed and its link stops working

A running app uses its one key: POST /v1/app/files, GET /v1/app/files, GET /v1/app/files/latest, GET /v1/app/files/:fileId, DELETE /v1/app/files/:fileId, always in the key's own project and environment. With @vibiz/app:

TypeScript
import { vibiz } from '@vibiz/app';
const avatar = await vibiz.files.upload(bytes, { name: 'avatar.png', contentType: 'image/png' });
await db.users.update(userId, { avatarUrl: avatar.url });
const cover = await vibiz.files.latest({ type: 'image', source: 'ai' });

Agents use the vibiz_files tool (list, latest, get, import, delete); its list rows carry the id, url, name, date, size, dimensions and the start of the prompt.

A public file's url is https://app.vibiz.app/f/<key>/<name>: <key> is 32 random characters, so a link can't be guessed or listed, and it never changes. The name at the end is only for people and downloads. Links are public on purpose: a page's <img>, an email, or a social platform fetching a post's media can't send a key.

For files that should not be reachable by link, upload with private: true (or PATCH { "private": true }). A private file's url is a signed link valid one hour (url_expires_at says until when); ask for the file again for a fresh one. The bare link without its signature answers 404.

Files are served with their own type, X-Content-Type-Options: nosniff, an ETag, Accept-Ranges: bytes (video and audio seek), and Content-Disposition: inline for images, video, audio and PDF (attachment for everything else). SVG and text are served sandboxed, as attachments.

Limits

Size50 MB a file
Typesimages (PNG, JPEG, GIF, WebP, AVIF, HEIC, SVG), video (MP4, WebM, QuickTime), audio (MP3, WAV, OGG, WebM, AAC, FLAC, M4A), PDF, text, CSV, Markdown, JSON, ZIP, and other binary files
RefusedHTML, JavaScript and XML: they would run as a page (invalid_request, param: "content-type")

A missing or generic type is worked out from the file's bytes and name.

Price

Not decided yet: storage is free during the beta. The bytes each project keeps are recorded every day (files.storage in usage), and the total shows on the Files page.