Skip to content
Instant public URLs · No account required

Drop a file.
Get a link that lasts.

TGBox turns any file into a permanent public URL in seconds. Files are stored durably in the cloud — sign in only when you want to manage or revoke what you've uploaded.

Anonymous by default 50 MB per file Permanent URLs

Drag a file here, or click to browse

Up to 50 MB per file. Files are public by default — sign in first if you want to manage them later.

PDF Images Video Audio Any file
Your files

Sign in to see your files

Anonymous uploads still get a public link — they just won't be listed here.

API keys

Sign in to create and manage API keys.

API reference

Integrate in minutes

Every endpoint speaks JSON over HTTPS. Uploads work without credentials — add them only when you want ownership and management capabilities.

Upload endpoints

Send a file, get back a permanent URL immediately.

POST
/v1/files

Accepts any file type up to 50 MB as multipart/form-data with field name file.

POST
/v1/images

Same as above, but rejects non-image files with 400 not_an_image. Accepts jpg, png, webp, gif.

cURL example
curl -X POST https://yourdomain.com/v1/files \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "file=@./document.pdf"

Authentication

Credentials change ownership — not upload access.

Uploads work with or without credentials. Passing credentials associates the file with your account so you can manage or delete it later.

Credential Upload Owner Manage later
None (anonymous) ✓ Yes None No
Session cookie ✓ Yes Signed-in user ✓ Yes
X-API-Key header ✓ Yes Key owner ✓ Yes

Read & metadata

Serve files, fetch metadata, and delete what you own.

GET
/i/{id}.{ext}

Serve image files inline. Cached at the edge for fast delivery.

GET
/f/{id}.{ext}

Serve documents, video, audio, and any other file type.

GET
/v1/files/{id}

Return JSON metadata: size, MIME type, sha256, status, and more.

DEL
/v1/files/{id}

Soft-delete a file. Requires ownership via session cookie or API key.

Responses & errors

201 means the URL is live now. Archival happens in the background.

201 Created
{
  "ok": true,
  "created": true,
  "id": "file_2wYpYjLK9tV",
  "url": "https://yourdomain.com/f/…",
  "status": "queued",
  "mime": "application/pdf",
  "ext": "pdf",
  "size": 1048576,
  "sha256": "64-char-hash",
  "original_name": "report.pdf",
  "is_image": false
}

Common errors

400 invalid_multipart_form malformed body
400 empty_file zero bytes
400 not_an_image wrong type
413 file_too_large over 50 MB
401 unauthorized bad credentials
404 not_found unknown / deleted

Rate limits

Treat 429 as retryable after the supplied delay.

TGBox is designed to sit behind an edge proxy (Cloudflare, Nginx). When rate-limited, use the Retry-After header value before retrying.

429 response
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 60

{ "ok": false, "error": "rate_limit_exceeded" }

Code examples

Drop-in snippets for the most common upload flows.

curl -X POST "https://yourdomain.com/v1/files" \
  -H "X-API-Key: tgbox_your_key_here" \
  -F "file=@./invoice.pdf"
function uploadFile(file, apiKey, onProgress) {
  return new Promise((resolve, reject) => {
    const form = new FormData();
    form.append("file", file);

    const xhr = new XMLHttpRequest();
    xhr.open("POST", "https://yourdomain.com/v1/files");
    if (apiKey) xhr.setRequestHeader("X-API-Key", apiKey);

    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable)
        onProgress(Math.round((e.loaded / e.total) * 100));
    };

    xhr.onload = () => {
      const data = JSON.parse(xhr.responseText || "{}");
      xhr.status < 300 ? resolve(data) : reject(data);
    };
    xhr.send(form);
  });
}
import requests
from pathlib import Path

def upload_file(path, api_key=None):
    file_path = Path(path)
    headers = {"X-API-Key": api_key} if api_key else {}

    with file_path.open("rb") as fh:
        files = {"file": (file_path.name, fh, "application/octet-stream")}
        resp = requests.post(
            "https://yourdomain.com/v1/files",
            headers=headers,
            files=files,
            timeout=(5, 120),
        )

    if resp.status_code == 429:
        retry = resp.headers.get("Retry-After", "30")
        raise RuntimeError(f"Rate limited; retry after {retry}s")

    resp.raise_for_status()
    return resp.json()
<?php
use GuzzleHttp\Client;

$client = new Client([
    "base_uri" => "https://yourdomain.com",
    "timeout" => 120,
]);

$path = __DIR__ . "/invoice.pdf";
$handle = fopen($path, "rb");

try {
    $response = $client->post("/v1/files", [
        "headers" => ["X-API-Key" => "tgbox_your_key_here"],
        "multipart" => [[
            "name"     => "file",
            "contents" => $handle,
            "filename" => basename($path),
        ]],
    ]);
    print_r(json_decode((string) $response->getBody(), true));
} finally {
    if (is_resource($handle)) fclose($handle);
}