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 - PUT 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

Anonyme Uploads funktionieren ohne Authentifizierung, aber mit engen Limits. Aufrufe ohne Schlüssel sind auf 50 Dateien pro rollierende 24 Stunden pro Gerät oder IP begrenzt, plus die Bandbreitenkontingente unten, und Dateien laufen nach 3 Tagen ab.

Die Authentifizierung mit einem kostenlosen Konto schaltet frei:

  • Kein tägliches Datei-Limit
  • Keine Upload-Bandbreitenbegrenzung
  • 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

Limits auf einen Blick

LimitOhne Token (anonym)Token eines Gratis-KontosToken eines Bezahl-Kontos
Dateien pro Tag50 / 24 StundenUnbegrenztUnbegrenzt
Upload-Bandbreite100 GB / 24 Stunden
(500 GB pro IP)
UnbegrenztUnbegrenzt
Maximale Dateigröße25 GB25 GB100 GB
Ablauf der DateiStandardmäßig 3 Tage, bis zu 7 über expiry_daysStandardmäßig 3 Tage, bis zu 7 über expiry_daysNie (Dateien bleiben dauerhaft)
SpeicherplatzKeiner (Dateien laufen ab)Keiner (Dateien laufen ab)100 GB - 1 TB dauerhaft, je nach Tarif

Es gibt keine Monatskontingente - alle Limits sind rollierende 24-Stunden-Fenster oder Limits pro Minute. Der Gratis-Tarif hat keinen festen Speicherplatz: Jede Datei läuft von selbst ab, es sammelt sich also nichts gegen ein Kontingent an.

Um dich zu authentifizieren, erstelle in deinem Konto ein persönliches API-Token und sende es bei jeder Anfrage als Bearer-Token:

Authorization: Bearer <token>

API-Token erstellen

Du musst angemeldet sein. Das vollständige Token wird nur einmal bei der Erstellung angezeigt — kopiere es an einen sicheren Ort. Du kannst ein Token jederzeit auf derselben Seite widerrufen.

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 danach wiederverwendet. Mit jeder Anfrage senden:

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, /file/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 – zusätzlich zum bisherigen visitor-token + IP-Fallback. 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 (Datei/Sammlung/Upload) zu verlieren – 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-Limit – Downloads sind unbegrenzt und ohne Drosselung (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/init60/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/partsOwner only120/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-multipartOwner only500/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/abortOwner only60/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/confirm60/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", "filename": "report.pdf", "size": 2202009, "human_size": "2.1 MB", "expires_at": "2026-04-15T12:00:00Z" }, "owner_token": "owner_v1_..." }
POST/file/reserve60/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", "expires_at": "2026-04-12T18:00:00Z" }, "owner_token": "owner_v1_..." }
POST/upload/init-batch500/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-batch500/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/collection30/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}/status120/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}/readyOwner only60/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 only60/min

Lösche eine Sammlung und alle ihre Dateien.

POST/collection/{id}/passwordOwner only30/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}/passwordOwner only30/min

Entferne das Passwort aus der Sammlung.

POST/collection/{id}/verify-password10/min

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

Request-Body

FeldTypBeschreibung
passwordstring · required
POST/collection/{id}/expiryOwner only30/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-downloadsOwner only30/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}/status120/min

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

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

Lösche eine Datei sofort.

POST/file/{id}/thumbnailOwner only120/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}/passwordOwner only30/min

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

DELETE/file/{id}/passwordOwner only30/min

Entferne das Passwort einer Datei.

POST/file/{id}/verify-password10/min

ĂśberprĂĽfe das Passwort einer Datei.

POST/file/{id}/expiryOwner only30/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-downloadsOwner only30/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 teilbare URL zurück. Kein Init/Confirm-Tanz. Ideal für Screenshot-Tools. Vollständige Anleitung unter /docs/sharex.

POST/sharex/upload20/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 "file=@screenshot.png"
Response
{ "success": true, "url": "https://storage.to/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/userBearer token

Gibt den authentifizierten Benutzer zurĂĽck.

Request
curl https://storage.to/api/user \ -H "Authorization: Bearer <token>"
Response
{ "id": 42, "name": "Ada", "email": "ada@example.com", "is_premium": true }
POST/auth/logoutBearer token

Widerruft das aktuelle Zugriffstoken.

Verschiedenes

GET/health

Liveness-Check. Gibt 200 mit { "status": "ok" } zurück, wenn der API-Worker läuft.

Response
{ "status": "ok" }
GET/activity

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

GET/bandwidth/status60/min

Aktuelle Upload-Kontingentnutzung für den Aufrufer – genutzt von der CLI und der Desktop-App, 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-analytics120/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-errors60/min

Sende einen Fehlerbericht aus der CLI oder der Desktop-App. Serverseitig dedupliziert – maximal 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.