דילוג לתוכן

למפתחים

חברו את החנות שלכם ל-Hosqi

API קטן לקריאה בלבד או קובץ אחד. Hosqi מייבא את המוצרים, שומר אותם מעודכנים, וה-AI עונה ללקוחות עם המחירים והמלאי האמיתיים שלכם.

החבילה כוללת את המדריך, כלי הבדיקה, סכמות JSON ודוגמת API עובדת.

המדריך זמין באנגלית ובארמנית.

You build a small read-only API on your shop. Hosqi reads it on a schedule, stores the catalog, and answers customers on Instagram, Messenger, WhatsApp and Telegram with your real products, prices and stock. Your shop stays the single source of the data.

Who does what

Keep your shop's business logic. You send its result, and Hosqi says exactly that.

  1. Owner. In Hosqi: Connections → Connected apps → Add a shop. Enter a name and the address the API will have, press Create a key, copy the key, press Add. The API does not have to exist yet: Hosqi does not call it at this step.
  2. Owner. Give the developer the key and the Domain token from Settings → For your developer on the shop's page. Use a safe channel, not an ordinary chat.
  3. Developer. Build the routes and keep the key and the token in the server's settings, not in the code. Check the result with the validator.
  4. Developer. Put it on production, run the validator on the production address and do the hand checks of section 12.
  5. Owner. Press Test connection, then Import and index. A shop of 8,000 products took about five minutes.
  6. Owner. Press Allow AI, then Run the AI test. A workflow that sells from this shop can be published after the test passes.

An API or a file?

Custom APIFeed file by link (section 13)
You buildTwo routes, three with /offersOne file at a link
Hosqi readsOnly what changedThe whole file
Price and stock in a replyChecked with your shop just before the reply, when you have /offersAs of the last read
Good forPrices and stock that change oftenA smaller catalog, or a shop that cannot host routes

The contract is version 1.1. It only adds prices in other currencies to 1.0 (section 7), so data written for 1.0 stays valid. It has three parts:

PartWhere
This guideEverything you need to build the API
openapi.jsonThe three routes, OpenAPI 3.1
JSON SchemaOne file per object, draft 2020-12, in validator/commerce_schemas/1.0/

1. Start in five minutes

Run these commands from the folder of this package. Run the reference shop, then run the validator against it. You need Python 3.11 or later.

python sample-api/server.py
pip install "jsonschema>=4.26,<5"
python validator/commerce_contract.py --api http://127.0.0.1:8787 --allow-local --bearer sample-key --all

The reference shop is sample-api/server.py. It uses only the standard library, so it is easy to read as an example. Its 26 records are in sample-api/catalog.json.

It reports locale_incomplete warnings on purpose, because the examples are shortened and most have English text only. CARPET-CUT reports endpoint_required, because the sample has no /quote and that product goes to a person.

Then point the validator at your own API:

python validator/commerce_contract.py --api https://shop.example/hosqi/v1 --bearer YOUR_KEY --all

Without --all it behaves like Test connection in Hosqi: it reads the manifest, the shop records and two pages of products, and stops at the first failed answer. With --all it reads everything as a sync does, with the waits and retries in section 10, and also checks counts and references and computes your level. Both respect max_rps. Add --json for the full report, and --token with the token Hosqi showed you to test the ownership proof.

The validator is one file and one folder. Keep commerce_contract.py next to commerce_schemas/.

Testing on your own computer. --allow-local lets the validator read a plain http:// address on your computer. Hosqi itself never does: it needs HTTPS on a public address. On a local address the domain proof of section 9 cannot pass, so --token reports ownership_missing there, and that is expected. Product URLs must still be on the domain of store.base_url, so use your real shop domain in the data while you test.

2. What to build

The merchant gives Hosqi a base URL, for example https://shop.example/hosqi/v1.

RouteRequiredPurpose
GET /manifestYesVersion, capabilities, counts, last data change
GET /recordsYesCatalog records, page by page
GET /offersNoCurrent price and availability for a few variants, just before a reply

Rules for every route:

  • HTTPS on port 443, TLS 1.2 or later, a public address.
  • Send Content-Type: application/json. Hosqi sends Accept-Encoding: gzip.
  • Hosqi does not follow redirects. Give the final URL.
  • All routes are read-only and safe to repeat.
  • Hosqi calls as Hosqi-Catalog/1.0, one request at a time, within max_rps (default 2). A firewall or bot check must let it through. It waits up to 30 seconds for a page, and a run stops after 30 minutes.

3. Authentication

MethodRequestUse
Bearer keyAuthorization: Bearer <key>Recommended
Named headerX-Api-Key: <key>, the name is your choiceWhen your shop already works this way
BasicAuthorization: Basic <base64>Older systems
Secret URLA long random path, no headerOnly for data that is already public

Hosqi stores the key sealed and never shows it again. The merchant can replace it at any time.

4. GET /manifest

Hosqi reads this first, on every run.

{
  "hcf": "1.1",
  "kind": "catalog_api",
  "store_name": "Example Shop",
  "generated_at": "2026-11-05T09:00:00+04:00",
  "data_modified_at": "2026-11-05T08:40:00+04:00",
  "counts": {"store": 1, "location": 1, "delivery_profile": 1, "policy": 2, "product": 1240},
  "variant_count": 5310,
  "capabilities": {"page_size_max": 200, "delta": true, "tombstones": true, "offers": true, "max_rps": 5},
  "verification": "hq-verify-7f2a91c0"
}
FieldRequiredNotes
hcf, kindYes1.1 (1.0 is still accepted), catalog_api
store_nameYesShown to the merchant
generated_atYesYour clock. Used for delta reads
data_modified_atNoLast change to any record. If it has not moved, Hosqi skips the read
countsYesRecords per type at this moment. Count what a full read returns: archived products yes, tombstones no. Always give product, even when it is 0
variant_countNoShown to the merchant
capabilities.page_size_maxYesLargest limit you accept, 1 to 1,000
capabilities.deltaYestrue if updated_since works
capabilities.tombstonesYestrue if deletions appear in delta reads
capabilities.offersYestrue if /offers exists
capabilities.max_rpsNoRequests per second Hosqi may make. Default 2
verificationSee section 9The token Hosqi shows the merchant

An ETag is recommended. Base it on the data, not on the body, because generated_at changes on every call. Hosqi sends If-None-Match, and a 304 means nothing changed.

5. GET /records

GET /records?limit=200&cursor=c2tpcD0yMDA&types=product

ParameterNotes
limitDefault 100, up to page_size_max. Hosqi never asks for more. Answer 400 to a value you do not accept, such as one below 1
cursorFrom the previous page. Absent on the first page. Answer 410 to one you do not recognise, a malformed one included
typesComma list of record types. Absent means all. A tombstone counts as its target type. Hosqi uses this only in Test connection, and tolerates an API that ignores it
updated_sinceRFC 3339. Only with capabilities.delta. Inclusive: return records changed at or after this time
{
  "hcf": "1.1",
  "generated_at": "2026-11-05T09:00:03+04:00",
  "mode": "full",
  "records": [
    {
      "type": "product", "id": "MUG-350-BLUE", "version": 31,
      "updated_at": "2026-11-05T08:40:00+04:00",
      "kind": "simple", "status": "active", "visibility": "public",
      "i18n": {"hy": {"title": "Կապույտ բաժակ, 350 մլ", "url": "https://shop.example/hy/p/mug-350-blue"}},
      "variants": [
        {
          "id": "MUG-350-BLUE", "sku": "MUG-350-BLUE",
          "pricing": {"mode": "fixed", "price": {"amount": "4500", "currency": "AMD"}},
          "availability": {"state": "in_stock"}
        }
      ]
    }
  ],
  "next_cursor": "c2tpcD0yMDA"
}

Paging rules:

  • next_cursor is null on the last page. An empty page with a cursor is allowed.
  • The cursor is opaque to Hosqi, and must stay valid for at least 60 minutes. An expired cursor returns 410, and Hosqi restarts the run once.
  • Keep the order stable, so paging neither skips nor repeats a record: by id in a full read, by updated_at then id in a delta read. Sorting by type first is fine.
  • Order between record types does not matter. Hosqi resolves references at the end of the run.
  • A page is at most 10 MB uncompressed. A record is at most 1 MB. A product has at most 2,500 variant rows.
  • An ID may appear on two pages if it changed during the read, and version decides. It must not appear twice on one page.

6. Changes and deletions

Full read. No updated_since. Hosqi reads to the last page and compares totals with counts. They may differ by up to 1% or 5 records, because a catalog can change during a read. A product missing from a completed full read is removed, after a guard: if more than 20% of active products would go, or the read is empty, Hosqi applies nothing and asks the merchant.

Delta read. Hosqi sends updated_since set to the generated_at of its last good run, minus 10 minutes. Return every record changed since then, with mode: "delta". The overlap means a record can arrive twice, and version makes that harmless.

version is an integer that only goes up for each record. The epoch of updated_at works. Hosqi applies a record only if its version is higher than the stored one. Raise it on every change. If you change a price and keep the version, Hosqi skips the record. This also holds for a tombstone: its version must be above the last version of the record it deletes, or Hosqi skips it and keeps selling.

To bring back a deleted ID, send the record with a higher version and restore: true.

Deletions.

CaseWhat your API doesWhat Hosqi does
Product deleted, tombstones=trueReturns a tombstone in delta reads for at least 30 daysStops selling it at once
Product no longer soldSends status: "archived" with a higher versionStops selling it at once
Product deleted, tombstones=falseNothingSees it at the next full read
Variant removedSends the whole product without itReplaces the product
{"type": "tombstone", "id": "MUG-350-BLUE", "target": "product", "version": 32,
 "updated_at": "2026-11-05T10:00:00+04:00", "deleted_at": "2026-11-05T10:00:00+04:00", "reason": "discontinued"}

How often Hosqi reads.

CapabilitiesScheduled run
delta=falseFull read, skipped when ETag or data_modified_at shows no change
delta=true, tombstones=trueDelta read, plus a full read at least once a week
delta=true, tombstones=falseDelta read, plus a full read at least once a day

Ping (optional)

Hosqi reads your API on its schedule. To have a change read within about a minute, call this address when something changes:

curl -X POST "https://app.hosqi.io/api/platform/catalog/v1/sources/SOURCE_ID/ping" \
  -H "Authorization: Bearer YOUR_PING_KEY"

The source page in Hosqi shows the address and creates the key, under For developers. The key is shown once. The body is not read, and a ping carries no data: Hosqi still reads the change from your API, only what changed if updated_since works, and everything if it does not.

AnswerMeaning
202Accepted. Pings are merged, and a source is read this way at most once a minute
401The key is wrong or was replaced
409The source is stopped or disconnected
503Hosqi is applying an update of this source right now. Try again after Retry-After seconds, or let the schedule read the change

7. Records

Every record has type, id, version and, in this API, updated_at. The record schemas leave updated_at optional because hosted files use them too, and records-page requires it. The schema of each type is the full field list.

typeSchemaPurpose
storestoreShop facts, markets, customer groups, checkout. Exactly one in a catalog
locationlocationBranch, warehouse, hours
delivery_profiledelivery-profileZones, methods, cut-offs, slots
policypolicyReturns, warranty, payment, installments, terms
productproductProduct with options, variants, rules, add-ons, components
price_listprice-listTiers, member, B2B, market and scheduled prices
promotionpromotionCoupons, automatic discounts, free shipping
inventoryinventoryFast stock update for a variant and location
tombstonetombstoneDeletion

Value rules.

  • Text is UTF-8 and plain. Hosqi strips HTML. A record is at most 32 levels deep.
  • Money is a decimal string plus an ISO 4217 code, never a number: {"amount": "4500", "currency": "AMD"}. A decimal is at most 24 characters.
  • Time is RFC 3339 with an offset. Local times are HH:MM and use an IANA zone from the same record, or the store.
  • Text in several languages uses i18n, keyed by BCP 47 tags such as hy, ru, en.
  • IDs are yours, unique per type, stable, and never reused for a different item. Do not put price or stock in an ID.
  • Unknown fields are ignored. Put your own data under x_<vendor>, and the AI will not see it.
  • An empty audience: {} means everyone.
  • Do not send cost prices, supplier data or customer personal data.

store

{
  "type": "store", "id": "store", "version": 8, "updated_at": "2026-11-05T08:00:00+04:00",
  "name": "Example Shop", "base_url": "https://shop.example", "status": "open", "timezone": "Asia/Yerevan",
  "default_locale": "hy", "locales": ["hy", "ru", "en"],
  "declared_complete": ["price_list", "promotion", "addons", "delivery_profile", "policy"],
  "markets": [
    {"id": "am", "countries": ["AM"], "currency": "AMD", "decimals": 0, "rounding": {"mode": "half_up"}, "tax": {"included": true}}
  ],
  "price_currencies": [{"currency": "USD"}, {"currency": "RUB", "decimals": 0}],
  "checkout": {"url_template": "https://shop.example/cart/add{?variant,qty}", "carries": ["variant", "qty"]}
}
  • markets[].currency is the currency the customer pays in. Hosqi never converts currencies. Variant prices use the currency of base_market, which is required when there are several markets. A price in another market comes from a price_list.
  • price_currencies (since 1.1) lists other currencies you take payment in and set your own prices in. See Prices in other currencies below.
  • declared_complete lists what is complete in your data, including what you do not have. Listing promotion with no promotion records means the shop has none.
  • Product, policy and checkout URLs must be on your domain or in checkout.allowed_hosts. Image URLs may be on any public HTTPS host.
  • store.endpoints is for live calculations (/quote, /availability), which come later. Leave it out for now. A live product then goes to a person, with the warning endpoint_required.

product

The product in section 5 is the smallest valid one: type, id, version, kind, status, visibility, i18n with title and url, and variants. Every ID is any text of up to 255 characters.

Every product has at least one variant, even a simple one. status must be active to reach the AI. visibility=members needs audience.

Send short_description and description in i18n for each language. Hosqi stores both, and the AI search reads them, so a customer can find a product by what it is for and not only by its name. short_description is one or two sentences and comes first, and a very long description is cut at the end. Plain text is best, and HTML is stripped.

availability.state is one of in_stock, out_of_stock, preorder (needs available_from), backorder and made_to_order (need lead_time), discontinued, unknown. qty must mean sellable stock. qty_disclosure is hidden unless you set exact or band.

pricing.mode:

modeNeedsMeaning
fixedpriceA fixed price. Add sale for a scheduled price, or compare_at for a "was" number
option_sumbasebase plus the price_delta of each chosen option value
per_measurerate, per, inputs, min_chargePrice by size or weight. Declare each input in product.live_inputs
live, on_requestOnly your shop knows. The AI hands off to a person
bundlebundle.price_ruleThe price comes from the parts

Options and variants

A product with more than one variant needs options and combinations.

{
  "type": "product", "id": "BOOK14", "version": 5, "updated_at": "2026-11-05T08:00:00+04:00",
  "kind": "variable", "status": "active", "visibility": "public",
  "i18n": {"en": {"title": "Book 14", "url": "https://shop.example/p/book14"}},
  "options": [
    {"id": "chip", "label": {"en": "Chip"}, "values": [{"id": "base", "label": {"en": "Base"}}, {"id": "pro", "label": {"en": "Pro"}}]},
    {"id": "ram", "label": {"en": "Memory"}, "values": [{"id": "16", "label": {"en": "16 GB"}}, {"id": "48", "label": {"en": "48 GB"}}]}
  ],
  "combinations": {
    "mode": "rules", "closed": true,
    "rules": [{"id": "r1", "when": {"chip": ["base"]}, "require": {"ram": ["16"]}, "why": {"en": "The base chip takes 16 GB only."}}]
  },
  "variants": [
    {"id": "BOOK14-BASE-16", "options": {"chip": "base", "ram": "16"}, "pricing": {"mode": "fixed", "price": {"amount": "759000", "currency": "AMD"}}, "availability": {"state": "in_stock"}},
    {"id": "BOOK14-PRO-16", "options": {"chip": "pro", "ram": "16"}, "pricing": {"mode": "fixed", "price": {"amount": "899000", "currency": "AMD"}}, "availability": {"state": "in_stock"}},
    {"id": "BOOK14-PRO-48", "options": {"chip": "pro", "ram": "48"}, "pricing": {"mode": "fixed", "price": {"amount": "1099000", "currency": "AMD"}}, "availability": {"state": "in_stock"}}
  ]
}
combinations.modeMeaningvariants holds
enumeratedOnly the listed rows are soldEvery valid combination
allEvery combination existsEvery combination, or a template row using *
rulesEvery combination allowed by rules existsOnly rows with their own SKU, stock or price. default_variant gives the rest
liveToo complex to describeOptional

closed is required. true means anything not valid under the mode does not exist, so the AI may refuse it. false means the data may be incomplete, so the AI says it will check.

A rule has when (option to values, AND across options, OR inside a list), then at least one of require and forbid, and an optional why the AI can repeat. A value * in a variant matches any value of that option.

In all and rules modes, every valid combination needs a price: a row of its own, a * row, or, in rules mode, default_variant. Otherwise the validator reports combination_uncovered.

delivery_profile, policy, location

{
  "type": "delivery_profile", "id": "dp", "version": 3, "updated_at": "2026-11-05T08:00:00+04:00",
  "timezone": "Asia/Yerevan",
  "methods": [
    {"id": "city", "type": "delivery", "zones": [{"country": "AM", "region": "Yerevan"}],
     "price": {"amount": "1000", "currency": "AMD"},
     "free_over": {"amount": "30000", "currency": "AMD"}, "free_over_basis": "subtotal_before_discounts",
     "same_day": true, "cutoff": {"time": "16:00"}},
    {"id": "pickup", "type": "pickup", "location_ids": ["main"], "price": {"amount": "0", "currency": "AMD"}}
  ]
}

Each method has zones or location_ids, and exactly one of price, price_bands, quote_required.

{
  "type": "policy", "id": "returns", "version": 2, "updated_at": "2026-11-05T08:00:00+04:00",
  "policy_type": "returns", "approved_at": "2026-11-01T10:00:00+04:00",
  "i18n": {"en": {"summary": "Unused items can be returned within 14 days."}},
  "returns": {"accepted": true, "window_days": 14, "who_pays": "customer"}
}

i18n.summary is the text the AI may repeat. approved_at says when the merchant approved it.

{
  "type": "location", "id": "main", "version": 1, "updated_at": "2026-11-05T08:00:00+04:00",
  "name": "Main shop", "address": "1 Example Street, Yerevan", "kinds": ["pickup", "shop"],
  "hours": [{"days": ["mon", "tue", "wed", "thu", "fri", "sat"], "from": "10:00", "to": "20:00"}]
}

An availability without location_id is your answer for the whole shop, however many locations you have. Hosqi does not add it up or work it out again, so send what your own site would tell a customer.

Branches, pickup and units

Keep your business logic. Send its result, and Hosqi says exactly that.

Stock by branch. availability.state stays your overall answer. availability.locations says where the product is, in the product record and in /offers. This product shows all three things in this section:

{
  "type": "product", "id": "PIPE-12", "version": 1762470000123, "updated_at": "2026-11-05T08:00:00+04:00",
  "kind": "simple", "status": "active", "visibility": "public", "delivery_profile_id": "pickup-only",
  "i18n": {"en": {"title": "Copper pipe", "url": "https://shop.example/p/pipe-12"}},
  "variants": [{
    "id": "PIPE-12", "pricing": {"mode": "fixed", "price": {"amount": "3200", "currency": "AMD"}},
    "sale_unit": {"unit": "ft", "display": "per ft", "step": "10", "min": "10"},
    "availability": {"state": "in_stock", "locations": [
      {"location_id": "main", "state": "in_stock"},
      {"location_id": "east", "state": "out_of_stock"}
    ]}
  }]
}
  • Each entry needs location_id and state. qty, qty_disclosure, lead_time, available_from and as_of are optional.
  • A product sent with locations replaces the branches Hosqi holds, so send all of them, including those at zero.
  • inventory records with location_id are the other way. Each branch then has its own version, and only a record newer than the product changes the overall state. If your product version already follows stock, use locations.

Pickup-only products. A product whose delivery_profile_id points to a profile with only pickup methods is never offered for delivery. Replies name the branches from the method's location_ids that have it, as pickup only: East, Main. The warning pickup_place_missing means the product is in stock, but in no branch of that method.

Units. A price with nothing after it reads as the price of one piece. For a product sold by a measure, add sale_unit to the variant, as above, and keep the title as it is on your site.

  • unit is a code. ft, m, yd, kg, lb, l and m2 are translated in replies, and piece, pc, pcs, each, ea, item and unit show nothing.
  • display is your own words, used in your default language and for any other code.
  • step and min say what can be ordered. qty must use the same unit as the price, or leave it out.

No public price. With on_request, live or disclosure: "verified_only", a reply says price on request, and a customer who needs that price goes to a person.

Prices in other currencies

Some shops sell in one country but take several currencies, and set a price in each themselves. Version 1.1 carries those prices next to the main one:

{
  "type": "product", "id": "PHONE-6", "version": 4, "updated_at": "2026-11-09T08:00:00+04:00",
  "kind": "simple", "status": "active", "visibility": "public",
  "i18n": {"en": {"title": "Northline Phone 6, 128 GB", "url": "https://shop.example/p/phone-6"}},
  "variants": [{
    "id": "PHONE-6", "availability": {"state": "in_stock"},
    "pricing": {
      "mode": "fixed",
      "price": {"amount": "189000", "currency": "AMD"},
      "currency_prices": {
        "USD": {"price": {"amount": "489", "currency": "USD"}},
        "RUB": {"price": {"amount": "39900", "currency": "RUB"},
                "sale": {"price": {"amount": "37900", "currency": "RUB"}, "valid_to": "2026-11-30T20:00:00Z"}}
      }
    }
  }]
}
  • Declare the currencies once in store.price_currencies, in the order you prefer, as in the store above. List only currencies you take payment in, at most 10, and never the currency of a market.
  • currency_prices gives each declared currency a price, and an optional sale and compare_at, all in that currency. A sale in one currency does not touch the others.
  • The main price stays in the market currency and stays required. Leave a currency out when the product has no price in it. Hosqi never converts, so that currency is not shown for this product.
  • It is the final price of the variant, used with mode fixed or option_sum. With any other mode it is ignored, with the warning currency_prices_ignored.
  • decimals defaults to the ISO 4217 minor unit. basis is fixed (the default) when you set the price, or shop_rate when your shop's system computed it from its own exchange rate.
  • The owner chooses in Hosqi which of these prices the AI shows, how many at once, and which comes first.
  • A key that is not in price_currencies, or an amount in another currency inside it, is currency_not_in_market. A repeated currency, or a market's currency, in price_currencies is price_currency_invalid.

Price lists, promotions, add-ons, bundles

All four appear in the examples in sample-api/catalog.json, and the schemas give every field.

  • A price_list entry is a tier when min_qty is above 1. Tiers need tier_mode (all_units or graduated) and tier_scope (variant or product). disclosure: "verified_only" keeps the numbers away from unverified contacts.
  • A promotion gives the text the AI may say. Hosqi does not compute discounts. A code needs code_disclosure.
  • addons change an order without being a variant. Each has a price with a kind, and carry says how the choice reaches your shop. text, number, date and file add-ons need validation, even if it is empty. carry: "link" needs addons or personalisation in checkout.carries.
  • A bundle product lists components. Bundles are one level deep.

8. GET /offers

Optional. With it, Hosqi can confirm a price or stock figure at the moment of a reply. Without it, Hosqi relies on the last sync and its age.

GET /offers?variant_ids=MUG-350-BLUE,BOOK14-PRO-48&market=am

{
  "hcf": "1.1",
  "generated_at": "2026-11-05T12:00:00+04:00",
  "offers": [
    {"variant_id": "MUG-350-BLUE", "product_id": "MUG-350-BLUE", "status": "ok",
     "pricing": {"mode": "fixed", "price": {"amount": "4500", "currency": "AMD"}},
     "availability": {"state": "in_stock"}},
    {"variant_id": "BOOK14-PRO-48", "status": "not_found"}
  ]
}
  • Up to 20 IDs. Answer 400 to more. market is required when the store has more than one market, and Hosqi leaves it out when there is one.
  • pricing and availability are required with ok, in the same shape as the catalog. An option_sum price stays option_sum.
  • pricing includes currency_prices when the variant has them, so one call checks every currency.
  • The reply needs Cache-Control: no-store.
  • Answer within 2,500 ms. Hosqi never holds a customer's reply longer than that.
  • not_found means Hosqi stops offering that variant.

9. Proving the domain is yours

Hosqi shows the merchant a token. Any one of these proves ownership:

  • /manifest returns the token in verification, and the API host is on the same domain as store.base_url.
  • A DNS TXT record on the shop's domain, with the value hosqi-verification=<token>.
  • A <meta name="hosqi-verification"> tag on the home page.

The owner finds the token on the shop's page, under Settings → For your developer → Domain token.

10. How Hosqi treats your answers

HTTPHosqi does
200Reads the body
304Nothing changed, the run ends as a success
400, 401, 403, 404Stops the run, tells the merchant
410 on /recordsRestarts once
429, 503Waits for Retry-After, up to 5 minutes
Other 5xx, timeoutRetries the same page after 2 s, 10 s and 60 s, then stops the run

A stopped run never changes the published catalog. You may describe an error with application/problem+json, and Hosqi shows its title to the merchant.

11. Validation

The validator is the code Hosqi runs. Severity decides what happens:

SeverityEffect
fatalNothing from this run is applied
errorThe record is rejected, and the previous version stays. If more than 2% of records are rejected, and more than one, the run becomes fatal (too_many_rejected). A delta read counts against the size of the catalog, not of the read
warningThe record is accepted. Fix it when you can
infoFor your attention only

Each issue has code, severity, record_type, record_id, pointer, message and hint. The commonest ones:

CodeWrongRight
invalid_money"price": 4500"price": {"amount": "4500", "currency": "AMD"}
missing_required"i18n": {"hy": {"title": "Բաժակ"}}"i18n": {"hy": {"title": "Բաժակ", "url": "https://shop.example/hy/p/mug"}}
option_value_unknownOption size has s, m, and a variant says "options": {"size": "xl"}Add xl to options, or fix the variant
duplicate_combinationTwo variants with {"size": "m", "color": "red"}One variant for each combination
combination_not_allowedA variant breaks a ruleFix the variant or the rule
url_off_domain"url": "https://cdn-shop.net/p/mug" on a source for shop.exampleUse your domain, or add the host to checkout.allowed_hosts
html_received/records returns a login pageExempt the route from the login or firewall rule
pagination_loopnext_cursor is the same on every pageReturn the next position, and null on the last page
count_mismatchcounts.product is 1,240 but 300 arriveFix the query, or return the true count

Level. Hosqi computes a level. It is never claimed.

LevelYour data hasThe AI can
L0A fatal problem, no store, or no active productNothing from this catalog
L1 Basicstore, products with id, title, URL, price, availabilityAnswer simple questions, send a product link
L2 VariantsClean options, variants, combinationsGuide option choice, refuse invalid combinations
L3 Commercedelivery_profile, policy (returns and payment, approved in the last 12 months), declared_completeAnswer delivery, returns and policy questions

The validator's level comes from the data alone. Hosqi's live checks, such as comparing links and prices with your site, can lower it.

What the validator cannot see. It tries updated_since with an old and a current time, and it measures /offers, but it cannot delete your data or change your prices. Test these by hand: a tombstone appears in a delta read, version rises on every change, a 304 comes back when nothing changed, and a long catalog pages without skipping a record.

12. Before you connect

  • /manifest and /records answer on HTTPS, port 443, with your key.
  • The validator with --all reports no fatal and no error.
  • version goes up on every change, for every record, and a tombstone is above the record it deletes.
  • After you change a price and delete a product, a delta read returns both.
  • A firewall or bot check lets Hosqi-Catalog/1.0 through.
  • You know how the merchant will prove the domain (section 9).
  • A change of stock at one branch reaches a delta read with a higher version.
  • A value that comes from settings, such as free_over, raises the version of its record when it changes.
  • The key and the domain token are in the server's settings, not in the code.

Then tell the owner. The owner presses Test connection, then Import and index. If something is refused, Hosqi shows its code and how to fix it.

13. A file instead of an API

When a small API is too much, the shop publishes one file at a link, and Hosqi reads it on a schedule. In Hosqi choose Feed file by link.

  • JSON Lines: UTF-8, one record per line, .jsonl or .jsonl.gz, on HTTPS.
  • The first line is a feed record. source_id is the Source ID under For your developer.
  • Each next line is a record of section 7, the same as /records returns.
  • Raise sequence with each new file, and give in counts how many records of each type follow.
  • Send an ETag. Hosqi sends If-None-Match, and a 304 means nothing changed.
  • Prove the domain with DNS or the meta tag of section 9.
{"type": "feed", "id": "feed", "version": 1762470000123, "hcf": "1.1", "source_id": "YOUR_SOURCE_ID", "mode": "full", "generated_at": "2026-11-05T08:00:00+04:00", "sequence": 1, "counts": {"store": 1, "product": 2500}}

Check the file before you publish it:

python validator/commerce_contract.py --file feed.jsonl

14. Mistakes we have seen

  • version follows only the product table. A price or stock change never reaches Hosqi. Use the latest change of every table that feeds the record.
  • version follows a job that rewrites rows on every run. Every product looks changed on every read. Follow the real change, not the time of the job.
  • A value from settings changes, such as free_over, and version stays. Hosqi applies only a higher version, also on a full read, so replies keep the old rule. Raise the version of that delivery_profile, store or location.
  • Only the main branch is sent. Products in other branches read as out of stock. Send your overall state, and every branch in locations.
  • location.name repeats the shop's name. Replies run long, and branches are cut to three and +1. Use a short name, such as Sun Valley.
  • The unit is in the title, or qty is in pieces while the price is per foot. The reply reads wrong. Keep the title as on your site, add sale_unit, and send qty in the unit of the price or not at all.
  • A price the public cannot see is sent as fixed. Customers read a price your site hides. Use on_request.
  • A product url is a placeholder, such as /product/pending-123. The customer gets a page that does not help. Send the page the product really has.
  • A login, firewall or bot check answers Hosqi. html_received, or the read stops. Let Hosqi-Catalog/1.0 through, and give the final address, because Hosqi does not follow redirects.