I built a QR Code API and here's what I learned

Published: (December 17, 2025 at 03:19 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Cover image for I built a QR Code API and here's what I learned

The API

Dead simple. One endpoint:

GET https://api.qrcodeapi.io/generate?data=&size=

It returns a QR code image.

Options

ParameterValues
formatpng, svg, or base64
size1001000 px
colorHex color for the QR code (e.g. #8b5cf6)
bgColorHex color for background (or transparent)
errorCorrectionL, M, Q, or H

Example (purple QR code on transparent background)

curl "https://api.qrcodeapi.io/generate?data=hello&color=8b5cf6&bgColor=transparent&format=svg"

Dynamic QR Codes

Static QR codes are fine until you need to change the destination URL—then you have to reprint everything. Dynamic QR codes point to a redirect URL you control, so you can change the target anytime without touching the printed QR.

// Create a dynamic link
const response = await fetch('https://api.qrcodeapi.io/links', {
  method: 'POST',
  headers: { 'X-API-Key': 'your-key' },
  body: JSON.stringify({ url: 'https://example.com/campaign-v1' })
});
// Returns a short code like "abc123"
// QR code points to: https://qr.qrcodeapi.io/abc123

Update the destination later

await fetch('https://api.qrcodeapi.io/links/abc123', {
  method: 'PUT',
  headers: { 'X-API-Key': 'your-key' },
  body: JSON.stringify({ url: 'https://example.com/campaign-v2' })
});

Scan Analytics

Every scan of a dynamic link is tracked:

  • Device type (mobile/tablet/desktop)
  • Country & city (via IP geolocation)
  • Referrer
  • Timestamp

No cookies, no fingerprinting—just basic HTTP request data, respecting privacy.

const stats = await fetch('https://api.qrcodeapi.io/analytics/abc123', {
  headers: { 'X-API-Key': 'your-key' }
});

// Example response:
{
  totalScans: 1247,
  uniqueScans: 892,
  byDevice: { mobile: 743, desktop: 401, tablet: 103 },
  byCountry: { US: 521, UK: 234, DE: 189, /* ... */ },
  byDay: [{ date: '2025-12-15', scans: 87 }, /* ... */]
}

Tech Stack

  • Runtime: Vercel Serverless Functions
  • Database: Supabase (PostgreSQL)
  • QR Generation: qrcode npm package
  • Image Processing: sharp (for logo overlays)
  • Geo‑IP: geoip-lite
  • Payments: Stripe

The whole thing is ~15 API endpoints consolidated into 9 serverless functions (Vercel Hobby plan has a 12‑function limit 😅).

TypeScript SDK

An npm package is also published:

npm install qrcode-api-sdk
import { QRCodeAPI } from 'qrcode-api-sdk';

const client = new QRCodeAPI({ apiKey: 'your-key' });

// Generate QR code
const qr = await client.generate({
  data: 'https://dev.to/',
  format: 'svg',
  color: '#000000'
});

// Create dynamic link
const link = await client.links.create({
  url: 'https://example.com/'
});

// Get analytics
const stats = await client.analytics.get(link.shortCode);

Full TypeScript types are included.

Pricing

PlanPriceQR codes / monthFeatures
Free$0100 (no credit card)Basic generation
Starter$9/mo5,000Dynamic links
Pro$29/mo50,000Analytics + dynamic links

The free tier is enough for most side projects; the paid tiers are priced for indie developers.

What I’d Do Differently

  • Start with dynamic QR codes first – that’s what people actually pay for. Static generation is a commodity.
  • Build the SDK earlier – a proper TypeScript SDK with types dramatically improves developer experience.
  • Don’t underestimate SEO – half my traffic comes from searches like “QR code API Node.js” or “dynamic QR code API”. Investing a weekend in SEO landing pages paid off.

Try It Out

  • 🌐 Website:
  • 📚 Documentation:
  • 📦 npm package:

I’d love feedback, especially on pricing and feature ideas. I’m considering:

  • Bulk generation endpoint (ZIP file with multiple QR codes)
  • QR code templates / styles
  • Webhook notifications for scan events

Let me know what would be useful! 🙏

Back to Blog

Related posts

Read more »

Rate limiters with node:http and redis

!Cover image for Rate limiters with node:http and redishttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%...

Drupal: Exploring Canvas (part 2)

Exploring Components In my previous post I got acquainted with the new module, and it got off to a promising start. In this post I’m going to explore component...