Skip to content

Storage and blobs

Private files with signed transfers, and public objects.

View as Markdown

Permissions

blob.read permits reads, blob.write permits uploads and deletion. The API key must belong to the project that owns the file, and it must stay on your server.

Upload flow

Request a ticket, PUT the file directly to the returned URL with the upload token, then confirm. Upload tickets are single-use; download tickets stay valid until they expire.

typescript
const ticket = await fetch(`${base}/api/storage/upload-ticket`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.VELTIC_API_KEY}`
  },
  body: JSON.stringify({ projectId, key: 'invoices/2026-09.pdf' })
}).then(response => response.json());

await fetch(ticket.uploadUrl, {
  method: 'PUT',
  headers: { 'x-veltic-upload-token': ticket.token },
  body: file
});

await fetch(`${base}/api/storage/confirm`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.VELTIC_API_KEY}` },
  body: JSON.stringify({ ticketId: ticket.id })
});

Downloads

The download ticket endpoint returns a time-limited URL that supports range requests, so media players and resumable downloads work. Your API key is never appended to that link. Every endpoint returns an expiry; use it rather than caching a URL indefinitely.

Public blobs

Blobs are reachable without authentication, which is the point. Freezing a blob URL into the database goes through the canonical production origin rather than a preview host, so a stored link cannot break when a preview environment is torn down.