Varun Kulkarni
All projects

StashFyle

Stack-agnostic file storage API—POST a file, get a URL.

  • DevTool
  • SaaS
  • Full Stack

Removing the file-upload tax

StashFyle started from a recurring frustration: file storage is rarely the interesting part of a product, but implementing it can consume an unreasonable amount of time. A simple upload often turns into bucket policies, IAM credentials, CORS rules, presigned URLs, an SDK, metadata tables, and a billing calculator before the first file reaches a user.

I wanted the smallest useful abstraction over that machinery. StashFyle is a REST API that works with anything capable of making an HTTP request. There is no required SDK, framework adapter, or upload widget: send a multipart file with an API key and receive a stable URL.

curl -X POST https://api.stashfyle.com/v1/upload \
  -H "Authorization: Bearer sk_live_xxx" \
  -F "file=@photo.jpg"
{
  "id": "f_abc123xyz",
  "url": "https://cdn.stashfyle.com/.../photo.jpg",
  "size": 248000,
  "type": "image/jpeg"
}

That constraint shaped the whole product. The API should feel equally natural from curl, JavaScript, Python, Go, Ruby, PHP, or a shell script. Choosing plain HTTP as the primary interface made StashFyle less magical, but much harder to outgrow.

Designing the one-request upload

The basic endpoint stays intentionally boring: one required file field and a few optional controls. A caller can organize files with folder, make one private with private=true, or give temporary output an expiration such as 1h, 7d, or 30d. Public uploads return a CDN URL immediately; private uploads return an ID that can be exchanged for time-limited access.

What happens behind one upload request
01AuthenticateValidate the API key and its capabilities.
02ProtectCheck origins, rate limits, file size, and storage quota.
03StoreWrite the object to Cloudflare R2 with a stable key.
04RecordSave metadata and update usage in Postgres.
05ReturnRespond with a file ID and CDN URL.

I kept the response format stable across endpoints and gave every failure a machine- readable code alongside a useful message. A caller can distinguish a file-size problem from an exhausted storage quota or a rate limit without parsing prose. Every response also carries rate-limit headers, which makes backoff and monitoring possible without another API call.

Listing files uses cursor pagination rather than page numbers, so adding new uploads does not reshuffle an integration’s place in the collection. The same API supports folders, metadata lookup, deletion, usage reporting, and health checks without changing the core mental model.

What sits behind the abstraction

The public surface is small because the service absorbs the coordination work. A Next.js route authenticates the request, applies account and plan rules, and sends the object to Cloudflare R2. StashFyle then records the file’s ownership, storage key, type, size, visibility, folder, and expiration in Postgres before returning an API-safe response.

A full service behind a deliberately small interface
APINext.js route handlers
IdentityHashed public and secret keys
ControlUpstash rate limits + plan quotas
StorageCloudflare R2 + CDN delivery
MetadataSupabase Postgres
BillingStripe subscriptions

I used R2 because it exposes the familiar S3 protocol while removing egress fees from the product’s cost model. Public files are served through a CDN URL. Private objects never receive a public URL; the service signs an R2 request for a caller-selected duration instead.

Public fileUpload → permanent CDN URL

The response can be stored and rendered immediately. Delivery happens from Cloudflare’s edge network.

Private fileUpload → file ID → signed URL

Access is granted on demand for one minute to seven days, without exposing the secret key to a browser.

Separating object storage from metadata also keeps the API flexible. The object key is deterministic and namespaced by user, folder, file ID, and original filename, while the database remains the source of truth for ownership and state. Deletion first marks the record unavailable to the product, removes the underlying object, and reconciles the user’s storage usage.

Making “works anywhere” safe

Stack-agnostic should not mean one credential with unlimited power. StashFyle has secret and public API keys with separate capabilities. Secret keys are intended for servers and can manage files or create signed URLs. Public keys can be embedded in a browser, but they are restricted to uploads and checked against an explicit allowlist of origins.

Keys are only shown once and stored as hashes. Each request resolves the key to its owner and plan, then passes through a sliding-window rate limiter keyed to that credential. The upload path also enforces file-size, storage, and subscription limits before writing data. Private-file operations verify both the caller and file ownership, so knowing a file ID is not enough to access it.

Public key · pk_

Browser-safe upload access, constrained by allowed origins. It cannot list files, create signed links, or retrieve private metadata.

Secret key · sk_

Server-side access to the complete API. It can manage files and issue temporary links, so it never belongs in client code.

Operating the service, not just the endpoint

Building the upload route was only part of making StashFyle usable as a service. Every request is logged with its endpoint, status, latency, file size, error code, API key, and originating IP. The dashboard turns those records into a practical view of files, keys, usage, billing, and failures rather than exposing the underlying infrastructure.

A file’s lifecycle
01UploadObject and metadata are created
02UsePublic CDN or temporary signed access
03ObserveUsage and request logs update
04ExpireScheduled cleanup removes temporary files

Auto-expiring files are handled by a scheduled cleanup job that removes the R2 object, soft-deletes its metadata, and returns the consumed storage to the account. Stripe controls plan state and limits. Downgrades use a grace period instead of immediately breaking downloads, which gives someone time to reduce usage before cleanup is needed.

The documentation was part of the product from the beginning. The quickstart gets from account creation to a real upload in a few minutes, and the Mintlify reference documents authentication, endpoint schemas, errors, rate-limit behavior, private files, and examples across multiple languages. A simple API only feels simple when its edge cases are easy to discover.

What I took away

StashFyle is an exercise in moving complexity to the right side of an interface. The developer integrating it should see one endpoint and one predictable response. The service still has to care about object storage, identity, abuse, quotas, billing, lifecycle management, and observability—it just should not make every customer rebuild those pieces.

I also chose to release the project under the MIT license. For infrastructure products, being able to inspect how credentials, storage, and limits are handled is useful in itself. The repository is both the implementation and a concrete explanation of what StashFyle does on someone’s behalf.