REST API

Sube archivos, crea colecciones y gestiona compartidos mediante HTTP. Todas las respuestas son JSON; no se requiere una clave de API para subidas anónimas.

Introducción

La API de storage.to impulsa nuestro CLI, app de escritorio, cargador web y cualquier cliente de terceros que quieras crear.

El flujo de subida tiene tres pasos:

  1. Iniciar — dinos que quieres subir un archivo. Te devolvemos una o más URL prefirmadas que apuntan a Cloudflare R2.
  2. Subir a R2PUT tus bytes directamente a las URL(s) prefirmadas. Los bytes no pasan por nuestros servidores.
  3. Confirmar — avísanos cuando la subida haya terminado. Creamos un registro de File y te damos una URL compartible.

URL base

https://storage.to/api

Todos los endpoints de abajo son relativos a esta base. Ejemplo: POST /upload/init significa POST https://storage.to/api/upload/init.

Autenticación

La mayoría de los endpoints no requieren autenticación. Las subidas anónimas son una función clave.

La autenticación es opcional y desbloquea:

  • Subidas vinculadas a tu cuenta (visibles en /dashboard)
  • Funciones premium (archivos permanentes, más almacenamiento)
  • Mutaciones basadas en la propiedad (eliminar, establecer contraseña, cambiar caducidad) sin necesidad de que coincida el visitor-token

Usamos tokens bearer de Laravel Sanctum. Emite un token mediante el traspaso OAuth del escritorio o el inicio de sesión web, y luego envíalo como:

Authorization: Bearer <token>

Token de visitante

Los clientes anónimos necesitan una forma de demostrar la propiedad de sus propias subidas sin tener una cuenta. Usamos un visitor token: una cadena aleatoria que el cliente genera una vez y reutiliza. Envíala con cada solicitud:

X-Visitor-Token: <random-string>

En la web, el token se guarda automáticamente en la cookie visitor_token. La CLI lo guarda en ~/.config/storageto/token (ver Documentación de la CLI).

Para los endpoints de mutación (borrar, establecer contraseña, cambiar la caducidad), la propiedad se confirma si o el token del visitante coincide o la solicitud proviene de la misma IP que creó el archivo. Ambas cosas pueden perderse (cookies borradas, cambios de red). El token del propietario es la prueba preferida a partir de ahora.

Token de propietario

Cada endpoint que crea recursos (/upload/init multipart, /upload/confirm, /upload/reserve, /collection) devuelve un owner_token en su respuesta. El token es una prueba firmada de propiedad vinculada a ese recurso específico, independiente de tu IP o del token del visitante.

Guarda el token junto con el ID del recurso y envíalo en cualquier mutación como:

Authorization: Owner <token>

O, si ya estás usando Authorization: Bearer para una sesión autenticada, envíalo como:

X-Owner-Token: <token>

El servidor acepta el token del propietario como prueba válida de propiedad junto con el respaldo heredado visitor token + IP: los clientes que tienen el token siguen funcionando después de cambiar de red o borrar cookies, y los clientes que no lo tienen siguen funcionando exactamente como antes.

Los tokens viven mientras lo haga el recurso, son seguros para persistir y no caducan de forma independiente. Perder un token significa perder el control de ese recurso (archivo/colección/subida): trátalos como contraseñas locales.

Errores

Los errores siguen una estructura consistente:

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

Códigos de estado HTTP comunes:

CódigoSignificado
200Correcto.
201Creado.
400Solicitud incorrecta (p. ej., se superó el límite de tamaño de la colección).
401Se requiere contraseña o es incorrecta.
403No autorizado (no eres el propietario del recurso).
404Recurso no encontrado o caducado.
422Falló la validación o hay una restricción de plan/cuota.
429Se alcanzó el límite de velocidad o la cuota de subida.
500Error del servidor. Revisa estado.

Límites de velocidad

Todos los límites de velocidad son por IP. Una respuesta 429 incluye los encabezados estándar Retry-After, X-RateLimit-Limit y X-RateLimit-Remaining.

ÁmbitoLímite
Inicio / confirmación / cancelación de subida60 / minuto
Finalización multipart500 / minuto
URLs de las partes multipart120 / minuto
Inicio / confirmación en lote500 / minuto
Consultas de estado (archivo y colección)120 / minuto
Ajustes (contraseña, caducidad, descargas máximas)30 / minuto
Verificación de contraseña10 / minuto
Creación de colección30 / minuto
Gestionar (listo, borrar)60 / minuto
Subida de miniatura120 / minuto
Subida con ShareX20 / día
Analítica de la app / errores120 y 60 / minuto

Cuota de subida: los clientes anónimos tienen dos límites en paralelo: 100 GB / 24 h por visitor token e 500 GB / 24 h por IP (el límite de IP detecta el tráfico sin token y redes compartidas). Cuando se supera cualquiera, recibirás un 429 con detalles. Esto es solo una subida cuota: las descargas son ilimitadas y sin limitación (se sirven directamente desde URL(s) firmadas de R2).

Subir

El flujo de subida en tres pasos para cualquier archivo, incluidos los de más de 5 GB (multipart automáticamente). Si solo necesitas una subida rápida tipo captura de pantalla, mira ShareX.

POST /upload/init 60/min

Inicia una subida. Para archivos >50 MB, la respuesta es una subida multipart (campo type: "multipart"); de lo contrario, un único PUT prefirmado.

Cuerpo de la solicitud

CampoTipoDescripción
filenamestring · requiredNombre de archivo original. Máx. 255 caracteres.
content_typestring · requiredTipo MIME.
sizeinteger · requiredTamaño del archivo en bytes. Mín. 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

Solicita URLs adicionales de partes para una carga multipart en progreso. Se usa cuando /init devolvió menos URLs de las que tienes partes (o cuando expiraron).

Cuerpo de la solicitud

CampoTipoDescripción
upload_idstring · requiredEl upload_id de /init.
part_numbersarray<int> · requiredNúmeros de partes para los que obtener URLs.
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

Finaliza una carga multipart en R2 una vez que todas las partes estén subidas.

Cuerpo de la solicitud

CampoTipoDescripción
upload_idstring · requiredEl upload_id de /init.
partsarray · requiredCada entrada: { partNumber, etag } de la respuesta de R2.
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

Cancela una carga multipart y limpia cualquier dato parcial en R2.

Cuerpo de la solicitud

CampoTipoDescripción
upload_idstring · requiredLa carga que se debe cancelar.
Request
curl -X POST https://storage.to/api/upload/abort \ -H "Content-Type: application/json" \ -d '{ "upload_id": "01HXYZ..." }'
POST /upload/confirm 60/min

Confirma que la carga está completa. Es cuando creamos el registro File y devolvemos la URL compartible.

Cuerpo de la solicitud

CampoTipoDescripción
filenamestring · requiredNombre de archivo original.
sizeinteger · requiredTamaño del archivo en bytes.
content_typestring · requiredTipo MIME.
r2_keystring · requiredEl r2_key de /init.
collection_idstring · optionalAdjuntar a una colección.
crc32integer · optionalSuma de verificación CRC32 para la verificación de integridad.
file_idstring(9) · optionalCompleta un ID de archivo previamente reservado.
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

Reserva un ID de archivo y una URL compartible antes de que los bytes estén listos. Útil cuando necesitas entregar un enlace primero y completar la carga después. La propiedad está vinculada a tu token de visitante + IP. Termina la carga más tarde con /upload/init + /upload/confirm, pasando file_id para confirmar.

Cuerpo de la solicitud

CampoTipoDescripción
filenamestring · optionalNombre de archivo de ejemplo. Por defecto es "Pending".
content_typestring · optionalTipo MIME de ejemplo.
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

Equivalente en lote de /upload/init, optimizado para el cargador web. Inicia hasta 250 archivos en un solo viaje.

Usado internamente por el cargador web. La mayoría de los clientes deberían preferir /upload/init para un solo archivo.

POST /upload/confirm-batch 500/min

Equivalente en lote de /upload/confirm. Confirma muchos archivos en un solo viaje.

Colecciones

Una colección agrupa varios archivos bajo una única URL de compartición (/c/{id}). Hasta 10.000 archivos y 25 GB en total.

POST /collection 30/min

Crea una nueva colección. Adjunta archivos después pasando collection_id en /upload/confirm.

Cuerpo de la solicitud

CampoTipoDescripción
expected_file_countinteger · optionalPista para marcar automáticamente la colección como lista una vez que todos los archivos esperados hayan confirmado.
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

Consulta el estado de una colección. También la marca automáticamente como lista si todos los archivos esperados han confirmado.

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

Marca la colección como lista para descargar. Normalmente no hace falta: las colecciones se ponen listas automáticamente cuando se alcanza expected_file_count.

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

Elimina una colección y todos sus archivos.

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

Establece una contraseña en la colección. Requiere 4–100 caracteres.

Cuerpo de la solicitud

CampoTipoDescripción
passwordstring · required4–100 caracteres.
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

Quita la contraseña de una colección.

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

Comprueba una contraseña. Devuelve 200 si es correcta y 401 si es incorrecta.

Cuerpo de la solicitud

CampoTipoDescripción
passwordstring · required
POST /collection/{id}/expiry Owner only 30/min

Cambia la caducidad de una colección.

Cuerpo de la solicitud

CampoTipoDescripción
daysinteger · optionalEntre 1 y 7 días a partir de ahora. Omite o null para que sea permanente (solo premium).
POST /collection/{id}/max-downloads Owner only 30/min

Establece un límite de descargas (burn-after-N-downloads). La colección se elimina automáticamente cuando se alcanza.

Cuerpo de la solicitud

CampoTipoDescripción
max_downloadsinteger · optional1–1000. Debe superar el número actual de descargas. null para quitar el límite.

Archivos

Todas las configuraciones a nivel de archivo (contraseña, caducidad, max-downloads) reflejan los endpoints de la colección. Solo el propietario.

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

Comprueba si un archivo aún está pendiente de su carga.

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

Elimina un archivo inmediatamente.

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

Sube una imagen de miniatura para un archivo de video o imagen (se usa en la página de descarga). Máx. 2 MB.

Cuerpo de la solicitud

CampoTipoDescripción
thumbnailimage · requiredCarga multipart. Máx. 2 MB.
Response
{ "success": true, "thumbnail_url": "https://..." }
POST /file/{id}/password Owner only 30/min

Establece una contraseña en un archivo. Requiere 4–100 caracteres.

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

Quita la contraseña de un archivo.

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

Verifica la contraseña de un archivo.

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

Cambia la caducidad de un archivo.

Cuerpo de la solicitud

CampoTipoDescripción
daysinteger · optionalEntre 1 y 7 días a partir de ahora. Omite o null para que sea permanente (solo premium).
POST /file/{id}/max-downloads Owner only 30/min

Limita el número total de descargas de un archivo. Se elimina automáticamente cuando se alcanza el límite.

Subida con ShareX

Endpoint de carga de una sola vez: envía un archivo multipart y recibe una URL compartible. Sin el baile de init/confirm. Ideal para herramientas de capturas de pantalla. Guía completa de configuración en /es/docs/sharex.

POST /sharex/upload 20/day

Sube una imagen o archivo directamente (formulario multipart, campo file). Máx. 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" }

Autenticación de escritorio

Para clientes autenticados (por ejemplo, la app de escritorio) que tengan un token de Sanctum.

GET /user Bearer token

Devuelve el usuario autenticado.

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

Revoca el token de acceso actual.

Varios

GET /health

Comprobación de estado. Envía un ping a la base de datos, al almacenamiento R2 y a la caché de Redis. Devuelve 200 si todo está en verde; 503 en caso contrario.

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

Flujo de actividad en vivo para el globo de la portada. Se guarda en caché en el borde de Cloudflare.

GET /bandwidth/status 60/min

Uso actual de la cuota de carga para quien llama: lo usan la CLI y la app de escritorio para mostrar la capacidad restante. La estructura de la respuesta cambia para usuarios autenticados. A pesar del nombre de la URL, esto solo registra subida bytes; las descargas no se cuentan.

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

Envía un evento de uso desde la CLI o la app de escritorio.

Cuerpo de la solicitud

CampoTipoDescripción
appstring · requireddesktop, cli o web.
versionstring · optionalVersión del cliente.
eventstring · requiredNombre del evento, por ejemplo upload_complete.
contextobject · optionalMetadatos adicionales.
POST /app-errors 60/min

Envía un informe de error desde la CLI o la app de escritorio. Dedupe en el servidor: máximo 10 del mismo error por hora.

Cuerpo de la solicitud

CampoTipoDescripción
appstring · requireddesktop, cli o web.
typestring · requiredClase/tipo de error.
messagestring · requiredMensaje de error.
stackstring · optionalTraza de la pila.
version, os, os_version, arch, contextvarious · optionalMetadatos de diagnóstico.