Visual product matching with image scope + Claude vision
Find images, identify them, structure the metadata — all in one script.
Prerequisites
- $
pip install anthropic requests - $
export SIFTQ_API_KEY=mk-... - $
export ANTHROPIC_API_KEY=sk-ant-...
How it works
Most search APIs treat images as a separate product with its own auth and shape. SiftQ's image scope returns canonical image URLs alongside dimensions; Claude's vision capability handles the identification half. Chain them and you have a complete "describe-to-classified-metadata" pipeline in 50 lines — useful for product matching, asset discovery, moderation pipelines, and visual research agents.
The recipe
import os
import json
import base64
import requests
import anthropic
claude = anthropic.Anthropic()
SIFTQ_KEY = os.environ["SIFTQ_API_KEY"]
def find_images(description: str, k: int = 8) -> list[dict]:
r = requests.post(
"https://api.siftq.com/v1/search",
headers={"Authorization": f"Bearer {SIFTQ_KEY}"},
json={"q": description, "scope": "image", "size": str(k)},
timeout=15,
)
r.raise_for_status()
return r.json().get("images", [])
def identify(image_url: str) -> dict:
"""Claude vision: classify and structure metadata from one image."""
try:
img = requests.get(image_url, timeout=10).content
except Exception as e:
return {"error": f"fetch failed: {e}"}
msg = claude.messages.create(
model="claude-sonnet-4-6",
max_tokens=600,
messages=[{
"role": "user",
"content": [
{"type": "image", "source": {
"type": "base64",
"media_type": "image/jpeg",
"data": base64.b64encode(img).decode(),
}},
{"type": "text", "text": (
"Return ONLY a JSON object with these keys: "
"subject (string), dominant_colors (array of 3 hex), "
"style (string), era (string), confidence (0-1)."
)},
],
}],
)
try:
return json.loads(msg.content[0].text)
except json.JSONDecodeError:
return {"error": "non-json response", "raw": msg.content[0].text}
def pipeline(query: str) -> list[dict]:
hits = find_images(query)
results = []
for h in hits:
meta = identify(h["imageUrl"])
results.append({
"url": h["imageUrl"],
"dims": f"{h.get('imageWidth')}×{h.get('imageHeight')}",
**meta,
})
return results
if __name__ == "__main__":
out = pipeline("vintage 1970s minimalist serif typography poster")
print(json.dumps(out, indent=2))
Variations
Upload an image you have, ask Claude to describe it, feed that description into SiftQ image scope. Effectively reverse-image search.
Run all identify() calls concurrently with anyio or asyncio.gather() — 10x speedup on a 10-image batch.
After identification, embed the subject strings (Cohere / Voyage / Jina) and cluster — auto-discovers categories in the result set.