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://you.ittybit.net/image.png?expiry=1765432100&signature=1234abcd5678efgh9012


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.com/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 = 'you.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 { signedUrl } = await createSignedUrl({ path: 'image.png' });
// Outputs: https://you.ittybit.net/image.png?expiry=1735689600&method=put&signature=a1b2c3d4e5f6...

// Upload to a specific folder
const { signedUrl: signedUrl2 } = await createSignedUrl({ path: 'nested/folders/image.png' });
// Outputs: https://you.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, links } = await response.json();
  • meta: contains information about the request.

  • data: contains the file object (or null if the upload failed).

  • error: will contain an error message if the upload failed (or null).

  • links: contains links to the file object resource.


File object

{
  "id": "file_abcdefgh1234",
  "kind": "image",
  "type": "image/png",
  "width": 3000,
  "height": 2000,
  "filesize": 12345678,
  "url": "https://you.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