Interfaces

HTTP API

The webfetch HTTP server — auth, endpoints, request/response schemas, error codes, and rate limits.

The HTTP API is a thin wrapper around the same core library that powers the CLI and the MCP server. Every endpoint mirrors an MCP tool. Request bodies validate via Zod; responses are JSON envelopes.

Try it: grab a free API key in 30 seconds at app.getwebfetch.com/signup, then point your requests at https://api.getwebfetch.com.

#Base URL

  • Self-hosted: http://127.0.0.1:7600 by default (--port / --host override).
  • webfetch Cloud: https://api.getwebfetch.com.

#Authentication

Every metered endpoint requires a bearer token:

Authorization: Bearer $WEBFETCH_KEY
  • Self-hosted: the server generates ~/.webfetch/server.token on first boot and prints it to stdout.
  • Cloud: your API key (prefix wf_live_) from app.getwebfetch.com/keys.

Export the key once and paste any example below without editing it:

export WEBFETCH_KEY=wf_live_yourkey

Missing or invalid token → 401 Unauthorized.

#Response envelope

Every response is JSON with this shape:

{ "ok": true, "data": { } }

On error:

{ "ok": false, "error": "human-readable reason" }

#Endpoints

Metered webfetch Cloud API endpoints live under /v1. The public Cloud utility endpoints, /providers and /health, remain unversioned; the self-hosted local server uses the same unversioned paths for every endpoint.

#`POST /v1/search` — federated image search

Auth: bearer key required. Cost: 1 fetch unit.

Request body (query is the only required field):

{
  "query": "drake portrait",
  "providers": ["wikimedia", "openverse", "unsplash"],
  "licensePolicy": "safe-only",
  "maxPerProvider": 3,
  "minWidth": 1200,
  "minHeight": 800,
  "safeSearch": "moderate",
  "timeoutMs": 15000
}

licensePolicy"safe-only" | "prefer-safe" | "any". safeSearch"strict" | "moderate" | "off".

Response data:

{
  "candidates": [
    {
      "url": "https://upload.wikimedia.org/wikipedia/commons/...",
      "thumbnailUrl": "https://...",
      "sourcePageUrl": "https://commons.wikimedia.org/...",
      "license": "CC_BY_SA",
      "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0/",
      "confidence": 0.95,
      "author": "Jane Photog",
      "authorUrl": "https://...",
      "attributionLine": "Photo by Jane Photog / CC BY-SA 4.0",
      "provider": "wikimedia",
      "width": 2048,
      "height": 1365,
      "mime": "image/jpeg"
    }
  ],
  "providerReports": [
    { "provider": "wikimedia", "ok": true, "count": 12, "timeMs": 340 },
    { "provider": "openverse", "ok": true, "count": 8, "timeMs": 520 }
  ],
  "warnings": []
}
curl -sS -X POST https://api.getwebfetch.com/v1/search \
  -H "Authorization: Bearer $WEBFETCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"drake portrait","licensePolicy":"safe-only"}'

#`POST /v1/artist` — artist images by kind

Auth: bearer key required. Cost: 1 fetch unit.

{
  "artist": "Taylor Swift",
  "kind": "portrait",
  "providers": ["spotify", "musicbrainz-caa", "wikimedia"],
  "licensePolicy": "safe-only"
}

kind"portrait" | "album" | "logo" | "performing". Defaults to "portrait". The adapter auto-picks the best providers per kind when providers is omitted.

Response shape is identical to /v1/search (candidates, providerReports, warnings).

curl -sS -X POST https://api.getwebfetch.com/v1/artist \
  -H "Authorization: Bearer $WEBFETCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"artist":"Taylor Swift","kind":"portrait","licensePolicy":"safe-only"}'

#`POST /v1/album` — canonical album artwork

Auth: bearer key required. Cost: 1 fetch unit.

{
  "artist": "Radiohead",
  "album": "In Rainbows",
  "minWidth": 1000
}

Returns the highest-resolution cover art available. Response shape: candidates, providerReports, warnings.

curl -sS -X POST https://api.getwebfetch.com/v1/album \
  -H "Authorization: Bearer $WEBFETCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"artist":"Radiohead","album":"In Rainbows","minWidth":1000}'

#`POST /v1/download` — fetch image bytes into the cloud cache

Auth: bearer key required. Cost: 2 fetch units.

{
  "url": "https://upload.wikimedia.org/wikipedia/commons/...",
  "maxBytes": 20971520
}

maxBytes defaults to 20 MB (20,971,520). Maximum 100 MB. Only image/* MIME types are accepted. The worker fetches the URL server-side, SHA-256-hashes the bytes, stores them in R2, and returns the hash as a stable cache key — identical bytes from different URLs collapse to the same key.

Response data:

{
  "url": "https://upload.wikimedia.org/wikipedia/...",
  "sha256": "4a9f3b...",
  "mime": "image/jpeg",
  "byteSize": 482019,
  "cacheKey": "4a9f3b..."
}
curl -sS -X POST https://api.getwebfetch.com/v1/download \
  -H "Authorization: Bearer $WEBFETCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"}'

#`POST /v1/probe` — list every image on a page with per-image license

Auth: bearer key required. Cost: 1 fetch unit.

{
  "url": "https://example.com/article",
  "respectRobots": true
}

respectRobots defaults to true. Returns an array of every image found on the page, each with an inferred license tag and confidence score.

curl -sS -X POST https://api.getwebfetch.com/v1/probe \
  -H "Authorization: Bearer $WEBFETCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://en.wikipedia.org/wiki/Drake_(musician)","respectRobots":true}'

#`POST /v1/license` — determine license for a specific URL

Auth: bearer key required. Cost: 1 fetch unit.

{
  "url": "https://upload.wikimedia.org/wikipedia/commons/...",
  "probe": false
}

probe: true also downloads and caches the image bytes (costs an additional R2 write). probe: false (default) is metadata-only and faster.

Response data:

{
  "license": "CC_BY_SA",
  "confidence": 0.95,
  "author": "Jane Photog",
  "attributionLine": "Photo by Jane Photog / CC BY-SA 4.0",
  "sourcePageUrl": "https://commons.wikimedia.org/...",
  "mime": "image/jpeg",
  "sha256": "4a9f3b...",
  "byteSize": 482019
}
curl -sS -X POST https://api.getwebfetch.com/v1/license \
  -H "Authorization: Bearer $WEBFETCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg","probe":false}'

#`POST /v1/similar` — reverse-image search

Auth: bearer key required. Cost: 3 fetch units. Plan: Pro or above.

{
  "url": "https://example.com/mystery.jpg",
  "providers": ["serpapi", "brave"]
}

providers is optional; omit to use all configured reverse-search providers. Requires at least one reverse-search provider key to be active on the platform side. Returns candidates, providerReports, warnings.

curl -sS -X POST https://api.getwebfetch.com/v1/similar \
  -H "Authorization: Bearer $WEBFETCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"}'

#`GET /providers` — list configured providers

Auth: none required on webfetch Cloud. Self-hosted servers require Authorization: Bearer <token>.

Returns all known provider IDs, the default provider set, which platform-managed provider keys are active on the Cloud side, and the metered endpoint list. When called with a valid session cookie the response also includes userPlan.

Response data:

{
  "all": ["wikimedia", "openverse", "unsplash", "pexels", "pixabay", "itunes",
          "musicbrainz-caa", "spotify", "youtube-thumb", "brave", "bing",
          "serpapi", "browser", "flickr", "internet-archive", "smithsonian",
          "nasa", "met-museum", "europeana", "library-of-congress",
          "wellcome-collection", "rawpixel", "burst", "europeana-archival"],
  "defaults": ["wikimedia", "openverse", "itunes", "musicbrainz-caa",
               "unsplash", "pexels", "pixabay", "spotify", "brave",
               "internet-archive", "smithsonian", "nasa", "met-museum",
               "flickr", "europeana", "library-of-congress",
               "wellcome-collection", "rawpixel", "burst"],
  "platformProvidersAvailable": true,
  "userPlan": null,
  "endpoints": [
    "/v1/search", "/v1/artist", "/v1/album", "/v1/download",
    "/v1/probe", "/v1/license", "/v1/similar"
  ]
}
curl -sS https://api.getwebfetch.com/providers

#`GET /health` — liveness probe

Auth: none required on webfetch Cloud. Self-hosted servers require Authorization: Bearer <token>.

curl -sS https://api.getwebfetch.com/health

Response:

{ "ok": true, "service": "webfetch-api", "env": "production" }

#Pricing & quota

Plan Price Fetches / period Period Rate limit /v1/similar
Free $0 100 daily 10 req/min no
Pro $19/mo 10,000 monthly 100 req/min yes
Team $79/mo 50,000 monthly 300 req/min yes
Enterprise contact us 1,000,000 monthly 1,000 req/min yes

Fetch unit costs by endpoint:

Endpoint Units
/v1/search, /v1/artist, /v1/album, /v1/probe, /v1/license 1
/v1/download 2
/v1/similar 3

Free plan has a hard daily cap — no overage charges, requests are rejected when the cap is hit. Pro and Team plans charge $0.015 and $0.010 per unit respectively for usage above the included amount.

Manage your subscription and view live usage at app.getwebfetch.com/billing. Full pricing details at getwebfetch.com/pricing.


#Common errors

#401 — missing or invalid key

{ "ok": false, "error": "unauthenticated" }

Confirm Authorization: Bearer wf_live_... is present and the key hasn't been revoked in app.getwebfetch.com/keys.

#402 — quota exhausted

{ "ok": false, "error": "quota exceeded" }

Free tier: resets at the next daily boundary (UTC midnight). Paid tiers: upgrade or wait for the monthly billing cycle to roll over.

#403 — endpoint not available on your plan

{ "ok": false, "error": "forbidden" }

/v1/similar requires Pro or above. Upgrade at app.getwebfetch.com/billing.

#429 — rate limit hit

{ "ok": false, "error": "rate limit exceeded" }

The response includes X-RateLimit-Reset (Unix seconds). Back off and retry after that timestamp. Limits are per API key.

#400 — validation error

{ "ok": false, "error": "query: Required" }

The error message includes the Zod field path. Fix the request body and retry.

#415 — non-image content type (download only)

{ "ok": false, "error": "disallowed content-type: text/html" }

/v1/download accepts only image/png, image/jpeg, image/webp, image/gif, image/avif, and image/svg+xml.

#500 — internal error

{ "ok": false, "error": "search failed" }

Retry once. If the error persists, open an issue.


#Full example: Node.js / TypeScript

const r = await fetch("https://api.getwebfetch.com/v1/search", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${process.env.WEBFETCH_KEY}`,
  },
  body: JSON.stringify({
    query: "kyoto temple",
    licensePolicy: "safe-only",
    minWidth: 1600,
  }),
});
const { ok, data, error } = await r.json();
if (!ok) throw new Error(error);
console.log(data.candidates[0]);

#Running the server (self-hosted)

bun run --cwd packages/server start
# or, Docker:
docker run -p 7600:7600 ghcr.io/ashlrai/webfetch server --host 0.0.0.0 --no-open

See Self-hosting for production deployment.