Customer file uploads for SaaS products

Dreep handles the files your customers upload inside your product: avatars, logos, attachments and documents. Your backend presigns each upload into a folder per tenant, signed URLs keep one tenant's files out of another's reach, OCR reads receipts and invoices, background removal cleans up product shots, and customers who insist on their own bucket get a dedicated BYOB project.

One upload, end to end

The tenant always comes from your session, never from the request body. Dreep validates the file; your backend decides which tenant it belongs to.

  1. User attaches invoice.pdf

    Inside your app

  2. Your API authorises

    tenantId from session, size and kind checked

  3. Presign

    folder: tenants/{tenantId}/documents

  4. Browser PUT, then confirm

    Single-use URL, 10 minutes

  5. OCR and signed links

    format=txt, expiresIn 300

Folder-per-tenant layout

Access is set on a root folder and inherited by every subfolder that uploads create later. Two or three roots cover a typical B2B product.

PathAccessHolds
brand/{tenantId}/logoPublicLogos shown on customer portals, invoices and emails
people/{tenantId}/{userId}PublicAvatars, served with width=128&height=128&radius=max&format=webp
tenants/{tenantId}/attachmentsSigned (root tenants/)Files on tickets, comments and tasks
tenants/{tenantId}/documentsSigned (inherited)Receipts, invoices, contracts and their OCR text
internal/exportsPrivateFiles only your staff open, inside the Dreep dashboard
A signed URL is checked against the asset and its expiry. Your backend issues it only after confirming the requesting user belongs to the tenant that owns the file.

A backend presign endpoint

The whole integration is one authenticated endpoint that presigns, one that confirms, and a helper that signs links when a user opens a file.

import { Dreep } from "dreep";

const dreep = new Dreep({
  apiKey: process.env.DREEP_API_KEY,
  signingSecret: process.env.DREEP_SIGNING_SECRET,
});

const MAX_BYTES = { attachments: 25_000_000, documents: 50_000_000 };

// POST /api/uploads/presign (your auth middleware sets req.user)
export const postUploadPresign = async (req, res) => {
  const { kind, filename, contentType, sizeBytes } = req.body;
  const { tenantId } = req.user; // never trust a tenantId sent by the client

  if (!MAX_BYTES[kind] || sizeBytes > MAX_BYTES[kind]) {
    return res.status(400).json({ error: "Upload not allowed" });
  }

  const upload = await dreep.presignUpload({
    filename,
    contentType,
    sizeBytes,
    folder: "tenants/" + tenantId + "/" + kind,
  });

  return res.json({ id: upload.id, uploadUrl: upload.uploadUrl });
};

// After loading the file row and checking file.tenantId === req.user.tenantId
export const fileLink = ({ asset }) => dreep.signedUrl(asset, { expiresIn: 300 });

// OCR a stored receipt or PDF in a Signed folder, no re-upload
export const receiptText = async ({ asset }) => {
  const res = await fetch(dreep.signedUrl(asset, { expiresIn: 60, transform: { format: "txt" } }));
  return res.text();
};

OCR for receipts and invoices, cutouts for product shots

Expense, accounting and procurement products need the text inside uploads. Request a stored image or PDF with format=txt, or upload and extract in one call with POST /ocr, which can also save the text as a .txt asset next to the file. The JavaScript SDK's extractText returns blocks with bounding boxes when you need positions, for example to find the total on a receipt. OCR is billed per page; extra pages come in packs of 1,000 for $4.99.

Catalogue and storefront builders get product photos shot on desks and kitchen tables. POST /bg-remove returns a new asset with a transparent background, and because the alpha channel is kept, bg=ffffff or any other colour is an ordinary transform afterwards. Every plan includes 25 removals; packs of 1,000 cost $4.99.

Office files, CSV, JSON and ZIP archives are stored as-is and served back unchanged. Images and video go through the full transform set.

When a customer requires their own bucket

Some enterprise customers will only sign if their files sit in a bucket they control. Bring Your Own Bucket supports AWS S3, Cloudflare R2, Wasabi, DigitalOcean Spaces, MinIO and other S3-compatible storage, on Starter or Pro.

BYOB is connected per project and only while the project is empty, and the bucket cannot be changed afterwards. So create a dedicated project for that customer, connect their bucket before the first upload, and store that project's API key against the tenant in your backend. Dreep writes a probe object to test the connection; originals and generated variants then live in the customer's bucket, while delivery still uses Dreep CDN URLs.

Building this yourself versus using Dreep

NeedBuild it yourselfWith Dreep
Direct uploadsBucket CORS, presign code, a confirm step, cleanup of abandoned uploadspresignUpload, PUT, confirmUpload
Tenant isolationBucket policies or a proxy that streams every file through your APISigned folders and locally signed URLs
Avatars and logosA resize worker, a queue, a cache and a CDN in frontURL parameters: width, height, radius, format
Receipts and invoicesA separate OCR vendor, contract and billingformat=txt or POST /ocr, billed per page
Product cutoutsA model to host or another API to integratePOST /bg-remove, then bg= on delivery

What each feature does for a SaaS team

Team seats

Free has 1 seat, Starter 5 and Pro 15, plus 5 more for $3/month. Seats are for your staff in the Dreep dashboard; your customers never need a Dreep account.

Inherited access

A new tenant's folders are created by their first upload and are Signed from the start, because tenants/ is Signed.

Typed SDKs

JavaScript and Go SDKs with typed errors, so a plan limit is a DreepLimitError you can show as a clear message rather than a 500.

Explicit limits

Exceeding a plan limit returns LIMIT_EXCEEDED. No automatic overage billing, so one heavy customer cannot run up your bill.

Who this is for

  • B2B products where customers upload avatars, logos, attachments and documents.
  • Expense, accounting and procurement tools that need text out of receipts and invoices.
  • Teams that must keep tenants' files isolated without proxying every download.
  • Vendors with a few enterprise customers who require their own bucket.

When Dreep is not the right fit

  • You need malware scanning of customer uploads. Dreep does not scan files; run your own scanner after confirm.
  • You need webhooks when an upload finishes. Dreep has none; your confirm call is the event.
  • You need in-browser previews of Word or Excel files. Office files are stored as-is, not converted.

Frequently asked questions

How do I keep one tenant from opening another tenant's files?

Put tenant files under a Signed root such as tenants/, read the tenant from the session rather than the request, and only issue a signed URL after checking the file belongs to that tenant. Keep expiries short, because anyone holding a signed URL can open it until it expires.

Do our customers need Dreep accounts?

No. Your backend holds the API key and issues presigned upload URLs and signed download URLs. Dreep team seats are only for your own staff using the dashboard.

Can a customer keep their files in their own S3 bucket?

Yes, with Bring Your Own Bucket on Starter or Pro. Create a dedicated, empty project for that customer and connect their bucket before any upload. The bucket cannot be changed later.

Which file types can customers upload?

Images (JPEG, PNG, WebP, GIF, TIFF, BMP, SVG, HEIC, HEIF), video (MP4, MOV, WebM) and raw files (PDF, DOC/DOCX, XLS/XLSX, PPT/PPTX, CSV, TXT, JSON, ZIP), up to 500 MB each. Raw files are stored as-is.

How is OCR billed?

Per OCR page. Extra pages are sold in packs of 1,000 for $4.99. Reading the text of an asset that has already been extracted is served from cache.

Can the API key live in our frontend?

No. The key is a full-project credential and must stay server-side. The JavaScript SDK refuses to construct in a browser. Browsers upload through presigned URLs your backend creates.

Related

Wire up tenant uploads in an afternoon

Create a Signed tenants folder, add one presign endpoint to your API and open a customer's invoice through a five-minute signed link. Start on the free plan.