REST API

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

Introducción

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

El flujo de subida tiene tres pasos:

  1. Inicio — indícanos que quieres subir un archivo. Te devolvemos una o más URLs prefirmadas que apuntan a Cloudflare R2.
  2. Subir a R2PUT tus bytes directamente a la(s) URL(s) prefirmada(s). Los bytes no pasan por nuestros servidores.
  3. Confirmar — indícanos que la subida terminó. Creamos un registro File y te entregamos una URL para compartir.

URL base

https://storage.to/api

Todos los endpoints a continuación 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 principal.

La autenticación es opcional y desbloquea:

  • Subidas vinculadas a tu cuenta (visibles en /dashboard)
  • Funciones premium (archivos permanentes, mayor almacenamiento)
  • Mutaciones basadas en propiedad (eliminar, establecer contraseña, cambiar expiración) sin necesidad de que el token de visitante coincida

Usamos tokens bearer Laravel Sanctum. Genera un token mediante el paso OAuth en escritorio o el inicio de sesión web, luego envíalo como:

Authorization: Bearer <token>

Token de visitante

Los clientes anónimos necesitan una forma de probar la propiedad de sus subidas sin una cuenta. Usamos un token de visitante — una cadena aleatoria que el cliente genera una vez y reutiliza. Envíalo con cada solicitud:

X-Visitor-Token: <random-string>

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

Para endpoints de mutación (eliminar, establecer contraseña, cambiar expiración), la propiedad se confirma si o el token de visitante coincide o la solicitud proviene de la misma IP que creó el archivo.

Errores

Los errores siguen un formato consistente:

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

Códigos de estado HTTP comunes:

CódigoSignificado
200OK.
201Creado.
400Solicitud incorrecta (p. ej. límite de tamaño de colección excedido).
401Contraseña requerida o incorrecta.
403No autorizado (no es el propietario del recurso).
404Recurso no encontrado o expirado.
422Validación fallida o restricción de plan/cupo.
429Límite de tasa o cuota de subida alcanzada.
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 / aborto de subida60 / minuto
Finalización multipartes500 / minuto
URLs de partes multipartes120 / minuto
Inicio / confirmación por lotes500 / minuto
Consultas de estado (archivo y colección)120 / minuto
Configuración (contraseña, expiración, descargas máximas)30 / minuto
Verificación de contraseña10 / minuto
Crear colección30 / minuto
Gestionar (listo, eliminar)60 / minuto
Subida de miniatura120 / minuto
Subida ShareX20 / día
Analíticas / errores de la app120 y 60 / minuto

Cuota de subida: Los clientes anónimos tienen dos límites en paralelo — 100 GB / 24 h por token de visitante e 500 GB / 24 h por IP (el límite de IP captura tráfico sin token y redes compartidas). Cuando se excede cualquiera, recibirás un 429 con detalles. Esto es solo una cuota subir — las descargas son ilimitadas y sin restricciones (servidas directamente desde URLs firmadas de R2).

Subir

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

POST /upload/init 60/min

Inicia una subida. Para archivos >50 MB la respuesta incluye part_urls para una subida multiparte; de lo contrario, una sola url.

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, "url": "https://r2.cloudflarestorage.com/...signed...", "r2_key": "uuid-abc123", "upload_id": null, "is_multipart": false }
Response · multipart
{ "success": true, "upload_id": "01HXYZ...", "r2_key": "uuid-abc123", "is_multipart": true, "part_size": 52428800, "part_urls": [ { "partNumber": 1, "url": "https://..." }, { "partNumber": 2, "url": "https://..." } ] }
POST /upload/parts 120/min

Solicita URLs adicionales para partes de una subida multiparte en curso. Se usa cuando /init devolvió menos URLs que partes tienes (o expiraron).

Cuerpo de la solicitud

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

Finaliza una subida multiparte 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 60/min

Cancela una subida multiparte y elimina cualquier dato parcial en R2.

Cuerpo de la solicitud

CampoTipoDescripción
upload_idstring · requiredLa subida a 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 subida está completa. Aquí creamos el registro File y devolvemos la URL para compartir.

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 · optionalChecksum CRC32 para verificación de integridad.
file_idstring(9) · optionalCompleta un ID de archivo reservado 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" } }
POST /file/reserve 60/min

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

Cuerpo de la solicitud

CampoTipoDescripción
filenamestring · optionalNombre de archivo de marcador. Por defecto "Pending".
content_typestring · optionalTipo MIME de marcador.
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" } }
POST /upload/init-batch 500/min

Equivalente por lotes de /upload/init, optimizado para el cargador web. Inicia hasta 250 archivos en un solo viaje de ida y vuelta.

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 por lotes de /upload/confirm. Confirma muchos archivos en un solo viaje de ida y vuelta.

Colecciones

Una colección agrupa múltiples archivos bajo una única URL para compartir (/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 · optionalSugerencia para marcar automáticamente la colección como lista una vez que todos los archivos esperados hayan sido confirmados.
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" } }
GET /collection/{id}/status 120/min

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

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 es necesario — las colecciones se marcan automáticamente como listas una vez que 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 entre 4 y 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

Elimina la contraseña de una colección.

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

Verifica una contraseña. Devuelve 200 si es correcta, 401 si es incorrecta.

Cuerpo de la solicitud

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

Cambiar la expiración de una colección.

Cuerpo de la solicitud

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

Establece un límite de descargas (elimina después de N descargas). La colección se elimina automáticamente al alcanzarlo.

Cuerpo de la solicitud

CampoTipoDescripción
max_downloadsinteger · optionalDe 1 a 1000. Debe superar el conteo actual de descargas. null para eliminar el límite.

Archivos

Todas las configuraciones a nivel de archivo (contraseña, expiración, descargas máximas) reflejan los endpoints de la colección. Solo para el propietario.

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

Verifica si un archivo aún está pendiente de subir.

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 en miniatura para un video o archivo de imagen (usada en la página de descarga). Máximo 2 MB.

Cuerpo de la solicitud

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

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

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

Eliminar la contraseña de un archivo.

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

Verificar la contraseña de un archivo.

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

Cambiar la expiración de un archivo.

Cuerpo de la solicitud

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

Limitar el total de descargas de un archivo. Se elimina automáticamente al alcanzarlo.

Subida ShareX

Punto de subida única — envía un archivo multipart, recibe una URL para compartir. Sin pasos de inicio/confirmación. Ideal para herramientas de captura 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áximo 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 (p. ej., la app de escritorio) que tengan un token 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.

Misceláneo

GET /health

Chequeo de estado. Hace ping a la base de datos, almacenamiento R2 y caché Redis. Devuelve 200 si todo está bien, 503 de lo contrario.

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

Transmisión en vivo de actividad para el globo de la página principal. Cacheado en el edge de Cloudflare.

GET /bandwidth/status 60/min

Uso actual de cuota de subida para el solicitante — usado por la CLI y la app de escritorio para mostrar capacidad restante. La forma de la respuesta varía para usuarios autenticados. A pesar del nombre de la URL, esto solo rastrea bytes subir; 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, p. ej. upload_complete.
contextobject · optionalMetadatos adicionales.
POST /app-errors 60/min

Envía un informe de error desde la CLI o la app de escritorio. Deducción de duplicados 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 pila.
version, os, os_version, arch, contextvarious · optionalMetadatos de diagnóstico.