API Docs
Live
Pages
Terms
Images
LuisSearch API
Free, open search API. BM25 ranking with title + host name boosting. No API key required for public use. CORS enabled — call from anywhere.
https://luisearch.pages.dev
All endpoints return JSON. Responses include up to 200 results for search, 100 for image search.
How it works
Three components running 24/7 on a local machine, exposed via Cloudflare Tunnel.
1
Web Crawler
Starts from 500+ seed URLs and sitemaps. BFS traversal, stores title + content (5KB cap) into SQLite. Filters junk URLs (carts, logins, CDN assets).
2
BM25 + Host Boost
BM25 scoring (same as Elasticsearch). Title matches +3/term. Host name matches +10/term — search "docker" gets docs.docker.com first.
3
Image Crawler
Visits indexed pages, extracts <img> alt text. Separate images.db. Prioritizes image-rich hosts like Apple, The Verge, iFixit.
Stack: Python · SQLite · BM25 · Cloudflare Pages · Cloudflare Tunnel
Response Schema
Fields returned in each result object from /api/search.
idintegerInternal page ID in the index
urlstringFull URL of the indexed page
titlestringPage title from the HTML <title> tag
snippetstringFirst ~300 chars of page content
Fields from /api/image-search:
srcstringAbsolute image URL
altstringAlt text / image description
page_urlstringPage the image was found on
hoststringDomain the image came from
Error Codes
All errors return JSON: {"error": "message"}
400Missing required fields in /api/keys/register (name + email)
401Invalid or pending API key — not approved yet
401Admin endpoint called without valid session token
404Endpoint doesn't exist
500Server error — DB issue or crawler problem
Omitting the key entirely works fine — unauthenticated requests are allowed for all public endpoints.
Code Examples
Copy-paste snippets for common languages.
JavaScript
Python
Swift
curl
TypeScript
Go
Kotlin
async function search(query) { const res = await fetch( 'https://luisearch.pages.dev/api/search?q=' + encodeURIComponent(query) ); const { results } = await res.json(); results.forEach(r => console.log(r.title, r.url)); } search('docker compose');
import urllib.request, urllib.parse, json def search(query): url = ('https://luisearch.pages.dev/api/search?q=' + urllib.parse.quote(query)) with urllib.request.urlopen(url) as r: data = json.loads(r.read()) for result in data['results']: print(result['title'], result['url']) search('docker compose')
import Foundation func search(_ query: String) async throws { var comps = URLComponents(string: "https://luisearch.pages.dev/api/search")! comps.queryItems = [URLQueryItem(name:"q", value:query)] let (data, _) = try await URLSession.shared.data(from: comps.url!) let json = try JSONSerialization.jsonObject(with: data) as! [String:Any] let results = json["results"] as! [[String:Any]] results.forEach { print($0["title"]!, $0["url"]!) } } Task { try await search("macbook") }
curl "https://luisearch.pages.dev/api/search?q=docker" # Image search curl "https://luisearch.pages.dev/api/image-search?q=macbook" # Stats curl "https://luisearch.pages.dev/api/stats" # With API key curl "https://luisearch.pages.dev/api/search?q=linux&key=ls_..."
interface SearchResult { id: number; url: string; title: string; snippet: string; } async function search(query: string): Promise<SearchResult[]> { const res = await fetch( `https://luisearch.pages.dev/api/search?q=${encodeURIComponent(query)}` ); const { results } = await res.json(); return results; } const results = await search('typescript handbook');
package main import ( "encoding/json" "fmt" "net/http" "net/url" ) func search(query string) { u := "https://luisearch.pages.dev/api/search?q=" + url.QueryEscape(query) resp, _ := http.Get(u) defer resp.Body.Close() var data map[string]any json.NewDecoder(resp.Body).Decode(&data) for _, r := range data["results"].([]any) { m := r.(map[string]any) fmt.Println(m["title"], m["url"]) } }
Embed Widget
Drop a live search box into any page in ~10 lines.
Embed code
<input id="q" placeholder="Search..."> <div id="results"></div> <script> document.getElementById('q').addEventListener('keydown', async e => { if (e.key !== 'Enter') return; const res = await fetch( 'https://luisearch.pages.dev/api/search?q=' + encodeURIComponent(e.target.value) ).then(r => r.json()); document.getElementById('results').innerHTML = res.results.slice(0,5).map(r => `<div><a href="${r.url}">${r.title}</a><p>${r.snippet}</p></div>` ).join(''); }); </script>
FAQ
Is there a rate limit?
No hard rate limit right now. This runs on a personal machine, so please be reasonable. If you're doing bulk lookups, add a small delay between requests.
Do I need an API key?
No. All public endpoints work without a key. A key gives you attribution in query logs and may unlock higher priority in the future.
What sites are indexed?
Tech-focused content: Apple, GitHub, MDN, Wikipedia, Docker, Kubernetes, Cloudflare, Arch Linux, Python docs, and 500+ more seed domains. Hit /api/hosts to see every indexed domain with page counts.
How fresh is the index?
The crawler runs continuously adding new pages. Re-crawling existing pages to pick up changes isn't done yet — pages reflect the state they were in when first crawled.
Can I request a domain to be indexed?
Yes — just ask and it can be added to the seed list in the next crawler run. Any public website works.
Why does my query return a Base64 answer?
The server detects queries like "base64 of hello" or "encode xyz to base64" and returns the encoded value directly as a quick answer, without hitting the search index.
How does ranking work?
BM25 is the base score (same algorithm as Elasticsearch). On top of that: +3 per term that appears in the page title, and +10 per term that appears in the domain name. So searching "docker" surfaces docs.docker.com above a random blog post about Docker.
How many results does the API return?
/api/search returns up to 200 results per query. /api/image-search returns up to 100. The mobile and desktop UIs paginate these — showing 50 at a time with a Load More button.
What is the image index?
A separate crawler visits already-indexed pages and extracts <img> tags with meaningful alt text. Images are stored in a separate database and searchable via /api/image-search. It prioritizes image-rich sites like Apple, The Verge, and iFixit.
Is this open source?
The crawler, server, and frontend are all custom-built in Python and vanilla HTML/JS. It's a personal project — no external search libraries, no Elastic, no Solr.
How big is the index?
Currently ~60,000 pages, 285,000 unique terms, and growing. The SQLite database is around 700MB stored on an external SSD. Check /api/stats for live numbers.
Can I use this in a production app?
You can, but be aware it's a personal server with no uptime guarantee. The Cloudflare tunnel may occasionally restart. For anything critical, cache results on your side.
What is LuisDrive?
LuisDrive is a personal cloud storage service built into LuisSearch. You get 3 GB free. Create an account at /drive.html — upload files, organize into folders, share links, and access your files from any device. All files are stored on a private SSD.
How do I sign up for LuisDrive?
Go to luisearch.pages.dev/drive.html and tap "Create account." Choose a username and password — your files are isolated from other users. Tap "Sign in" on return visits. Your session token is stored locally in the browser.
Are there search operators?
Yes. Use site:github.com to limit results to a domain, intitle:keyword to require a word in the page title, and -word to exclude a term. Example: docker site:docs.docker.com -windows
Does LuisSearch collect personal data?
No accounts are required to search. Queries are not logged permanently. Drive accounts store only your username, a hashed password, and your files. Nothing is sold or shared.
Why is the server sometimes slow?
The backend runs on a single personal machine over a Cloudflare Tunnel. Occasionally the tunnel reconnects (1–3 second blip) or SQLite is busy with a crawl write. Results are usually under 200ms on a warm query.
Can I download my LuisDrive files?
Yes — tap the download icon on any file. You can also bulk-download multiple files at once by selecting them with long-press and tapping the download button in the toolbar. Downloads use a secure time-limited token in the URL so they work in any browser tab.
What file types can I upload to LuisDrive?
Any file type is accepted — images, videos, PDFs, code, archives, etc. Images and PDFs show an inline preview. The 3 GB quota applies to total storage across all your files.
Is there a file size limit for LuisDrive uploads?
Individual file size is limited by your browser and the Cloudflare Tunnel (practical max ~500 MB per file). Your total across all files is capped at 3 GB free storage.
How do I get a quick answer (calculator, converter)?
Type a math expression like 25 * 4 or a unit conversion like 100 f to c or 5 km to miles directly into the search bar. The answer appears as a quick-answer card above the results without hitting the index.
How do I search for images?
After running any search on the main site, tap the "Images" tab that appears below the search bar. Results pull from the image index via /api/image-search — they're sorted by relevance to the alt text of indexed images.
Can I bookmark or star search results?
Yes — tap the star (☆) icon on any result to save it. Saved results appear in the "Saved" section in the side panel. Bookmarks are stored in your browser's localStorage and persist across sessions.
Does the search support multiple languages?
BM25 is language-agnostic — you can search in any language the crawler has seen. The index is currently English-heavy since seed domains are mostly English tech sites, but results in other languages do appear when available. Note: many Wikipedia pages in the index are from non-English Wikipedia editions (Spanish, French, German, etc.), so some Wikipedia results may be in a different language.
What's the difference between the mobile and desktop sites?
The mobile site (/mobile.html) is touch-optimized: larger tap targets, safe-area padding for iPhone notches, swipeable image previews, and bottom-anchored navigation. Both hit the same API and show the same results.
What happens when the Cloudflare Tunnel URL changes?
The tunnel URL (trycloudflare.com) changes when the server restarts. When that happens, the BASE URL in the frontend scripts needs to be updated and the site redeployed. The public luisearch.pages.dev domain always stays the same for the static frontend.
Can I export search results?
Yes — on the desktop site there's an Export button in the toolbar that lets you download results as JSON, CSV, or a plain list of URLs. Useful for batch processing or saving a result set.
What features are coming soon?
A lot is in the works! Here's what's planned:

Search
✅ Images tab with 6,900+ indexed images
✅ Videos tab with 1,000+ YouTube videos
• Re-crawling pages to keep content fresh
• Spelling suggestions / "Did you mean?"
• Search filters (date, domain, content type)
• Related searches at the bottom of results
• News tab with recent articles

LuisDrive
• Public share links for files and folders
• Drag-and-drop folder uploads
• In-browser text/code editor
• Storage expansion beyond 3 GB
• File versioning / revision history

Platform
• iOS home screen app (PWA) with offline support
• API key dashboard to view your usage
• More seed domains and faster crawl speed
• Dedicated uptime monitoring page

Have a feature request? It can be added to the seed list or roadmap — just ask.
How does the 🖼 Images tab work?
As the web crawler indexes pages it extracts every image it finds and stores the URL, alt text, and source page in a separate images database. When you click Images and search, results are matched against the alt text. Over 6,900 images are currently indexed and the number grows as more pages are crawled.
How does the 🎬 Videos tab work?
A dedicated video crawler scrapes YouTube across 60+ topic queries — programming, science, math, education, and more — and stores video metadata (title, thumbnail, duration, channel) in a separate videos database. Over 1,000 videos are indexed. No videos are downloaded; tapping a result opens YouTube directly.
How many images and videos are indexed?
Live counts are shown right below the search bar on the homepage. You can also check the API directly:

/api/image-stats{"images": N}
/api/video-stats{"videos": N}

Currently: 6,950+ images and 1,043+ videos.