Files
direct-img.link/README.md

4.5 KiB

🔗 direct-img.link

Live images in markdown, powered by search.

Give your AI a system instruction to embed images using direct-img.link and they just work — no uploads, no APIs, no tokens.

Usage

![orange cat](https://direct-img.link/orange+cat)
![sunset at beach](https://direct-img.link/sunset+at+beach)
![current us president](https://direct-img.link/current+us+president)

That's it. The image is searched, cached, and served.

How It Works

  1. A request hits direct-img.link/<query>
  2. If cached (within 30 days) → serves the image instantly from edge
  3. If not cached → searches via image API → compresses to WebP → caches in R2 → serves

URL Format

Use + to separate words, just like Google:

https://direct-img.link/orange+cat
https://direct-img.link/new+york+city
Query URL
orange cat /orange+cat
spider-man /spider-man
u.s. president /u.s.+president
90's fashion /90%27s+fashion
"exact phrase" /%22exact+phrase%22

For AI System Prompts

Add this to your system prompt:

When including images in your markdown responses, use https://direct-img.link/<query>
as the image URL. Use + to separate words. Example: ![orange cat](https://direct-img.link/orange+cat)

Rate Limits

Global (Cloudflare WAF)

Applied to all requests before they hit any function:

Rule Limit Action
Global rate limit 60 requests/min per IP Block for 1 min
Burst protection 10 requests/10s per IP Challenge

Cache hits and new searches both count toward these limits.

New Searches (Cache Misses)

  • 10 new searches per day per IP (resets at midnight UTC)
  • Cache hits are unlimited (within WAF limits above)

Only fresh searches that call the image API count toward the daily limit. If your query is already cached by anyone, it's free.

Caching

  • Images are cached for 30 days
  • After expiry, the next request triggers a fresh search
  • This keeps time-sensitive queries (e.g. /us+president) reasonably current

Support

This is a free community service. Donations help cover API and infrastructure costs, and allow us to offer higher rate limits for everyone.

BTC Address: bc1qkqdmhk0we49qn74ua9752ysfxzd7uxqettymhv


Infrastructure

Cloudflare Resources

Resource Name Purpose
R2 Bucket direct-img-store Stores compressed WebP images
KV Namespace DIRECT_IMG_CACHE Query → cache existence + timestamp
KV Namespace DIRECT_IMG_RATE Per-IP daily new-search counter

R2: direct-img-store

Key is derived deterministically from the query — no need to store it in KV.

Key format: <sha256-of-normalized-query>.webp

Example: "orange cat"a1b2c3d4...ef.webp

All images stored as compressed WebP.

KV: DIRECT_IMG_CACHE

Confirms a cached image exists for a query. The R2 key is derived from the same query at request time.

Key: normalized query (lowercase, trimmed, spaces from +)

orange cat

Value:

{"t":1719000000}

t = unix timestamp when cached. Useful for debugging and cache-age headers.

TTL: 30 days (expirationTtl: 2592000) — KV auto-deletes expired keys. No cron needed.

Size: ~20 bytes per entry. Free tier (1 GB) supports millions of entries.

KV: DIRECT_IMG_RATE

Tracks daily new-search count per IP.

Key: <ip>:<YYYY-MM-DD>

192.168.1.1:2025-01-15

Value:

{"c":7}

c = count of new searches made today.

TTL: 48 hours (expirationTtl: 172800) — generous buffer past midnight, auto-cleanup.

Cloudflare WAF Rules (Dashboard)

Set manually in Security → WAF → Rate limiting rules:

  1. Global rate limit

    • Match: URI Path starts with /
    • Rate: 60 requests per 1 minute
    • Per: IP
    • Action: Block for 60 seconds
  2. Burst protection

    • Match: URI Path starts with /
    • Rate: 10 requests per 10 seconds
    • Per: IP
    • Action: Managed Challenge

Environment Variables / Secrets

Variable Description
BING_API_KEY Bing Image Search API subscription key

Stack

  • Cloudflare Pages — hosting + edge functions
  • Cloudflare R2 — image storage (zero egress fees)
  • Cloudflare KV — metadata cache + rate limiting
  • Cloudflare WAF — global rate limiting + DDoS protection
  • Bing Image Search API — image sourcing

direct-img.link — because ![](https://direct-img.link/thing) should just work.