Logo YoinkDocs
Documentationv0.1.0

One URL in.
The right logos out.

Logo Yoink is a self-hosted API and CLI that discovers the real logo files a website exposes, validates them, and ranks the best icon and wordmark—without generating or inventing artwork.

Deterministic runtime Node.js 22+ No database MIT licensed
01

Two canonical roles

assets.icon is for compact, square placements. assets.logo is the wider wordmark for headers and cards.

02

Evidence, not guesses

Every returned candidate includes its source, dimensions, role scores, variant metadata, and the reasons behind its score.

03

Preference-aware

Ask for light or dark surfaces, color treatment, and transparent or opaque backgrounds. Matching is best-effort.

01

Quickstart

Run locally in five commands.

You need Node.js 22 or newer, npm, and internet access to inspect public websites. No database or API key is required.

Terminal
$ git clone https://github.com/Hendrikc4/logo-yoink.git
$ cd logo-yoink
$ npm ci
$ npx playwright install chromium
$ cp .env.example .env.local
$ npm start

Verify your setup

npm run smoke starts an isolated local server, verifies the homepage and API validation, then shuts it down. It does not contact a third-party site. For the full suite, run npm run check.

02

First request

Extract from any public website.

Send JSON to the endpoint on the instance you control. The hosted endpoint powers the public demo and is intentionally rate-limited.

$ curl -sS http://127.0.0.1:4310/api/extract \
  -H 'content-type: application/json' \
  -d '{"website":"stripe.com"}'
POST/api/extract

Discovers, validates, deduplicates, and ranks logo assets for one public HTTP or HTTPS website.

Request body

websitestring · required

A hostname or public HTTP(S) URL, up to 2,048 characters. Bare domains such as stripe.com are accepted.

preferencesobject · optional

Role-specific appearance preferences. Only icon and logo keys are accepted.

wikimediaFallbackboolean · optional

Defaults to true. Set to false to keep this request first-party-only.

03

Request options

Choose the right treatment.

Icon and logo preferences use the same fields. Omitted values normalize to any.

themeany | light | dark

The surface the asset will sit on. Use dark to prefer light artwork for a dark UI.

colorany | color | white | black

The desired artwork treatment.

backgroundany | transparent | opaque

Whether the returned file should carry transparency.

Request with role-specific preferences
{
  "website": "stripe.com",
  "preferences": {
    "icon": { "color": "color" },
    "logo": {
      "theme": "dark",
      "color": "white",
      "background": "transparent"
    }
  }
}

Preferences are best-effort. A matching eligible asset ranks first when available; otherwise Logo Yoink returns the strongest eligible fallback.

04

Response

Canonical picks, variants, and proof.

assets

The canonical icon and logo. Start here for most integrations.

assetVariants

Ordered, semantically distinct treatments. The selected asset is first.

assetFamilies

Related delivery-size copies grouped into artwork families.

candidates

Every validated candidate with scores and evidence.

diagnostics

Discovery counts, fallbacks, network work, and duration.

selectedByRole

Compatibility aliases: icon, wide, and legacy favicon.

Representative response

200 OK · application/json
{
  "input": "stripe.com",
  "domain": "stripe.com",
  "homepage": "https://stripe.com/",
  "preferences": {
    "icon": { "theme": "any", "color": "any", "background": "any" },
    "logo": { "theme": "dark", "color": "white", "background": "transparent" }
  },
  "assets": {
    "icon": null,
    "logo": {
      "resolvedUrl": "https://…/logo.svg",
      "dataUrl": "data:image/svg+xml;base64,…",
      "format": "svg",
      "width": 512,
      "height": 170,
      "source": "dom-img",
      "variant": { "theme": "dark", "color": "white", "background": "transparent" },
      "role_scores": { "icon": 28, "wide": 84, "favicon": 12 },
      "predicted_roles": ["wide"],
      "score_reasons": ["wide shape +30", "home linked +12"]
    }
  },
  "assetVariants": {
    "icon": [],
    "logo": [{
      "resolvedUrl": "https://…/logo.svg",
      "certainty": { "score": 84, "band": "high" }
    }]
  },
  "variantPolicy": { "minimumRoleScore": 45 },
  "assetFamilies": [],
  "candidates": [],
  "diagnostics": { "discovered": 19, "validated": 8, "browserUsed": false, "durationMs": 1240 }
}
05

Errors & limits

Predictable failure modes.

400

Invalid JSON fields, preference values, URL, or an extraction that could not complete.

403

A cross-site browser request was rejected by the demo origin policy.

413

The JSON request body is larger than the configured limit (2 KB by default).

415

The request is not application/json.

429

The per-client or global rate window is exhausted. Respect Retry-After.

503

All extraction slots are busy. Retry after the provided delay.

Successful responses expose RateLimit-Limit and RateLimit-Remaining. Errors use the stable shape { "error": "Human-readable message." }.

06

CLI

Use it without a server.

Rank and print JSON
$ npm run cli -- stripe.com
$ npm run cli -- stripe.com --theme dark --background transparent
$ npm run cli -- stripe.com --no-wikimedia-fallback
$ npm run cli -- stripe.com --role logo --download ./downloads/stripe
--themeany | light | dark

Preference for the logo destination surface.

--backgroundany | transparent | opaque

Preference for the logo file background.

--roleicon | logo

Choose which canonical winner to download. Icon-first is the default.

--downloaddirectory

Write the selected validated asset to disk.

--deep-wideflag

When needed, follow up to two strong first-party brand, press, or media links and inspect official ZIP kits.

--spa-bundlesflag

With deep discovery, inspect one same-origin entry bundle (up to 2.2 MB) for strong logo asset literals.

--no-wikimedia-fallbackflag

Disable the default exact-domain Wikidata/Commons missing-role fallback.

07

Configuration

Safe defaults, optional horsepower.

VariableDefaultPurpose
HOST127.0.0.1

Local server bind address.

PORT4310

Local server port.

BROWSER_DISCOVERY1

Set to 0 to disable local Chromium discovery.

JINA_API_KEYunset

Optional recovery for blocked or unusable homepages.

BESTICON_URLunset

Optional URL for a self-hosted Besticon service.

PUBLIC_DEMO_ALLOW_JINA1

Set to 0 to keep web/API requests off Jina.

PUBLIC_DEMO_BROWSER1

Set to 0 to disable Chromium for web/API requests.

PUBLIC_DEMO_WIKIMEDIA1

Set to 0 to disable exact-domain Wikidata/Commons recovery.

DEMO_RATE_LIMIT20 / 10 min

Per-client in-process request window.

DEMO_GLOBAL_RATE_LIMIT60 / min

Per-instance global request window.

DEMO_MAX_CONCURRENT2

Simultaneous extraction ceiling.

Logo Yoink loads .env.local automatically. Keep secrets there; it is gitignored. The checked-in .env.example contains safe local defaults.

08

Deployment

Host it where your app lives.

The repository includes a Vercel function adapter and static configuration. The same app can run as a long-lived Node process with npm start.

01

Node process

Set HOST=0.0.0.0 in a container or VM, expose PORT, install Chromium, and run npm start.

02

Vercel

Import the repository. vercel.json serves the static site, maps the API function, includes serverless Chromium, and applies security headers.

09

Under the hood

A bounded extraction pipeline.

1
Discover

Read the public signals.

HTML images, picture sources, safe inline SVGs, schema data, metadata, manifests, touch icons, mask icons, tiles, and favicons.

2
Validate

Prove every asset.

Fetch under byte and time budgets, verify actual image bytes, measure dimensions and visible content, then reject unsafe or unusable files.

3
Consolidate

Collapse the duplicates.

Deduplicate by URL and byte hash, then group delivery-size variations of the same artwork into asset families.

4
Rank

Select by role.

Score source quality, shape, size, placement, home-link evidence, company agreement, visual treatment, and negative context separately for icon and wordmark.

5
Recover

Escalate only when needed.

Bounded browser, deep first-party, SPA-bundle, cached favicon, Besticon, optional Jina, or exact-domain Wikidata/Commons recovery can fill a missing role.

10

Ranking

AI-labeled. Deterministic at runtime.

No model is called during extraction. AI-assisted review was used offline to label benchmark candidates and evaluate changes; the live ranker is an interpretable ruleset.

45Medium certainty

Minimum role score for an additional semantic variant.

70High certainty

Threshold for the high confidence band.

2,277Adjudicated labels

Frozen candidate judgments used to measure identity, role, and usability.

The ranker deliberately abstains when evidence is weak. A null canonical role is safer than silently returning a partner mark, product icon, UI glyph, or stale identity.

11

Security

Designed for hostile URLs.

  • Public targets only. Private, loopback, and reserved network destinations are rejected, including after redirects.

  • Bounded network work. HTML, images, bundles, redirects, time, and candidate counts have explicit ceilings.

  • Validated content. A response claiming to be an image is not trusted until its bytes pass format and renderability checks.

  • Hardened demo route. JSON-only input, origin checks, rate limits, coalescing, concurrency control, generic errors, and restrictive browser headers.

12

Limitations

Know where certainty ends.

  • Some websites block automated clients or hide assets behind interactions that a bounded pass will not perform.
  • A site may expose a product icon instead of its corporate identity.
  • Dimensions and transparent-pixel bounds cannot identify every padded or unusually composed wordmark.
  • Redirected, acquired, or rebranded domains can continue serving stale assets.

Use the canonical picks for the common path, but preserve the candidate evidence and variants when human review matters.

Pixel-art cowboy riding a horse

Ready to ride?

Self-host the whole thing.

Open source, inspectable, and yours to deploy.
Get the source