Trawley
Integration guides

Claude (Anthropic SDK)

Give Claude a tool that searches your Trawley scraper via the Anthropic SDK.

This guide gives Claude a search_listings tool backed by Trawley's hybrid search endpoint, using the Anthropic TypeScript SDK and its tool-use loop.

bash
npm install @anthropic-ai/sdk

Define the tool

A tool is a name, a description, and a JSON Schema for its input.

ts
const searchListings = {
  name: 'search_listings',
  description:
    'Search live property listings from acmehomes.co.uk. Accepts a natural ' +
    'language query; constraints like price, bedrooms, or date are understood. ' +
    'Returns structured records.',
  input_schema: {
    type: 'object',
    properties: {
      query: {
        type: 'string',
        description: 'What to find, e.g. "3 bed houses under £500k with a garden"',
      },
    },
    required: ['query'],
  },
} as const

const TRAWLEY_SCRAPER_ID = 'scr_8f2a1c9e'

async function runSearch(query: string) {
  const params = new URLSearchParams({ search: query, take: '10' })
  const res = await fetch(
    `https://api.trawley.ai/v1/scrapers/${TRAWLEY_SCRAPER_ID}/hybrid?${params}`,
  )
  const { data } = await res.json()
  return data
}

Run the tool-use loop

Claude decides when to call the tool. When it does, run the search and send the result back so it can finish its answer.

ts
import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic()

const messages: Anthropic.MessageParam[] = [
  { role: 'user', content: 'Find a 3 bed house near Kendal with a garden under £500k.' },
]

while (true) {
  const response = await client.messages.create({
    model: 'claude-sonnet-4-5',
    max_tokens: 1024,
    tools: [searchListings],
    messages,
  })

  messages.push({ role: 'assistant', content: response.content })

  const toolUse = response.content.find((block) => block.type === 'tool_use')
  if (response.stop_reason !== 'tool_use' || !toolUse) {
    // No tool call — Claude has answered.
    const text = response.content.find((b) => b.type === 'text')
    console.log(text?.text)
    break
  }

  const results = await runSearch((toolUse.input as { query: string }).query)

  messages.push({
    role: 'user',
    content: [
      {
        type: 'tool_result',
        tool_use_id: toolUse.id,
        content: JSON.stringify(results),
      },
    ],
  })
}

The shape is always the same: send the tools, watch for a tool_use block, run your function, and return a tool_result with the matching tool_use_id. Claude loops until it has what it needs to answer.

Tips

  • Trim the records you return to the fields Claude needs. Smaller tool results are cheaper and keep the answer focused.
  • One query input is enough. Hybrid search interprets price, bedrooms, and dates from the query string, so you do not need a parameter per filter.
  • Return an empty array gracefully when a scraper has no completed run, so Claude can say nothing was found.

What's next