# shamz.ai developers guide

> Complete API reference, SDKs, and integration guides for developers.

Base URL: `https://api.shamz.ai`

Always use `https`. A plain `http` request is redirected rather than refused,
so the key crosses the network in the clear before the redirect is followed.

## Quickstart

1. **Get your API keys** — Sign up and retrieve your AI front desk ID and API credentials from the dashboard.
2. **Embed the widget** — Add the embed script and initialisation code to your website.
3. **Configure webhooks** — Set up endpoints to receive real-time events from chat conversations.
4. **Test and deploy** — Verify your integration in sandbox mode, then switch to production.

## Authentication

- Widget and chat requests: send your API key in the `X-API-KEY` header.
- Partner endpoints (Catalogue Push API): `Authorization: Bearer shamz_live_…`
- Never expose API keys in client-side code.

```bash
curl https://api.shamz.ai/api/widget/chat \
  -H "X-API-KEY: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "msg": {
      "type": "text",
      "content": "Hello"
    }
  }'
```

## Widget embedding

Add the embed script. Replace `YOUR_API_KEY` with your API key from the dashboard.

```html
<script src="https://widget.shamz.ai/v1/shamz-widget.js"></script>
<script>
  ShamzWidget.init({
    chatbotId: 'YOUR_CHATBOT_ID',
    bubblePosition: 'bottom-right'
  });
</script>
```

### Configuration options

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `chatbotId` | string | required | The chatbot to connect to. Found in the dashboard. |
| `apiBaseUrl` | string | 'https://api.shamz.ai' | Only needed if you are pointed at another environment. |
| `bubblePosition` | string | 'bottom-right' | 'bottom-right' | 'bottom-left' |
| `bubbleSize` | string | 'medium' | 'small' | 'medium' | 'large' |
| `primaryColor` | string | from dashboard | Overrides the accent colour set on the chatbot. |
| `language` | string | from dashboard | 'en' | 'ar' — overrides the server-side setting. |
| `autoOpen` | boolean | false | Open the widget automatically after autoOpenDelay seconds. |

## REST API

### POST /api/widget/chat

Send a message to the AI front desk. Include `session_id` to keep context.

```json
{
  "session_id": "sess_123",
  "msg": {
    "type": "text",
    "content": "I want to book an appointment"
  },
  "survey": {
    "name": "John Doe",
    "email": "john@example.com",
    "phone": "+97312345678"
  }
}
```

## Catalogue Push API

For shops whose own software sends us the catalogue. The assistant answers from
what you send: names, prices, stock and specifications. We never call back into
your system.

### Getting a key

Dashboard: E-commerce → Push API. App: Shop → API keys. The secret is shown once
and cannot be recovered. Switched-off keys stop working on the next request.

```bash
curl https://api.shamz.ai/api/partners/v1/status \
  -H "Authorization: Bearer shamz_live_a1b2c3d4e5f6a7b8_YOUR_SECRET"
```

### PUT /api/partners/v1/catalog

Your WHOLE catalogue. Items previously sent and omitted here are taken off sale.
Returns `202`; images are fetched after we reply.

- `external_id` (required) — Your own id — how we recognise the same product next time.
- `name · price · currency` (required) — What the assistant quotes back to customers. An unknown currency code is rejected rather than guessed.
- `images · url` (recommended) — WhatsApp product cards need both. Without either a product is still saved and searchable, but cannot appear in a carousel — the response tells you which.
- `specs` (optional) — Goes to the assistant’s knowledge, not the card — so it can answer “does it support eSIM”. Leave out anything you do not know rather than sending “not specified”.
- `sequence` (optional) — Any number that goes up — unix seconds or milliseconds both work. Stops a late retry putting an old price back. Only skips updates; a full catalogue is always applied.

```json
{
  "items": [
    {
      "external_id": "SKU-1021",
      "name": "iPhone 16 Pro 256GB",
      "name_ar": "آيفون 16 برو 256 جيجا",
      "price": 389.5,
      "currency": "KWD",
      "stock_qty": 4,
      "category": "Phones",
      "url": "https://your-shop.com/p/iphone-16-pro",
      "images": ["https://your-shop.com/img/iphone16pro.jpg"],
      "specs": { "Storage": "256GB", "RAM": "8GB", "Warranty": "1 year" },
      "sequence": 1792008852
    }
  ]
}
```

Shop-wide knowledge (delivery, returns, warranty) can be sent alongside;
matching questions are replaced, not duplicated.

```json
{
  "shop": {
    "knowledge": [
      { "question": "Do you deliver across Kuwait?",
        "answer": "Yes, within 24 hours. Orders over KWD 50 ship free." },
      { "question": "What is the warranty?",
        "answer": "One year from the local agent on all phones." }
    ]
  },
  "items": [ ... ]
}
```

### PATCH /api/partners/v1/items

Absolute values, never takes items off sale. Call when stock moves.

```json
{
  "items": [
    { "external_id": "SKU-1021", "stock_qty": 2, "sequence": 1792008852 },
    { "external_id": "SKU-1044", "price": 42.0, "in_stock": false, "sequence": 1792008852 }
  ]
}
```

### POST /api/partners/v1/carts

Optional. Upsert of current cart contents. `recovery_url` and one of
`customer_phone` / `customer_email` are required; a cart missing either is
rejected rather than stored, because it could never send. `discount_code` is
your own checkout code — we never mint one for a pushed shop.

```json
{
  "carts": [{
    "external_id": "CART-9911",
    "recovery_url": "https://your-shop.com/cart/9911",
    "customer_phone": "+96555512345",
    "total_amount": 429.5,
    "currency": "KWD",
    "items": [
      { "external_id": "SKU-1021", "name": "iPhone 16 Pro 256GB",
        "quantity": 1, "price": 429.5 }
    ],
    "discount_code": "COMEBACK10"
  }]
}
```

### POST /api/partners/v1/orders

Send every order. Stops follow-ups for buyers and attributes recoveries. Carts
and orders: 300 requests/minute. The merchant must enable cart follow-ups first —
until then these calls answer normally, store nothing, and say so.

```json
{
  "orders": [{
    "external_id": "SO-55021",
    "cart_external_id": "CART-9911",
    "customer_phone": "+96555512345",
    "total_amount": 429.5,
    "currency": "KWD",
    "discount_code": "COMEBACK10"
  }]
}
```

### GET /api/partners/v1/status

Live counts, carousel readiness, last push and image results. `image_errors` is
capped at 25 entries, keyed by your `external_id`. `carousel_ready` means two
things deliberately: in the push response it counts what you just sent carrying
both a photo and a link; here it counts what we can show right now.

```json
{
  "items_active": 412,
  "items_inactive": 3,
  "carousel_ready": 402,
  "last_full_push_at": "2026-01-14T15:17:02Z",
  "last_update_at":    "2026-01-14T15:41:20Z",
  "last_run": {
    "kind": "snapshot",
    "at": "2026-01-14T15:17:02Z",
    "accepted": 412, "created": 18, "updated": 394, "deactivated": 3,
    "rejected": [{ "external_id": "SKU-9", "error": "unknown currency 'XY'" }],
    "warnings": { "SKU-77": ["no_image"] },
    "images_stored": 394, "images_skipped": 18,
    "image_errors": { "SKU-77": "HTTP 404" },
    "images_at": "2026-01-14T15:17:09Z"
  }
}
```

### Examples

```php
<?php
$key  = 'shamz_live_a1b2c3d4e5f6a7b8_YOUR_SECRET';
$body = json_encode(['items' => [[
  'external_id' => 'SKU-1021',
  'name'        => 'iPhone 16 Pro 256GB',
  'price'       => 389.5,
  'currency'    => 'KWD',
  'stock_qty'   => 4,
  'url'         => 'https://your-shop.com/p/iphone-16-pro',
  'images'      => ['https://your-shop.com/img/iphone16pro.jpg'],
  'sequence'    => time(),
]]], JSON_UNESCAPED_UNICODE);

$ch = curl_init('https://api.shamz.ai/api/partners/v1/catalog');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST  => 'PUT',
  CURLOPT_POSTFIELDS     => $body,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER     => [
    'Authorization: Bearer ' . $key,
    'Content-Type: application/json',
  ],
]);
echo curl_exec($ch);
```

```python
import time, requests

KEY = "shamz_live_a1b2c3d4e5f6a7b8_YOUR_SECRET"

r = requests.put(
    "https://api.shamz.ai/api/partners/v1/catalog",
    headers={"Authorization": f"Bearer {KEY}"},
    json={"items": [{
        "external_id": "SKU-1021",
        "name": "iPhone 16 Pro 256GB",
        "price": 389.5,
        "currency": "KWD",
        "stock_qty": 4,
        "url": "https://your-shop.com/p/iphone-16-pro",
        "images": ["https://your-shop.com/img/iphone16pro.jpg"],
        "sequence": int(time.time()),
    }]},
)
print(r.status_code, r.json())
```

### Responses and limits

```json
{
  "accepted": 412, "created": 18, "updated": 394, "deactivated": 3,
  "carousel_ready": 402, "images_pending": 18,
  "rejected": [{ "external_id": "SKU-9", "error": "unknown currency 'XY'" }],
  "warnings": { "SKU-77": ["no_image"] }
}
```

- **401** — The key is unknown, wrong or switched off. Never says which.
- **409** — A push is already running, or the shop is not set up yet.
- **413** — Body too large. 8 MB and 2,000 items for a catalogue; 1 MB and 1,000 for an update.
- **429** — One full catalogue every 5 minutes, 60 updates a minute. Honour Retry-After.
- Image links must be public `https`; send links, not base64.
- One bad row never costs the rest of the payload — read `rejected` and
  `warnings` on every 2xx.
- We never call back to your system; you own data freshness.

## Webhooks

- `conversation.started` — New conversation initiated
- `conversation.completed` — Conversation ended
- `appointment.created` — New appointment booked
- `appointment.cancelled` — Appointment cancelled
- `ticket.created` — New service desk ticket
- `ticket.updated` — Ticket status changed
- `lead.captured` — New lead captured
- `lead.qualified` — Lead scored as hot/warm

```json
{
  "event": "appointment.created",
  "timestamp": "2026-01-14T10:30:00Z",
  "data": {
    "appointment_id": "apt_123",
    "customer_name": "John Doe",
    "customer_phone": "+97312345678",
    "appointment_type": "Consultation",
    "scheduled_at": "2026-01-14T14:00:00Z",
    "agent_name": "Dr. Smith"
  }
}
```

### Setup

1. Go to your AI front desk settings in the dashboard
2. Navigate to Integrations → Webhooks
3. Enter your webhook URL and select events to subscribe to
4. Save and test your webhook

## SDKs

None are published yet. The REST API above is the integration surface today.

- **JavaScript / TypeScript** (coming soon) — A typed client for the REST API. Until it ships, call the endpoints directly — every one of them is documented above.
- **Python** (coming soon) — For backend integrations and automation. The Catalogue Push examples show the same calls with `requests`.
- **React component** (coming soon) — A drop-in component. The embed script already works in React — see the example below.

```jsx
import { useEffect } from 'react';

const SRC = 'https://widget.shamz.ai/v1/shamz-widget.js';

export default function ShamzChat({ chatbotId }) {
  useEffect(() => {
    // The bundle attaches window.ShamzWidget, so load it once and
    // initialise after it resolves.
    let script = document.querySelector(`script[src="${SRC}"]`);
    if (!script) {
      script = document.createElement('script');
      script.src = SRC;
      script.async = true;
      document.body.appendChild(script);
    }

    const start = () => window.ShamzWidget?.init({ chatbotId });
    if (window.ShamzWidget) start();
    else script.addEventListener('load', start);

    return () => window.ShamzWidget?.destroy?.();
  }, [chatbotId]);

  return null;
}
```

## Rate limits

| Plan | Requests/minute | Concurrent connections |
| --- | --- | --- |
| Starter | 60 | 10 |
| Growth | 300 | 50 |
| Enterprise | Custom | Custom |

## Best practices

- **Use session IDs:** Always include session_id to maintain conversation context.
- **Handle errors:** Implement proper error handling and retry logic.
- **Cache responses:** Cache static content like FAQs and service lists.
- **Webhooks over polling:** Use webhooks instead of polling for real-time updates.
- **Secure your keys:** Never expose API keys in client-side code.
- **Monitor usage:** Track your API usage to avoid hitting rate limits.

## Error handling

```javascript
try {
  const response = await fetch('https://api.shamz.ai/api/widget/chat', {
    method: 'POST',
    headers: {
      'X-API-KEY': apiKey,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
  });

  if (!response.ok) {
    if (response.status === 429) {
      // Rate limit hit, implement exponential backoff
      await sleep(1000);
      return retry();
    }
    throw new Error(`API error: ${response.status}`);
  }

  const data = await response.json();
  return data;
} catch (error) {
  console.error('Shamz API error:', error);
  // Fallback to error message
}
```
