RapidfileAPI
Sign in
  1. Upload API
  2. Image upload API

Image upload API for user photos

Profile pictures, product photos, galleries and screenshots: accept images straight from the browser and serve them from a CDN link.

Where images come from in your app

Most apps start with one image field, usually an avatar, and grow from there: product listings with several photos, chat attachments, support tickets with screenshots, community galleries. Each one has the same needs. The upload must be fast on a phone, the server must decide what is allowed, and the result must be a URL you can put in an <img> tag right away.

With the Rapidfile Upload API the image never passes through your server. Your backend approves the upload and the browser sends the bytes to Rapidfile. When it finishes you have a link such as https://cdn.rapidfile.cc/Xy7Kq2Lm9P/avatar.png.

Only allow images, only at sensible sizes

Your upload endpoint is the gatekeeper. Check the user, the MIME type and the size before you ask Rapidfile for an upload URL. Use customId or metadata to remember who the image belongs to; both come back in the webhook.

server: validate, then create the upload
// app/api/avatar/route.ts
const ALLOWED = ['image/png', 'image/jpeg', 'image/webp', 'image/avif', 'image/gif'];

export async function POST(req: Request) {
  const user = await auth(req);
  if (!user) return Response.json({ error: 'unauthorized' }, { status: 401 });
  const { name, size, type } = await req.json();
  if (!ALLOWED.includes(type)) return Response.json({ error: 'bad_type', message: 'Images only' }, { status: 400 });
  if (size > 10 * 1024 * 1024) return Response.json({ error: 'too_large', message: 'Max 10 MB' }, { status: 413 });

  const res = await fetch('https://api.rapidfile.cc/v1/uploads', {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.RAPIDFILE_SECRET_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ name, size, type, customId: `avatar-${user.id}`, metadata: { userId: user.id } }),
  });
  return Response.json(await res.json(), { status: res.status });
}

The client side is one call with the SDK. Show a local preview with URL.createObjectURL(file) while it uploads, then swap in the CDN URL:

browser
import { uploadFile } from './rapidfile-sdk.js';

preview.src = URL.createObjectURL(picked);
const file = await uploadFile(picked, {
  getUpload: '/api/avatar',
  onProgress: ({ percent }) => (bar.value = percent),
});
avatar.src = file.url; // https://cdn.rapidfile.cc/<id>/<name>

Resizing on the client

Phone cameras produce 4 to 12 MB photos, and an avatar shown at 96 pixels doesn't need that. Downscale in the browser before uploading: draw the image to a canvas, export a WebP or JPEG with canvas.toBlob(), and upload that blob as a File. Uploads finish faster, storage lasts longer, and every CDN request is lighter for your users.

browser: downscale before upload
async function downscale(file, max = 1024) {
  const bmp = await createImageBitmap(file);
  const scale = Math.min(1, max / Math.max(bmp.width, bmp.height));
  const canvas = new OffscreenCanvas(Math.round(bmp.width * scale), Math.round(bmp.height * scale));
  canvas.getContext('2d').drawImage(bmp, 0, 0, canvas.width, canvas.height);
  const blob = await canvas.convertToBlob({ type: 'image/webp', quality: 0.85 });
  return new File([blob], file.name.replace(/\.\w+$/, '.webp'), { type: 'image/webp' });
}

Private photos

Not every image should be public. ID documents, medical photos and private messages should be uploaded with "acl": "private". Private files have no public URL. When an authorized user opens the page, your server calls POST /v1/files/:id/signed-url with a short expiresIn and renders that URL instead.

Keeping your database in sync

When an image finishes uploading, Rapidfile sends a signed file.uploaded webhook containing the file, including your customId and metadata. Use it to set the user's avatar URL even if they closed the tab right after the upload completed. When you replace an avatar, delete the old file with DELETE /v1/files/:id so it doesn't count toward your storage.

What image hosting costs

Images are small but viewed often, which is why per-GB egress pricing adds up. Rapidfile plans include a daily allowance of CDN requests and no bandwidth fees: Free 100,000/day, Starter 500,000/day, Growth 2,000,000/day, Scale 10,000,000/day. See pricing. If you want a ready-made gallery with shareable pages rather than an API, look at Rapidfile Media.

Frequently asked questions

Which image formats can I upload?

Any file type is stored as uploaded with the MIME type you declare. You decide which types are allowed in your own upload endpoint, for example PNG, JPEG, WebP, AVIF and GIF.

Does Rapidfile resize or convert images?

No. Files are stored and served exactly as uploaded. Resize in the browser before uploading (example above) or upload several sizes.

Can I use the URL directly in an img tag?

Yes. Public files have a permanent CDN URL that you can use in img, picture or CSS background images.

How do I replace a user's avatar?

Upload the new image, save its URL, then delete the old file with DELETE /v1/files/:id.

Can I keep photos private?

Yes. Upload with acl "private" and generate signed URLs that expire, up to 7 days.

Keep reading