Documentation

API & embedding guide

Everything you need to integrate the Tonta uploader — authenticate, send uploads, embed metadata and remove files. These endpoints reflect the current production implementation.

Quick start

Sign in at dash.tonta.io, create an uploader, and copy the generated API key. Each uploader controls its allowed domains, resizing rules, watermarking, and whether originals are retained.

Embed the uploader widget

HTML
<div class="my-uploader"></div>

<script src="https://tonta.io/uploader/uploader.js"
        data-backend="https://tonta.io/uploader/upload.php"
        data-target=".my-uploader"
        data-api-key="YOUR_API_KEY"
        data-callback="handleUpload"></script>

<script>
window.handleUpload = {
  onUploadComplete(file, result) {
    document.querySelector('#preview').src = result.link;
  }
};
</script>

Authentication

Every API request is authenticated with the uploader's API key. The key is tied to its uploader configuration and inherits its domain restrictions.

cURL
curl -X POST https://tonta.io/uploader/upload.php \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "file=@image.jpg"
Domain restrictions
  • Add every origin that will embed the uploader (e.g. studio.example.com).
  • Origins not on the list receive 403 Domain not allowed.
  • Rotate keys in the dashboard if one is ever exposed.

Upload API

MethodEndpointDescription
POST/uploader/upload.phpUpload one or more files via multipart/form-data

Form fields

FieldRequiredDescription
fileYesThe file input; repeat to send multiple files.
metadataNoJSON blob stored alongside the file (visible in the dashboard).
uploader_idNoForce a specific uploader config when the key owns several.
xmp_*NoXMP fields — see the metadata section.
JS
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('metadata', JSON.stringify({ gallery: 'seniors', sequence: 12 }));
formData.append('xmp_title', 'Sunset Portrait');

const response = await fetch('https://tonta.io/uploader/upload.php', {
  method: 'POST',
  headers: { 'X-API-Key': 'YOUR_API_KEY' },
  body: formData
});
const result = await response.json();
console.log(result.link);

Response

JSON
{
  "success": true,
  "id": "rT2F6Dn",
  "link": "https://files.tonta.io/rT2F6Dn.jpg",
  "sfname": "rT2F6Dn_1920.jpg",
  "size": 245632,
  "versions": [
    {
      "label": "Web",
      "url": "https://files.tonta.io/rT2F6Dn_1920.jpg",
      "dimensions": "1920x1280",
      "format": "webp"
    }
  ]
}

All asset URLs are served from the Tonta CDN at files.tonta.io. If originals are disabled, the original block is omitted and link points to the first processed version.

Delete API

Use the delete endpoint to remove files created by a given uploader. Requests must include the same X-API-Key used for uploads.

MethodEndpointDescription
POST/uploader/delete.phpDelete by smallid, smallids, or base id
cURL
curl -X POST https://tonta.io/uploader/delete.php \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{ "smallids": ["rT2F6Dn_1920.jpg", "rT2F6Dn_1024.jpg"] }'

Alternatively, pass { "id": "rT2F6Dn" } to remove every version for that base ID. The endpoint responds with:

JSON
{ "success": true, "deleted": 2, "requested": 2, "errors": [] }

Metadata & XMP

Enable “XMP Metadata Embedding” in an uploader's advanced settings to embed caption data directly into image files. Supported fields:

FieldXMP propertyNotes
xmp_titledc:titleHuman-readable title
xmp_descriptiondc:descriptionCaption / alt text
xmp_keywordsdc:subjectComma-separated keywords
xmp_creatordc:creatorPhotographer or studio
xmp_copyrightdc:rightsCopyright string

If XMP embedding is disabled, these fields are ignored — but you can still store structured metadata via the metadata JSON payload for dashboard use.

Signed URLs & privacy

By default every file Tonta stores is publicly fetchable at https://files.tonta.io/<file_name> (and your white-label files.* domain). You can opt into a signed-URL access model where bare URLs return 401 and access requires a short-lived HMAC token. Policy applies in three cascading levels: album > image > variation, with variation winning, then image, then album. Default at every level is public.

The cascade in plain terms

  • Album-level (whole uploader): set with POST /api/policy-album.php. Affects every file.
  • Image-level (all variations of one image): set with POST /api/policy-image.php. Overrides album.
  • Variation-level (a specific {base}_{size}.{ext} file): set with POST /api/policy-variation.php. Overrides image. API-only — no dashboard UI by design.

All three endpoints update Cloudflare's edge KV instantly and purge affected URLs from the global cache so changes take effect within seconds.

Common patterns

Watermark protection (the photographer case)
  • Mark the image private at the image level.
  • Override every derivative size (_512, _1024, etc.) back to public with the variation endpoint.
  • Result: original 401s without a signed URL; small versions display freely in any public gallery.
  • When a buyer pays, mint a signed URL for the original with a short TTL and hand it over.
Time-limited share link
  • Leave album public; mark the specific image private.
  • Call /api/sign-url.php with the buyer's session TTL.
  • Return the signed URL to the buyer; it dies on expiry.

POST /api/sign-url.php

Mint a time-limited signed URL for a file.

JSON
// Headers: X-API-Key: up_xxxxxx
{
  "base_id": "tTQg3q1nWjpxUUbDmdWI",    // 20-char alphanumeric file ID
  "variation": "512",                  // "original" | "256" | "512" | "1024" | ...
  "ttl_seconds": 3600,                 // optional; min 60, max ~10 years
  "extension": "webp",                 // optional; resolved from DB if absent
  "domain": "files.tonta.io"          // optional; defaults to files.tonta.io
}

Response:

JSON
{
  "success": true,
  "url": "https://files.tonta.io/tTQg3q1nWjpxUUbDmdWI_512.webp?exp=1748627200&u=upl_xxx&sig=2f7c...",
  "file_name": "tTQg3q1nWjpxUUbDmdWI_512.webp",
  "expires_at": 1748627200,
  "ttl_seconds": 3600
}

If ttl_seconds is omitted, the uploader's configured default (set in the dashboard's File Access section or via /api/update-signed-url-ttl.php) is used.

POST /api/policy-album.php

Set the whole uploader's default privacy.

JSON
// Headers: X-API-Key: up_xxxxxx
{
  "policy": "private"     // "public" | "private"
}

Re-resolves the cascade for every file in the uploader, updates KV, and purges the affected URLs from the edge cache in the background.

POST /api/policy-image.php

Override one image (and all its variations).

JSON
// Headers: X-API-Key: up_xxxxxx
{
  "base_id": "tTQg3q1nWjpxUUbDmdWI",
  "policy": "public"      // "public" | "private" | "default" (clears override)
}

POST /api/policy-variation.php

Override a single file (specific size/extension). API-only — too granular for the dashboard UI.

JSON
// Headers: X-API-Key: up_xxxxxx
{
  "file_name": "tTQg3q1nWjpxUUbDmdWI_512.webp",
  "policy": "private"     // "public" | "private" | "default"
}

POST /api/update-signed-url-ttl.php

Set the uploader's default TTL for signed URLs (used when /api/sign-url.php is called without ttl_seconds, and for dashboard-rendered owner views).

JSON
// Headers: X-API-Key: up_xxxxxx
{
  "ttl_seconds": 3600      // min 60, max ~10 years
}

Gallery interaction

If an uploader has a public or password-protected gallery, the gallery mints signed URLs for its visitors server-side after they pass its own access check. Private/signed file policy does not hide files from gallery visitors — it only blocks people who don't go through the gallery (direct URL hot-linkers, scrapers). To hide files from gallery visitors too, set the gallery itself to Private.

Verification semantics
  • 200 · correct signature, before expiry
  • 401 · missing or invalid signature on a private file
  • 403 · signature provided but expiry has passed

Video webhook

Tonta hands large video uploads off to serverless GPUs for processing. When enabled, completion updates the uploader record automatically. If you need your own webhook, hook into the dashboard's onUploadComplete callback and forward the result payload to your system.

Troubleshooting

Common errors
  • 401 Invalid API key — ensure you copied the key from the uploader you're targeting.
  • 403 Domain not allowed — add the site's origin to the uploader's allowed domains.
  • File exceeds upload_max_filesize — raise the uploader's max size or compress before upload.
  • Storage limit exceeded — upgrade your plan or delete files via the dashboard / delete API.

Still stuck? Contact support and we'll help you debug.