Developer Hub

Platform Integrations

Connect your storefront to Razlio. We support native connections for Shopify and WooCommerce, as well as a fully agnostic Custom REST API interface for bespoke setups like Django, Laravel, or Express.

Custom REST API

Razlio communicates with your store strictly over two HTTP endpoints that you host and maintain. You configure a custom auth header in our dashboard, which we pass on every request.

GET/products

We poll this on a schedule and pull product changes since the last sync.

POST/orders

When our AI agent successfully places an order, we send the structured payload to you here.

Example Authenticated RequestHTTP
GET /api/products HTTP/1.1
  Host: your-store.com
  Authorization: Bearer sk_live_123456

Return 401 Unauthorized if this header is missing or incorrect.

Bearer auto-prefix

When you configure the header name as Authorization in the dashboard and paste a raw token (e.g. sk_live_…), we automatically prefix it with Bearer on every outgoing request. If you already typed Bearer …, Basic …, Token … etc., we leave it alone. Custom header names like X-API-Key are sent verbatim.

Try it — build your cURL

Fill in the same values you will enter in the dashboard and copy the commands below. These are byte-identical to the requests Razlio sends, so if they work in your terminal the integration will work.

Resolved URLs
GEThttps://your-store.com/api/razlio/products
POSThttps://your-store.com/api/razlio/orders
1. Connection test — what Razlio calls when you save
curl -sS -i -X GET 'https://your-store.com/api/razlio/products?limit=1' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json'
Expect 200 and a JSON array. A 401 or 403 means the auth header is wrong; anything that is not an array fails the sync with “product response must be a JSON array”.
2. Incremental sync — every run after the first
curl -sS -X GET 'https://your-store.com/api/razlio/products?updated_at_min=2026-05-03T00:00:00' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json'
Expect 200 and a JSON array containing only products changed at or after that timestamp. Returning everything still works, it is just slower.
3. Order push — when the AI places an order
curl -sS -i -X POST 'https://your-store.com/api/razlio/orders' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "order_number": "RZL-00042",
  "currency": "BDT",
  "customer": {
    "name": "Rahim Ahmed",
    "phone": "01712345678",
    "email": null
  },
  "delivery": {
    "address": "House 12, Road 5, Dhanmondi, Dhaka",
    "zone": "Dhaka City",
    "cost": "60.00"
  },
  "payment_method": "cod",
  "items": [
    {
      "external_id": "SKU-RED-SHIRT-M",
      "variant_external_id": "SKU-RED-SHIRT-L",
      "product_name": "Red T-Shirt",
      "variant_name": "Size: L",
      "quantity": 2,
      "unit_price": "600.00",
      "line_total": "1200.00"
    }
  ],
  "subtotal": "1200.00",
  "total": "1260.00",
  "notes": null,
  "created_at": "2026-05-03T09:14:00+00:00"
}'
Expect 2xx and a body containing a non-empty external_id, e.g. {"external_id": "ORDER-9821"}. Without it the push is recorded as failed and retried.
GET{base}{products_path}

Products Endpoint

Returns a JSON array of your products. On subsequent syncs, we pass a timestamp via query param to perform a highly efficient delta sync.

updated_at_minISO 8601

Return products updated at or after this timestamp. We always send it timezone-aware, in UTC: 2026-05-03T00:00:00+00:00. Parse it as ISO 8601 rather than a fixed format — a naive parser rejects the offset.

On the first sync, and on any full re-sync, the parameter is omitted entirely. Return your whole catalogue when it is absent.

Response (200 OK)json
[
  {
    "external_id": "SKU-RED-SHIRT-M",
    "name": "Red T-Shirt",
    "description": "100% cotton crew-neck.",
    "category": "Clothing",
    "price": "550.00",
    "compare_at_price": "650.00",
    "currency": "BDT",
    "sku": "SKU-RED-SHIRT-M",
    "slug": "red-t-shirt",
    "source_url": "https://yourshop.com/products/red-t-shirt",
    "status": "active",
    "tags": ["new", "summer"],
    "images": [
      { "url": "https://cdn.example.com/red-m.jpg", "alt": "Red shirt", "is_primary": true }
    ],
    "variants": [
      {
        "name": "Size",
        "value": "M",
        "price_adjustment": "0.00",
        "sku": "SKU-RED-SHIRT-M",
        "stock_quantity": 12
      },
      {
        "name": "Size",
        "value": "L",
        "price_adjustment": "50.00",
        "sku": "SKU-RED-SHIRT-L",
        "stock_quantity": 4
      }
    ]
  },
  {
    "external_id": "MUG-CERAMIC-01",
    "name": "Ceramic Mug",
    "description": "350ml, dishwasher safe.",
    "category": "Kitchen",
    "price": "250.00",
    "currency": "BDT",
    "sku": "MUG-CERAMIC-01",
    "slug": "ceramic-mug",
    "source_url": "https://yourshop.com/products/ceramic-mug",
    "status": "active",
    "stock_quantity": 40,
    "tags": ["gift"],
    "images": [
      { "url": "https://cdn.example.com/mug.jpg", "alt": "Ceramic mug", "is_primary": true }
    ],
    "variants": []
  }
]

Product Field Reference

Only external_id is required. Every other field falls back to the value in the third column, so a missing field is silently accepted — but a field of the wrong shape rejects that product. Rejected products are skipped, counted, and reported back to the merchant with a reason; the rest of the catalogue still imports.

Product

FieldTypeIf omittedNotes
external_idrequiredstringYour stable internal ID. This is the matching key for every future sync, so it must not change for the lifetime of the product — re-keying creates a duplicate rather than an update. Max 128 characters.
namestring"Untitled"Max 255 characters. Part of what the AI searches over.
descriptionstring | nullnullSearched by the AI. Plain text or HTML both work.
categorystring | nullnullA single category name, max 120 characters. Not a path and not a list.
pricestring | number"0.00"Decimal, up to 12 digits with 2 decimal places. Send "550.00", not "৳550" or "1,550" — a currency symbol or thousands separator rejects the row.
compare_at_pricestring | number | nullnullThe struck-through "was" price. Same format as price.
currencystring"BDT"Max 8 characters.
skustring | nullnullMax 120 characters. Shown to the merchant; not used for matching.
slugstring | nullnullWhat your storefront calls this product in its own URL — the adidas-running-comfort-blue-red in yourshop.com/products/adidas-running-comfort-blue-red. Combined with your store's storefront URL it becomes the link the AI gives customers. Send it exactly as your storefront writes it, dots and percent-escapes included: it is used as-is and never rewritten. A slug we cannot use verbatim (a raw space, a slash, a ?) is discarded, and the link is then derived from the product name as though no slug had been sent. A slug in a non-Latin script is still a valid slug — send it percent-encoded, the way WooCommerce stores it, and it is used as-is. When no slug is sent the link is derived from the product name instead, but only when that name is plain unaccented A–Z once accents are folded (Café is fine, Blåbær is not). Anything else gets no link at all, because a partial transliteration is a URL that looks right and 404s. Max 255 characters.
source_urlstring | nullnullThe product's own page, in full. Takes priority over slug — it is the only URL we know resolves, so send it if you have it. Also accepted as url or permalink, whichever your shop software calls it. Never send an image or media URL here: those show no preview in Messenger and open a raw image file when tapped. Photos are delivered as real attachments from the images field instead. Must be a complete http(s) URL on a single line — anything else is ignored and the slug is used instead.
status"active" | "draft" | "archived""active"Only active products are visible to the AI. archived is the removal signal — see below. Any other value rejects the row.
stock_quantityinteger | nullnullProduct-level stock, used only when the product has no variants. Omit it and the product is always available; send 0 and it is out of stock. Omitting and sending zero are different claims.
in_stockboolean | nullnullAvailability without a count, for merchants who do not track quantities. Ignored when stock_quantity is present.
tagsstring[][]Must be an array. A comma-separated string is not split — it is iterated character by character.
imagesobject[][]Each entry is an object with url, alt and is_primary. An array of bare URL strings rejects the row.
variantsobject[][]One entry per purchasable combination — see the variants rule below.

Variant

FieldTypeIf omittedNotes
namestring"Option"The axis label, e.g. Size. Max 64 characters.
valuestring""The full combination, e.g. M or M / Red. Max 120 characters.
price_adjustmentstring | number | nullnullAdded to the product price for this combination. May be negative.
skustring | nullnullMax 120 characters.
stock_quantityinteger0Zero means this combination is unavailable. When a product has variants, these values decide availability and the product-level stock fields are ignored.

Keep external_id stable

It is the only thing tying your product to ours. If it is derived from something mutable — a SKU, a slug, a variant — then editing that field makes us insert a second product instead of updating the first.

One variant row per purchasable SKU

Variants are matched on name plus value together. For a two-axis product put the whole combination in value ("M / Red"), one row each. Sending Size and Colour as separate rows describes the axes but loses which combinations actually exist, so per-combination price and stock cannot be represented.

Removing a product

Send it with status: "archived". Dropping it from the feed is not a removal signal — a delta sync cannot tell a deleted product from an unchanged one, so it stays live and the AI keeps offering it.

POST{base}{orders_path}

Orders Endpoint

You must persist this incoming JSON payload in your database and respond with your newly created internal order ID.

Idempotency Rule: If you receive the same order_number twice due to a network timeout, treat it as the same order and return the existing external_id.
Request Payloadjson
{
  "order_number": "RZL-00042",
  "currency": "BDT",
  "customer": {
    "name": "Rahim Ahmed",
    "phone": "01712345678",
    "email": null
  },
  "delivery": {
    "address": "House 12, Road 5, Dhanmondi, Dhaka",
    "zone": "Dhaka City",
    "cost": "60.00"
  },
  "payment_method": "cod",
  "items": [
    {
      "external_id": "SKU-RED-SHIRT-M",
      "variant_external_id": "SKU-RED-SHIRT-L",
      "product_name": "Red T-Shirt",
      "variant_name": "Size: L",
      "quantity": 2,
      "unit_price": "600.00",
      "line_total": "1200.00"
    }
  ],
  "subtotal": "1200.00",
  "total": "1260.00",
  "notes": null,
  "created_at": "2026-05-03T09:14:00+00:00"
}
Expected Response (200 OK)json
{
  "external_id": "ORDER-9821"
}

Shopify Integration

Razlio connects to your Shopify store using the official Shopify Admin REST API. We use cursor-based pagination and incremental polling to efficiently map your entire catalog.

Setup Guide

  1. Log into your Shopify Admin dashboard.
  2. Navigate to Settings > Apps and sales channels.
  3. Click Develop apps and then Create an app (Name it "Razlio Sync").
  4. Under Configuration, click "Configure" next to Admin API Integration.
  5. Assign the required permissions (see right sidebar) and click Save.
  6. Go to API Credentials, click Install app, and reveal your Admin API Access Token.
  7. Paste this Token along with your `.myshopify.com` domain into the Razlio Dashboard.

Required API Scopes

  • read_products
  • read_inventory
  • write_orders
  • read_customers

WooCommerce Integration

Razlio connects to your WordPress/WooCommerce site using the core WooCommerce REST API (v3) via HTTP Basic Auth. We utilize the modified_after filter for lightning-fast delta syncs.

Setup Guide

  1. Log into your WordPress wp-admin dashboard.
  2. Navigate to WooCommerce > Settings > Advanced > REST API.
  3. Click Add key.
  4. Add a Description (e.g., "Razlio Integration").
  5. Set Permissions to Read/Write.
  6. Click Generate API key.
  7. Copy the Consumer Key and Consumer Secret before leaving the page.
  8. Paste these keys along with your base URL into the Razlio Dashboard.
Important: Your WordPress site MUST have a valid SSL certificate (HTTPS). WooCommerce API blocks Basic Auth over plain HTTP by default.

Required Details

  • Base URLhttps://your-store.com
  • Consumer Keyck_...
  • Consumer Secretcs_...

Website Chat Widget

The Razlio chat widget is a single-tag drop-in script that puts your AI agent on any website. It loads asynchronously, makes zero network calls before the visitor opens the bubble, and only ~12 KB gzipped over the wire. Conversations sync to the same inbox as your Messenger and WhatsApp threads.

Vanilla JS — no framework, no build step, no React/Vue baggage
Self-contained styles (no CSS conflicts with your site)
Visitor sessions persisted in localStorage so returning users keep the same thread
WebSocket streaming so assistant replies render token-by-token
i18n-aware — auto-detects EN / BN based on the store's KB language

Where to get your tokens

  1. Go to Dashboard → Channels → Website Widget.
  2. Click Enable Widget if it isn't already.
  3. Copy the data-token shown on the panel.

The token is a public identifier — safe to paste in HTML. It only proves "this page belongs to a store that has the widget enabled". The real auth boundary is a session JWT issued by the server when the visitor opens the bubble.

Embed Snippet

Paste this one line as close to </body> as you can. That's it. The bubble will appear in the bottom-right of every page that includes the script.

Replace YOUR_WIDGET_TOKEN with the value from your dashboard. The token always starts with wgt_ followed by 32 characters.
Drop-in script tagHTML
<script async
        src="https://app.razlio.com/widget.js"
        data-token="YOUR_WIDGET_TOKEN"
        data-api="https://app-api.razlio.com"></script>

Platform-Specific Setup

Shopify

  1. Admin → Online Store → Themes.
  2. Click Actions → Edit code on your live theme.
  3. Open layout/theme.liquid.
  4. Paste the snippet just above </body>.
  5. Save. Refresh your store — bubble appears.

WordPress / WooCommerce

  1. Install the free WPCode (Insert Headers and Footers) plugin.
  2. Go to Code Snippets → + Add Snippet → HTML Snippet.
  3. Paste the script in the Footer location.
  4. Set Active and Save.
  5. Or: theme footer.php just before </body>.

Next.js (App Router)

In your root app/layout.tsx:

tsx
import Script from "next/script";

export default function RootLayout({ children }) {
  return (
    <html><body>
      {children}
      <Script async
        src="https://app.razlio.com/widget.js"
        data-token={process.env.NEXT_PUBLIC_RAZLIO_TOKEN}
        data-api="https://app-api.razlio.com" />
    </body></html>
  );
}

Plain HTML / Static Site

  1. Open each .html page (or your shared template).
  2. Paste the snippet just before the closing </body> tag.
  3. Re-deploy. Works on Netlify, Vercel, GitHub Pages, S3, anywhere.

Google Tag Manager (any site)

Useful when you can't edit your theme directly. GTM injects the snippet at runtime — works on Shopify Plus, Webflow, Wix, Squarespace, etc.

  1. GTM → Tags → New → Custom HTML.
  2. Paste the Razlio script into the HTML field.
  3. Trigger: All Pages (or any page-view trigger).
  4. Save → Submit the workspace version.

Configuration Attributes

All configuration lives on the <script> tag itself as data-* attributes. No JavaScript API to call.

data-tokenrequired

Your public widget token. Get it from Dashboard → Channels → Website Widget.

data-apioptional

API base. Defaults to https://api.razlio.com (production). On staging or self-hosted deployments, set this to https://app-api.razlio.com.

asyncrecommended

Standard HTML attribute. Lets the browser load the widget without blocking page rendering. Always include.

Identifying logged-in customers (optional): If your site already knows the visitor's email or phone, you can hand it to the widget after load so the conversation lands under their existing customer record. Get in touch and we'll share the JS hook.
Complete exampleHTML
<!-- Razlio Chat Widget -->
<script async
  src="https://app.razlio.com/widget.js"
  data-token="wgt_a1b2c3d4e5f6...32chars"
  data-api="https://app-api.razlio.com">
</script>
Verifying it worksRefresh your site → the chat bubble should appear at bottom-right. Click it → say "hi" → you should see the AI reply within a couple of seconds. The conversation also shows up live in your Razlio dashboard inbox.

How the Sync Engine Works

All platform integrations are powered by our core asynchronous task runner. Here's exactly how we guarantee consistency across platforms:

  • 1
    Scheduled Polling & Cursors
    Our worker cluster polls your endpoint every 5 hours. On successful completion we write a last_synced_at timestamp to our database. You can also trigger a sync yourself at any time from the dashboard.
  • 2
    The 5-Minute Safety Buffer
    On the next run, we take that timestamp, subtract exactly 5 minutes (to prevent race conditions with in-flight transactions), and send it back to you as the updated_at_min (or WooCommerce's modified_after).
  • 3
    Rate Limit Backoffs (429 / 5xx)
    On a 429 or a 5xx we read your Retry-After header, wait, and retry once. Without that header we wait 2 seconds; we never wait longer than 30. A 4xx other than 429 is not retried — we treat it as your answer.

Integration Support

Getting authentication errors or missing products? Reach out to our technical support engineers directly.

Contact Support