Nishchinto AI Product Feed API — merchant integration contract (v1)
Expose this one read-only HTTPS endpoint on your website and Nishchinto AI keeps your catalog in sync automatically: new products appear, prices and stock refresh on a schedule, and the AI agent quotes your live numbers to customers in Messenger and WhatsApp instead of a stale copy.
Your website stays the source of truth. Nishchinto AI never writes to it.
Already have a product feed? You do not have to implement this contract. Nishchinto AI can read a Shopify
/products.json, a Google Merchant / Facebook catalog JSON, or any JSON shape at all — a business saves a field mapping in Settings → Product Feed and we adapt to your existing keys. This document describes the default: implement it and no mapping or configuration is needed. Everything below about authentication, pagination and error handling applies either way.
Flow
You give Nishchinto AI (Settings → Product Feed):
- Feed URL — e.g.
https://yourshop.com.bd/api/alapai/products. Must be public HTTPS; private, loopback and internal hosts are rejected. - API key — a random secret you generate (32+ characters). Nishchinto AI sends it on every request, including the one-off "read this feed" check the owner runs when connecting, so the key can be pasted in and tested before anything is saved.
Authentication
Required — and enforced by you, not by us.
This is the part of the contract that only your side can implement. Nishchinto AI presents the key on every request; whether an unauthenticated request is refused is decided entirely by your endpoint. If your route does not check the header, the feed is public no matter what is configured on our side, and anyone who learns the URL can read your whole catalog, stock and pricing — competitors and scrapers included. Earlier integrations were exactly that: a plain public URL with nothing in front of it. This contract exists to end that.
So: the key check is your middleware's job.
Authorization: Bearer <key> is supported as an alternative if it fits your
stack better; the owner picks which one in Settings.
Rules — all of these are yours to implement:
- Reject the request yourself. Check the header in middleware, before any catalog query runs. Nishchinto AI cannot make an open endpoint safe.
- Compare with a constant-time function (
hash_equalsin PHP), never==. - Reply 401 with a JSON body when the key is missing or wrong. Do not reply 200 with an empty list — see Never return an empty catalog below.
- Generate the key randomly (
php artisan tinker→Str::random(48)), store it in.env, and never commit it. - To rotate: accept both the old and the new key for a day, have the owner update Settings → Product Feed, then drop the old one.
Already connected with a public feed URL? It keeps working — Nishchinto AI still imports feeds with no authentication, and nothing needs to change on the day you upgrade. But an unauthenticated feed stays readable by anyone with the URL, so add the middleware, generate a key, and paste it into Settings → Product Feed. The switch is one save; there is no re-import and no downtime.
Want more than a shared secret? Ask us for our egress IP address and allowlist it at your firewall or in the middleware — every Nishchinto AI feed fetch comes from one static address. Request signing (HMAC) is not part of v1: Nishchinto AI does not sign feed requests today, so do not require a signature.
The endpoint — GET {base}/alapai/products
Query parameters
| Param | Sent by Nishchinto AI | Meaning |
|---|---|---|
page | 1, 2, 3… | 1-based page number |
limit | 250 | Maximum products per page |
Pagination is required. Nishchinto AI always sends these two parameters and walks
pages until it receives a page with fewer than limit products (or an empty
one). Two consequences:
- Order must be stable across pages and across the whole walk — order by
primary key, not by anything that can change mid-crawl (
updated_at, price, popularity). An unstable order silently skips and duplicates products. - You must actually honour
page. If you ignore it and return the whole catalog every time, Nishchinto AI stops after detecting the repeat, but you will have served your entire catalog twice for nothing.
If your catalog fits comfortably in one response you can cap limit at
whatever your server handles and return a short page — the walk ends there.
Response — 200
Send Content-Type: application/json. count and generated_at are for your
own debugging — Nishchinto AI reads products.
Product fields
| Field | Type | Required | Notes |
|---|---|---|---|
external_id | string | yes | Your stable product id. This is the dedup key: it must never change for a product, and must never be reused for a different one. A product whose external_id changes is imported as a brand-new product and the old one is marked out of stock. |
name | string | yes | Product title as the customer should hear it. |
price | number or string | yes | The effective selling price — what the customer pays today. Must be > 0. 1850, "1850.00" and "1,850 BDT" all parse; the decimal part is dropped. A product with no positive price is skipped. |
description | string | no | Plain text preferred. HTML is accepted and stripped. |
regular_price | number | no | Struck-through "before" price. Send it only when it is genuinely higher than price. 0 or null means "no regular price", never "free". |
flash_price | number | no | Live flash-sale price. Same 0 = "none" rule. Display only — if a flash price is what the customer pays, put it in price too. |
brand | string | no | |
category | string | no | A single category name (not a path). Nishchinto AI creates it on first sight. |
product_code | string | no | Style/model code shared by colour variants of the same design. Lets the agent answer "what other colours are there?". |
in_stock | boolean | no* | Use when you do not publish quantities. true imports as available. |
stock | integer | no* | Preferred. A real quantity; 0 means sold out. Beats in_stock when both are sent. |
images | string[] | no | Absolute HTTPS URLs, in display order — the first is the main photo. Nishchinto AI downloads and re-hosts them. Relative paths are ignored. |
variations | object[] | no | Per-size / per-colour rows. See below. |
* Send at least one of stock / in_stock. If both are absent the product is
imported out of stock — deliberately fail-closed, so the agent never sells
something you did not confirm you have.
Variation fields
Send variations when one product has several sizes or colours the customer
must choose between. Without it, a multi-size product collapses into one stock
number and the agent can sell a size you do not have.
| Field | Type | Required | Notes |
|---|---|---|---|
variation_id | string | yes | The id your own site uses for this exact variant — the value your cart/checkout expects. If Nishchinto AI also places orders on your site (see WEBSITE_ORDER_API.md), this is what makes the order name the right size. A variation without it is dropped. |
size | string | no | "M", "42", "500ml" |
color | string | no | |
sku | string | no | |
stock | integer | no | Quantity for this variant. 0 = that size is sold out. |
price | number | no | Only when this variant costs more/less than the product price. |
Send at most 50 variations per product; extras are ignored.
Never return an empty catalog
If your catalog query fails, your database is mid-deploy, or the key is wrong,
do not answer 200 {"products": []}. An empty feed would otherwise mean
"every product has been discontinued", and a resync marks the whole catalog out
of stock — the agent then tells every customer that everything is unavailable.
Return an error status instead (503 is right for "temporarily broken"). Nishchinto AI
refuses an empty page from a feed that previously had products and keeps the
last known catalog, but that safety net exists for accidents; returning the
correct status is what keeps your shop selling.
Error responses
| Status | When | Nishchinto AI's behaviour |
|---|---|---|
401 / 403 | Missing or wrong API key | Sync fails, the owner sees "Feed rejected our credentials" in the dashboard |
429 | You are rate-limiting us | Sync fails and retries on the next scheduled run; send Retry-After |
503 | Temporarily unavailable | Sync fails, catalog untouched, retried next run |
5xx | Anything else | Same |
Body shape for errors (any JSON is fine, this is the convention):
Do not leak internals in error responses. Turn debug mode off in production
(APP_DEBUG=false) — a framework stack trace exposes source code, file paths
and sometimes credentials to anyone who can reach the URL.
Limits Nishchinto AI enforces
| Limit | Value | Why |
|---|---|---|
| Connect timeout | 10s | |
| Read timeout | 30s per page | Keep a page under 250 products |
| Max response size | 25 MB per page | Paginate rather than sending everything |
| Max redirects | 3 | Each hop must also be public HTTPS |
| Scheme | HTTPS only | The API key travels on every request |
| Fetch frequency | every 6h or 24h, plus manual syncs | Not a load concern for any normal shop |
Health check (optional) — GET {base}/alapai/health
Useful for the owner to confirm the key works before running a first import.
Reference implementation (Laravel)
Tested against Laravel 10/11. Adjust model and column names to your schema.
.env
config/services.php
app/Http/Middleware/VerifyAlapaiKey.php
Register it (Laravel 11 — bootstrap/app.php):
Laravel 10 — add the alias to $middlewareAliases in app/Http/Kernel.php.
routes/api.php
app/Http/Controllers/AlapaiFeedController.php
Test it before handing over the URL:
Checklist before going live
- URL is public HTTPS with a valid certificate.
- No key → 401. Wrong key → 401. Correct key → 200.
- Key compared with
hash_equals, read from.env, not committed. -
?page=2&limit=250returns the second 250 products, not the first. - Ordering is by primary key, stable between requests.
-
external_idis your permanent product id and never changes. - Prices are the effective selling price;
regular_priceonly when it is genuinely higher. - Products with sizes send
variations, each with thevariation_idyour own checkout expects. - Image URLs are absolute and load in a browser without a session.
- A broken catalog query returns 503, never
200with[]. -
APP_DEBUG=falsein production. - One page responds in well under 30 seconds and 25 MB.
Versioning
alapai_feed_version: 1 identifies this contract. Fields will only ever be
added within v1 — unknown fields are ignored, so you can adopt new ones when
you are ready. Anything incompatible ships as v2 with both supported in
parallel.
Questions, or want your egress-IP allowlist entry? Contact support.