curl Upload: File to Link, Three Commands

The curl Upload File to Link Flow: POST, PUT, Confirm

POST for a presigned URL, curl -X PUT the bytes, POST confirm. The link is a permanent CDN URL you can paste into Discord, Slack, a CI log, or a support ticket.

This is the curl upload file to link flow: POST the file's metadata to get a presigned URL, PUT the bytes to that URL, then POST to confirm. Three commands, and what comes back is a permanent CDN address you can paste anywhere. curl is the whole SDK here. Every command below is copy-paste. Swap in an API key from your API keys page and a folder id from the dashboard. The auth header is the raw key value, with no Bearer prefix.

Step 1 asks the API for somewhere to put the bytes.

bash
# Step 1: request a presigned upload URL
curl -sf -X POST https://api.cdn22.net/v1/files/<FOLDER_ID> \
  -H "Authorization: $CDN22_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filesMetadata":[{"name":"report.pdf","size":248173,"type":"application/pdf"}]}'

# Response
# {
#   "success": true,
#   "urls": [
#     { "url": "https://<bucket>.s3.<region>.amazonaws.com/...&X-Amz-Signature=...",
#       "id":  "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
#       "key": "<owner>/<folder>/report.pdf" }
#   ]
# }

Step 2 sends the file itself. This PUT goes to storage, not to the API, so it carries no Authorization header at all. The signature baked into the presigned URL is the authorization. Sending your API key here is the single most common way this flow breaks.

bash
# Step 2: PUT the bytes to the presigned URL. No auth header, and quote the URL.
curl -sf --upload-file ./report.pdf \
  -H "Content-Type: application/pdf" \
  "https://<bucket>.s3.<region>.amazonaws.com/...&X-Amz-Signature=..."

--upload-file is the command-line face of libcurl's CURLOPT_UPLOAD: it streams the file straight from disk and implies -X PUT, so you never need both. curl -X PUT --data-binary @report.pdf moves the same bytes but buffers the file first, so prefer --upload-file for anything large. Add -# for a progress bar, and --retry 3 --retry-connrefused so a dropped connection re-attempts the transfer instead of failing the whole pipeline.

Step 3 finalizes the file, and the CDN URL goes live.

bash
# Step 3: confirm the upload with the id from step 1
curl -sf -X POST https://api.cdn22.net/v1/files/confirm-upload \
  -H "Authorization: $CDN22_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids":["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"]}'

# A public file is now served from:
# https://cdn.cdn22.net/<owner>/<folder>/report.pdf

To stop copy-pasting ids by hand, capture step 1's response and pull the fields out with jq. That is a complete uploader in about ten lines. Drop it in your shell profile and upload report.pdf works on any machine that has curl and jq.

bash
upload() {
  resp=$(curl -sf -X POST https://api.cdn22.net/v1/files/<FOLDER_ID> \
    -H "Authorization: $CDN22_API_KEY" -H "Content-Type: application/json" \
    -d "$(jq -n --arg n "$(basename "$1")" \
               --argjson s "$(wc -c < "$1" | tr -d ' ')" \
               '{filesMetadata:[{name:$n,size:$s,type:"application/octet-stream"}]}')")

  curl -sf --retry 3 --upload-file "$1" "$(jq -r '.urls[0].url' <<< "$resp")"

  curl -sf -X POST https://api.cdn22.net/v1/files/confirm-upload \
    -H "Authorization: $CDN22_API_KEY" -H "Content-Type: application/json" \
    -d "$(jq -c '{ids:[.urls[0].id]}' <<< "$resp")"
}

Because every call is plain HTTPS on port 443, the same commands run from a laptop, a cron job, a Docker image, GitHub Actions, or GitLab CI with no credentials file to distribute. Unlike aws s3 cp, the only secret is the one API key already in your environment. Keep -f on the two API calls so a non-2xx response sets a non-zero exit code your CI step can actually catch.

Which link you get depends on the folder the file landed in. A file in a public folder is served straight from the CDN at a permanent URL, so the address never changes and the bytes cache at the edge. A file in a private folder is not reachable that way; you ask the API for a time-limited download URL instead.

bash
# Time-limited link for a private file. expiresIn is seconds; 3600 = one hour.
curl -sf -H "Authorization: $CDN22_API_KEY" \
  "https://api.cdn22.net/v1/files/signed/<FILE_ID>?expiresIn=3600"

# {"success":true,"url":"https://...&X-Amz-Expires=3600","expiresIn":3600}

expiresIn accepts 60 to 604800 seconds (seven days is the presigning ceiling); omit it and you get the 600-second default. If you would rather hand out one stable address for a private file, POST /v1/p with {"fileId":"..."} returns a permanent link that redirects to a freshly signed URL on each visit, so the address you shared keeps working without you reissuing it.

Be precise about what expires there: expiresIn bounds how long a download URL stays valid, not how long the file lives. To expire the file itself, add ttlSeconds next to filesMetadata in step 1 (or an absolute expiresAt in ISO 8601). Once the TTL passes, the API stops issuing signed URLs for that file and a sweep removes the object and its metadata within the hour. Omit both fields and nothing changes: the file stays until you remove it with DELETE /v1/files/<id>.

bash
# Step 1 with a one-hour file TTL. 60s minimum, 365 days maximum.
curl -sf -X POST https://api.cdn22.net/v1/files/<FOLDER_ID> \
  -H "Authorization: $CDN22_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filesMetadata":[{"name":"proof.pdf","size":248173,"type":"application/pdf"}],"ttlSeconds":3600}'

A throwaway temp-file API is the other option here. Disposable one-command hosts win when you want zero setup and no account: no key, one curl, and it deletes itself on a schedule you do not set. cdn22.net is the better fit when the link has to keep working. A build artifact linked from release notes, an image a bot posts into a channel, a video a client opens next quarter, a PDF attached to a support ticket someone reopens: every one of those breaks the day the host expires the URL.

Here you get a CDN-backed address, folders you can list and re-fetch through the API, and deletion on your schedule instead of theirs. See temporary file hosting for the expiring-link side and the transfer.sh alternative for the self-hosted comparison.

Sharing the result is just pasting the URL, because that is all it is. Discord and Slack render a direct image or video link inline when the Content-Type is right, which is the usual reason to push a file to a CDN before posting instead of attaching it. The same three commands run server-side in a bot: Discord bot file storage and Slack file storage cover that setup.

Need the same flow from somewhere other than a shell? The full upload-a-file guide walks the identical three calls from curl, the browser, and Node.js. The cloud file storage API covers the wider endpoint set, the file upload API documents the upload endpoints on their own, file to link covers the same job from the dashboard when you are not in a terminal, file upload CLI covers shell ergonomics, API-key auth covers key handling and rotation, and Node.js and Python port the same three calls into a runtime.

Pricing is prepaid credits with no subscription: you pay for the storage and bandwidth you actually use, so a script pushing a few build artifacts costs a few cents. Create an API key and run the commands above against your own folder.

Benefits With No Complexity

Global CDN delivery

Your files are served from 450+ edge locations worldwide.

Edge-cached worldwide

Files are cached close to your users for fast delivery.

Signed-URL security

Private files stay protected with time-limited access.

What You Get

Unlimited files

Upload as many files as you need.

Unlimited storage

There is no storage limit.

Public + Private storage

Private files are fully secured.

CDN ready links

Upload directly to 450+ edge locations worldwide.

Prepaid credits

No subscription. Pay only for the storage and bandwidth you use.

More coming soon

We have plenty of features coming!

How cdn22.net Works

1. Upload

Create a project and upload your first file.

2. Copy

Copy the CDN link.

3. Use Anywhere

Paste and enjoy the blazing speed.

Why Developers Choose cdn22.net

A Better Way to Store & Deliver Files

Throwaway temp-file APIaws s3 cpscp / rsynccdn22.net
Upload with one command and no account
Link still resolves months later
Global CDN delivery on the file URL
Single API key, no credentials file
List and re-fetch past uploads over the API
Expiring signed URLs for private files

Calculate Your Needs

Storage

0GB

Egress

0GB

CDN Bandwidth

0GB
Total: $0.000/month

Need storage, egress, and request fees broken out — and compared against AWS list price for the same bytes? Use the CDN & storage cost calculator.

Create an API Key and Upload with curl

  • Global CDN delivery
  • Edge-cached worldwide
  • Signed-URL security
  • Unlimited files
  • Unlimited storage
  • Public + Private storage
  • No subscription — prepaid credits keep spend predictable
Create Your API Key

Frequently Asked Questions

What exactly are the three curl commands?
POST /v1/files/{folderId} with file metadata returns {success, urls:[{url,id}]}; curl --upload-file to that presigned url (no auth header; the URL signature is the auth); POST /v1/files/confirm-upload with {"ids":["ID"]} to finalize. The confirmed file is then served from a CDN URL.
How do I fix curl: (26) couldn't open file?
Exit code 26 means curl could not read the local path you passed. Check the path is correct relative to your current directory, that the file is readable, that a name containing spaces is quoted, and that you used --upload-file ./report.pdf (a plain path) or --data-binary @report.pdf (with the leading @). Omitting the @ makes curl send the literal filename as the body instead of the file.
Why does the presigned PUT return 403 SignatureDoesNotMatch?
Almost always because an Authorization header was sent on step 2. The presigned URL authorizes itself, so adding your API key breaks it. Also quote the URL in your shell so & does not split the command, and re-run step 1 if the URL has expired, since presigned URLs are short-lived.
What Content-Type should curl send?
The two API calls are application/json. On the step-2 PUT, send the file's real MIME type (for example application/pdf or image/png) and keep it consistent with the type you declared in step 1. A wrong Content-Type is what makes a browser download an image instead of rendering it.
How do I avoid copy-pasting the URL and ID?
Capture step 1's response in a variable and pull .urls[0].url and .urls[0].id with jq -r, then reference those vars in steps 2 and 3. Wrapped in a shell function, the whole uploader is about ten lines.
How do I authenticate?
Pass your API key as the raw Authorization header value, with no Bearer prefix, on the POST calls. The PUT to the presigned URL needs no header. Create and rotate keys from the API keys page in your dashboard.
Can I show a progress bar or retry large uploads?
Yes. Add -# to the PUT step for a progress bar, and --retry 3 --retry-connrefused so a dropped connection re-attempts the byte transfer. --upload-file streams from disk, so file size is not bounded by memory.
When should I read the full upload guide instead?
Use this page when curl is the whole integration. Read /guides/upload-file when you need the identical three calls from a browser file input or a Node.js backend, or want the full response fields documented.
Can curl upload a file to a link that expires?
Both can expire, and they are separate settings. For a private file, GET /v1/files/signed/{id}?expiresIn=SECONDS returns a signed download URL valid for 60 to 604800 seconds (600 by default). To expire the file itself, pass ttlSeconds in the step 1 body, from 60 seconds to 365 days. Pass neither and the object stays until you call DELETE /v1/files/{id}.
Is this a temporary file host that deletes uploads automatically?
It can be, per upload. Send ttlSeconds (or expiresAt) with the step 1 metadata and that file is deleted when the TTL passes; signed URLs stop resolving at the expiry instant and the object is swept within the hour. Uploads without a TTL keep the default behaviour and persist until you delete them, so a release note and a throwaway proof can live in the same account.
How do I share the uploaded file link in Discord or Slack?
Paste the URL. A confirmed public file is a plain HTTPS CDN address, and both Discord and Slack render a direct image or video link inline when the Content-Type is correct, which is why the type you send on the step-2 PUT matters. A bot runs the same three curl calls server-side and posts the resulting URL.
General Questions
Is there a subscription?
No. cdn22.net uses prepaid credits, so storage, bandwidth, and API usage are deducted from your balance as you go. The app explains the payment step before uploads are enabled — no monthly subscription and no per-user fees.
What is cdn22.net?
cdn22.net is a developer-first file platform that makes it simple to store, secure, and deliver files globally. It provides signed URLs, public/private access, and an API-first design so you can integrate file delivery into any app without the usual complexity.
How does billing work?
cdn22.net uses prepaid credits. As you use storage, bandwidth, and API requests, credits are deducted daily. When your balance runs low, we automatically recharge it using your saved card. If an auto-recharge doesn't go through, your files and links stay put — you simply update your payment method or top up manually to keep going. No monthly subscriptions — just simple usage-based pricing.
How secure is my data?
All files are encrypted at rest and in transit. You can use signed URLs for private files, control access with permissions, and rely on enterprise-grade infrastructure for data protection.

Related

cdn22.net
Copyright © 2026
All rights reserved
ContactGuidesGlossaryStatusLegal