· Nacho Coll · Tutorials · 10 min de lectura
IPFS Upload API — Tutorial completo para desarrolladores
Aprende a subir ficheros a IPFS mediante REST API. Ejemplos completos de código en JavaScript, Node.js y curl. Sube JSON, imágenes y utiliza tokens firmados para subidas desde el cliente.

IPFS Upload API — Tutorial completo para desarrolladores
Subir ficheros a IPFS debería ser tan sencillo como hacer una petición POST. En este tutorial harás justamente eso: subirás 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 ficheros, recupera metadatos y genera tokens firmados para subidas seguras desde el navegador. ¿Eres nuevo en IPFS? Empieza con ¿Qué es IPFS Pinning? para entender los fundamentos antes de meterte en el código.

Lo que vas a construir
- 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 las subidas.
- Consultar tu historial de subidas por rango de fechas.
- Recuperar los detalles de un fichero concreto.
- Crear tokens firmados con tiempo limitado para que un navegador pueda subir directamente sin exponer tu clave API.
- Gestionar errores e implementar lógica de reintentos.
Todo se ejecuta contra una única URL base: https://api.ipfs.ninja
1. Configuración — 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, ponle un nombre y copia la clave inmediatamente: no volverá a mostrarse.

Tu clave tiene este aspecto: bws_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6. Todos los ejemplos siguientes la envían en la cabecera X-Api-Key.
2. Subir JSON a IPFS
La subida más sencilla: pasa un objeto JavaScript plano como content y la API lo serializa, lo fija 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);Ejecútalo con node upload-json.mjs. Una respuesta correcta tiene esta forma:
{
"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, así que el contenido es accesible de inmediato en cualquier navegador.
3. Subir una imagen
Los ficheros binarios (imágenes, PDFs, audio) se envían como cadenas codificadas 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 automáticamente el tipo MIME: PNG, JPEG, WebP, GIF y PDF están todos soportados. No se necesitan cabeceras adicionales ni sobreescribir el content-type.
Con curl, la misma operación tiene este aspecto:
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. Subida con metadatos
Cada subida acepta dos campos opcionales: description (etiqueta de texto libre) y metadata (parejas clave-valor arbitrarias). Ambos se guardan junto al CID y se devuelven al listar o recuperar el fichero más adelante.
// 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 ficheros desde tu lado sin tener que parsear el propio contenido de IPFS.
5. Fijar un CID existente
Si ya tienes contenido en IPFS y quieres asegurarte de que sigue disponible, fíjalo 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 ficheros
Recupera todos los ficheros que has subido dentro de una ventana temporal. 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. Detalles del fichero
Recupera el registro completo de un CID, incluyendo metadatos, tamaño y marcas temporales:
// 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. Subidas desde el cliente con tokens firmados
Incrustar una clave API en un bundle de navegador es un riesgo de seguridad. En su lugar, genera un token firmado de vida corta en tu servidor y pásaselo 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 en el 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 caduca automáticamente. Los tokens son de uso múltiple: pueden utilizarse varias veces hasta que caducan o se revocan. El useCount se registra pero no se aplica como límite.
9. Gestión de errores
La API utiliza códigos de estado HTTP convencionales. Estos son los que deberías gestionar de forma explícita:
| Estado | Significado | Qué hacer |
|---|---|---|
| 400 | Bad request — campos que faltan o no son válidos | Comprueba que content está presente y es válido |
| 401 | Unauthorized — clave API errónea o ausente | Verifica la cabecera X-Api-Key |
| 413 | Payload too large | Reduce el tamaño del fichero o divide el contenido |
| 429 | Rate limited | Haz back off y reintenta con retardo exponencial |
| 500 | Server error | Reintenta tras una pequeña espera |
Una función de subida 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 y funcional
Un único script que ejerce cada endpoint cubierto en este tutorial. Guárdalo como ipfs-demo.mjs y ejecútalo 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.");Sustituye bws_your_key_here por una clave real y ejecuta el script. Cada paso imprime su resultado para que puedas seguirlo.
11. Siguientes pasos
Ahora sabes cómo subir, fijar, listar y recuperar ficheros a través de la IPFS Upload API y cómo mantener seguras las subidas desde el navegador con tokens firmados. Para una visión más amplia que incluye la UI del dashboard y ejemplos en Python, consulta Cómo subir ficheros a IPFS. Por dónde seguir desde aquí:
- API Reference — Documentación completa de los endpoints en ipfs.ninja/docs.
- Pasarelas personalizadas — Sirve contenido IPFS desde tu propio dominio. Consulta la guía de configuración de pasarela.
- Analítica — Sigue el volumen de subidas, el ancho de banda y el número de pins en el dashboard.
- Cliente HTTP — No se requiere ningún SDK. Puedes usar el
fetch()estándar o cualquier cliente HTTP para interactuar con la API.
Si te topas con problemas, abre un ticket en support.ipfs.ninja o únete a la comunidad en Discord.
