Custom API contract · version 2
Let the assistant use your system
Implement a few HTTPS routes under a base URL of your choosing and connect it in the dashboard. Seler calls them live while a customer is chatting — to quote a price, place an order, read the customer's account, book a time — and never stores what they return. JSON in, JSON out, UTF-8.
Four halves, pick what you have
The contract is in four capabilities. A store implements the first two; a clinic might implement only customers and appointments. Your /verify answer says which you implement, and the assistant is given tools for exactly those.
| catalog | searchProducts, getProduct | The assistant quotes prices and availability and sends photos. |
| orders | placeOrder, getOrderStatus, cancelOrder | Cash-on-delivery orders, with the customer's explicit confirmation. |
| customers | lookupCustomerRecord, and lead push | Balance, points, history — whatever you expose; and every lead the assistant captures lands in your CRM. |
| appointments | listAppointmentSlots, bookAppointment | Real slots from your calendar, booked with the customer's explicit confirmation. |
Requests from Seler
Authorization: Bearer <secret from the dashboard>
Accept: application/json
X-Seler-Contract: 2
Content-Type: application/json (on POST)
Idempotency-Key: <key> (on order and appointment creation)- Answer
401when the bearer token is not the secret the owner gave you. - The base URL must be
https://on a public host. Private addresses,localhostand plain HTTP are refused. - Answer within 10 seconds. Responses larger than 5 MB are dropped.
- Redirects are followed (at most 3), but answer directly where you can.
- Paths are appended to your base URL: with a base of
https://shop.example.com/seler, verification isGET https://shop.example.com/seler/verify.
Routes
| Method | Path | Capability | Purpose |
|---|---|---|---|
| GET | /verify | catalog | Prove the secret; name, currency, capabilities |
| GET | /products | catalog | Search the catalogue |
| GET | /products/{id} | catalog | One product, with variants |
| POST | /orders | orders | Place an order (idempotent) |
| GET | /orders/{reference} | orders | An order by number or id |
| POST | /orders/{reference}/cancel | orders | Cancel an order |
| GET | /customers/lookup | customers | The record behind a verified phone number |
| POST | /customers | customers | A lead the assistant captured |
| GET | /appointments/slots | appointments | Free slots |
| POST | /appointments | appointments | Book a slot (idempotent) |
Verify
Called when the owner presses connect, and again whenever Seler re-checks the connection. Return the display name, the currency as an ISO 4217 code, and the capabilities you implement.
{ "name": "Aroma Baghdad", "currency": "IQD", "capabilities": ["catalog", "orders", "customers"] }catalog
Search products
q is what the customer asked for, in their own words, often Arabic. Search however your catalogue searches best; Seler re-ranks what you return. limit is a hint (at most 50). category is optional. Return only products a customer may buy now or hear about.
{
"products": [
{
"id": "42",
"title": "عباية سوداء كلاسيك",
"description": "قماش كريب، مقاسات 52 إلى 60.",
"price": 45000,
"compareAtPrice": 60000,
"currency": "IQD",
"imageUrl": "https://shop.example.com/img/42.jpg",
"url": "https://shop.example.com/p/42",
"categories": ["عبايات"],
"inStock": true,
"quantity": 12,
"variants": [
{ "id": "42-54", "title": "54", "price": 45000, "inStock": true, "quantity": 3, "options": { "المقاس": "54" } },
{ "id": "42-56", "title": "56", "price": 45000, "inStock": false, "options": { "المقاس": "56" } }
]
}
]
}One product
{ "id": "42", "title": "عباية سوداء كلاسيك", "price": 45000, "inStock": true, "variants": [] }The product shape
| id | string or number | Required. Stable; it comes back in orders as variantId when there are no variants. |
| title | string | Required. |
| price | number | Required. In the store currency, no formatting. |
| inStock | boolean | Required. Whether it can be sold right now. |
| quantity | integer or null | Optional. Never shown to customers; below 4 becomes "few left". Omit or null when you do not track stock. |
| description | string or null | Optional. Plain text; the assistant reads the first 400 characters. |
| compareAtPrice | number or null | Optional. The old price, shown as a discount only when higher than price. |
| currency | string | Optional. Defaults to the currency from /verify. |
| imageUrl | URL or null | Optional. Sent to the customer as a photo when available. |
| url | URL or null | Optional. A link the customer can open. |
| categories | string[] | Optional. |
| variants | Variant[] | Optional. Each has id, title, price, inStock, and optionally quantity and options (a map of option name to value). Orders reference a variant id when variants exist. |
orders
Create an order
Cash on delivery, the way the assistant sells. Make this idempotent: if you have already seen idempotencyKey, return the order you created then, with 200, and do not create another. A retry after a timeout must never charge a customer twice.
{
"idempotencyKey": "seler-33333333-…-a1b2c3d4",
"lines": [ { "variantId": "42-54", "quantity": 1 } ],
"customer": { "name": "زينب الحسيني", "phone": "+9647701234567", "email": null },
"address": { "line1": "حي الجامعة، شارع 12", "city": "بغداد", "province": "بغداد", "country": "IQ", "notes": null },
"note": "الاتصال قبل التوصيل",
"source": "seler"
}{
"id": "8812",
"number": "1001",
"status": "confirmed",
"total": 45000,
"currency": "IQD",
"placedAt": "2026-09-07T14:03:00Z",
"trackingUrl": null,
"customerPhone": "+9647701234567",
"lines": [ { "title": "عباية سوداء كلاسيك — 54", "quantity": 1, "price": 45000 } ]
}Look an order up
The reference is the customer-facing number or your id, without a leading #. Always return customerPhone: Seler shows an order only to the person whose verified number it was placed under, because order numbers are printed on receipts and easy to guess.
{ "id": "8812", "number": "1001", "status": "shipped", "total": 45000, "customerPhone": "+9647701234567", "trackingUrl": "https://courier.example/track/ABC", "lines": [] }Cancel an order
{ "reason": "العميل غيّر رأيه" }Answer 409 with a message when the order can no longer be cancelled; the assistant tells the customer to contact the team.
The order shape
| id | string or number | Required. Your internal id. |
| number | string or number | Required. What the customer sees on the receipt. |
| status | string | Required. pending, confirmed, processing, shipped, delivered, cancelled, refunded — or the synonyms new, preparing, out_for_delivery, completed, canceled. Anything else is "unknown". |
| total | number | Required. |
| customerPhone | string or null | Required for lookups to work. The number the order was placed under. |
| currency | string | Optional. Defaults to the store currency. |
| placedAt | ISO 8601 or null | Optional. |
| trackingUrl | URL or null | Optional. Sent to the customer when present. |
| lines | { title, quantity, price }[] | Optional. What was ordered, as titles. |
customers
Look a customer up
Called when the customer asks about their account, their history, their balance — anything the team would look up by phone. The number is always the one Seler verified on the conversation, never one the customer typed into the chat, so your system may trust it the way it trusts a caller who has proved their number.
{
"customer": {
"id": "C-1042",
"name": "زينب الحسيني",
"fields": {
"الرصيد": "125,000 د.ع",
"نقاط الولاء": 340,
"آخر زيارة": "2026-08-30",
"عضوية": "ذهبية"
}
}
}{ "customer": null }| id | string or number or null | Optional. Your id. |
| name | string or null | Optional. |
| fields | object | Label → value. Strings (≤ 300 chars), numbers, booleans or null; up to 30. The assistant repeats these to the customer, so include only what the customer themselves may hear. No internal notes, no margins. |
Receive a lead
Whenever the assistant writes down someone to follow up with — a quote, a callback, a viewing — it also sends the lead here, once, best effort. Create or update the record; answer with its id.
{
"name": "زينب الحسيني",
"phone": "+9647701234567",
"note": "تريد عرض سعر لتجهيز مطبخ كامل",
"conversationId": "33333333-…",
"source": "seler"
}{ "customer": { "id": "C-1042" } }appointments
Free slots
Called before the assistant offers a time. It never invents one. from and to are ISO dates or datetimes as the customer asked (“Sunday morning” becomes a date); service is what they named, if anything. Return up to 30 slots and, if useful, the services you offer so the assistant can ask which.
{
"services": ["تنظيف أسنان", "فحص", "تبييض"],
"slots": [
{ "id": "s-2026-09-16-0930", "startsAt": "2026-09-16T09:30:00+03:00", "endsAt": "2026-09-16T10:00:00+03:00", "service": "تنظيف أسنان" },
{ "id": "s-2026-09-16-1100", "startsAt": "2026-09-16T11:00:00+03:00", "endsAt": "2026-09-16T11:30:00+03:00", "service": "تنظيف أسنان" }
]
}Book a slot
Called only after the customer has seen the service, the date and the time and said yes. Idempotent on the key, like orders: a retry returns the appointment already made.
{
"idempotencyKey": "seler-33333333-…-s-2026-09-16-0930",
"slotId": "s-2026-09-16-0930",
"service": "تنظيف أسنان",
"customer": { "name": "زينب الحسيني", "phone": "+9647701234567" },
"note": null,
"source": "seler"
}{ "appointment": { "id": "apt-5521", "startsAt": "2026-09-16T09:30:00+03:00", "endsAt": "2026-09-16T10:00:00+03:00", "service": "تنظيف أسنان", "status": "confirmed" } }Answer 409 with a message when the slot was taken in the meantime; the assistant offers the customer another one.
Errors
Use HTTP status codes; a JSON body with a message is welcome and is shown to the team in the call log (never to customers).
| 401 / 403 | Wrong secret | The connection is marked as needing reconnection. |
| 404 | No such product, order or record | The assistant says it could not find it. |
| 409 / 422 | Cannot do that | Out of stock, cannot cancel, slot taken, invalid line. The message helps the team. |
| 429 | Slow down | Seler backs off and retries later. |
| 5xx | Your side is down | Marked as temporary; the connection is retried, not dropped. |
Test it before connecting
curl -sS https://shop.example.com/seler/verify \
-H "Authorization: Bearer <secret>" -H "X-Seler-Contract: 2"
curl -sS "https://shop.example.com/seler/products?q=test&limit=5" \
-H "Authorization: Bearer <secret>" -H "X-Seler-Contract: 2"Checklist
- HTTPS on a public host; the secret checked on every route.
/verifyreturnsname,currencyandcapabilities.- Every product has
id,title,priceandinStock. - Order and appointment creation are idempotent on the key.
- Orders return
customerPhonein the same format they were placed with. fieldson a customer record hold only what the customer may hear.- Unknown routes and wrong methods answer
404, not a redirect to a web page.