Integra las subidas en dos llamadas

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.

El flujo en dos pasos

La API key nunca llega al navegador.

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).

2. El navegador hace el PUT

El archivo viaja directo del navegador al bucket usando uploadUrl. Tu servidor nunca recibe los bytes del archivo.

3. Guardas publicUrl

Es la URL definitiva del archivo en tu dominio de CDN. Guárdala en tu base de datos y úsala donde la necesites.

Referencia de la API

Base: https://uploads.wavesoft.cloud con la cabecera x-api-key

POST/presign

Devuelve una URL firmada de subida y la URL pública definitiva del archivo. La URL firmada expira en expiresAt.

CampoDescripción
filenameNombre del archivo, con extensión.
contentTypeTipo MIME declarado. Debe coincidir con el del PUT.
sizeBytesTamaño en bytes. Se valida contra tu plan.
locationCarpeta lógica dentro del bucket, por ejemplo avatars.
POST/confirm

Opcional. Marca la subida como completada para que las métricas de tu panel reflejen solo archivos que llegaron. No afecta la disponibilidad del archivo.

CampoDescripción
eventIdEl identificador devuelto por /presign.

Códigos de error

CódigoSignificado
400Cuerpo inválido: falta un campo o el JSON está mal formado.
401API key ausente o inválida en la cabecera x-api-key.
403Dominio de origen no permitido para este proyecto (CORS).
409Cuota de almacenamiento del plan superada: la subida se rechaza.
413El archivo supera el tamaño máximo por archivo de tu plan.
429Límite de subidas alcanzado. Revisa Retry-After antes de reintentar.

El prompt para tu IA

Cópialo y pégaselo a Cursor, Claude o ChatGPT.

prompt
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.

Ejemplos por tecnología

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