← All posts

Introducing APULODI: file infrastructure without the complexity

September 5, 20269 min readPlatformAnnouncements

Almost every application you will ever ship has a file problem. Avatars, invoices, CSV imports, PDF exports, product photos, chat attachments. And in our experience the story always starts the same boring way: you create a bucket, wire up the SDK, write an upload endpoint, and ship it. Eighteen months later you own all of this:

None of these problems is hard on its own. That is exactly the trap. Each one gets rebuilt by every team, slightly worse, under deadline pressure. We did it ourselves, several times, at several companies. APULODI is us extracting that plumbing into a platform, once, properly.

What APULODI is

APULODI is file infrastructure for developers. One API — and an official TypeScript SDK on top of it — covering the entire life of a file: upload, organize, version, describe, deliver, observe, delete. You create a project, generate an API key, and everything you do with that key is isolated from every other customer. There is no infrastructure to run, no buckets to name, no sweeper jobs to remember.

It is not a wrapper around "an S3-compatible thing" that we expose for you. APULODI owns the developer experience: identity, authorization, folders, versioning, metadata, events, quotas. The storage layer is ours to evolve — you never see it, and none of our public contract depends on it.

How an upload actually works

The most important engineering decision in APULODI is that file bytes never touch our API. Here is the full life of an upload:

  1. Your server asks APULODI for an upload: filename, content type, size (POST /v1/files/upload). We validate everything, check your project's quotas, generate the file ID and the storage key server-side, and record the file as pending.
  2. We hand you back a presigned upload URL bound to that exact key and content type, with a short expiry.
  3. You PUT the bytes straight to storage. Your application servers are not in the path at all.
  4. You call the complete endpoint. We do not take your word for it — we verify the object in storage, check the actual size and content type against what you declared, and only then mark the file uploaded. A mismatch marks the upload failed with a specific error code.

The payoff: a two-gigabyte video upload never holds open a connection to your API, never lands in a memory buffer, and never gets rate-limited by the same process that serves your login page. Your API tier stays small and cheap; the storage layer does what storage layers are good at.

For files too large for a single request, the same idea extends into multipart sessions: you open a session, receive presigned URLs for each part, upload parts in parallel, retry any individual part that failed, and complete the session. Parts are 8 MiB, sessions are tracked server-side, and the SDK does the part bookkeeping for you — it switches from a single PUT to multipart automatically once a file crosses the 8 MiB threshold.

Everything that comes with it

CapabilityWhat you get
FoldersLogical paths like users/avatars. Created automatically by path-aware uploads. Renames and moves never touch the physical layout.
VersioningReplacing a file bumps its version and retains the previous bytes. file.version tells you where you are.
MetadataArbitrary JSON key/value pairs per file — userId, documentType, source — validated and size-bounded.
CopyServer-side copies between folders without bytes crossing your network.
SearchName search, path/content-type/status filters, validated sorting, cursor pagination.
WebhooksA signed event for every state change, with retries and redelivery.
Usage & quotasDaily metered rollups and enforceable per-project limits with descriptive 403 errors.

Folders deserve a special mention because they are where most DIY setups go wrong. In APULODI, users/avatars is a real, addressable thing — you can list a folder's children — but it is deliberately decoupled from how bytes are laid out internally. You name things the way your product thinks; we handle the way storage thinks.

What it looks like in code

Install the SDK and upload your first file:

ts
import { Apulodi } from "@apulodi/sdk";

const apulodi = new Apulodi({
  apiKey: process.env.APULODI_API_KEY!,
});

// 1. Upload: the SDK gets a presigned URL, PUTs the bytes, and completes
//    the upload — returning the verified file record.
const file = await apulodi.files.upload({
  file: buffer,
  fileName: "passport.jpg",
  contentType: "image/jpeg",
  path: "kyc/documents",
  metadata: { userId: "usr_1042", documentType: "passport" },
});

console.log(file.id, file.version, file.size); // file_8f92… 1 48291

// 2. List everything in that folder, newest first.
const { data } = await apulodi.files.list({ path: "kyc/documents" });

// 3. Hand your user a short-lived download URL.
const { url } = await apulodi.files.downloadUrl(file.id);

If you prefer raw HTTP, the same flow is three calls — request, PUT, complete — and every endpoint is documented with request/response examples in the API reference.

Tenancy: organizations, projects, keys

An organization owns members and billing. Inside it, projects are the isolation boundary — one per app or environment, like production-api and mobile-app. API keys belong to a project, never to a person. Object keys are derived server-side from the organization, project and file IDs, so a client cannot influence where its bytes land, and a key from project A gets a plain 404 — not a 403 — when it reaches for project B's files. We do not confirm that the file exists.

This matters more than it sounds. When you eventually store customer documents, the question "could a leaked test key read production data?" must have exactly one answer, and it must be no.

Choices we made, and what they cost

We would rather document our trade-offs than pretend they do not exist:

What is next

The event pipeline that powers webhooks today is the same one that will drive media processing — thumbnails, transcoding, transformations — without you having to run workers. A CDN layer comes after that. And then the part we are personally most invested in: local-currency billing. We are starting with Malawi — PayChangu, Airtel Money, TNM Mpamba, pricing in Kwacha — because we have watched talented developers abandon a perfectly good platform at the checkout simply because they do not hold a card that talks to Stripe. That is a product requirement here, not a nice-to-have.

Come build

The Quickstart takes you from zero to a verified upload in a few minutes, and the free plan is genuinely free — no card, no trial clock. If something feels off, or a file API question keeps you up at night, we want to hear about it.