CloPOS DataStream (Clopos)

CloPOS DataStream (Clopos)

Written by AI, not reviewed by a human. Please apply the written-by-ai-not-reviewed label. The contract below comes from Clopos's published OpenAPI specification — no live Clopos brand has been exercised.

Integrates Clopos as a NexSigns DataStream source.

Source name

CloPOS

Vendor API

Clopos Open API v2, host https://integrations.clopos.com/open-api/v2

Docs

developer.clopos.com — with an OpenAPI document at /api-reference/v2/openapi.json, a llms.txt index and a .md for every page. Well-documented by the standards of this set.

Status

CODE COMPLETE, UNVERIFIED LIVE

Configuration

{ "DataSource": "CloPOS", "Version": "1.0", "ClientID": "<Replace Clopos Client ID>", "ClientSecret": "<Replace Clopos Client Secret>", "Brand": "<Replace Clopos Brand>", "IntegratorID": "<Replace Clopos Integrator ID>", "VenueID": "", "ProductTypes": "DISH,GOODS", "Currency": "", "UseInventory": false, "IncludeHiddenItems": false, "IncludeUnavailableItems": true }

Field

Meaning

Field

Meaning

ClientID / ClientSecret

Issued by Clopos. The secret is never logged; the ID appears only as ****xxxx.

Brand

The top-level tenant — a chain, a cloud-kitchen operator. A short string such as openapitest. All data (venues, products, orders) lives inside a brand.

IntegratorID

Identifies Kuusoft as a partner, not the customer. Requested from Clopos once. See the warning below.

VenueID

Overrides the venue baked into the token, via the optional x-venue header. Numeric. Blank uses the token's default, which in practice is the brand's main venue.

ProductTypes

Comma-separated server-side filter. Default DISH,GOODS. See Why this filter matters.

Currency

Display only. Clopos exposes no currency code on any read resource; nothing in the price path reads this.

UseInventory

Pulls the stop list. Off by default; see Availability.

IncludeHiddenItems

hidden is the operator's own "do not show this on menus" switch — exactly what a menu board should honour, hence off by default. An inactive product (status: 0) is never shown regardless: it is not sellable at all.

A helper endpoint lists the brand's venues:

GET /api/nexsigns/DataStreams/GetCloPOSAvailableVenueList?DataStreamGUID=...

Four credentials, and the test/production trap

A test integrator can only call a non-production brand. Clopos flags every integrator ID as is_test: true or false and enforces the pairing on both /v2/auth and every authenticated route, rejecting a mismatch with "Integrator is in test mode. But brand is not in test mode". Building against a sandbox brand needs a test integrator ID; going live needs a production one — a separate request to Clopos. The refresh log passes Clopos's own message through verbatim because it is more useful than anything we could write.

Authentication is POST /open-api/v2/auth with all four values, returning a JWT that encodes brand, venue, integrator and environment.

The JWT goes in an x-token header, not Authorization. Clopos rejects a Bearer Authorization header outright. This is the most common way to mis-implement the API.

Tokens last one hour and are cached in DataStream.TempIntegratorData, scoped by a fingerprint of the client ID plus the brand so rotating credentials cannot silently reuse an old token. Caching is not just efficiency: /v2/auth is rate-limited to 60 requests/minute per client IP and Clopos explicitly asks integrators to reuse a token until it is near expiry.

Rate limits

Three tiers, each on a one-minute sliding window with standard RateLimit-* response headers:

  • 60/min per client IP on the auth endpoint (brute-force guard).

  • 600 failed requests/min per client IP across authenticated routes — only responses ≥ 400 count, so legitimate traffic never touches it.

  • 300/min per integrator_id:brand pair — the one to plan against. It is shared by every stream on that brand and every token minted for it; minting more tokens does not buy more quota, and distributing across workers does not multiply it.

The integration honours RateLimit-Reset as well as retry-after on a 429, backing off twice before deferring to the next cycle.

API contract

Call

Purpose

Call

Purpose

POST /v2/auth

Credentials → JWT.

GET /venues

Validates VenueID, supplies the venue name.

GET /categories

type=PRODUCT, include_children=true. Returns a nested tree.

GET /products

With with[]=category,modifications,codes.

GET /products/stop-list

Optional.

Envelope is { success, data[], total } — and success is checked as well as the HTTP status, because storing an error envelope as the catalog would resolve every binding to {E2}. Paging is page/limit (products cap at 100, categories at 999); the end condition is arithmetic against total, with a short page as the backstop.

Clopos's filter syntax is positional tuples, not the usual field=value: filters[0][0] names the column and filters[0][1] carries the value(s). Easy to get wrong and easy to mistake for a bug when reading the request.

Why the ProductTypes filter matters

A real brand's INGREDIENT and PREPARATION rows vastly outnumber its sellable products. Filtering to DISH,GOODS server-side is the difference between two pages and thirty — and thirty pages would blow the 25-second sync budget.

Prices are decimal major units. No division anywhere.

How the catalog is normalized

{ "source": "CloPOS", "schema_version": 1, "brand": "...", "venue_id": "...", "venue_name": "...", "currency": "", "categories": [ { "id", "name", "parent_id", "parent_name", "sort_order" } ], "items": [ { "id", "name", "description", "category_ids": [], "image", "thumbnail", "barcode", "product_type", "cooking_time", "sold_by_weight", "unit_weight", "sold_out", "variations": [ { "id", "name", "barcode", "price_amount", "cost_price", "sold_out", "is_default" } ] } ] }

Sizes are "modifications"

Clopos calls a size/variant a modification: a child product row whose parent_id points at the base product, carrying its own price and its own barcodes. A modification's price is its OWN FULL PRICE, not an upcharge — so the sync stores it verbatim and adds nothing to it.

This is the opposite of LightspeedPOS and Eats365POS, whose modifier prices are upcharges. Getting it backwards would silently double every sized item.

A product with no modifications gets one synthetic unnamed variation carrying its own price, and the picker hides the Variation row.

Variation labels

A modification's own name is usually blank and its full_name carries "Pizza (Large)". The sync prefers an explicit name, then falls back to trimming the parent's name off the front and stripping one balanced bracket pair — so the dropdown reads "Large" rather than repeating the item name on every row. Anything that does not match that pattern is returned unchanged; a wrong guess would be worse than a verbose label.

Categories

Clopos returns a nested tree (children[] recursively) which the sync flattens into a list carrying parent_id / parent_name, so the picker labels a subcategory "Parent / Child". Unlike Loyverse, Clopos does carry an operator-assigned position, so that arrangement is honoured — with a null position sorting to the end, because no ordinal means "no opinion", not "first".

Inactive and hidden categories are dropped along with their whole subtree: a child of a hidden parent is not something a menu board should surface even if the child itself is visible.

Images and barcodes

Images come from media[].urls (original / extra_large / thumb); the first entry wins. Barcodes read the codes[] array first and fall back to the deprecated scalar barcode field, which keeps this working against a brand on an older deployment.

Availability

UseInventory is off by default, for a specific reason: an unfiltered stop list reports every product that has ANY limitation on it, which is not the same thing as "sold out right now" for a brand that uses stock limits as a soft warning. The pull is also non-fatal — a board renders fine without availability, so a stop-list failure logs a warning and the sync continues with everything reading available.

Data fields available in the Smart Menu Editor

Name, Variation Name, Price, Description, Image URL, Thumbnail URL, Barcode, Cost Price, Cooking Time, Product Type, Inventory Status, Product ID, Venue Name, Currency.

No calories — Clopos has no nutrition concept. A calories binding resolves to a blank, not an error token.

Multi-venue and multi-brand designs

A Clopos product id is an auto-increment key inside one brand's database. Within a brand the same catalog serves every venue, so the id always hits and the fallback never fires. The picker records barcode and name for the cross-brand case; there is no SKU field in Clopos, which is why that chain is one link shorter than CloverPOS's.

Troubleshooting

Symptom

Likely cause

Symptom

Likely cause

"Integrator is in test mode. But brand is not in test mode"

A test IntegratorID pointed at a production brand. Request a production integrator ID from Clopos.

Authentication fails with all four values present

Check the Brand string exactly — it is a slug, not a display name.

HTTP 401 on every route after a successful auth

The token is being sent as Authorization: Bearer instead of x-token. (The integration does this correctly; this is for anyone testing by hand.)

HTTP 429

The 300/min per integrator:brand budget is shared with every other stream on that brand. Reduce stream count or raise the refresh interval.

Sized items are priced roughly double

Something has started adding the parent price to the modification price. Modifications carry their own full price — see the warning above.

Board shows ingredients and prep items

ProductTypes has been widened past DISH,GOODS.

Variation dropdown reads "Pizza (Large)"

The parent-name trim did not match — the operator's full_name is not composed the usual way. Harmless, but reportable.

Known gaps

  • modificator_groups (true modifiers — extras, sauces) are fetched but not projected; only modifications fold into sizes.

  • Price lists (/price-lists) are not applied, so a venue using a non-default price list will show base prices. This is the most likely source of a "wrong prices" report.

  • Recipes, packages, taxes and stations are not projected.

  • No webhook — hourly polling only. Clopos lists webhooks as roadmap, not shipped.

Verification status

Done: builds clean (0 errors); the editor partial parsed with the real Razor engine (0 parser errors) and verified pure ASCII; the contract taken from Clopos's own OpenAPI document and its per-page Markdown rather than the rendered docs.

Not done: no live Clopos call; no offline assertion harness; no end-to-end walkthrough. Onboarding is blocked until Kuusoft has an integrator_id from Clopos — that request should go out early, since a test one is needed even to build against a sandbox brand.