Quikturnv1.0
SDKs

Server Client

Server-side SDK with batch operations, streaming, and secret key authentication

The server client is designed for Node.js backends, API routes, and build-time scripts. It uses secret key authentication via the Authorization header and supports batch fetching, streaming, and higher resolution logos.

Installation

npm install @quikturn/logos
import { QuikturnLogos } from "@quikturn/logos/server";

Creating a Client

const client = new QuikturnLogos({
  secretKey: process.env.QT_SECRET_KEY!,  // Requiredsk_* key
  baseUrl: undefined,                      // Optional override
  maxRetries: 2,                           // Optional retry count
});

Server-side only

The server client requires a secret key (sk_*). Never expose secret keys in client-side code or public repositories.

Key Differences from Browser Client

FeatureBrowser ClientServer Client
Key typepk_* (publishable)sk_* (secret)
Auth method?token= query paramAuthorization: Bearer header
Max logo size800px1200px
Output formatBlob URLBuffer
Batch operationsNoYes
StreamingNoYes

Methods

client.get(domain, options?)

Fetches a single logo and returns a Node.js Buffer.

const { buffer, contentType, metadata } = await client.get("stripe.com", {
  size: 512,
  format: "png",
  greyscale: false,
  theme: "light",
});

// Write to file
import { writeFile } from "fs/promises";
await writeFile("stripe-logo.png", buffer);

Returns:

interface ServerLogoResponse {
  buffer: Buffer;
  contentType: string;  // e.g., "image/png"
  metadata: LogoMetadata;
}

client.getMany(domains, options?)

Batch-fetches multiple logos with concurrency control. Returns an AsyncGenerator that yields results in order.

const domains = ["stripe.com", "github.com", "vercel.com", "linear.app"];

for await (const result of client.getMany(domains, {
  concurrency: 5,          // Max parallel requests (default: 5)
  continueOnError: true,   // Don't stop on individual failures
  signal: abortController.signal, // Optional AbortSignal
  size: 256,
  format: "webp",
})) {
  if (result.success) {
    console.log(`${result.domain}: ${result.buffer!.length} bytes`);
  } else {
    console.error(`${result.domain}: ${result.error!.code}`);
  }
}

Yields:

interface BatchResult {
  domain: string;
  success: boolean;
  buffer?: Buffer;
  contentType?: string;
  metadata?: LogoMetadata;
  error?: LogoError;
}

Batch features:

  • Preserves input order
  • Automatic retry with exponential backoff on rate limits (up to 3 retries per domain)
  • continueOnError: true yields failed results instead of throwing
  • Respects AbortSignal for cancellation

client.getStream(domain, options?)

Returns a ReadableStream for zero-copy piping. Ideal for proxying logos to clients or writing large batches to disk.

import { createWriteStream } from "fs";
import { Readable } from "stream";

const stream = await client.getStream("stripe.com", { size: 1200 });

// Pipe to file (Node.js)
const nodeStream = Readable.fromWeb(stream);
nodeStream.pipe(createWriteStream("stripe-logo.png"));

client.getUrl(domain, options?)

Returns the URL without the token (since server auth uses the Authorization header, the token is not in the URL).

const url = client.getUrl("stripe.com", { size: 256 });
// → "https://logos.getquikturn.io/stripe.com?size=256"

Event Listeners

Same events as the browser client:

client.on("rateLimitWarning", (remaining, limit) => {
  logger.warn(`Rate limit: ${remaining}/${limit} remaining`);
});

client.on("quotaWarning", (remaining, limit) => {
  logger.warn(`Monthly quota: ${remaining}/${limit} remaining`);
});

Error Handling

The server client throws the same typed errors as the browser client:

import { LogoError, RateLimitError, NotFoundError } from "@quikturn/logos";

try {
  const { buffer } = await client.get("example.com");
} catch (err) {
  if (err instanceof RateLimitError) {
    // err.retryAfter — seconds to wait
    // err.remaining — requests left
    // err.resetAt — Date when window resets
  } else if (err instanceof NotFoundError) {
    // err.domain — the domain that wasn't found
  }
}

Use Cases

API Route (Next.js App Router)

Proxy logos through your backend to keep secret keys hidden:

// app/api/logo/[domain]/route.ts
import { QuikturnLogos } from "@quikturn/logos/server";

const client = new QuikturnLogos({
  secretKey: process.env.QT_SECRET_KEY!,
});

export async function GET(
  _req: Request,
  { params }: { params: { domain: string } }
) {
  try {
    const { buffer, contentType } = await client.get(params.domain, {
      size: 256,
    });
    return new Response(buffer, {
      headers: {
        "Content-Type": contentType,
        "Cache-Control": "public, max-age=86400",
      },
    });
  } catch (err) {
    return new Response("Logo not found", { status: 404 });
  }
}

Batch Download Script

Download logos for an entire portfolio:

import { QuikturnLogos } from "@quikturn/logos/server";
import { writeFile, mkdir } from "fs/promises";

const client = new QuikturnLogos({
  secretKey: process.env.QT_SECRET_KEY!,
});

const portfolio = [
  "stripe.com", "github.com", "vercel.com", "linear.app",
  "figma.com", "notion.so", "slack.com", "shopify.com",
];

await mkdir("logos", { recursive: true });

let success = 0;
let failed = 0;

for await (const result of client.getMany(portfolio, {
  concurrency: 3,
  continueOnError: true,
  size: 512,
  format: "png",
})) {
  if (result.success) {
    await writeFile(`logos/${result.domain}.png`, result.buffer!);
    success++;
  } else {
    console.error(`Failed: ${result.domain} — ${result.error!.code}`);
    failed++;
  }
}

console.log(`Done: ${success} downloaded, ${failed} failed`);

Express Middleware

Serve logos with caching:

import express from "express";
import { QuikturnLogos, LogoError } from "@quikturn/logos/server";

const app = express();
const client = new QuikturnLogos({
  secretKey: process.env.QT_SECRET_KEY!,
});

app.get("/logo/:domain", async (req, res) => {
  try {
    const { buffer, contentType } = await client.get(req.params.domain, {
      size: Number(req.query.size) || 128,
    });
    res.set("Content-Type", contentType);
    res.set("Cache-Control", "public, max-age=86400");
    res.send(buffer);
  } catch (err) {
    if (err instanceof LogoError && err.code === "NOT_FOUND_ERROR") {
      res.status(404).send("Logo not found");
    } else {
      res.status(500).send("Internal error");
    }
  }
});

Rate Limit Tiers

Secret keys follow the same tier-based rate limits as publishable keys:

TierRequests/MinMonthly Quota
Free100500,000
Launch5001,000,000
Growth5,0005,000,000
Enterprise50,00010,000,000

The SDK automatically retries on 429 responses with exponential backoff.

On this page