Most hosts give you two states: a file exists, or it is gone. DurableFile has a retention clock instead. Every file has an end date, and every file can have that date pushed back — at the identical URL.

The default: 14 days

An upload with no expiry fields serves for 14 days:

curl -X POST https://durablefile.com/v1/upload \
  -H "Authorization: Bearer $DURABLEFILE_KEY" \
  -F "file=@handoff.csv"

The response tells you exactly when the link stops working:

{
  "hash": "…",
  "status": "live",
  "public_url": "https://f.durablefile.com/f/…/handoff.csv",
  "expires_at": "2026-09-06T17:40:57.000Z"
}

There is no option to skip the clock. That is a deliberate product choice: a $1 payment cannot fund unbounded storage for an unbounded time, so the contract states its limit up front instead of quietly changing later.

Choosing your own window

Send one of two fields, never both:

# Relative: stop serving in 72 hours.
curl -X POST "https://durablefile.com/v1/upload?expires_after_hours=72" \
  -H "Authorization: Bearer $DURABLEFILE_KEY" \
  -F "file=@handoff.csv"

# Absolute: stop serving at an exact instant.
curl -X POST "https://durablefile.com/v1/upload?expires_at=2026-12-01T00:00:00Z" \
  -H "Authorization: Bearer $DURABLEFILE_KEY" \
  -F "file=@handoff.csv"

The rules are strict and return exact errors:

  • expires_after_hours must be greater than zero. A computed zero is rejected instead of silently meaning something else, so a bug in your duration math surfaces as an error.
  • The maximum window is 4,320 hours — 180 days.
  • expires_at accepts epoch seconds or an ISO 8601 time, must be in the future, and must be within 180 days.
  • Sending both fields is a conflicting_expiry error.
  • Sending an explicit null is an expiry_required error. Omit the field if you want the default; there is no way to ask for no expiry.

Expiry is evaluated when the public URL is requested, not by a background job. The link stops serving at its deadline even if no scheduled task ever runs. A separate hourly job reclaims the bytes afterward.

Renewing before the clock runs out

Two gestures renew a file, and both keep the identical URL:

# Republish: an empty body gives a fresh 14-day window.
curl -X POST https://durablefile.com/v1/files/$HASH/publish \
  -H "Authorization: Bearer $DURABLEFILE_KEY" \
  -H "Content-Type: application/json" -d '{}'

# Or set the window explicitly, up to 180 days.
curl -X POST https://durablefile.com/v1/files/$HASH/publish \
  -H "Authorization: Bearer $DURABLEFILE_KEY" \
  -H "Content-Type: application/json" -d '{"expires_after_hours":4320}'

Re-uploading the same bytes does the same thing, which makes an idempotent CI job a renewal mechanism by accident: each run pushes the deadline out. The mechanics of why the URL is stable are covered in the content-addressing article.

A file that expired but has not been purged yet can still be renewed by the same call. After the purge, the bytes are gone and you must upload them again — which produces the same URL anyway.

Hiding a link early

curl -X POST https://durablefile.com/v1/files/$HASH/unpublish \
  -H "Authorization: Bearer $DURABLEFILE_KEY"

The public URL returns 404 from that moment. The bytes stay until the claim's expires_at, and they keep using your quota until then, so a republish can bring the link back before the deadline.

Deleting immediately

curl -X DELETE https://durablefile.com/v1/files/$HASH \
  -H "Authorization: Bearer $DURABLEFILE_KEY"

Delete frees quota immediately instead of waiting for expiry. It removes your claim, and GET /v1/me reflects the freed bytes right away.

One nuance in a content-addressed world: if another account independently claims the same bytes, the shared object and its URL survive your delete. The stored object disappears only when the last claim is gone.

The contract in one table

Action Public URL Bytes Your quota
Live claim serves stored used
Manual unpublish 404 kept until expiry still used
Expiry passes 404 purged within the hour freed automatically
Republish or re-upload serves again, identical URL kept used again
Delete 404 unless another account claims it kept only if claimed elsewhere freed now

Two things worth remembering. Files are public while live, so an expiry is not a way to protect something uploaded by mistake — delete it. And anyone who downloaded a file while it was live keeps their copy; the expiry controls future downloads only.