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

To authenticate, generate a personal API token from your account and send it as a bearer token on every request:

Authorization: Bearer <token>

Generate API token

You need to be signed in. The full token is shown only once at creation, so copy it somewhere safe. You can revoke a token at any time from the same page.

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.