· Nacho Coll · Tutorials · 10 min de lectura
IPFS Upload API — Tutorial completo para desarrolladores
Aprende a subir archivos a IPFS vía REST API. Ejemplos de código completos en JavaScript, Node.js y curl. Sube JSON, imágenes, usa tokens firmados para uploads del lado del cliente.

IPFS Upload API — Tutorial completo para desarrolladores
Subir archivos a IPFS debería ser tan simple como hacer una petición POST. En este tutorial harás exactamente eso — subir documentos JSON, imágenes y PDFs a IPFS con nada más que fetch() y una clave API. Al final tendrás un script completo de Node.js que sube contenido, lista archivos, recupera metadatos y genera tokens firmados para uploads seguros del lado del cliente. ¿Nuevo en IPFS? Empieza con ¿Qué es IPFS Pinning? para entender los fundamentos antes de sumergirte en el código.

Lo que construirás
- Subir un objeto JSON a IPFS y obtener un identificador de contenido (CID).
- Subir una imagen binaria desde el disco.
- Adjuntar descripciones y metadatos personalizados a los uploads.
- Consultar tu historial de uploads por rango de fechas.
- Recuperar detalles de un archivo específico.
- Crear tokens firmados con tiempo limitado para que un navegador pueda subir directamente sin exponer tu clave API.
- Manejar errores e implementar lógica de reintento.
Todo se ejecuta contra una única URL base: https://api.ipfs.ninja
1. Setup — Regístrate y obtén una clave API
- Regístrate para una cuenta gratuita (no se requiere tarjeta de crédito).
- Ve a API Keys en la barra lateral del dashboard.
- Haz clic en Create key, dale un nombre y copia la clave inmediatamente — no se mostrará de nuevo.

Tu clave se ve como bws_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6. Cada ejemplo a continuación la envía en el header X-Api-Key.
2. Subir JSON a IPFS
El upload más simple: pasa un objeto JavaScript plano como content y la API lo serializa, lo pinea en IPFS y devuelve el CID.
// upload-json.mjs
const API = "https://api.ipfs.ninja";
const API_KEY = "bws_your_key_here";
const payload = {
content: {
name: "Galactic Badge #42",
description: "Proof of attendance — Galactic Meetup 2026",
attributes: [
{ trait_type: "Event", value: "Galactic Meetup" },
{ trait_type: "Year", value: 2026 }
]
}
};
const res = await fetch(`${API}/upload/new`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": API_KEY
},
body: JSON.stringify(payload)
});
const data = await res.json();
console.log("CID :", data.cid);
console.log("Size:", data.sizeMB, "MB");
console.log("IPFS:", data.uris.ipfs);
console.log("URL :", data.uris.url);Ejecuta con node upload-json.mjs. Una respuesta exitosa se ve así:
{
"cid": "bafkreigx7gq...",
"sizeMB": 0.0004,
"uris": {
"ipfs": "ipfs://bafkreigx7gq...",
"url": "https://ipfs.ninja/ipfs/bafkreigx7gq..."
}
}El campo url apunta a una pasarela HTTP pública, por lo que el contenido es inmediatamente accesible en cualquier navegador.
3. Subir una imagen
Los archivos binarios (imágenes, PDFs, audio) se envían como strings codificados en base64 en el campo content.
// upload-image.mjs
import { readFileSync } from "node:fs";
const API = "https://api.ipfs.ninja";
const API_KEY = "bws_your_key_here";
const imageBuffer = readFileSync("./photo.png");
const base64 = imageBuffer.toString("base64");
const res = await fetch(`${API}/upload/new`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": API_KEY
},
body: JSON.stringify({ content: base64 })
});
const data = await res.json();
console.log("Image CID:", data.cid);
console.log("Gateway :", data.uris.url);La API detecta el tipo MIME automáticamente — PNG, JPEG, WebP, GIF y PDF son todos soportados. No se requieren headers adicionales ni overrides de content-type.
Con curl la misma operación se ve así:
BASE64=$(base64 -w 0 photo.png)
curl -X POST https://api.ipfs.ninja/upload/new \
-H "Content-Type: application/json" \
-H "X-Api-Key: bws_your_key_here" \
-d "{\"content\": \"$BASE64\"}"4. Upload con metadatos
Cada upload acepta dos campos opcionales: description (etiqueta de texto libre) y metadata (pares clave-valor arbitrarios). Ambos se almacenan junto con el CID y se devuelven cuando listas o recuperas el archivo más tarde.
// upload-with-metadata.mjs
const API = "https://api.ipfs.ninja";
const API_KEY = "bws_your_key_here";
const res = await fetch(`${API}/upload/new`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": API_KEY
},
body: JSON.stringify({
content: { title: "Meeting Notes", body: "Q1 roadmap recap..." },
description: "Q1 2026 planning meeting notes",
metadata: {
project: "acme-app",
author: "dana",
version: "1"
}
})
});
const data = await res.json();
console.log("CID:", data.cid);Los metadatos facilitan filtrar y organizar archivos en tu lado sin parsear el contenido IPFS mismo.
5. Pinear un CID existente
Si ya tienes contenido en IPFS y quieres asegurarte de que se mantenga disponible, pínealo por CID:
// pin-cid.mjs
const API = "https://api.ipfs.ninja";
const API_KEY = "bws_your_key_here";
const res = await fetch(`${API}/pin`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": API_KEY
},
body: JSON.stringify({
cid: "bafkreigx7gq...",
description: "Pinned from external source"
})
});
const data = await res.json();
console.log("Pinned:", data.cid);6. Listar tus archivos
Recupera cada archivo que has subido dentro de una ventana de tiempo. Los parámetros de consulta from y to son timestamps Unix en milisegundos.
// list-files.mjs
const API = "https://api.ipfs.ninja";
const API_KEY = "bws_your_key_here";
const now = Date.now();
const oneWeekAgo = now - 7 * 24 * 60 * 60 * 1000;
const url = `${API}/upload/list?from=${oneWeekAgo}&to=${now}`;
const res = await fetch(url, {
headers: { "X-Api-Key": API_KEY }
});
const files = await res.json();
for (const file of files) {
console.log(`${file.cid} ${file.description ?? "(no description)"}`);
}Con curl:
FROM=$(($(date +%s) * 1000 - 604800000))
TO=$(($(date +%s) * 1000))
curl -s "https://api.ipfs.ninja/upload/list?from=$FROM&to=$TO" \
-H "X-Api-Key: bws_your_key_here" | jq .7. Obtener detalles del archivo
Recupera el registro completo para un solo CID, incluyendo metadatos, tamaño y timestamps:
// get-file.mjs
const API = "https://api.ipfs.ninja";
const API_KEY = "bws_your_key_here";
const CID = "bafkreigx7gq...";
const res = await fetch(`${API}/file/${CID}`, {
headers: { "X-Api-Key": API_KEY }
});
const details = await res.json();
console.log(JSON.stringify(details, null, 2));8. Uploads del lado del cliente con tokens firmados
Embeber una clave API en un bundle de navegador es un riesgo de seguridad. En su lugar, genera un token firmado de corta duración en tu servidor y pásalo al cliente.
Servidor (Express)
// server.mjs
import express from "express";
const app = express();
const API = "https://api.ipfs.ninja";
const API_KEY = process.env.IPFS_API_KEY;
app.post("/api/upload-token", async (req, res) => {
const response = await fetch(`${API}/upload/signed-url`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": API_KEY
},
body: JSON.stringify({
name: "browser-upload",
expiresIn: 600 // token valid for 10 minutes
})
});
const { token, tokenId, expiresAt } = await response.json();
res.json({ token, tokenId, expiresAt });
});
app.listen(3000, () => console.log("Server running on :3000"));Cliente de navegador
<!-- upload.html -->
<input type="file" id="filePicker" />
<button id="uploadBtn">Upload to IPFS</button>
<pre id="result"></pre>
<script>
const API = "https://api.ipfs.ninja";
document.getElementById("uploadBtn").addEventListener("click", async () => {
// 1. Get a signed token from your own backend
const tokenRes = await fetch("/api/upload-token", { method: "POST" });
const { token } = await tokenRes.json();
// 2. Read the selected file as base64
const file = document.getElementById("filePicker").files[0];
if (!file) return alert("Pick a file first");
const base64 = await new Promise((resolve) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result.split(",")[1]);
reader.readAsDataURL(file);
});
// 3. Upload directly to IPFS using the signed token
const uploadRes = await fetch(`${API}/upload/new`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Signed ${token}`
},
body: JSON.stringify({
content: base64,
description: file.name
})
});
const data = await uploadRes.json();
document.getElementById("result").textContent = JSON.stringify(data, null, 2);
});
</script>El navegador nunca ve tu clave API. El token firmado expira automáticamente. Los tokens son multi-uso — se pueden usar varias veces hasta que expiren o sean revocados. El useCount se rastrea pero no se aplica como límite.
9. Manejo de errores
La API usa códigos de estado HTTP convencionales. Aquí están los que deberías manejar explícitamente:
| Status | Significado | Qué hacer |
|---|---|---|
| 400 | Bad request — campos faltantes o inválidos | Verifica que content esté presente y sea válido |
| 401 | Unauthorized — clave API mala o faltante | Verifica el header X-Api-Key |
| 413 | Payload demasiado grande | Reduce el tamaño del archivo o divide el contenido |
| 429 | Rate limited | Retírate y reintenta con delay exponencial |
| 500 | Error del servidor | Reintenta tras un delay corto |
Una función de upload resistente con backoff exponencial:
// resilient-upload.mjs
const API = "https://api.ipfs.ninja";
const API_KEY = "bws_your_key_here";
async function uploadWithRetry(content, retries = 3) {
for (let attempt = 1; attempt <= retries; attempt++) {
const res = await fetch(`${API}/upload/new`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": API_KEY
},
body: JSON.stringify({ content })
});
if (res.ok) return await res.json();
const body = await res.text();
if (res.status === 401) {
throw new Error(`Authentication failed: ${body}`);
}
if (res.status === 413) {
throw new Error(`Payload too large — reduce file size. ${body}`);
}
if (res.status === 429 || res.status >= 500) {
const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
console.warn(`Attempt ${attempt} failed (${res.status}). Retrying in ${delay}ms...`);
await new Promise((r) => setTimeout(r, delay));
continue;
}
throw new Error(`Upload failed (${res.status}): ${body}`);
}
throw new Error("Upload failed after all retries");
}
// Usage
const data = await uploadWithRetry({ hello: "world" });
console.log("CID:", data.cid);10. Ejemplo completo funcional
Un único script que ejercita cada endpoint cubierto en este tutorial. Guárdalo como ipfs-demo.mjs y ejecuta con node ipfs-demo.mjs.
// ipfs-demo.mjs
import { readFileSync } from "node:fs";
const API = "https://api.ipfs.ninja";
const API_KEY = "bws_your_key_here";
const headers = {
"Content-Type": "application/json",
"X-Api-Key": API_KEY
};
async function request(method, path, body) {
const opts = { method, headers };
if (body) opts.body = JSON.stringify(body);
const res = await fetch(`${API}${path}`, opts);
if (!res.ok) throw new Error(`${method} ${path} → ${res.status}: ${await res.text()}`);
return res.json();
}
// --- 1. Upload JSON ---
console.log("--- Upload JSON ---");
const jsonResult = await request("POST", "/upload/new", {
content: { name: "Demo Token", edition: 1 },
description: "Tutorial demo — JSON upload",
metadata: { tutorial: "true" }
});
console.log("CID:", jsonResult.cid);
console.log("URL:", jsonResult.uris.url);
// --- 2. Upload an image (if photo.png exists) ---
try {
const img = readFileSync("./photo.png");
console.log("\n--- Upload Image ---");
const imgResult = await request("POST", "/upload/new", {
content: img.toString("base64"),
description: "Tutorial demo — image upload"
});
console.log("CID:", imgResult.cid);
console.log("URL:", imgResult.uris.url);
} catch {
console.log("\n--- Skipping image upload (no photo.png found) ---");
}
// --- 3. Pin the JSON CID ---
console.log("\n--- Pin CID ---");
const pinResult = await request("POST", "/pin", {
cid: jsonResult.cid,
description: "Pinned from tutorial"
});
console.log("Pinned:", pinResult.cid);
// --- 4. Get file details ---
console.log("\n--- File Details ---");
const details = await request("GET", `/file/${jsonResult.cid}`);
console.log(JSON.stringify(details, null, 2));
// --- 5. List recent files ---
console.log("\n--- Recent Files ---");
const now = Date.now();
const oneHourAgo = now - 60 * 60 * 1000;
const files = await request("GET", `/upload/list?from=${oneHourAgo}&to=${now}`);
console.log(`Found ${files.length} file(s) in the last hour`);
for (const f of files) {
console.log(` - ${f.cid} ${f.description ?? ""}`);
}
// --- 6. Create a signed upload token ---
console.log("\n--- Signed Token ---");
const tokenResult = await request("POST", "/upload/signed-url", {
name: "demo-token",
expiresIn: 300
});
console.log("Token ID :", tokenResult.tokenId);
console.log("Expires :", new Date(tokenResult.expiresAt).toISOString());
// --- 7. Upload using the signed token ---
console.log("\n--- Upload with Signed Token ---");
const signedRes = await fetch(`${API}/upload/new`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Signed ${tokenResult.token}`
},
body: JSON.stringify({
content: { note: "Uploaded with a signed token" }
})
});
const signedData = await signedRes.json();
console.log("CID:", signedData.cid);
console.log("\nDone.");Reemplaza bws_your_key_here con una clave real y ejecuta el script. Cada paso imprime su resultado para que puedas seguir.
11. Siguientes pasos
Ahora sabes cómo subir, pinear, listar y recuperar archivos a través de la IPFS Upload API y cómo mantener los uploads del navegador seguros con tokens firmados. Para una visión más amplia que incluye la UI del dashboard y ejemplos en Python, mira Cómo subir archivos a IPFS. Aquí es a dónde ir desde aquí:
- Referencia de API — Documentación completa de endpoints en ipfs.ninja/docs.
- Gateways personalizados — Sirve contenido IPFS desde tu propio dominio. Mira la guía de setup de gateway.
- Analytics — Rastrea volumen de upload, ancho de banda y conteos de pin en el dashboard.
- Cliente HTTP — No se requiere SDK. Puedes usar
fetch()estándar o cualquier cliente HTTP para interactuar con la API.
Si te encuentras con problemas, abre un ticket en support.ipfs.ninja o únete a la comunidad en Discord.
