← Woolly

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.

GET/api/search?e=manufacturing&key=wk_…&q=cnc+machining
ParamWhat it is
erequiredYour engine id. It is the part after /e/ in your hosted URL. Here we use manufacturing.
keyrequiredYour 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.
qoptionalThe 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), and snippet.
  • snippet is an HTML string. Matched terms are wrapped in <b>; everything else is escaped, so it is safe to assign to innerHTML. 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.

GET/api/media?e=manufacturing&key=wk_…&q=lathe
ParamWhat it is
erequiredYour engine id.
qoptionalSorts images whose source page matches to the front, so media tracks the search. Without it you get the engine's images.
limitoptionalHow 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, its alt text, the page_url it appeared on, and that page_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. snippet is already safe HTML; if you inject title, host, or url as HTML too, escape them first, or set them with textContent like 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.