· Nacho Coll · Tutorials · 10 min de lectura
IPFS Upload API — Tutorial complet per a desenvolupadors
Aprèn a pujar fitxers a IPFS via REST API. Exemples complets de codi en JavaScript, Node.js i curl. Puja JSON, imatges, fes servir tokens signats per a pujades des del client.

IPFS Upload API — Tutorial complet per a desenvolupadors
Pujar fitxers a IPFS hauria de ser tan senzill com fer una petició POST. En aquest tutorial faràs exactament això — pujaràs documents JSON, imatges i PDF a IPFS amb res més que fetch() i una clau API. Al final tindràs un script complet de Node.js que puja contingut, llista fitxers, recupera metadades i genera tokens signats per a pujades segures des del client. Nou a IPFS? Comença per Què és IPFS Pinning? per entendre els fonaments abans d’endinsar-te al codi.

El que construiràs
- Puja un objecte JSON a IPFS i obtén un identificador de contingut (CID).
- Puja una imatge binària des del disc.
- Adjunta descripcions i metadades personalitzades a les pujades.
- Consulta l’historial de pujades per rang de dates.
- Recupera els detalls d’un fitxer específic.
- Crea tokens signats amb temps limitat perquè un navegador pugui pujar directament sense exposar la teva clau API.
- Gestiona errors i implementa lògica de reintents.
Tot s’executa contra una única URL base: https://api.ipfs.ninja
1. Configuració — Registrar-se i obtenir una clau API
- Registra’t per obtenir un compte gratuït (no cal targeta de crèdit).
- Vés a API Keys a la barra lateral del tauler.
- Fes clic a Create key, dona-li un nom i copia la clau immediatament — no es tornarà a mostrar.

La teva clau té un aspecte com bws_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6. Tots els exemples següents l’envien a la capçalera X-Api-Key.
2. Pujar JSON a IPFS
La pujada més senzilla: passa un objecte JavaScript simple com a content i l’API el serialitza, el fixa a IPFS i retorna 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);Executa-ho amb node upload-json.mjs. Una resposta correcta té aquesta forma:
{
"cid": "bafkreigx7gq...",
"sizeMB": 0.0004,
"uris": {
"ipfs": "ipfs://bafkreigx7gq...",
"url": "https://ipfs.ninja/ipfs/bafkreigx7gq..."
}
}El camp url apunta a una passarel·la HTTP pública, així que el contingut és immediatament accessible des de qualsevol navegador.
3. Pujar una imatge
Els fitxers binaris (imatges, PDF, àudio) s’envien com a cadenes codificades en base64 al camp 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);L’API detecta automàticament el tipus MIME — PNG, JPEG, WebP, GIF i PDF estan tots suportats. No calen capçaleres addicionals ni sobreescriptures de content-type.
Amb curl la mateixa operació té aquest aspecte:
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. Pujada amb metadades
Cada pujada accepta dos camps opcionals: description (etiqueta de text lliure) i metadata (parells clau-valor arbitraris). Tots dos es guarden al costat del CID i es retornen quan llistes o recuperes el fitxer més tard.
// 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);Les metadades faciliten filtrar i organitzar fitxers a la teva banda sense haver d’analitzar el contingut IPFS en si.
5. Fixar un CID existent
Si ja tens contingut a IPFS i vols assegurar-te que es manté disponible, fixa’l per 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. Llistar els teus fitxers
Recupera tots els fitxers que has pujat dins una finestra temporal. Els paràmetres de consulta from i to són timestamps Unix en mil·lisegons.
// 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)"}`);
}Amb 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. Detalls d’un fitxer
Recupera el registre complet per a un sol CID, incloent-hi metadades, mida i 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. Pujades des del client amb tokens signats
Incrustar una clau API en un paquet de navegador és un risc de seguretat. En lloc d’això, genera un token signat de curta durada al teu servidor i passa’l al client.
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"));Client al 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 mai veu la teva clau API. El token signat caduca automàticament. Els tokens són reutilitzables — es poden fer servir diverses vegades fins que caduquen o es revoquen. El useCount es registra però no s’aplica com a límit.
9. Gestió d’errors
L’API fa servir codis d’estat HTTP convencionals. Aquests són els que hauries de gestionar explícitament:
| Estat | Significat | Què fer |
|---|---|---|
| 400 | Bad request — camps que falten o no vàlids | Comprova que content hi sigui i sigui vàlid |
| 401 | Unauthorized — clau API errònia o absent | Verifica la capçalera X-Api-Key |
| 413 | Payload too large | Redueix la mida del fitxer o divideix el contingut |
| 429 | Rate limited | Fes back off i reintenta amb retard exponencial |
| 500 | Server error | Reintenta després d’un retard curt |
Una funció de pujada resistent amb 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. Exemple complet i funcional
Un únic script que exercita cada endpoint cobert en aquest tutorial. Desa’l com a ipfs-demo.mjs i executa’l amb 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.");Substitueix bws_your_key_here per una clau real i executa l’script. Cada pas imprimeix el seu resultat perquè puguis seguir-ho.
11. Pròxims passos
Ara saps com pujar, fixar, llistar i recuperar fitxers a través de l’IPFS Upload API i com mantenir segures les pujades des del navegador amb tokens signats. Per a una visió més àmplia que inclou la interfície del tauler i exemples de Python, consulta Com pujar fitxers a IPFS. On anar a partir d’aquí:
- API Reference — Documentació completa dels endpoints a ipfs.ninja/docs.
- Passarel·les personalitzades — Serveix contingut IPFS des del teu propi domini. Consulta la guia de configuració de passarel·la.
- Analítica — Fes un seguiment del volum de pujades, l’ample de banda i el nombre de pins al tauler.
- Client HTTP — No cal cap SDK. Pots fer servir el
fetch()estàndard o qualsevol client HTTP per interactuar amb l’API.
Si trobes problemes, obre un tiquet a support.ipfs.ninja o uneix-te a la comunitat al Discord.
