1. Tu backend pide la URL firmada
POST /presign con el nombre, tipo y tamaño del archivo. La respuesta incluye uploadUrl (temporal) y publicUrl (definitiva).
Tu servidor pide una URL firmada y el navegador sube directo al bucket. En tu panel esta misma guía aparece con tu clave y tus límites.
La API key nunca llega al navegador.
POST /presign con el nombre, tipo y tamaño del archivo. La respuesta incluye uploadUrl (temporal) y publicUrl (definitiva).
El archivo viaja directo del navegador al bucket usando uploadUrl. Tu servidor nunca recibe los bytes del archivo.
Es la URL definitiva del archivo en tu dominio de CDN. Guárdala en tu base de datos y úsala donde la necesites.
Base: https://uploads.wavesoft.cloud con la cabecera x-api-key
/presignDevuelve una URL firmada de subida y la URL pública definitiva del archivo. La URL firmada expira en expiresAt.
| Campo | Descripción |
|---|---|
filename | Nombre del archivo, con extensión. |
contentType | Tipo MIME declarado. Debe coincidir con el del PUT. |
sizeBytes | Tamaño en bytes. Se valida contra tu plan. |
location | Carpeta lógica dentro del bucket, por ejemplo avatars. |
/confirmOpcional. Marca la subida como completada para que las métricas de tu panel reflejen solo archivos que llegaron. No afecta la disponibilidad del archivo.
| Campo | Descripción |
|---|---|
eventId | El identificador devuelto por /presign. |
| Código | Significado |
|---|---|
400 | Cuerpo inválido: falta un campo o el JSON está mal formado. |
401 | API key ausente o inválida en la cabecera x-api-key. |
403 | Dominio de origen no permitido para este proyecto (CORS). |
409 | Cuota de almacenamiento del plan superada: la subida se rechaza. |
413 | El archivo supera el tamaño máximo por archivo de tu plan. |
429 | Límite de subidas alcanzado. Revisa Retry-After antes de reintentar. |
Cópialo y pégaselo a Cursor, Claude o ChatGPT.
Integra subida de archivos con Upload Files (NextWave Software) en este proyecto.
API base: https://uploads.wavesoft.cloud
Autenticación: cabecera x-api-key con la clave YOUR_API_KEY.
La clave vive solo en el servidor (variable de entorno), nunca en el navegador.
Flujo:
1. Backend: POST /presign con JSON { filename, contentType, sizeBytes, location }.
Respuesta: { eventId, uploadUrl, publicUrl, expiresAt }.
2. Navegador: PUT del archivo directo a uploadUrl,
con Content-Type igual al declarado en /presign.
3. Backend (opcional): POST /confirm con { eventId }, solo para métricas.
4. Guarda publicUrl en la base de datos; el archivo se sirve desde el CDN.
Límites de ejemplo: archivos de hasta 25 MB y 5.000 subidas al mes.
Crea:
- Un endpoint de servidor que llame a /presign leyendo la clave
de una variable de entorno (UPLOADS_API_KEY).
- Un componente o script de subida que pida la URL a ese endpoint,
haga el PUT desde el navegador y devuelva publicUrl al terminar.
- Manejo de errores: si /presign responde 4xx, muestra el mensaje al usuario.El mismo flujo en tres stacks. El de HTML incluye un Worker de Cloudflare para quien no tiene backend.
// app/api/upload-url/route.ts
export async function POST(req: Request) {
const { filename, contentType, sizeBytes } = await req.json();
const res = await fetch("https://uploads.wavesoft.cloud/presign", {
method: "POST",
headers: {
// La clave vive en el servidor, nunca uses NEXT_PUBLIC_
"x-api-key": process.env.UPLOADS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
filename,
contentType,
sizeBytes,
location: "uploads",
}),
});
if (!res.ok) {
return Response.json(
{ error: "No se pudo firmar la subida" },
{ status: res.status }
);
}
return Response.json(await res.json());
}
// components/fileUploader.tsx
"use client";
import { useState } from "react";
export function FileUploader() {
const [publicUrl, setPublicUrl] = useState<string | null>(null);
const [uploading, setUploading] = useState(false);
async function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
setUploading(true);
try {
// 1. Pide la URL firmada a tu propio endpoint
const presign = await fetch("/api/upload-url", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
sizeBytes: file.size,
}),
}).then((r) => r.json());
// 2. Sube el archivo directo al bucket
await fetch(presign.uploadUrl, {
method: "PUT",
headers: { "Content-Type": file.type },
body: file,
});
// 3. Guarda presign.publicUrl donde lo necesites
setPublicUrl(presign.publicUrl);
} finally {
setUploading(false);
}
}
return (
<div>
<input type="file" onChange={handleChange} disabled={uploading} />
{publicUrl && <a href={publicUrl}>Ver archivo</a>}
</div>
);
}Cuando tengas tu cuenta, este mismo código aparece en tu panel ya con tu clave y tus límites.
Crear mi cuenta