Skip to content

How to Upload Files ​

Upload images, videos, and audio to Get3W storage, then pass the returned download_url to any model input. The client makes one small authenticated request, then uploads the bytes directly to storage with a short-lived URL. File bytes never pass through the API gateway.

Endpoint ​

POST https://api.get3w.com/v1/media/uploads
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Request Body ​

json
{
  "filename": "image.png",
  "size": 1024000,
  "content_type": "image/png"
}
FieldTypeRequiredDescription
filenamestringYesOriginal filename, including its extension
sizeintegerYesExact file size in bytes; maximum 200 MB (209,715,200 bytes)
content_typestringNoMIME type; inferred from the extension when omitted

Supported formats:

TypeExtensions
imagejpg jpeg png gif webp bmp tiff tif
videomp4 webm mov
audiomp3 wav m4a aac flac

Response ​

json
{
  "type": "image",
  "download_url": "https://get3w-media-1252164560.cos.ap-guangzhou.myqcloud.com/tmp/uploads/2026/10/17/3f2a....png",
  "filename": "image.png",
  "size": 1024000,
  "upload": {
    "method": "PUT",
    "url": "https://...signed...",
    "headers": {
      "Content-Type": "image/png",
      "x-cos-acl": "public-read",
      "x-cos-forbid-overwrite": "true"
    },
    "expires_at": "2026-10-17T02:15:00Z"
  }
}
FieldDescription
typeimage, video, or audio
download_urlPublic URL to use as a model input once the upload succeeds
upload.methodAlways PUT
upload.urlShort-lived signed upload URL (valid for 15 minutes)
upload.headersHeaders you must send with the PUT, exactly as returned
upload.expires_atUTC time after which upload.url stops working

Upload the exact file bytes with the returned method, URL, and headers. A successful PUT returns any 2xx status, and download_url is ready to use immediately.

Examples ​

bash
FILE="/path/to/image.png"
FILE_SIZE=$(wc -c < "$FILE" | tr -d ' ')

TICKET=$(curl --fail-with-body --silent --show-error \
  -X POST "https://api.get3w.com/v1/media/uploads" \
  -H "Authorization: Bearer $GET3W_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg filename "$(basename "$FILE")" --argjson size "$FILE_SIZE" \
    '{filename: $filename, size: $size}')")

UPLOAD_URL=$(printf '%s' "$TICKET" | jq -r '.upload.url')
UPLOAD_HEADERS=()
while IFS= read -r header; do
  UPLOAD_HEADERS+=(-H "$header")
done < <(printf '%s' "$TICKET" | jq -r \
  '.upload.headers | to_entries[] | "\(.key): \(.value)"')

curl --fail-with-body --request PUT "$UPLOAD_URL" \
  "${UPLOAD_HEADERS[@]}" \
  --upload-file "$FILE"

printf '%s' "$TICKET" | jq -r '.download_url'
python
import mimetypes
import os
from pathlib import Path

import requests

path = Path("/path/to/image.png")
payload = {"filename": path.name, "size": path.stat().st_size}
content_type = mimetypes.guess_type(path.name)[0]
if content_type:
    payload["content_type"] = content_type

ticket_response = requests.post(
    "https://api.get3w.com/v1/media/uploads",
    headers={"Authorization": f"Bearer {os.environ['GET3W_API_KEY']}"},
    json=payload,
    timeout=30,
)
ticket_response.raise_for_status()
ticket = ticket_response.json()

with path.open("rb") as file:
    upload_response = requests.put(
        ticket["upload"]["url"],
        headers=ticket["upload"]["headers"],
        data=file,
        timeout=300,
    )
upload_response.raise_for_status()
print(ticket["download_url"])
javascript
import { readFile, stat } from 'node:fs/promises';
import { basename } from 'node:path';

const filePath = '/path/to/image.png';
const ticketResponse = await fetch('https://api.get3w.com/v1/media/uploads', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.GET3W_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    filename: basename(filePath),
    size: (await stat(filePath)).size,
  }),
});
if (!ticketResponse.ok) throw new Error(await ticketResponse.text());
const ticket = await ticketResponse.json();

const uploadResponse = await fetch(ticket.upload.url, {
  method: ticket.upload.method,
  headers: ticket.upload.headers,
  body: await readFile(filePath),
});
if (!uploadResponse.ok) throw new Error(await uploadResponse.text());
console.log(ticket.download_url);

Errors ​

CodeDescription
400Missing or invalid fields (Invalid parameters), invalid filename, unsupported format, or content type mismatch
401Missing or invalid API key
413File exceeds 200 MB
429Upload rate limit exceeded (60 tickets per minute); retry later

The PUT to upload.url can also fail with 403 if the ticket has expired, the headers differ from the returned ones, or the uploaded byte count does not match the declared size. Request a new ticket in that case.

Reusing a ticket after a successful PUT returns 409, because existing objects are never overwritten. Request a new ticket for every file.

Important Notes ​

  • The upload URL is a temporary credential. Do not log, persist, or share it
  • Send your API key only to api.get3w.com. Never add an Authorization header to the PUT request
  • Treat upload.url as opaque. Its hostname and storage provider may change. Always use the full URL returned for that upload; do not construct or rewrite it
  • One ticket, one file. Tickets cannot be reused, and an existing object is never overwritten
  • Retention: uploaded files are kept for 7 days, then deleted automatically. They are intended only as inference inputs. See Data Retention Policy

Next Steps ​

Released under the MIT License.