Skip to content

Instantly share code, notes, and snippets.

@mhmd-azeez
Created August 7, 2026 18:29
Show Gist options
  • Select an option

  • Save mhmd-azeez/49214dfe778c36ef247204070837e8e0 to your computer and use it in GitHub Desktop.

Select an option

Save mhmd-azeez/49214dfe778c36ef247204070837e8e0 to your computer and use it in GitHub Desktop.
Message24 Sync API Requirements

Message24 Product Sync API Requirements

Message24 answers customer DMs (Instagram, WhatsApp, Facebook, TikTok) with an AI agent. To answer "do you have this in red, size M, how much?" the agent searches a local copy of the shop's catalog. So we pull your products into our database and keep them fresh.

Read-only, inbound only — we never write to your system in this scope. We already do this for WooCommerce, ERPNext, ShopLink and one custom vendor API, so the shape below is what our importer already consumes.

Two endpoints

GET /shop                                   → which shop this key belongs to, and its currency
GET /products?page=&limit=&since=           → the catalog

Both public HTTPS, both authenticated with a per-shop API key in a header (X-Api-Key: <key>), which the owner generates and revokes from your dashboard. That key plus your base URL is the whole credential we store, encrypted.

GET /shop

{ "id": "88", "name": "Zara Erbil", "currency": "IQD" }

We show name back to the owner when they paste their key, so they can confirm they connected the right shop and their API key is valid. currency labels every price in the feed.

GET /products

Param
page, limit pagination Stable ordering — by id or created_at, not by anything that changes. Response returns totalPages or hasNextPage.
since ISO 8601 Only products whose updatedAt is newer, including deleted ones. This is how we stay current between full crawls.
{
  "products": [
    {
      "id": "1042",
      "sku": "TS-001",
      "price": 85.0,
      "originalPrice": 100.0,
      "discountEndsAt": "2026-09-01T00:00:00Z",
      "inStock": true,
      "isActive": true,
      "updatedAt": "2026-08-01T10:32:00Z",
      "deletedAt": null,
      "category": "Clothing -> Men -> T-Shirts",
      "brand": "Zara",
      "translation": {
        "en": { "name": "Cotton T-Shirt", "description": "100% cotton, regular fit" },
        "ar": { "name": "تي شيرت قطن",   "description": "قطن ١٠٠٪" },
        "ku": { "name": "تیشێرتی لۆکە",  "description": "١٠٠٪ لۆکە" }
      },
      "images": { "main": "https://cdn.../1.jpg", "other": ["https://cdn.../2.jpg"] },
      "attributes": [{ "name": "Color", "values": ["Black", "White"] }],
      "attributeImages": { "Black": ["https://cdn.../blk.jpg"] },
      "variants": [
        { "id": "9001", "attributes": { "Color": "Black" }, "price": 85.0, "inStock": true,  "sku": "TS-001-BLK" },
        { "id": "9002", "attributes": { "Color": "White" }, "price": 85.0, "inStock": false, "sku": "TS-001-WHT" }
      ]
    }
  ],
  "pagination": { "currentPage": 1, "totalPages": 7, "hasNextPage": true, "total": 634 }
}

Fields

Field Required Notes
id yes Stable and permanent — our upsert key. If it changes we create a duplicate.
translation.*.name yes en, ar, ku (Kurdish Sorani). Send what the shop filled in; missing languages are fine.
price yes Number, current selling price. No symbol, no formatting.
inStock yes Boolean, or a quantity we read as > 0.
isActive yes Published/enabled in the shop. Inactive → hidden from the agent, row kept.
updatedAt yes ISO 8601 with timezone. What since filters on.
deletedAt yes ISO 8601, null when live.
sku strongly Used in search, and quoted to the customer for follow-up.
translation.*.description strongly Plain text or HTML (we strip tags). This is what the AI reads to answer detail questions.
images.main, images.other strongly Absolute URLs. First 5 are used, in order.
category strongly Full path if you have a tree: "Clothing -> Men -> T-Shirts", or an array of ancestor names.
brand strongly Plain name.
originalPrice, discountEndsAt if discounted Pre-discount price, and when the discount stops (omit if open-ended).
attributes, variants if applicable See rule 6.
attributeImages optional Photos per attribute value, e.g. per colour. First 2 per value are used.
barcode, videoUrl optional

Rules that matter

  1. price is already the selling price. If a discount is live, price is the discounted figure and originalPrice is what it was. Never send a scheduled or expired discount — we can't repair a price that was wrong at import time.
  2. updatedAt changes on every write — including a price or stock change made by a background job, a bulk import, or anything else your admin UI isn't involved in. If it doesn't, since silently skips the update and the AI quotes a stale price.
  3. Deleted products still come back in a since window, carrying deletedAt. A product that just disappears from an incremental response is invisible to us and we'd go on selling it forever. (If soft-delete doesn't fit your model: GET /products/deleted?since= returning ids.)
  4. An empty list means "zero products", never a failure. We deactivate products that vanish from a full crawl, so a broken response returning [] wipes a catalog. Errors must be HTTP errors.
  5. Image URLs must be publicly fetchable — no auth, no signed expiry, stable over time. We download each one and re-host it on our S3, then send it to customers over WhatsApp and Instagram.
  6. Every variant carries the full attribute map, spelled and cased exactly as in attributes. The agent quotes per-variant price and inStock. Omit both fields for products without variants.
  7. Be consistent about the Kurdish key. ku, ckb, kur — any is fine, tell us which.
  8. Tell us your rate limit. We pull a whole catalog in one pass, a few requests per second.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment