REST-API

Dateien hochladen, Sammlungen erstellen und Freigaben per HTTP verwalten. Alle Antworten sind JSON; für anonyme Uploads ist kein API-Schlüssel erforderlich.

Einführung

Die storage.to-API treibt unsere CLI, Desktop-App, Web-Uploader und alle Drittanbieter-Clients, die du bauen möchtest.

Der Upload-Ablauf besteht aus drei Schritten:

  1. Initialisieren — Sag uns, dass du eine Datei hochladen möchtest. Wir geben dir eine oder mehrere vorab signierte URLs zurück, die auf Cloudflare R2 zeigen.
  2. In R2 hochladen — :Lege deine Bytes direkt in die vorab signierten URL(s). Die Bytes laufen nicht über unsere Server.
  3. Bestätigen — Sag uns, dass der Upload fertig ist. Wir erstellen einen File-Eintrag und geben dir eine teilbare URL.

Basis-URL

https://storage.to/api

Alle Endpoints unten sind relativ zu dieser Basis. Beispiel: POST /upload/init bedeutet POST https://storage.to/api/upload/init.

Authentifizierung

Die meisten Endpunkte erfordern keine Authentifizierung. Anonyme Uploads sind eine Kernfunktion.

Authentifizierung ist optional und ermöglicht:

  • Uploads, die mit deinem Konto verknüpft sind (sichtbar unter /dashboard)
  • Premium-Funktionen (dauerhafte Dateien, mehr Speicherplatz)
  • Mutationen basierend auf Eigentum (löschen, Passwort setzen, Ablauf ändern), ohne dass der visitor-token-Abgleich nötig ist

Wir verwenden Laravel Sanctum-Bearer-Tokens. Erstelle ein Token über den Desktop-OAuth-Übergang oder die Web-Anmeldung und sende es dann als:

Authorization: Bearer <token>

Besuchertoken

Anonyme Clients brauchen eine Möglichkeit, die Inhaberschaft ihrer eigenen Uploads ohne Konto nachzuweisen. Wir verwenden ein visitor-token — eine zufällige Zeichenkette, die der Client einmal erzeugt und wiederverwendet. Sende es mit jeder Anfrage:

X-Visitor-Token: <random-string>

Im Web wird das Token automatisch im visitor_token-cookie gespeichert. Die CLI speichert es unter ~/.config/storageto/token (siehe CLI-Dokumentation).

Bei Mutation-Endpunkten (löschen, Passwort setzen, Ablauf ändern) ist die Inhaberschaft bestätigt, wenn entweder das Visitor-Token übereinstimmt oder die Anfrage von derselben IP kommt, die die Datei erstellt hat. Beides kann verloren gehen (gelöschte Cookies, Netzwerkwechsel). Das Owner-Token ist der bevorzugte Nachweis für die Zukunft.

Owner-Token

Jeder Endpunkt zum Erstellen von Ressourcen (/upload/init multipart, /upload/confirm, /upload/reserve, /collection) gibt in seiner Antwort ein owner_token zurück. Das Token ist ein signierter Nachweis der Inhaberschaft, der an diese spezielle Ressource gebunden ist—unabhängig von deiner IP oder deinem Visitor-Token.

Speichere das Token zusammen mit der Ressourcen-ID und sende es bei jeder Mutation als:

Authorization: Owner <token>

Oder, wenn du bereits Authorization: Bearer für eine authentifizierte Session verwendest, sende es so:

X-Owner-Token: <token>

Der Server akzeptiert das Owner-Token als gültigen Nachweis der Inhaberschaft zusammen mit dem alten Fallback visitor-token + IP—Clients, die das Token haben, funktionieren auch nach dem Wechsel des Netzwerks oder dem Löschen von Cookies weiter, und Clients ohne Token funktionieren weiterhin genau wie zuvor.

Tokens leben so lange wie die Ressource, sind sicher zum Speichern und laufen nicht unabhängig ab. Ein verlorenes Token bedeutet, die Kontrolle über diese Ressource zu verlieren (Datei/Sammlung/Upload)—behandle sie wie lokale Passwörter.

Fehler

Fehler folgen einem einheitlichen Schema:

{
  "success": false,
  "error": "Human-readable message"
}

Häufige HTTP-Statuscodes:

CodeBedeutung
200OK.
201Erstellt.
400Ungültige Anfrage (z. B. Größenlimit der Sammlung überschritten).
401Passwort erforderlich oder falsch.
403Nicht autorisiert (nicht der Besitzer der Ressource).
404Ressource nicht gefunden oder abgelaufen.
422Validierung fehlgeschlagen oder Einschränkung durch Tarif/Kontingent.
429Ratelimit oder Upload-Kontingent erreicht.
500Serverfehler. Prüfe Status.

Ratenbegrenzungen

Alle Ratelimits gelten pro IP. Eine 429-Antwort enthält die Standard-Header Retry-After, X-RateLimit-Limit und X-RateLimit-Remaining.

GeltungsbereichLimit
Upload initialisieren / bestätigen / abbrechen60 / Minute
Multipart-Abschluss500 / Minute
Multipart-Teil-URLs120 / Minute
Batch-Initialisierung / -Bestätigung500 / Minute
Statusabfragen (Datei & Sammlung)120 / Minute
Einstellungen (Passwort, Ablauf, max. Downloads)30 / Minute
Passwortüberprüfung10 / Minute
Sammlung erstellen30 / Minute
Verwalten (bereit, löschen)60 / Minute
Thumbnail-Upload120 / Minute
ShareX-Upload20 / Tag
App-Analytics / Fehler120 und 60 / Minute

Upload-Kontingent: Anonyme Clients haben zwei parallele Limits — 100 GB / 24 h pro visitor-token und 500 GB / 24 h pro IP (das IP-Limit fängt tokenlosen Traffic und geteilte Netzwerke ab). Wenn eines davon überschritten wird, bekommst du einen 429 mit Details. Das ist nur ein Upload Kontingent — Downloads sind unbegrenzt und ungedrosselt (werden direkt über R2-signierte URLs bereitgestellt).

Upload

Der Drei-Schritte-Upload-Flow für jede Datei—inklusive Dateien über 5 GB (automatisch multipart). Wenn du nur einen schnellen Upload im Screenshot-Stil brauchst, schau stattdessen bei ShareX vorbei.

POST /upload/init 60/min

Starte einen Upload. Bei Dateien >50 MB ist die Antwort ein Multipart-Upload (Feld type: "multipart"); ansonsten ein einzelnes presigned PUT.

Request-Body

FeldTypBeschreibung
filenamestring · requiredUrsprünglicher Dateiname. Max. 255 Zeichen.
content_typestring · requiredMIME-Typ.
sizeinteger · requiredDateigröße in Bytes. Min. 1.
Request
curl -X POST https://storage.to/api/upload/init \ -H "Content-Type: application/json" \ -H "X-Visitor-Token: abc123" \ -d '{ "filename": "report.pdf", "content_type": "application/pdf", "size": 2202009 }'
Response · single upload
{ "success": true, "type": "single", "upload_url": "https://r2.cloudflarestorage.com/...signed...", "headers": { "Host": ["..."] }, "r2_key": "uuid-abc123" }
Response · multipart
{ "success": true, "type": "multipart", "upload_id": "01HXYZ...", "r2_key": "uuid-abc123", "part_size": 33554432, "total_parts": 4, "initial_urls": { "1": "https://...", "2": "https://..." }, "owner_token": "owner_v1_..." }
POST /upload/parts Owner only 120/min

Fordert zusätzliche Part-URLs für einen laufenden Multipart-Upload an. Wird verwendet, wenn /init weniger URLs zurückgegeben hat als du Parts hast (oder sie abgelaufen sind).

Request-Body

FeldTypBeschreibung
upload_idstring · requiredDie upload_id von /init.
part_numbersarray<int> · requiredPart-Nummern, für die URLs abgerufen werden sollen.
Request
curl -X POST https://storage.to/api/upload/parts \ -H "Content-Type: application/json" \ -d '{ "upload_id": "01HXYZ...", "part_numbers": [3, 4] }'
Response
{ "success": true, "part_urls": [ { "partNumber": 3, "url": "https://..." }, { "partNumber": 4, "url": "https://..." } ] }
POST /upload/complete-multipart Owner only 500/min

Schließt einen Multipart-Upload auf R2 ab, sobald alle Parts hochgeladen sind.

Request-Body

FeldTypBeschreibung
upload_idstring · requiredDie upload_id von /init.
partsarray · requiredJeder Eintrag: { partNumber, etag } aus der R2-Antwort.
Request
curl -X POST https://storage.to/api/upload/complete-multipart \ -H "Content-Type: application/json" \ -d '{ "upload_id": "01HXYZ...", "parts": [ { "partNumber": 1, "etag": "\"abc...\"" }, { "partNumber": 2, "etag": "\"def...\"" } ] }'
Response
{ "success": true }
POST /upload/abort Owner only 60/min

Bricht einen Multipart-Upload ab und räumt alle unvollständigen Daten auf R2 auf.

Request-Body

FeldTypBeschreibung
upload_idstring · requiredDer Upload, der abgebrochen werden soll.
Request
curl -X POST https://storage.to/api/upload/abort \ -H "Content-Type: application/json" \ -d '{ "upload_id": "01HXYZ..." }'
POST /upload/confirm 60/min

Bestätige, dass der Upload abgeschlossen ist. In diesem Schritt erstellen wir den File-Eintrag und geben die freigabefähige URL zurück.

Request-Body

FeldTypBeschreibung
filenamestring · requiredUrsprünglicher Dateiname.
sizeinteger · requiredDateigröße in Bytes.
content_typestring · requiredMIME-Typ.
r2_keystring · requiredDie r2_key von /init.
collection_idstring · optionalAn eine Sammlung anhängen.
crc32integer · optionalCRC32-Checksumme zur Integritätsprüfung.
file_idstring(9) · optionalErfülle eine zuvor reserviert-Datei-ID.
Request
curl -X POST https://storage.to/api/upload/confirm \ -H "Content-Type: application/json" \ -H "X-Visitor-Token: abc123" \ -d '{ "filename": "report.pdf", "size": 2202009, "content_type": "application/pdf", "r2_key": "uuid-abc123" }'
Response
{ "success": true, "file": { "id": "FQxyz1234", "url": "https://storage.to/FQxyz1234", "raw_url": "https://storage.to/r/FQxyz1234", "filename": "report.pdf", "size": 2202009, "human_size": "2.1 MB", "expires_at": "2026-04-15T12:00:00Z" }, "owner_token": "owner_v1_..." }
POST /file/reserve 60/min

Reserviere eine Date-ID und eine freigabefähige URL vor die Bytes bereit sind. Praktisch, wenn du zuerst einen Link weitergeben und den Upload danach erfüllen musst. Die Berechtigung ist an dein Visitor-Token + deine IP gebunden. Schließe den Upload später mit /upload/init + /upload/confirm ab und übergib dabei file_id zur Bestätigung.

Request-Body

FeldTypBeschreibung
filenamestring · optionalPlatzhalter-Dateiname. Standard: "Pending".
content_typestring · optionalPlatzhalter-MIME-Typ.
Request
curl -X POST https://storage.to/api/file/reserve \ -H "X-Visitor-Token: abc123"
Response
{ "success": true, "file": { "id": "FQxyz1234", "url": "https://storage.to/FQxyz1234", "raw_url": "https://storage.to/r/FQxyz1234", "expires_at": "2026-04-12T18:00:00Z" }, "owner_token": "owner_v1_..." }
POST /upload/init-batch 500/min

Batch-Äquivalent von /upload/init, optimiert für den Web-Uploader. Startet bis zu 250 Dateien in einem einzigen Round-Trip.

Wird intern vom Web-Uploader verwendet. Die meisten Clients sollten lieber single-file /upload/init verwenden.

POST /upload/confirm-batch 500/min

Batch-Äquivalent von /upload/confirm. Bestätigt viele Dateien in einem einzigen Round-Trip.

Sammlungen

Eine Sammlung gruppiert mehrere Dateien unter einer einzigen Freigabe-URL (/c/{id}). Bis zu 10.000 Dateien und insgesamt 25 GB.

POST /collection 30/min

Erstelle eine neue Sammlung. Hänge Dateien danach an, indem du collection_id an /upload/confirm übergibst.

Request-Body

FeldTypBeschreibung
expected_file_countinteger · optionalHinweis für die automatische Markierung der Sammlung als bereit, sobald alle erwarteten Dateien bestätigt wurden.
Request
curl -X POST https://storage.to/api/collection \ -H "Content-Type: application/json" \ -H "X-Visitor-Token: abc123" \ -d '{ "expected_file_count": 3 }'
Response
{ "success": true, "collection": { "id": "ABC123xyz", "url": "https://storage.to/c/ABC123xyz", "expires_at": "2026-04-15T12:00:00Z" }, "owner_token": "owner_v1_..." }
GET /collection/{id}/status 120/min

Frage den Status einer Sammlung ab. Markiert die Sammlung außerdem automatisch als bereit, wenn alle erwarteten Dateien bestätigt wurden.

Request
curl https://storage.to/api/collection/ABC123xyz/status
Response
{ "success": true, "files": [ /* file objects: id, url, filename, size, ... */ ], "is_uploading": false, "file_count": 3, "expected_file_count": 3, "total_size": 6291456, "human_total_size": "6 MB" }
POST /collection/{id}/ready Owner only 60/min

Markiere die Sammlung als bereit zum Download. Normalerweise nicht nötig — Sammlungen werden automatisch bereit, sobald expected_file_count erreicht ist.

DELETE /collection/{id} Owner only 60/min

Lösche eine Sammlung und alle ihre Dateien.

POST /collection/{id}/password Owner only 30/min

Setze ein Passwort für die Sammlung. Erfordert 4–100 Zeichen.

Request-Body

FeldTypBeschreibung
passwordstring · required4–100 Zeichen.
Request
curl -X POST https://storage.to/api/collection/ABC123xyz/password \ -H "X-Visitor-Token: abc123" \ -d '{ "password": "hunter22" }'
DELETE /collection/{id}/password Owner only 30/min

Entferne das Passwort aus der Sammlung.

POST /collection/{id}/verify-password 10/min

Prüfe ein Passwort. Gibt 200 bei Erfolg zurück, 401 bei falschem Passwort.

Request-Body

FeldTypBeschreibung
passwordstring · required
POST /collection/{id}/expiry Owner only 30/min

Ändere die Ablaufzeit einer Sammlung.

Request-Body

FeldTypBeschreibung
daysinteger · optionalIn 1–7 Tagen. Weglassen oder null für dauerhaft (nur Premium).
POST /collection/{id}/max-downloads Owner only 30/min

Setze ein Download-Limit (burn-after-N-downloads). Die Sammlung wird automatisch gelöscht, sobald es erreicht ist.

Request-Body

FeldTypBeschreibung
max_downloadsinteger · optional1–1000. Muss den aktuellen Download-Zähler übersteigen. null, um das Limit zu entfernen.

Dateien

Alle Einstellungen auf Dateiebene (Passwort, Ablauf, max-downloads) spiegeln die Collection-Endpunkte wider. Nur für den Owner.

GET /file/{id}/status 120/min

Prüfe, ob eine Datei noch auf ihren Upload wartet.

Response
{ "pending": false }
DELETE /file/{id} Owner only 60/min

Lösche eine Datei sofort.

POST /file/{id}/thumbnail Owner only 120/min

Lade ein Vorschaubild für eine Video- oder Bilddatei hoch (wird auf der Download-Seite verwendet). Max. 2 MB.

Request-Body

FeldTypBeschreibung
thumbnailimage · requiredMultipart-Upload. Max. 2 MB.
Response
{ "success": true, "thumbnail_url": "https://..." }
POST /file/{id}/password Owner only 30/min

Setze ein Passwort für eine Datei. Erfordert 4–100 Zeichen.

DELETE /file/{id}/password Owner only 30/min

Entferne das Passwort einer Datei.

POST /file/{id}/verify-password 10/min

Überprüfe das Passwort einer Datei.

POST /file/{id}/expiry Owner only 30/min

Ändere die Ablaufzeit einer Datei.

Request-Body

FeldTypBeschreibung
daysinteger · optionalIn 1–7 Tagen. Weglassen oder null für dauerhaft (nur Premium).
POST /file/{id}/max-downloads Owner only 30/min

Begrenze die Gesamtzahl der Downloads einer Datei. Wird automatisch gelöscht, wenn das Limit erreicht ist.

ShareX-Upload

One-shot-Upload-Endpunkt — sende eine Multipart-Datei und erhalte eine freigabefähige URL zurück. Kein init/confirm-Tanz. Ideal für Screenshot-Tools. Vollständige Anleitung unter /de/docs/sharex.

POST /sharex/upload 20/day

Lade ein Bild oder eine Datei direkt hoch (multipart-Formular, Feld file). Max. 25 MB.

Request
curl -X POST https://storage.to/api/sharex/upload \ -F "[email protected]"
Response
{ "success": true, "url": "https://storage.to/FQxyz1234", "raw_url": "https://storage.to/r/FQxyz1234", "filename": "screenshot.png", "expires_at": "2026-04-15T12:00:00Z" }

Desktop-Authentifizierung

Für authentifizierte Clients (z. B. die Desktop-App), die einen Sanctum-Token besitzen.

GET /user Bearer token

Gibt den authentifizierten Benutzer zurück.

Request
curl https://storage.to/api/user \ -H "Authorization: Bearer <token>"
Response
{ "id": 42, "name": "Ada", "email": "[email protected]", "is_premium": true }
POST /auth/logout Bearer token

Widerruft das aktuelle Zugriffstoken.

Verschiedenes

GET /health

Health-Check. Sendet Pings an die Datenbank, den R2-Speicher und den Redis-Cache. Gibt 200 zurück, wenn alles grün ist, sonst 503.

Response
{ "status": "healthy", "checks": { "database": "ok", "storage": "ok", "cache": "ok" }, "timestamp": "2026-04-12T12:00:00Z" }
GET /activity

Live-Aktivitätsstream für den Globus auf der Startseite. Im Edge-Cache von Cloudflare gespeichert.

GET /bandwidth/status 60/min

Aktuelle Upload-Kontingentnutzung für den Aufrufer — wird von der CLI und der Desktop-App genutzt, um die verbleibende Kapazität anzuzeigen. Das Antwortformat unterscheidet sich für authentifizierte Nutzer. Trotz des URL-Namens werden hier nur Upload Bytes erfasst; Downloads werden nicht mitgezählt.

Response · anonymous
{ "success": true, "authenticated": false, "has_token": true, "limit_bytes": 107374182400, "limit_gb": 100, "used_bytes": 12345678, "used_gb": 0.01, "remaining_bytes": 107361836722, "remaining_gb": 99.99, "window_hours": 24 }
Response · authenticated
{ "success": true, "authenticated": true, "plan": "premium" }
POST /app-analytics 120/min

Sende ein Nutzungsereignis über die CLI oder die Desktop-App.

Request-Body

FeldTypBeschreibung
appstring · requireddesktop, cli oder web.
versionstring · optionalClient-Version.
eventstring · requiredEreignisname, z. B. upload_complete.
contextobject · optionalZusätzliche Metadaten.
POST /app-errors 60/min

Sende einen Fehlerbericht über die CLI oder die Desktop-App. Serverseitig dedupliziert — max. 10 vom gleichen Fehler pro Stunde.

Request-Body

FeldTypBeschreibung
appstring · requireddesktop, cli oder web.
typestring · requiredFehlerklasse/-typ.
messagestring · requiredFehlermeldung.
stackstring · optionalStacktrace.
version, os, os_version, arch, contextvarious · optionalDiagnose-Metadaten.