← Back to Setch

Setch developer resources

Documentation

Everything you need to add Setch to a website, sync a product catalog, and integrate the API.

Setch API integration

This guide covers the two ways to integrate Setch:

  1. Add the public Setch widget to a website.
  2. Send product catalog data to the authenticated Catalog API and search it from a trusted server.

The widget uses a public site key. Catalog management uses a private account token. Do not put an account token in browser code.

API base URL

The production API base URL is:

https://setch.vercel.app

Set it in your shell before running the examples:

export SETCH_API="https://setch.vercel.app"

For local development with Django running directly, use http://127.0.0.1:8000. When using the Next.js app's /api proxy, use the app origin instead.

All request and response bodies are JSON unless stated otherwise. Send Content-Type: application/json on JSON requests.

1. Get an account token

Create an account or sign in to receive a token:

curl -X POST "$SETCH_API/api/auth/login/" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "you@example.com",
    "password": "YOUR_PASSWORD"
  }'

Registration uses POST /api/auth/register/ with email, password (at least 8 characters), and optional name. A successful login returns:

{
  "token": "YOUR_ACCOUNT_TOKEN",
  "email": "you@example.com",
  "name": "Example User"
}

Send this token only from trusted server-side code:

Authorization: Token YOUR_ACCOUNT_TOKEN

Tokens grant access to account-owned sites and catalogs. Store them as secrets; never expose them in a public website, mobile bundle, or source-control repository.

2. Create a site and install the widget

Create a site key:

curl -X POST "$SETCH_API/api/sites/create/" \
  -H "Authorization: Token $SETCH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Acme Store"}'

The response contains a public site_key and a ready-to-paste snippet:

{
  "site_key": "site_abc123",
  "name": "Acme Store",
  "snippet": "<script\n  src=\"https://setch.vercel.app/widget.js\"\n  data-setch-site=\"site_abc123\"\n  data-setch-api=\"https://setch.vercel.app\"\n  async\n></script>"
}

Paste the returned snippet before </body> on the pages where Setch should be available. The example uses the production deployment; use the snippet returned by another deployment rather than guessing its widget or API host:

<script
  src="https://setch.vercel.app/widget.js"
  data-setch-site="site_abc123"
  data-setch-api="https://setch.vercel.app"
  async
></script>

The widget indexes visible page content in the browser, including headings, page metadata, and supported product data in JSON-LD. It sends page snapshots to POST /api/sites/ingest/ and uses the page context when creating voice sessions. This is browser-side page ingestion, not a server-side crawler. Keep the tag on pages whose content should be available to the site search.

The data-setch-site value is a public identifier, not an account credential. The widget's voice feature also requires the Setch API deployment to have its voice provider configured.

3. Add products to the catalog

Catalog operations use the account token. Create a catalog source for the site:

curl -X POST "$SETCH_API/api/catalog/sources/" \
  -H "Authorization: Token $SETCH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "site_key": "site_abc123",
    "name": "Acme product catalog",
    "source_type": "api"
  }'

The response includes the numeric source id. Use it as SOURCE_ID in the product endpoints. Source types currently include api, feed, platform, and crawl; selecting a type does not install a native connector.

Product data shape

Products are identified by a stable external_id within a source. A new product needs a non-empty title.

{
  "external_id": "jacket-001",
  "title": "Trail Shell Jacket",
  "description": "Lightweight waterproof jacket for rainy hikes.",
  "brand": "Acme Outdoor",
  "categories": ["Outdoor", "Jackets"],
  "images": ["https://store.example/images/jacket-001.jpg"],
  "url": "https://store.example/products/jacket-001",
  "price": {
    "amount": "129.00",
    "currency": "USD"
  },
  "availability": "in_stock",
  "attributes": {
    "waterproof": true,
    "weight": "lightweight"
  },
  "variants": [
    {
      "external_id": "jacket-001-small",
      "sku": "JACKET-001-S",
      "title": "Small",
      "attributes": {
        "size": "S",
        "color": "forest"
      },
      "price": {
        "amount": "129.00",
        "currency": "USD"
      },
      "availability": "in_stock",
      "inventory_quantity": 12
    }
  ]
}

Prices accept decimal strings or integers, not JSON floating-point numbers. Currency should be a three-letter ISO 4217 code. Supported availability values are in_stock, out_of_stock, preorder, backorder, discontinued, and unknown. Product images and URLs must be absolute URLs.

Upsert one product

POST and PUT both upsert a product:

curl -X PUT "$SETCH_API/api/catalog/sources/$SOURCE_ID/products/upsert/" \
  -H "Authorization: Token $SETCH_TOKEN" \
  -H "Content-Type: application/json" \
  --data @product.json

The response includes the product and an action of created or updated. Reuse the same external_id to update a product; do not generate a new ID on each sync.

Upsert a batch

Send between 1 and 200 products per request:

curl -X POST "$SETCH_API/api/catalog/sources/$SOURCE_ID/products/bulk/" \
  -H "Authorization: Token $SETCH_TOKEN" \
  -H "Content-Type: application/json" \
  --data @products.json

The body must wrap the array:

{
  "products": [
    {
      "external_id": "jacket-001",
      "title": "Trail Shell Jacket",
      "price": {
        "amount": "129.00",
        "currency": "USD"
      }
    }
  ]
}

Bulk upserts return a sync job, per-product results, and errors. Row-level errors can coexist with successful rows. Check both job.status and errors; the status can be succeeded, partial, or failed.

Update and remove products

  • GET /api/catalog/sources/{source_id}/products/ lists products (default limit 50, maximum 100). Use ?include_deleted=true to include soft-deleted products.
  • GET /api/catalog/sources/{source_id}/products/{external_id}/ returns one product.
  • DELETE /api/catalog/sources/{source_id}/products/{external_id}/ soft-deletes a product. It is not a hard delete.
  • POST /api/catalog/sources/{source_id}/sync/ accepts a full batch. Missing products are only marked deleted when both delete_missing and complete_snapshot are true, and only if the batch has no errors. Use this only when the submitted list is a complete snapshot.

Feed and platform integration status

The Catalog API accepts normalized JSON product records. Server-side CSV/XML parsing and remote feed fetching are not enabled. Fetch and normalize a feed in your own trusted integration, then send products using bulk upsert. Native Shopify and WooCommerce connectors are not currently available.

4. Search the catalog

The catalog search endpoint requires an account token and a site owned by that account:

curl -G "$SETCH_API/api/catalog/search/" \
  -H "Authorization: Token $SETCH_TOKEN" \
  --data-urlencode "site_key=site_abc123" \
  --data-urlencode "q=waterproof jacket" \
  --data-urlencode "availability=in_stock" \
  --data-urlencode "max_price=150" \
  --data-urlencode "limit=20"

Optional query parameters:

Parameter Meaning
q Text query (up to 500 characters).
availability Availability value, such as in_stock.
currency Three-letter currency code.
min_price, max_price Inclusive price bounds. Pass decimal values as strings.
brand Case-insensitive exact brand filter.
category Category filter.
limit Page size, 1–100; default 20.
offset Number of matching products to skip; default 0.
include_deleted Include soft-deleted products; default false.

The response shape is:

{
  "site_key": "site_abc123",
  "total": 1,
  "limit": 20,
  "offset": 0,
  "results": []
}

Catalog search currently uses deterministic text matching and structured filters; it does not provide semantic ranking. Keep account-token calls on your server. If you need browser-side search, proxy the request through your application and enforce your own authorization and rate limits.

Site and widget endpoints

Method Endpoint Access Purpose
POST /api/sites/create/ Account token Create a site key and embed snippet.
GET /api/sites/mine/ Account token List the signed-in account's sites.
PATCH /api/sites/{site_key}/ Account token Rename an owned site.
POST /api/sites/ingest/ Public site key in JSON body Store/update a page snapshot. Normally called by the widget.
GET /api/sites/page/?site_key=...&url=... Public Retrieve the latest snapshot for a site and URL.
GET /api/sites/{site_key}/pages/ Account token List indexed page summaries.
GET /api/sites/{site_key}/analytics/?days=30 Account token Read site question/session analytics.
POST /api/voice/session/ Public Mint a short-lived voice session credential. Normally called by the widget.

The widget handles page ingestion and voice-session creation itself. These endpoints are listed for integrations that need to understand the flow; do not use the voice-session endpoint as a substitute for the Catalog API.

Pay-as-you-go voice billing

Setch has no subscription. Top up the wallet in Nigerian naira; 10% of each successful deposit is Setch's service fee and 90% becomes usage credit. For example, a ₦10,000 deposit adds ₦9,000 to the wallet.

Paid voice sessions are billed from provider-reported Realtime response-token usage and input-transcription usage. Setch applies the published rates for the configured OpenAI models and converts the resulting USD cost to NGN using the deployment's OPENAI_USD_TO_NGN_RATE. The exchange rate is captured when each voice session starts. Keep model rates and the configured model names aligned with OpenAI's current pricing.

Wallets never go negative. If a final completed response costs more than the remaining credit, Setch charges the available balance, absorbs the excess, and stops the session; another paid session requires a top-up.

Wallet endpoints require the account token:

Method Endpoint Purpose
GET /api/wallet/ Read wallet balance and minimum deposit.
POST /api/wallet/topup/ Start a Flutterwave checkout; send {"amount":10000} in whole naira.
POST /api/wallet/topup/verify/ Verify a completed checkout using its reference and transaction_id.
GET /api/wallet/transactions/ Read wallet transactions.

The top-up response contains a checkout_url; redirect the customer there. Credits are not added until Setch verifies the payment directly with Flutterwave. Configure Flutterwave to call POST /api/wallet/webhook/flutterwave/ and set the deployment's secret keys server-side. Do not treat the browser redirect as proof of payment.

The voice widget also uses POST /api/voice/call/ and a short-lived private meter grant to negotiate and meter paid sessions. These are widget protocol endpoints, not a replacement for the account-authenticated Wallet API.

Before enabling paid voice for owned sites, configure the Django backend with OPENAI_API_KEY, OPENAI_USD_TO_NGN_RATE, FLUTTERWAVE_SECRET_KEY, FLUTTERWAVE_WEBHOOK_SECRET_HASH, NEON_VOICE_METER_URL, and VOICE_METERING_SHARED_SECRET. Deploy the voicemeter Neon Function with the same OpenAI key and metering secret plus VOICE_API_BASE_URL; its invocation URL is the value for NEON_VOICE_METER_URL. The full variable list and model defaults are in backend/.env.example.

Errors and limits

Validation errors use HTTP 400 and return a JSON object describing invalid fields. Common statuses include:

  • 401 Unauthorized: missing or invalid account token.
  • 404 Not Found: site, source, or product does not exist or is not owned by the authenticated account.
  • 429 Too Many Requests: request was throttled.
  • 503 Service Unavailable: voice sessions are not configured on the server.
  • 402 Payment Required: a site has no available usage credits.

Current default limits include 60 anonymous requests per minute, 30 authentication requests per minute, 120 page-ingest requests per minute, and 60 catalog requests per minute. Limits are per deployment and may be adjusted by its operator.

Current integration boundaries

  • The widget indexes the current page in the visitor's browser; it does not crawl a whole website from the server.
  • The Catalog API is the supported path for syncing canonical product, variant, offer, and stock data.
  • Bulk product requests are limited to 200 products; send larger catalogs in batches.
  • Feed URLs can be recorded, but the server does not fetch them. CSV/XML parsing is not implemented.
  • Native Shopify/WooCommerce connectors and published Python/TypeScript SDKs are not currently available.
  • Keep account tokens and provider secrets server-side. Only the site key and generated embed snippet belong in public HTML.