DEV Community

Om Prakash
Om Prakash

Posted on • Originally published at pixelapi.dev

How I Built a Family Photo Restoration Service With a Few API Calls

My grandmother passed away a few years ago and left behind a shoebox of photographs. Most of them were from the 1940s and 50s — black-and-white portraits, wedding shots, a few faded color prints from the 60s that had taken on that orange-yellow cast that film from that era tends to develop over time.

My mom asked if I could "do something with them." I said sure, thinking I'd just run them through some Lightroom presets and call it a day.

That was not what happened.


The actual problem with old photos

Scanning the prints was straightforward enough. The problems started when I looked at what I had:

  • Deep scratches running across faces
  • Foxing (those brown spots that develop on old paper)
  • Faded regions where the emulsion had degraded
  • Black-and-white prints that my family wanted to see in color
  • One wedding photo so water-damaged it looked like someone had poured tea on it

Manual restoration in Photoshop is genuinely skilled work. I know my way around a healing brush, but getting colorization right on a 1947 portrait — figuring out what color a dress actually was, making skin tones look natural rather than painted — that's a different discipline entirely.

I'd heard about PixelAPI from a thread about image processing APIs. Their Photo Restore endpoint handles exactly this: scratch removal, damage repair, and colorization in a single call. I figured I'd test it on the worst photo in the box first.


The integration

The API is straightforward. You POST a base64-encoded image and get back a restored version. I built a small Node script to batch-process the scanned folder:

import fs from "fs";
import path from "path";
import fetch from "node-fetch";

const PIXELAPI_KEY = process.env.PIXELAPI_KEY;

async function restorePhoto(imagePath) {
  const imageBuffer = fs.readFileSync(imagePath);
  const base64Image = imageBuffer.toString("base64");
  const ext = path.extname(imagePath).slice(1).toLowerCase();
  const mimeType = ext === "jpg" ? "image/jpeg" : `image/${ext}`;

  const response = await fetch("https://pixelapi.dev/api/photo-restore", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${PIXELAPI_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      image: `data:${mimeType};base64,${base64Image}`,
      enhance: true,
      colorize: true,
    }),
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`API error for ${imagePath}: ${error}`);
  }

  const result = await response.json();
  return result.output_url;
}

async function processFolder(inputDir, outputDir) {
  fs.mkdirSync(outputDir, { recursive: true });

  const files = fs.readdirSync(inputDir).filter(f =>
    [".jpg", ".jpeg", ".png", ".tif", ".tiff"].includes(
      path.extname(f).toLowerCase()
    )
  );

  console.log(`Processing ${files.length} photos...`);

  for (const file of files) {
    const inputPath = path.join(inputDir, file);
    const outputName = `restored_${path.parse(file).name}.jpg`;
    const outputPath = path.join(outputDir, outputName);

    try {
      process.stdout.write(`  ${file} → `);
      const restoredUrl = await restorePhoto(inputPath);

      // Download the result
      const imgResponse = await fetch(restoredUrl);
      const buffer = await imgResponse.buffer();
      fs.writeFileSync(outputPath, buffer);

      console.log(`saved`);
    } catch (err) {
      console.log(`FAILED: ${err.message}`);
    }
  }
}

processFolder("./scans", "./restored");
Enter fullscreen mode Exit fullscreen mode

I ran this against twenty-three scanned prints. It took maybe fifteen minutes end-to-end, most of which was my scanner.


What the results actually looked like

The water-damaged wedding photo came out better than I expected, though not perfect — there were regions where the emulsion damage was severe enough that some detail just wasn't recoverable. The API did a good job of making it look intentional rather than damaged, which is about the best you can ask for.

The colorization was the part that genuinely surprised me. The 1947 portrait of my great-grandparents — the API inferred natural-looking skin tones, added subtle variation to what had been a flat gray suit, and gave my great-grandmother's dress a dusty blue that my grandmother, when I showed her (she was in her 90s at the time), said looked about right for what she remembered.

I'm not saying it's perfect. A trained photo restoration artist would catch things the model misses. But for a shoebox of family prints that were otherwise just going to keep fading, it was genuinely useful.


Where this fits into a real product

After seeing the results on my own family photos, I started thinking about where you'd actually wire this into a product.

For e-commerce sellers of vintage goods: if you're selling antique prints, postcards, or old photographs themselves, being able to show a restored preview alongside the original scan is a real value-add for buyers.

For genealogy apps: this is an obvious integration point. Any app where users upload family trees and historical photos could run restoration automatically on upload and store both versions.

For professional archivists and local historical societies: a lot of these organizations have thousands of scanned photos sitting in folders. A batch script like the one above, run once, would get them restored versions without any manual effort per image.

For photo book services: wedding album restoration is a specific niche — families who have damaged prints from decades-old ceremonies and want to assemble a new album. Automating the restoration step before layout would save a lot of manual cleanup.


A few practical notes

Scan quality matters upstream. If you're starting from a 150 DPI scan, the restoration can only do so much. I got the best results from 600 DPI scans, even though the final output doesn't need to be that resolution — more input data gives the model more to work with.

The colorize flag is optional. If you're doing archival work where you want to preserve the black-and-white character of the originals, leave it off. The damage repair and sharpening work independently of colorization.

Batch in series, not parallel. I initially tried to run all requests concurrently and hit rate limits. The simple for loop above, processing one at a time, was more reliable and not meaningfully slower given that I was doing this once.


The shoebox project took me an afternoon. My mom cried when she saw the restored wedding photo of her parents. That's the outcome I care about. The fact that it was also just a few dozen lines of code is what makes this kind of work accessible for developers who aren't professional photo restorers — which is most of us.

Top comments (0)