Signed Uploads

Introduction

When uploading files from a client app, you can't safely include your API Key in the request.

You could post the file to your server, and then upload from there. However, this adds latency and needless extra bandwidth costs.

So ittybit allows you to create a signed upload url for the file, then upload the file direct from your client to that url.

If you're uploading directly from a server, serverless function, or other secure environment, then you can use simple uploads.


How it works

A signed upload url includes a valid signature which will grant access to your project for a limited time.

Once you have the signed url, you make a PUT request with the file in the request body.

When the upload completes, ittybit will return a File object in the response.


Signed urls

A signed url looks like this:

https://example.ittybit.net/image.png?expiry=1765432100&signature=1234abcd5678efgh9012

The domain is your project's delivery domain (see Domains for info on setting up custom domains) e.g. https://example.ittybit.net.

The filename is the name of the file you want to upload e.g. image.png.

You can optionally include a folder to upload the file to a specific folder e.g. nested/folders/image.png.

The expiry is a unix timestamp for some time in the near future. The url will stop working after this time e.g. expiry=1765432100.

The signature is a base64 encoded string. The string that is encoded is the domain, folder, filename, and expiry values, signed with your project's API key.


1. Get a signed url

To get a valid signed url, you will need to make a request to your server, get the url, and return it to your client.

If you're using a REST API for data fetching (could be powered by Express/Node, Laravel, Rails, or similar) then this would probably be a request to an API route.

Next.js, SvelteKit, and similar frontend frameworks can use server actions to generate or fetch a signed url directly.

// Example fetch request to your own API server
const response = await fetch('https://yourapi.com/upload-url', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    // Add your authentication headers
  },
  body: JSON.stringify({
    filename: 'image.png',
  }),
});
const { signedUrl } = await response.json();

Server-side code

From your server-side code, you can safely use your project's API key. This can authenticate a request to the ittybit API, or be used to generate a signature directly.


A. Ittybit API

You can use the /signatures endpoint to get a signed url from the ittybit API.

Send a request from your backend to the signatures endpoint.

const response = await fetch('https://api.ittybit.net/signatures', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer ITTYBIT_API_KEY' },
  body: JSON.stringify({ 
    filename: 'image.png', 
    method: 'put' 
  }),
});
const { data, error } = await response.json();
if (error) {
  console.error(error);
  // maybe handle the error
}
const signedUrl = data?.url;

return res.json({ signedUrl });

B. Generate signatures

You can skip the additional request by generating a signature yourself. It's not much extra code, and it's pretty much always faster because there's no network round trip.

import crypto from 'crypto';

const DOMAIN = 'example.ittybit.net';
const ITTYBIT_API_KEY = process.env.ITTYBIT_API_KEY;

function generateSignature({ string }) {
  const hmac = crypto.createHmac('sha256', ITTYBIT_API_KEY);
  hmac.update(string);
  const base64 = hmac.digest('base64url');
  return base64;
}

async function createSignedUrl({ path }) {
  try {
    const expiry = Math.floor(Date.now() / 1000) + 60 * 60; // 1 hour from now
    const string = `${path}?expiry=${expiry}&method=put`;
    const signature = generateSignature({ string });
    const signedUrl = `https://${DOMAIN}/${string}&signature=${signature}`;
    return { signedUrl };
  } catch (error) {
    // handle the error
  }
}

// Example usage
const signedUrl1 = await createSignedUrl({ path: 'image.png' });
// Outputs: https://example.ittybit.net/image.png?expiry=1735689600&method=put&signature=a1b2c3d4e5f6...

// Upload to a specific folder
const signedUrl2 = await createSignedUrl({ path: 'nested/folders/image.png' });
// Outputs: https://example.ittybit.net/nested/folders/image.png?expiry=1735689600&method=put&signature=a1b2c3d4e5f6...

A JS/node backend example is given above. More language examples are coming soon but please contact us and we'd be happy to help you write something that fits into your stack.


2. Upload the file

Once you have the signed url, you can upload the file to ittybit.


Simple upload

For most files, you can use a single PUT request.

const response = await fetch(signedUrl, {
  method: 'PUT',
  body: file,
});

Resumable upload

For large files (100MB+), you can use the signedUrl for multiple PUT requests containing different chunks of the file.

const response = await fetch(signedUrl, {
  method: 'PUT',
  headers: {
    'Content-Range': `bytes=0-16777216/100000000`,
  },
  body: chunk,
});

See Resumable Uploads for more details.


3. Handle the response

When your upload completes, ittybit will return a File object in the response.

const { meta, data, error } = await response.json();

The meta object contains information about the request.

The data object contains the file object.

The error object will only be present if the upload failed.


File object

{
  "id": "file_abcdefgh1234",
  "kind": "image",
  "type": "image/png",
  "width": 3000,
  "height": 2000,
  "filesize": 12345678,
  "url": "https://example.ittybit.net/image.png",
  "created": "2025-01-01T01:23:45Z",
  "updated": "2025-01-01T01:23:45Z"
}

You can persist this info to your database, and use it to serve the file to your users.


On this page