Developer docs
Embed your engine
Every Woolly engine comes with a hosted search page at woolly.run/e/your-id. This is the other way in. The same index, over a plain JSON API, so you can build search into your own site with your markup, your styles, your layout. Two GET endpoints, no API key, no SDK.
01 Search API
Full-text search across one engine. Returns ranked results as JSON.
| Param | What it is |
|---|---|
| erequired | Your engine id. It is the part after /e/ in your hosted URL. Here we use manufacturing. |
| keyrequired | Your engine's search key, from your dashboard. It reads that one engine and nothing else, so it is fine in front-end code. You can also send it as an X-Woolly-Key header. |
| qoptional | The search query. URL-encode it. Leave it off to get engine info and total with an empty results array, handy for an empty state. |
Response
{
"engine": {
"id": "manufacturing",
"name": "Manufacturing",
"color": "#F2A63C",
"tagline": "Machining, materials, and the shop floor."
},
"total": 48213,
"matches": 137,
"results": [
{
"url": "https://example.com/cnc-basics",
"host": "example.com",
"title": "CNC machining, from stock to part",
"image": "https://example.com/cover.jpg",
"snippet": "A practical intro to <b>CNC</b> <b>machining</b>."
}
]
}- engine carries the id, name, accent
color, and tagline, so you can label your UI to match. - total is how many pages the engine has indexed. matches is how many matched
q, which can be far more than you get back. - results is the top 20, best first. Each has
url,host,title,image(a URL, or""when there is none), andsnippet. - snippet is an HTML string. Matched terms are wrapped in
<b>; everything else is escaped, so it is safe to assign toinnerHTML. The other fields are plain text.
Fetch and render
Vanilla JavaScript. Builds each result as a node, links the title to result.url, and drops the snippet in as HTML. Paste your own key into the browser bar with a query to see the raw JSON first.
// expects <ul id="results"></ul> somewhere on the page
const ENGINE = 'manufacturing'
const KEY = 'wk_your_search_key' // dashboard → your engine
async function search(q) {
const url = `https://woolly.run/api/search?e=${ENGINE}&key=${KEY}&q=${encodeURIComponent(q)}`
const res = await fetch(url)
const { results } = await res.json()
const list = document.querySelector('#results')
list.innerHTML = ''
for (const r of results) {
const li = document.createElement('li')
const link = document.createElement('a')
link.href = r.url
link.textContent = r.title // plain text, set the safe way
const host = document.createElement('span')
host.textContent = r.host
const snippet = document.createElement('p')
snippet.innerHTML = r.snippet // HTML: escaped text with <b> on matches
li.append(link, host, snippet)
list.append(li)
}
}
search('cnc machining')02 Media API
Images the crawler found across the engine, with the page each came from. Same shape of call.
| Param | What it is |
|---|---|
| erequired | Your engine id. |
| qoptional | Sorts images whose source page matches to the front, so media tracks the search. Without it you get the engine's images. |
| limitoptional | How many to return. Default 60, max 120. |
Response
{
"images": [
{
"src": "https://example.com/lathe.jpg",
"alt": "A CNC lathe mid-cut",
"page_url": "https://example.com/lathe-guide",
"page_title": "Working with a CNC lathe"
}
]
}- Each image has
src, itsalttext, thepage_urlit appeared on, and thatpage_title.
03 Drop-in example
A search box and a results list you can paste straight into any page. Nothing to install. Swap the engine id, the markup, and the styles for your own.
<!-- Woolly search in your page. Your markup, your styles. -->
<form id="ws">
<input name="q" placeholder="Search manufacturing" autocomplete="off" />
<button type="submit">Search</button>
</form>
<ul id="ws-results"></ul>
<style>
#ws { display: flex; gap: 8px; max-width: 480px; }
#ws input { flex: 1; padding: 8px 10px; }
#ws-results { list-style: none; padding: 0; }
#ws-results li { margin: 16px 0; }
#ws-results a { font-weight: 600; text-decoration: none; }
#ws-results .host { color: #888; font-size: 13px; margin-left: 8px; }
#ws-results b { background: #fde68a; }
</style>
<script>
const ENGINE = 'manufacturing'
const KEY = 'wk_your_search_key' // dashboard → your engine
const form = document.querySelector('#ws')
const out = document.querySelector('#ws-results')
form.addEventListener('submit', async (e) => {
e.preventDefault()
const q = form.q.value.trim()
if (!q) return
const url = `https://woolly.run/api/search?e=${ENGINE}&key=${KEY}&q=${encodeURIComponent(q)}`
const { results } = await (await fetch(url)).json()
out.innerHTML = results.map((r) => `
<li>
<a href="${r.url}">${r.title}</a>
<span class="host">${r.host}</span>
<p>${r.snippet}</p>
</li>`).join('')
})
</script>- The compact version above builds each row from a template string.
snippetis already safe HTML; if you injecttitle,host, orurlas HTML too, escape them first, or set them withtextContentlike the fetch example does.
Would rather not build the UI? Every engine has a hosted page at /e/your-id. This API is for when you want the results under your own design instead. Back to Woolly.