jetskibayConnect
← All tools

TOOLS / VISUAL COMMERCE

Inspired visual search.

Reads one photograph, cuts every separately purchasable object out of it, and returns retailer links for each — over MCP or HTTP.

Mock studioPublic information Protected execution
Six numbered cutout panels lifting away from a single photograph on a dark field
OverviewYour first readThe MCP endpointLimits, costs and honestyExamples & prompts

Inspired visual product search

Give Inspired one photograph. It names every separately purchasable object in the frame, cuts each one out on its own white background, and returns places to buy it.

That is three operations most image APIs make you assemble yourself: a reader that can tell a shirt from the vest worn over it, an editor that can rebuild the half of the shirt the vest was hiding, and a visual search that shops the rebuilt cutout rather than the original crowded photo.

What one call gives you

For a photograph of a person in an outfit, a living room, or a desk:

  • Up to six objects, each with a plain name (skirt, floor lamp, over-ear headphones) and a description of what is actually visible — colour, material, shape, pattern, the details you can see and the ones you cannot.
  • One product image per object, extracted individually by Pruna p-image-edit and centred on plain white, with whatever the occluding object hid rebuilt in the same material and finish.
  • Up to ten shopping matches per object, from Google Lens in product mode, ordered as Lens ordered them, with title, retailer link, price and image.
  • A mood board: the cutouts composed into one square image.

The subject is a parameter

The same three moves work on anything photographed. domain selects what to look for, and with it the reading prompt, the JSON schema the reader fills, and the extraction instruction:

| domain | Looks for | Aliases | | --- | --- | --- | | fashion (default) | Garments, shoes, bags, jewellery worn by a person | clothing, clothes, outfit, apparel, style | | furniture | Sofas, chairs, tables, lighting, rugs, decor | interior, home | | devices | Phones, laptops, audio, cameras, appliances | tech, electronics | | beauty | Skincare, makeup, fragrance, haircare, tools | — | | everything | Any distinctly purchasable object | any, all |

focus narrows a domain with up to 160 characters of your own words — domain=everything, focus=kitchen appliances only.

Two ways to call it

MCP, at https://inspired.jetskibay.com/mcp, is the executable endpoint an agent runtime connects to. Three tools: list_focus_domains, start_visual_search, get_visual_search. Authenticated with your API key as a bearer token.

HTTP, at https://inspired.jetskibay.com/api/v1, is the same three capabilities for anything that is not an agent, plus a synchronous variant and key management. Both surfaces mint the same jobs from the same code, so neither can do something the other cannot.

What it does not do

  • It does not verify. No stage re-checks an extracted object against the original photo. Every object is returned as spotted and every match as visually similar, not as confirmed.
  • It does not read brands. A brand is only named when a brand mark is clearly legible; otherwise the description stays descriptive.
  • It does not guess. A photo with nothing clearly enough visible to shop for returns an empty list, and that is a successful read — it still costs one request.
  • It is not fast. A full read is minutes, not seconds: one vision call, one extraction per object run strictly one at a time, and up to two searches in flight. Start a job and poll it.
  • It does not accept image URLs. Send bytes, or a public Pinterest pin URL. There is no parameter that would make this an open image proxy.

Where the work happens

Cloudflare Workers, with a Cloudflare Workflow as the durable job. The reading call goes to OpenAI, extraction to Pruna p-image-edit, search to Google Lens through SerpAPI. Uploads, cutouts and results live in Cloudflare R2 for the life of the job. Keys are stored only as a SHA-256 digest, and the email address a key was minted against is stored only as a keyed digest.

Make it your own.

EXAMPLES & PROMPTS
#!/usr/bin/env bash
# One whole read over MCP, with curl and nothing else.
#
# The Streamable HTTP transport is plain POSTed JSON-RPC, so a shell is enough
# to prove a connection works before you wire up a runtime.
#
# Needs: INSPIRED_KEY (your API key) and a photograph.
#   export INSPIRED_KEY=isk_your_key_here
#   ./mcp-session.sh living-room.jpg furniture

set -euo pipefail

MCP_URL="${MCP_URL:-https://inspired.jetskibay.com/mcp}"
PHOTO="${1:?usage: mcp-session.sh <photo> [domain]}"
DOMAIN="${2:-fashion}"
: "${INSPIRED_KEY:?set INSPIRED_KEY to your Inspired API key}"

call() {
  curl -sS "$MCP_URL" \
    -H "Authorization: Bearer $INSPIRED_KEY" \
    -H 'content-type: application/json' \
    -H 'accept: application/json' \
    --data-binary @-
}

# 1. Handshake. The server answers with the one protocol revision it implements.
printf '\n== initialize ==\n'
call <<'JSON'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
  "protocolVersion":"2026-07-28",
  "capabilities":{},
  "clientInfo":{"name":"mcp-session.sh","version":"1.0.0"}}}
JSON

# A notification has no id, so the server answers 202 with no body.
call >/dev/null <<'JSON'
{"jsonrpc":"2.0","method":"notifications/initialized"}
JSON

# 2. Discovery. Free, but still authenticated: there is no anonymous listing.
printf '\n== tools/list ==\n'
call <<'JSON'
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
JSON

printf '\n== list_focus_domains ==\n'
call <<'JSON'
{"jsonrpc":"2.0","id":3,"method":"tools/call",
 "params":{"name":"list_focus_domains","arguments":{}}}
JSON

# 3. Start the read. This is the call that spends one request.
#    The image travels as base64 inside the JSON-RPC arguments.
printf '\n== start_visual_search ==\n'
case "$PHOTO" in
  *.png) MEDIA_TYPE=image/png ;;
  *.webp) MEDIA_TYPE=image/webp ;;
  *.gif) MEDIA_TYPE=image/gif ;;
  *) MEDIA_TYPE=image/jpeg ;;
esac
IMAGE_B64="$(base64 < "$PHOTO" | tr -d '\n')"

STARTED="$(
  jq -nc --arg image "$IMAGE_B64" --arg type "$MEDIA_TYPE" --arg domain "$DOMAIN" \
    '{jsonrpc:"2.0",id:4,method:"tools/call",params:{
       name:"start_visual_search",
       arguments:{image:$image,imageMediaType:$type,domain:$domain}}}' | call
)"
echo "$STARTED" | jq '.result.structuredContent'

REQUEST_ID="$(echo "$STARTED" | jq -r '.result.structuredContent.requestId')"
if [ "$REQUEST_ID" = "null" ]; then
  echo "the read did not start; the tool error above says why" >&2
  exit 1
fi

# 4. Collect it. Polling is free, so the only cost of a slow read is patience.
printf '\n== get_visual_search ==\n'
for _ in $(seq 1 60); do
  STATE="$(
    jq -nc --arg id "$REQUEST_ID" \
      '{jsonrpc:"2.0",id:5,method:"tools/call",params:{
         name:"get_visual_search",arguments:{requestId:$id}}}' | call
  )"
  STATUS="$(echo "$STATE" | jq -r '.result.structuredContent.status')"
  echo "  $STATUS"
  if [ "$STATUS" = "complete" ]; then
    echo "$STATE" | jq -r '.result.content[0].text'
    exit 0
  fi
  if [ "$(echo "$STATE" | jq -r '.result.isError')" = "true" ]; then
    echo "$STATE" | jq -r '.result.content[0].text' >&2
    exit 1
  fi
  sleep "$(echo "$STATE" | jq -r '(.result.structuredContent.pollAfterMs // 3000) / 1000')"
done

echo "still running after 60 polls; the requestId stays valid: $REQUEST_ID" >&2
exit 1
Open raw example

Connect your workflow.

MCP · Bearer tokenhttps://inspired.jetskibay.com/mcp

Streamable HTTP MCP. Three tools: list_focus_domains, start_visual_search, get_visual_search.

REST · Bearer tokenhttps://inspired.jetskibay.com/api/v1

The same pipeline over JSON: POST /jobs to start a read, GET /jobs/{id} to collect it.

Published by Inspired. Fictional brand, working prototype.