1

Authenticate

Get an access token using your credentials

2

Index a Product

Send a product image to create a searchable embedding

3

Search by Text

Find products using a natural language description

You're Ready!

Your visual search is working. Explore the full API.

1 Register Your Account Portal admin only

Developer? You don't need this step. Trooply accounts are created by the portal admin (the business owner who pays for the plan). Your admin adds you as a Developer user in Portal → Team, then shares an email + password with you. Skip to Step 2 to use those credentials. The steps below are only relevant when a new company signs up.

The portal admin creates a free account at /signup to get the account's API credentials. After registration they receive a verification email. Clicking the link in that email activates the account (or call the /verify?token=xxx endpoint directly).

import httpx

# Step 1a: Register a new account
response = httpx.post(
    "https://search.trooply.ai/register",
    json={
        "client_name": "My E-Commerce App",
        "email": "[email protected]",
        "password": "secureP@ssw0rd!"
    }
)
data = response.json()
print("Registration successful:", data)

# Step 1b: Verify your email (use the token from the email link)
verify = httpx.get("https://search.trooply.ai/verify?token=YOUR_VERIFICATION_TOKEN")
print("Verification:", verify.json())
// Step 1a: Register a new account
const response = await fetch('https://search.trooply.ai/register', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        client_name: 'My E-Commerce App',
        email: '[email protected]',
        password: 'secureP@ssw0rd!'
    })
});
const data = await response.json();
console.log('Registration successful:', data);

// Step 1b: Verify your email (use the token from the email link)
const verify = await fetch(
    'https://search.trooply.ai/verify?token=YOUR_VERIFICATION_TOKEN'
);
console.log('Verification:', await verify.json());
# Step 1a: Register a new account
curl -X POST https://search.trooply.ai/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My E-Commerce App",
    "email": "[email protected]",
    "password": "secureP@ssw0rd!"
  }'

# Step 1b: Verify your email
curl "https://search.trooply.ai/verify?token=YOUR_VERIFICATION_TOKEN"
Check your email for the verification link. Your account will remain inactive until you verify. You can verify programmatically at /verify?token=xxx.

2 Get Your Credentials

After logging in to this dashboard, navigate to the Credentials tab in the sidebar. You will find your Client ID and Client Secret there. The Client Secret is displayed only once when it is generated — copy it and store it in a secure location (e.g., environment variable or secrets manager). If you lose it, you can regenerate a new one but the old secret will be immediately invalidated.

import os

# Store credentials securely as environment variables
# export VISUAL_SEARCH_CLIENT_ID="your_client_id_here"
# export VISUAL_SEARCH_CLIENT_SECRET="your_client_secret_here"

CLIENT_ID = os.environ["VISUAL_SEARCH_CLIENT_ID"]
CLIENT_SECRET = os.environ["VISUAL_SEARCH_CLIENT_SECRET"]

print(f"Client ID loaded: {CLIENT_ID[:8]}...")
print("Client Secret loaded: ****")
// Store credentials securely as environment variables
// In your .env file:
//   VISUAL_SEARCH_CLIENT_ID=your_client_id_here
//   VISUAL_SEARCH_CLIENT_SECRET=your_client_secret_here

const CLIENT_ID = process.env.VISUAL_SEARCH_CLIENT_ID;
const CLIENT_SECRET = process.env.VISUAL_SEARCH_CLIENT_SECRET;

console.log(`Client ID loaded: ${CLIENT_ID.slice(0, 8)}...`);
console.log('Client Secret loaded: ****');
# Your credentials are found on the Credentials page of this dashboard.
#
# Client ID:     Visible at all times on the Credentials page
# Client Secret: Shown ONCE when generated — store it securely
#
# For the OAuth 2.0 flow you will use:
#   client_id     = YOUR_CLIENT_ID
#   client_secret  = YOUR_CLIENT_SECRET
#
# Example: export them as environment variables
export VISUAL_SEARCH_CLIENT_ID="your_client_id_here"
export VISUAL_SEARCH_CLIENT_SECRET="your_client_secret_here"
Your Client Secret is shown only once. Store it securely. If you lose it, regenerate a new one on the Credentials page.

3 Authenticate with OAuth 2.0

There are two ways to obtain an access token. Use Client Credentials Grant for machine-to-machine (server-side) integration, or Email/Password Login for user-facing flows. The returned token expires in 3600 seconds (1 hour). Include it as Authorization: Bearer TOKEN on all subsequent API requests.

Method A: Client Credentials Grant (recommended for servers)

import httpx

# --- Method A: Client Credentials Grant (machine-to-machine) ---
response = httpx.post(
    "https://search.trooply.ai/oauth/token",
    data={
        "grant_type": "client_credentials",
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_CLIENT_SECRET"
    },
    headers={"Content-Type": "application/x-www-form-urlencoded"}
)
token_data = response.json()
access_token = token_data["access_token"]
print(f"Token obtained, expires in {token_data['expires_in']}s")

# --- Method B: Email / Password Login ---
response = httpx.post(
    "https://search.trooply.ai/oauth/login",
    json={
        "email": "[email protected]",
        "password": "secureP@ssw0rd!"
    }
)
token_data = response.json()
access_token = token_data["access_token"]
print(f"Logged in as {token_data.get('client_name')}")

# Use the token for all subsequent requests
headers = {"Authorization": f"Bearer {access_token}"}
// --- Method A: Client Credentials Grant (machine-to-machine) ---
const tokenRes = await fetch('https://search.trooply.ai/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: 'YOUR_CLIENT_ID',
        client_secret: 'YOUR_CLIENT_SECRET'
    })
});
const tokenData = await tokenRes.json();
let accessToken = tokenData.access_token;
console.log(`Token obtained, expires in ${tokenData.expires_in}s`);

// --- Method B: Email / Password Login ---
const loginRes = await fetch('https://search.trooply.ai/oauth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        email: '[email protected]',
        password: 'secureP@ssw0rd!'
    })
});
const loginData = await loginRes.json();
accessToken = loginData.access_token;
console.log(`Logged in as ${loginData.client_name}`);

// Use the token for all subsequent requests
const headers = { 'Authorization': `Bearer ${accessToken}` };
# --- Method A: Client Credentials Grant (machine-to-machine) ---
curl -X POST https://search.trooply.ai/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET"

# --- Method B: Email / Password Login ---
curl -X POST https://search.trooply.ai/oauth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "secureP@ssw0rd!"
  }'

# Both return: { "access_token": "eyJ...", "token_type": "bearer", "expires_in": 3600 }
# Use the token on all subsequent requests:
# -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
The access token expires in 3600 seconds. Include Authorization: Bearer TOKEN on every subsequent API request.

4 Index Your First Product

Add a product to the visual search index by sending its image URL and metadata. The API will download the image, extract a CLIP embedding, and store it in the vector database for instant visual similarity search.

import httpx

headers = {"Authorization": f"Bearer {access_token}"}

response = httpx.post(
    "https://search.trooply.ai/v1/products",
    headers=headers,
    json={
        "product_id": "shoe-001",
        "image_url": "https://example.com/blue-sneaker.jpg",
        "metadata": {
            "name": "Blue Running Sneaker",
            "category": "footwear",
            "brand": "SportMax",
            "price": 89.99,
            "color_name": "blue",
            "tags": ["running", "sports", "casual"]
        }
    }
)

result = response.json()
print(result)
# {
#   "status": "indexed",
#   "product_id": "shoe-001",
#   "vector_id": "...",
#   "message": "Product image indexed successfully"
# }
const response = await fetch('https://search.trooply.ai/v1/products', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        product_id: 'shoe-001',
        image_url: 'https://example.com/blue-sneaker.jpg',
        metadata: {
            name: 'Blue Running Sneaker',
            category: 'footwear',
            brand: 'SportMax',
            price: 89.99,
            color_name: 'blue',
            tags: ['running', 'sports', 'casual']
        }
    })
});

const result = await response.json();
console.log(result);
// {
//   status: 'indexed',
//   product_id: 'shoe-001',
//   vector_id: '...',
//   message: 'Product image indexed successfully'
// }
curl -X POST https://search.trooply.ai/v1/products \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "shoe-001",
    "image_url": "https://example.com/blue-sneaker.jpg",
    "metadata": {
        "name": "Blue Running Sneaker",
        "category": "footwear",
        "brand": "SportMax",
        "price": 89.99,
        "color_name": "blue",
        "tags": ["running", "sports", "casual"]
    }
  }'

# Response:
# {
#   "status": "indexed",
#   "product_id": "shoe-001",
#   "vector_id": "...",
#   "message": "Product image indexed successfully"
# }

5 Bulk Index Products

Index many products at once using the bulk endpoint. Provide a list of products and an optional callback_url to receive a webhook when the job finishes. The response includes a job_id that you can poll with GET /v1/jobs/{job_id}. You can also use POST /v1/products/demo to install demo products for quick testing.

import httpx
import time

headers = {"Authorization": f"Bearer {access_token}"}

# Bulk index multiple products
response = httpx.post(
    "https://search.trooply.ai/v1/products/bulk",
    headers=headers,
    json={
        "products": [
            {
                "product_id": "prod-001",
                "image_url": "https://example.com/red-dress.jpg",
                "metadata": {"name": "Red Summer Dress", "category": "dresses"}
            },
            {
                "product_id": "prod-002",
                "image_url": "https://example.com/black-jacket.jpg",
                "metadata": {"name": "Black Leather Jacket", "category": "outerwear"}
            }
        ],
        "callback_url": "https://your-server.com/webhook"
    },
    timeout=60.0
)

result = response.json()
job_id = result["job_id"]
print(f"Bulk job started: {job_id}")

# Poll for job completion
while True:
    status = httpx.get(
        f"https://search.trooply.ai/v1/jobs/{job_id}",
        headers=headers
    ).json()
    print(f"Status: {status['status']}")
    if status["status"] in ("completed", "failed"):
        break
    time.sleep(2)

# Alternatively, install demo products for testing
demo = httpx.post(
    "https://search.trooply.ai/v1/products/demo",
    headers=headers
)
print("Demo products:", demo.json())
// Bulk index multiple products
const bulkRes = await fetch('https://search.trooply.ai/v1/products/bulk', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        products: [
            {
                product_id: 'prod-001',
                image_url: 'https://example.com/red-dress.jpg',
                metadata: { name: 'Red Summer Dress', category: 'dresses' }
            },
            {
                product_id: 'prod-002',
                image_url: 'https://example.com/black-jacket.jpg',
                metadata: { name: 'Black Leather Jacket', category: 'outerwear' }
            }
        ],
        callback_url: 'https://your-server.com/webhook'
    })
});

const bulkData = await bulkRes.json();
const jobId = bulkData.job_id;
console.log(`Bulk job started: ${jobId}`);

// Poll for job completion
const pollJob = async (id) => {
    while (true) {
        const res = await fetch(`https://search.trooply.ai/v1/jobs/${id}`, {
            headers: { 'Authorization': `Bearer ${accessToken}` }
        });
        const status = await res.json();
        console.log(`Status: ${status.status}`);
        if (status.status === 'completed' || status.status === 'failed') return status;
        await new Promise(r => setTimeout(r, 2000));
    }
};
await pollJob(jobId);

// Alternatively, install demo products for testing
const demoRes = await fetch('https://search.trooply.ai/v1/products/demo', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${accessToken}` }
});
console.log('Demo products:', await demoRes.json());
# Bulk index multiple products
curl -X POST https://search.trooply.ai/v1/products/bulk \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "products": [
        {
            "product_id": "prod-001",
            "image_url": "https://example.com/red-dress.jpg",
            "metadata": {"name": "Red Summer Dress", "category": "dresses"}
        },
        {
            "product_id": "prod-002",
            "image_url": "https://example.com/black-jacket.jpg",
            "metadata": {"name": "Black Leather Jacket", "category": "outerwear"}
        }
    ],
    "callback_url": "https://your-server.com/webhook"
  }'

# Response: { "job_id": "job-abc-123", "status": "processing", "total": 2 }

# Poll for job completion
curl https://search.trooply.ai/v1/jobs/job-abc-123 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Install demo products for testing
curl -X POST https://search.trooply.ai/v1/products/demo \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

6 Search by Image

Find visually similar products using an image. You have two options: search by providing an image URL, or upload an image file directly. Both endpoints accept limit (max results) and threshold (minimum similarity score 0-1).

import httpx

headers = {"Authorization": f"Bearer {access_token}"}

# Option A: Search by image URL
response = httpx.post(
    "https://search.trooply.ai/v1/search/url",
    headers=headers,
    json={
        "image_url": "https://example.com/query-sneaker.jpg",
        "limit": 10,
        "threshold": 0.5
    }
)
results = response.json()
print(f"Found {results['count']} results in {results['query_time_ms']}ms")
for item in results["results"]:
    print(f"  {item['product_id']}: {item['similarity_score']:.2f}")

# Option B: Search by uploading an image file
with open("query-image.jpg", "rb") as f:
    upload_response = httpx.post(
        "https://search.trooply.ai/v1/search",
        headers=headers,
        files={"image": ("query-image.jpg", f, "image/jpeg")},
        data={"limit": "10", "threshold": "0.5"}
    )
upload_results = upload_response.json()
print(f"Upload search found {upload_results['count']} results")
// Option A: Search by image URL
const urlRes = await fetch('https://search.trooply.ai/v1/search/url', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        image_url: 'https://example.com/query-sneaker.jpg',
        limit: 10,
        threshold: 0.5
    })
});
const urlResults = await urlRes.json();
console.log(`Found ${urlResults.count} results in ${urlResults.query_time_ms}ms`);
urlResults.results.forEach(item => {
    console.log(`  ${item.product_id}: ${item.similarity_score.toFixed(2)}`);
});

// Option B: Search by uploading an image file
const formData = new FormData();
formData.append('image', fileInput.files[0]);  // from an <input type="file">
formData.append('limit', '10');
formData.append('threshold', '0.5');

const uploadRes = await fetch('https://search.trooply.ai/v1/search', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${accessToken}` },
    body: formData
});
const uploadResults = await uploadRes.json();
console.log(`Upload search found ${uploadResults.count} results`);
# Option A: Search by image URL
curl -X POST https://search.trooply.ai/v1/search/url \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://example.com/query-sneaker.jpg",
    "limit": 10,
    "threshold": 0.5
  }'

# Response:
# {
#   "query_time_ms": 45,
#   "count": 5,
#   "results": [
#     {"product_id": "shoe-001", "similarity_score": 0.95, "metadata": {...}}
#   ]
# }

# Option B: Search by uploading an image file
curl -X POST https://search.trooply.ai/v1/search \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -F "[email protected]" \
  -F "limit=10" \
  -F "threshold=0.5"

7 Search by Text

Search for products using natural language descriptions. The API uses the CLIP model to convert your text query into a visual embedding and then finds products with visually similar images. This works best with descriptive terms like colors, shapes, styles, and object types.

import httpx

headers = {"Authorization": f"Bearer {access_token}"}

response = httpx.post(
    "https://search.trooply.ai/v1/search/text",
    headers=headers,
    json={
        "query": "red summer dress",
        "limit": 10,
        "threshold": 0.3
    }
)

results = response.json()
print(f"Found {results['count']} results in {results['query_time_ms']}ms")
for item in results["results"]:
    meta = item.get("metadata", {})
    print(f"  {item['product_id']}: {item['similarity_score']:.2f} - {meta.get('name', 'N/A')}")
const response = await fetch('https://search.trooply.ai/v1/search/text', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        query: 'red summer dress',
        limit: 10,
        threshold: 0.3
    })
});

const results = await response.json();
console.log(`Found ${results.count} results in ${results.query_time_ms}ms`);
results.results.forEach(item => {
    const name = item.metadata?.name || 'N/A';
    console.log(`  ${item.product_id}: ${item.similarity_score.toFixed(2)} - ${name}`);
});
curl -X POST https://search.trooply.ai/v1/search/text \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "red summer dress",
    "limit": 10,
    "threshold": 0.3
  }'

# The CLIP model converts "red summer dress" into a visual embedding
# and returns products whose images visually match that description.

8 Hybrid & Advanced Search

Combine multiple search strategies for more precise results. The platform offers several advanced search modes:

  • Hybrid SearchPOST /v1/search/hybrid — Combine image + text + color filters + metadata filters with a configurable visual_weight.
  • Multi-Image SearchPOST /v1/search/multi-image — Search with multiple reference images. Aggregation modes: average, union, best.
  • Crop SearchPOST /v1/search/crop — Search using a cropped region of an image.
  • Filtered SearchPOST /v1/search/filtered — Visual search with Qdrant payload filters.
  • Batch SearchPOST /v1/search/batch — Run up to 20 queries in one request.

Below is an example of Hybrid Search:

import httpx

headers = {"Authorization": f"Bearer {access_token}"}

response = httpx.post(
    "https://search.trooply.ai/v1/search/hybrid",
    headers=headers,
    json={
        "image_url": "https://example.com/dress.jpg",
        "query": "summer casual",
        "colors": ["#ff0000", "#ffffff"],
        "metadata_filters": [
            {"field": "category", "operator": "eq", "value": "dresses"}
        ],
        "visual_weight": 0.7,
        "limit": 10
    }
)

results = response.json()
print(f"Hybrid search: {results['count']} results in {results['query_time_ms']}ms")
for item in results["results"]:
    print(f"  {item['product_id']}: {item['similarity_score']:.2f}")
const response = await fetch('https://search.trooply.ai/v1/search/hybrid', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        image_url: 'https://example.com/dress.jpg',
        query: 'summer casual',
        colors: ['#ff0000', '#ffffff'],
        metadata_filters: [
            { field: 'category', operator: 'eq', value: 'dresses' }
        ],
        visual_weight: 0.7,
        limit: 10
    })
});

const results = await response.json();
console.log(`Hybrid search: ${results.count} results in ${results.query_time_ms}ms`);
results.results.forEach(item => {
    console.log(`  ${item.product_id}: ${item.similarity_score.toFixed(2)}`);
});
curl -X POST https://search.trooply.ai/v1/search/hybrid \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://example.com/dress.jpg",
    "query": "summer casual",
    "colors": ["#ff0000", "#ffffff"],
    "metadata_filters": [
        {"field": "category", "operator": "eq", "value": "dresses"}
    ],
    "visual_weight": 0.7,
    "limit": 10
  }'

NEW Multi-Modal Fusion Search

Combine image and text embeddings for powerful refined search. Provide an image for visual context and text to refine attributes.

  • Fusion SearchPOST /v1/search/fusion — Blend image + text with configurable image_weight (0-1).
  • Pre-filters — All search endpoints now support category, price_min, price_max, color filters.
  • Category-aware ranking — Results in the same category as the query get boosted automatically.

Use cases:

  • "Find products like this image but in blue"
  • "Similar style shoe under $100"
  • Image provides visual style, text refines color/size/material
# Fusion search: image + text combined
response = httpx.post(
    "https://search.trooply.ai/v1/search/fusion",
    headers={"Authorization": f"Bearer {token}"},
    json={
        "image_url": "https://example.com/red-dress.jpg",
        "query": "like this but in blue",
        "image_weight": 0.6,
        "limit": 10,
        "category": "Clothing",
        "price_max": 150
    }
)
results = response.json()
print(f"Fusion: {results['count']} results in {results['query_time_ms']}ms")
const response = await fetch('https://search.trooply.ai/v1/search/fusion', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        image_url: 'https://example.com/red-dress.jpg',
        query: 'like this but in blue',
        image_weight: 0.6,
        limit: 10,
        category: 'Clothing',
        price_max: 150
    })
});
const results = await response.json();
curl -X POST https://search.trooply.ai/v1/search/fusion \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://example.com/red-dress.jpg",
    "query": "like this but in blue",
    "image_weight": 0.6,
    "limit": 10,
    "category": "Clothing",
    "price_max": 150
  }'

9 Search Results & Feedback

After receiving search results from any endpoint, you can record user interactions to improve ranking over time. Submit feedback for clicks, add-to-cart, and purchase actions. Use the analytics endpoints to understand search trends and popular products.

  • Submit Feedback: POST /v1/search/feedback — actions: "click", "add_to_cart", "purchase"
  • Popular Products: GET /v1/search/feedback/popular?days=30&limit=20
  • Search Analytics: GET /v1/search/analytics?days=30
  • Suggestions: GET /v1/search/suggestions?limit=10
import httpx

headers = {"Authorization": f"Bearer {access_token}"}

# Record user interaction feedback
feedback_response = httpx.post(
    "https://search.trooply.ai/v1/search/feedback",
    headers=headers,
    json={
        "result_product_id": "shoe-001",
        "action": "click"
    }
)
print("Feedback recorded:", feedback_response.json())

# Get popular products (last 30 days)
popular = httpx.get(
    "https://search.trooply.ai/v1/search/feedback/popular",
    headers=headers,
    params={"days": 30, "limit": 20}
)
print("Popular products:", popular.json())

# Get search analytics
analytics = httpx.get(
    "https://search.trooply.ai/v1/search/analytics",
    headers=headers,
    params={"days": 30}
)
print("Analytics:", analytics.json())

# Get search suggestions
suggestions = httpx.get(
    "https://search.trooply.ai/v1/search/suggestions",
    headers=headers,
    params={"limit": 10}
)
print("Suggestions:", suggestions.json())
const headers = {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
};

// Record user interaction feedback
const feedbackRes = await fetch('https://search.trooply.ai/v1/search/feedback', {
    method: 'POST',
    headers,
    body: JSON.stringify({
        result_product_id: 'shoe-001',
        action: 'click'  // "click", "add_to_cart", or "purchase"
    })
});
console.log('Feedback recorded:', await feedbackRes.json());

// Get popular products (last 30 days)
const popularRes = await fetch(
    'https://search.trooply.ai/v1/search/feedback/popular?days=30&limit=20',
    { headers: { 'Authorization': `Bearer ${accessToken}` } }
);
console.log('Popular products:', await popularRes.json());

// Get search analytics
const analyticsRes = await fetch(
    'https://search.trooply.ai/v1/search/analytics?days=30',
    { headers: { 'Authorization': `Bearer ${accessToken}` } }
);
console.log('Analytics:', await analyticsRes.json());

// Get search suggestions
const suggestionsRes = await fetch(
    'https://search.trooply.ai/v1/search/suggestions?limit=10',
    { headers: { 'Authorization': `Bearer ${accessToken}` } }
);
console.log('Suggestions:', await suggestionsRes.json());
# Record user interaction feedback
curl -X POST https://search.trooply.ai/v1/search/feedback \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "result_product_id": "shoe-001",
    "action": "click"
  }'

# Get popular products (last 30 days, top 20)
curl "https://search.trooply.ai/v1/search/feedback/popular?days=30&limit=20" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Get search analytics
curl "https://search.trooply.ai/v1/search/analytics?days=30" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Get search suggestions
curl "https://search.trooply.ai/v1/search/suggestions?limit=10" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

10 Set Up Webhooks

Register webhook endpoints to receive real-time notifications about product and search events. Provide an HMAC secret so you can verify that incoming payloads are authentic. See the Webhooks & Events page for full event documentation.

import httpx
import hmac
import hashlib

headers = {"Authorization": f"Bearer {access_token}"}

# Register a webhook
response = httpx.post(
    "https://search.trooply.ai/v1/search/webhooks",
    headers=headers,
    json={
        "url": "https://your-server.com/webhooks/visual-search",
        "events": [
            "product.indexed",
            "product.updated",
            "product.deleted",
            "product.duplicate_detected"
        ],
        "secret": "your-hmac-secret"
    }
)
webhook = response.json()
print(f"Webhook registered: {webhook['id']}")

# List all webhooks
webhooks = httpx.get(
    "https://search.trooply.ai/v1/search/webhooks",
    headers=headers
).json()
print(f"Active webhooks: {len(webhooks)}")

# Delete a webhook
httpx.delete(
    f"https://search.trooply.ai/v1/search/webhooks/{webhook['id']}",
    headers=headers
)
print("Webhook deleted")

# --- Example: Verify incoming webhook payload ---
def verify_webhook(payload_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload_body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)
// Register a webhook
const webhookRes = await fetch('https://search.trooply.ai/v1/search/webhooks', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        url: 'https://your-server.com/webhooks/visual-search',
        events: [
            'product.indexed',
            'product.updated',
            'product.deleted',
            'product.duplicate_detected'
        ],
        secret: 'your-hmac-secret'
    })
});
const webhook = await webhookRes.json();
console.log(`Webhook registered: ${webhook.id}`);

// List all webhooks
const listRes = await fetch('https://search.trooply.ai/v1/search/webhooks', {
    headers: { 'Authorization': `Bearer ${accessToken}` }
});
const webhooks = await listRes.json();
console.log(`Active webhooks: ${webhooks.length}`);

// Delete a webhook
await fetch(`https://search.trooply.ai/v1/search/webhooks/${webhook.id}`, {
    method: 'DELETE',
    headers: { 'Authorization': `Bearer ${accessToken}` }
});
console.log('Webhook deleted');

// --- Example: Verify incoming webhook (Node.js / Express) ---
const crypto = require('crypto');
function verifyWebhook(payloadBody, signature, secret) {
    const expected = crypto
        .createHmac('sha256', secret)
        .update(payloadBody)
        .digest('hex');
    return crypto.timingSafeEqual(
        Buffer.from(`sha256=${expected}`),
        Buffer.from(signature)
    );
}
# Register a webhook
curl -X POST https://search.trooply.ai/v1/search/webhooks \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/visual-search",
    "events": [
        "product.indexed",
        "product.updated",
        "product.deleted",
        "product.duplicate_detected"
    ],
    "secret": "your-hmac-secret"
  }'

# List all webhooks
curl https://search.trooply.ai/v1/search/webhooks \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Delete a webhook
curl -X DELETE https://search.trooply.ai/v1/search/webhooks/WEBHOOK_ID \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

11 Secure Your Account

Harden your account with two-factor authentication, scoped API keys, and IP whitelisting. Use the security overview endpoint to audit your current configuration.

  • Enable 2FA: POST /v1/security/2fa/setup to get a QR code, then POST /v1/security/2fa/enable with the TOTP code.
  • Scoped API Keys: POST /v1/security/api-keys with specific scopes and expiration.
  • IP Whitelist: POST /v1/security/ip-whitelist to restrict access by IP or CIDR range.
  • Security Overview: GET /v1/security/overview
import httpx

headers = {"Authorization": f"Bearer {access_token}"}

# --- Enable Two-Factor Authentication ---
# Step A: Set up 2FA (returns a QR code URL to scan with an authenticator app)
setup = httpx.post(
    "https://search.trooply.ai/v1/security/2fa/setup",
    headers=headers
).json()
print(f"Scan this QR code: {setup['qr_code_url']}")

# Step B: Confirm with the TOTP code from your authenticator
enable = httpx.post(
    "https://search.trooply.ai/v1/security/2fa/enable",
    headers=headers,
    json={"totp_code": "123456"}
).json()
print("2FA enabled:", enable)

# --- Create a Scoped API Key ---
api_key = httpx.post(
    "https://search.trooply.ai/v1/security/api-keys",
    headers=headers,
    json={
        "name": "Production Read-Only",
        "scopes": {
            "products": ["read"],
            "search": ["read"]
        },
        "expires_in_days": 365
    }
).json()
print(f"API Key created: {api_key['key']}")

# --- IP Whitelist ---
whitelist = httpx.post(
    "https://search.trooply.ai/v1/security/ip-whitelist",
    headers=headers,
    json={
        "ip_address": "203.0.113.0/24",
        "description": "Office network"
    }
).json()
print("IP whitelisted:", whitelist)

# --- Security Overview ---
overview = httpx.get(
    "https://search.trooply.ai/v1/security/overview",
    headers=headers
).json()
print("Security overview:", overview)
const headers = {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
};

// --- Enable Two-Factor Authentication ---
// Step A: Set up 2FA
const setupRes = await fetch('https://search.trooply.ai/v1/security/2fa/setup', {
    method: 'POST', headers
});
const setup = await setupRes.json();
console.log(`Scan this QR code: ${setup.qr_code_url}`);

// Step B: Confirm with the TOTP code
const enableRes = await fetch('https://search.trooply.ai/v1/security/2fa/enable', {
    method: 'POST',
    headers,
    body: JSON.stringify({ totp_code: '123456' })
});
console.log('2FA enabled:', await enableRes.json());

// --- Create a Scoped API Key ---
const apiKeyRes = await fetch('https://search.trooply.ai/v1/security/api-keys', {
    method: 'POST',
    headers,
    body: JSON.stringify({
        name: 'Production Read-Only',
        scopes: {
            products: ['read'],
            search: ['read']
        },
        expires_in_days: 365
    })
});
const apiKey = await apiKeyRes.json();
console.log(`API Key created: ${apiKey.key}`);

// --- IP Whitelist ---
const whitelistRes = await fetch('https://search.trooply.ai/v1/security/ip-whitelist', {
    method: 'POST',
    headers,
    body: JSON.stringify({
        ip_address: '203.0.113.0/24',
        description: 'Office network'
    })
});
console.log('IP whitelisted:', await whitelistRes.json());

// --- Security Overview ---
const overviewRes = await fetch('https://search.trooply.ai/v1/security/overview', {
    headers: { 'Authorization': `Bearer ${accessToken}` }
});
console.log('Security overview:', await overviewRes.json());
# --- Enable Two-Factor Authentication ---
# Step A: Set up 2FA (returns QR code URL)
curl -X POST https://search.trooply.ai/v1/security/2fa/setup \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Step B: Confirm with the TOTP code from your authenticator app
curl -X POST https://search.trooply.ai/v1/security/2fa/enable \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"totp_code": "123456"}'

# --- Create a Scoped API Key ---
curl -X POST https://search.trooply.ai/v1/security/api-keys \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production Read-Only",
    "scopes": {
        "products": ["read"],
        "search": ["read"]
    },
    "expires_in_days": 365
  }'

# --- IP Whitelist ---
curl -X POST https://search.trooply.ai/v1/security/ip-whitelist \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ip_address": "203.0.113.0/24",
    "description": "Office network"
  }'

# --- Security Overview ---
curl https://search.trooply.ai/v1/security/overview \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

12 Manage Your Team

Create users, assign them to groups, and control permissions at a granular level. Groups define which API operations each team member can access.

  • Create User: POST /v1/users
  • List Users: GET /v1/users
  • Create Group: POST /v1/user-groups
  • List Groups: GET /v1/user-groups
  • Available Permissions: GET /v1/user-groups/permissions
import httpx

headers = {"Authorization": f"Bearer {access_token}"}

# Create a permission group
group = httpx.post(
    "https://search.trooply.ai/v1/user-groups",
    headers=headers,
    json={
        "name": "Developers",
        "permission_names": [
            "products:read",
            "search:read",
            "search:write"
        ]
    }
).json()
print(f"Group created: {group['id']} - {group['name']}")

# Create a new user and assign to the group
user = httpx.post(
    "https://search.trooply.ai/v1/users",
    headers=headers,
    json={
        "email": "[email protected]",
        "password": "secureP@ss123!",
        "full_name": "Jane Dev",
        "group_ids": [group["id"]]
    }
).json()
print(f"User created: {user['id']} - {user['email']}")

# List all users
users = httpx.get(
    "https://search.trooply.ai/v1/users",
    headers=headers
).json()
print(f"Total users: {len(users)}")

# List all groups
groups = httpx.get(
    "https://search.trooply.ai/v1/user-groups",
    headers=headers
).json()
print(f"Total groups: {len(groups)}")

# Get available permissions
permissions = httpx.get(
    "https://search.trooply.ai/v1/user-groups/permissions",
    headers=headers
).json()
print("Available permissions:", permissions)
const headers = {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
};

// Create a permission group
const groupRes = await fetch('https://search.trooply.ai/v1/user-groups', {
    method: 'POST',
    headers,
    body: JSON.stringify({
        name: 'Developers',
        permission_names: [
            'products:read',
            'search:read',
            'search:write'
        ]
    })
});
const group = await groupRes.json();
console.log(`Group created: ${group.id} - ${group.name}`);

// Create a new user and assign to the group
const userRes = await fetch('https://search.trooply.ai/v1/users', {
    method: 'POST',
    headers,
    body: JSON.stringify({
        email: '[email protected]',
        password: 'secureP@ss123!',
        full_name: 'Jane Dev',
        group_ids: [group.id]
    })
});
const user = await userRes.json();
console.log(`User created: ${user.id} - ${user.email}`);

// List all users
const usersRes = await fetch('https://search.trooply.ai/v1/users', {
    headers: { 'Authorization': `Bearer ${accessToken}` }
});
const users = await usersRes.json();
console.log(`Total users: ${users.length}`);

// List all groups
const groupsRes = await fetch('https://search.trooply.ai/v1/user-groups', {
    headers: { 'Authorization': `Bearer ${accessToken}` }
});
const groups = await groupsRes.json();
console.log(`Total groups: ${groups.length}`);

// Get available permissions
const permsRes = await fetch('https://search.trooply.ai/v1/user-groups/permissions', {
    headers: { 'Authorization': `Bearer ${accessToken}` }
});
console.log('Available permissions:', await permsRes.json());
# Create a permission group
curl -X POST https://search.trooply.ai/v1/user-groups \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Developers",
    "permission_names": ["products:read", "search:read", "search:write"]
  }'

# Create a new user and assign to the group
curl -X POST https://search.trooply.ai/v1/users \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "secureP@ss123!",
    "full_name": "Jane Dev",
    "group_ids": ["GROUP_UUID_HERE"]
  }'

# List all users
curl https://search.trooply.ai/v1/users \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# List all groups
curl https://search.trooply.ai/v1/user-groups \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Get available permissions
curl https://search.trooply.ai/v1/user-groups/permissions \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

13 Billing & Upgrade

View your current plan, upgrade via Stripe checkout, and monitor usage and invoices. Available plans:

  • Free — 50 products, 10 requests/min
  • Basic ($29.99/mo) — 5,000 products, 60 requests/min
  • Premium ($99.99/mo) — 50,000 products, 200 requests/min
  • Enterprise ($299.99/mo) — Unlimited products, unlimited requests/min
import httpx

headers = {"Authorization": f"Bearer {access_token}"}

# Get current plan
plan = httpx.get(
    "https://search.trooply.ai/v1/billing/current-plan",
    headers=headers
).json()
print(f"Current plan: {plan['plan_name']} - {plan['max_products']} products")

# Upgrade to Premium
checkout = httpx.post(
    "https://search.trooply.ai/v1/billing/checkout",
    headers=headers,
    json={"plan_name": "premium"}
).json()
print(f"Checkout URL: {checkout['checkout_url']}")

# Get usage for current billing period
usage = httpx.get(
    "https://search.trooply.ai/v1/billing/usage",
    headers=headers
).json()
print(f"API calls this period: {usage['api_calls']}")
print(f"Products indexed: {usage['products_count']}")

# Get invoices
invoices = httpx.get(
    "https://search.trooply.ai/v1/billing/invoices",
    headers=headers
).json()
for inv in invoices:
    print(f"  {inv['date']}: ${inv['amount']} - {inv['status']}")
const authHeaders = { 'Authorization': `Bearer ${accessToken}` };

// Get current plan
const planRes = await fetch('https://search.trooply.ai/v1/billing/current-plan', {
    headers: authHeaders
});
const plan = await planRes.json();
console.log(`Current plan: ${plan.plan_name} - ${plan.max_products} products`);

// Upgrade to Premium
const checkoutRes = await fetch('https://search.trooply.ai/v1/billing/checkout', {
    method: 'POST',
    headers: { ...authHeaders, 'Content-Type': 'application/json' },
    body: JSON.stringify({ plan_name: 'premium' })
});
const checkout = await checkoutRes.json();
console.log(`Checkout URL: ${checkout.checkout_url}`);
// Redirect the user: window.location.href = checkout.checkout_url;

// Get usage for current billing period
const usageRes = await fetch('https://search.trooply.ai/v1/billing/usage', {
    headers: authHeaders
});
const usage = await usageRes.json();
console.log(`API calls this period: ${usage.api_calls}`);
console.log(`Products indexed: ${usage.products_count}`);

// Get invoices
const invoicesRes = await fetch('https://search.trooply.ai/v1/billing/invoices', {
    headers: authHeaders
});
const invoices = await invoicesRes.json();
invoices.forEach(inv => {
    console.log(`  ${inv.date}: $${inv.amount} - ${inv.status}`);
});
# Get current plan
curl https://search.trooply.ai/v1/billing/current-plan \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Upgrade to Premium (returns a Stripe checkout URL)
curl -X POST https://search.trooply.ai/v1/billing/checkout \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"plan_name": "premium"}'

# Get usage for current billing period
curl https://search.trooply.ai/v1/billing/usage \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Get invoices
curl https://search.trooply.ai/v1/billing/invoices \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Plans:
#   free       -  50 products,    10 req/min
#   basic      -  $29.99/mo,   5K products,  60 req/min
#   premium    -  $99.99/mo,  50K products, 200 req/min
#   enterprise - $299.99/mo, unlimited

Saved Requests

No saved requests yet
Quick Fill Browse all 31 endpoints →
Two things to know before you hit Send
1. Permissions. Most products:* and search:* endpoints are gated by per-user-group scopes. Owner-direct logins (via /oauth/token with client credentials) bypass every check; user logins (via /oauth/login) inherit scopes from their user group. If you see 403 Missing required permission, either swap to a client-credentials token or grant your group products:read / write / delete / bulk and search:execute.
2. /oauth/token needs form-encoded. Per OAuth 2.0 §3.2 the token endpoint is application/x-www-form-urlencoded, not JSON. Type the body here as JSON like {"grant_type":"client_credentials","client_id":"…","client_secret":"…"} and the playground will re-encode it automatically.

Request

Custom Headers

Response

// Send a request to see the response...

Available Webhook Events

Subscribe to any combination of the following events when registering a webhook endpoint:

product.indexed
Fired when a new product has been successfully indexed and its vector embedding stored.
product.updated
Fired when an existing product's image or metadata has been updated and re-indexed.
product.deleted
Fired when a product has been removed from the index.
product.duplicate_detected
Fired when a newly indexed product is found to be visually very similar to an existing product (similarity > 0.95).

Register a Webhook

Register an HTTPS endpoint to receive event payloads. Provide a secret to enable HMAC-SHA256 signature verification.

import httpx

headers = {"Authorization": f"Bearer {access_token}"}

webhook = httpx.post(
    "https://search.trooply.ai/v1/search/webhooks",
    headers=headers,
    json={
        "url": "https://your-server.com/webhooks/visual-search",
        "events": [
            "product.indexed",
            "product.updated",
            "product.deleted",
            "product.duplicate_detected"
        ],
        "secret": "whsec_your_hmac_secret_here"
    }
).json()

print(f"Webhook ID: {webhook['id']}")
print(f"URL: {webhook['url']}")
print(f"Events: {webhook['events']}")
const webhook = await fetch('https://search.trooply.ai/v1/search/webhooks', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        url: 'https://your-server.com/webhooks/visual-search',
        events: [
            'product.indexed',
            'product.updated',
            'product.deleted',
            'product.duplicate_detected'
        ],
        secret: 'whsec_your_hmac_secret_here'
    })
}).then(r => r.json());

console.log(`Webhook ID: ${webhook.id}`);
console.log(`URL: ${webhook.url}`);
console.log(`Events: ${webhook.events}`);
curl -X POST https://search.trooply.ai/v1/search/webhooks \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/visual-search",
    "events": [
        "product.indexed",
        "product.updated",
        "product.deleted",
        "product.duplicate_detected"
    ],
    "secret": "whsec_your_hmac_secret_here"
  }'

Verify HMAC Signatures

Every webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 digest of the raw request body, computed using your webhook secret. Always verify this signature before processing the payload to ensure it was sent by Visual Image Search and not tampered with.

import hmac
import hashlib
from fastapi import Request, HTTPException

WEBHOOK_SECRET = "whsec_your_hmac_secret_here"

async def handle_webhook(request: Request):
    # Read raw body
    body = await request.body()
    signature = request.headers.get("X-Webhook-Signature", "")

    # Compute expected signature
    expected = "sha256=" + hmac.new(
        WEBHOOK_SECRET.encode(),
        body,
        hashlib.sha256
    ).hexdigest()

    # Compare securely
    if not hmac.compare_digest(expected, signature):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Parse and process the event
    import json
    event = json.loads(body)
    print(f"Event: {event['event']}")
    print(f"Product ID: {event['data']['product_id']}")
    return {"status": "received"}
const crypto = require('crypto');
const express = require('express');
const app = express();

const WEBHOOK_SECRET = 'whsec_your_hmac_secret_here';

// IMPORTANT: Use raw body for signature verification
app.post('/webhooks/visual-search',
    express.raw({ type: 'application/json' }),
    (req, res) => {
        const signature = req.headers['x-webhook-signature'] || '';
        const body = req.body; // raw Buffer

        // Compute expected signature
        const expected = 'sha256=' + crypto
            .createHmac('sha256', WEBHOOK_SECRET)
            .update(body)
            .digest('hex');

        // Compare securely
        if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
            return res.status(401).json({ error: 'Invalid signature' });
        }

        // Parse and process the event
        const event = JSON.parse(body.toString());
        console.log(`Event: ${event.event}`);
        console.log(`Product ID: ${event.data.product_id}`);
        res.json({ status: 'received' });
    }
);
# The X-Webhook-Signature header format:
#   sha256=<hex-digest>
#
# To verify manually (for testing):
BODY='{"event":"product.indexed","data":{"product_id":"shoe-001"}}'
SECRET="whsec_your_hmac_secret_here"

# Compute the expected HMAC-SHA256 signature
EXPECTED=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
echo "Expected signature: sha256=$EXPECTED"

# Compare with the X-Webhook-Signature header value from the request

Example Webhook Payloads

Each webhook delivery sends a JSON payload with an event field and a data object containing event-specific information.

product.indexed

{
  "event": "product.indexed",
  "timestamp": "2026-02-27T10:30:00Z",
  "webhook_id": "wh_abc123",
  "data": {
    "product_id": "shoe-001",
    "vector_id": "vec_xyz789",
    "image_url": "https://example.com/blue-sneaker.jpg",
    "metadata": {
      "name": "Blue Running Sneaker",
      "category": "footwear",
      "brand": "SportMax"
    },
    "indexed_at": "2026-02-27T10:30:00Z"
  }
}

product.updated

{
  "event": "product.updated",
  "timestamp": "2026-02-27T11:00:00Z",
  "webhook_id": "wh_abc123",
  "data": {
    "product_id": "shoe-001",
    "changes": ["image_url", "metadata.price"],
    "previous_image_url": "https://example.com/blue-sneaker.jpg",
    "new_image_url": "https://example.com/blue-sneaker-v2.jpg",
    "updated_at": "2026-02-27T11:00:00Z"
  }
}

product.deleted

{
  "event": "product.deleted",
  "timestamp": "2026-02-27T12:00:00Z",
  "webhook_id": "wh_abc123",
  "data": {
    "product_id": "shoe-001",
    "deleted_at": "2026-02-27T12:00:00Z"
  }
}

product.duplicate_detected

{
  "event": "product.duplicate_detected",
  "timestamp": "2026-02-27T13:00:00Z",
  "webhook_id": "wh_abc123",
  "data": {
    "new_product_id": "shoe-002",
    "existing_product_id": "shoe-001",
    "similarity_score": 0.97,
    "new_image_url": "https://example.com/blue-sneaker-copy.jpg",
    "existing_image_url": "https://example.com/blue-sneaker.jpg",
    "detected_at": "2026-02-27T13:00:00Z"
  }
}

API Credentials

Client ID
Loading...
Client Secret
************************************
Base URL
Loading...
Your client secret is only shown once when generated. Store it securely. If you lose it, you will need to regenerate a new one.
AVAILABLE

CS-Cart / Multi-Vendor

Full integration addon for CS-Cart 4.x and Multi-Vendor. Auto-sync products, visual + text + natural-language search on storefront, similar products on product pages, webhook support.

Auto-sync Image Search Text Search NL Search Similar Products Webhooks Bulk Sync Feedback

Version 1.4.1 · PHP 7.4+ · 45 KB · updated 2026-04-20 · fixes admin ArgumentCountError; async queue, HMAC webhooks, retry/backoff, failed-items view, product-list status filter, category exclude, search-as-you-type, portal analytics

Download Addon Setup Guide
COMING SOON

Shopify

Shopify app for visual search integration. Auto-sync products via Shopify webhooks, search by image on storefront.

COMING SOON

WooCommerce

WordPress/WooCommerce plugin for visual product search. Sync products, add search-by-image widget.

COMING SOON

Magento / Adobe Commerce

Magento 2 extension for visual search. Automatic product indexing, configurable search widget.

SDK & Developer Tools

Python SDK

Official Python client library for the Visual Image Search API. Covers OAuth, products, search (text / image / voice), AI chat (streaming + NL), webhooks, jobs, and the new custom-fields / search-config / merchandising / widget-keys surfaces — with ready-to-run examples.

Version 1.1.0 · Python 3.9+ · 15 KB · depends on httpx
Download SDK
pip install trooply-visual-search-python-sdk-v1.2.0.zip

Postman Collection

Pre-configured collection with all API endpoints and example requests.

Postman Environment

Environment file pre-filled with your credentials and base URL.

0 API Calls Today
0 Searches Today
0 Total Products
200 Rate Limit (req/min)

Plan Information

Current Plan Loading...
Product Limit --
Monthly API Calls --
Rate Limit --