This is the public format every merchant must implement to ship listings to the aggregator. One spec, two encodings (JSON and XML), eight verticals.
A feed is a single URL on your servers that returns the catalog for one vertical. We pull it on a schedule, parse, validate, dedup against other merchants, and surface your offers on listing pages. We charge per outbound click; the spec itself is free to implement.
1. Overview
- One feed → one vertical. If you sell across multiple verticals (e.g. products + services), expose multiple feed URLs.
- Format: JSON or XML, your choice. Same data model in both.
- Encoding: UTF-8.
- Compression:
gzipstrongly recommended viaContent-Encoding. - Cache: ETags and
Last-Modifiedhonored. - Pagination: optional
page.nextcursor; up to 50 pages followed. - Auth: none / Basic / custom header / Bearer.
2. Envelope
JSON
{
"version": "1.0",
"merchant": { "slug": "demoshop", "name": "Demo Shop" },
"generated_at": "2026-05-08T12:00:00Z",
"currency": "EUR",
"language": "et",
"country": "EE",
"vertical": "products",
"items": [ /* … */ ],
"page": { "next": "https://demoshop.test/feed/products.json?page=2" }
}XML
<?xml version="1.0" encoding="UTF-8"?>
<feed version="1.0" xmlns="https://find-it.es/feed/1.0">
<merchant>
<slug>demoshop</slug>
<name>Demo Shop</name>
</merchant>
<generated_at>2026-05-08T12:00:00Z</generated_at>
<currency>EUR</currency>
<language>et</language>
<country>EE</country>
<vertical>products</vertical>
<items>
<item>…</item>
</items>
</feed>| Field | Required | Notes |
|---|---|---|
version | yes | Must be "1.0". Major-version bumps are breaking. |
merchant.slug | yes | Must equal the slug we registered for you. |
merchant.name | yes | Human-readable. |
generated_at | yes | ISO 8601 UTC. |
currency | yes | ISO 4217 default; per-item override allowed. |
language | yes | ISO 639-1; per-item override allowed. |
country | yes | ISO 3166-1 alpha-2; per-item override allowed. |
vertical | yes | One of: products, apartments, vehicles, pets, trips, tickets, jobs, services. |
items | yes | Array of items. May be paginated; up to 500,000 per feed. |
page.next | optional | URL of the next page. We follow up to 50. |
The XML namespace https://find-it.es/feed/1.0 is configurable via PUBLIC_FEED_NS_BASE; the validator accepts any value the operator has configured.
3. Item: common fields
These apply to every vertical. Vertical-specific fields go in attributes (see §4).
{
"external_id": "SKU-12345",
"title": "Apple iPhone 15 Pro 256GB Titanium",
"description": "<p>HTML allowed; sanitized server-side.</p>",
"category_path": "Electronics/Phones/Smartphones",
"category_id": "merchant-internal-cat-42",
"url": "https://demoshop.test/p/iphone-15-pro?utm=findit",
"images": [
{ "url": "https://cdn.demoshop.test/iphone-1.webp", "alt": "Front" }
],
"price": 1199.0,
"currency": "EUR",
"original_price": 1299.0,
"availability": "in_stock",
"stock": 15,
"condition": "new",
"delivery": { "cost": 4.99, "days_min": 1, "days_max": 3 },
"identifiers": {
"gtin": "0194253433538",
"mpn": "MTV03ZD/A",
"brand": "Apple",
"model": "iPhone 15 Pro"
},
"attributes": { "color": "Titanium", "storage_gb": 256 }
}| Field | Required | Notes |
|---|---|---|
external_id | yes | Unique per merchant. Used to dedup across runs. 1–200 chars. |
title | yes | 5–250 chars. |
description | optional | HTML allowed; sanitized. Allowed tags: p, br, ul, ol, li, strong, em, a[href]. |
category_path | recommended | /-separated. Mapped to our taxonomy. |
category_id | optional | Your internal id; helps when path is inconsistent. |
url | yes | Click-out URL. Must be https://. May contain tracking params. |
images | recommended | First image is primary. Up to 20. |
price | yes | Number > 0. Float OK. |
currency | optional | Defaults to envelope currency. |
original_price | optional | If present, must be > price for sale display. |
availability | optional | in_stock / limited / preorder / oos. |
stock | optional | Integer ≥ 0. |
condition | optional | new / used / refurbished. See §3.1 for used/refurbished items. |
delivery.* | optional | Cost (in currency) and SLA in days. |
identifiers.* | optional but powerful | See §5. |
attributes | depends on vertical | See §4. |
3.1 Used & refurbished items (products)
We compare offers on one canonical listing page per product. Condition belongs to your offer, not to the product, so keep it out of the title:
- Do set
conditiontousedorrefurbished. - Do send the cosmetic grade in
attributes.grade(e.g.A+,A,AB,B,BC,C) and optionally battery health inattributes.battery(e.g."100"). - Don't put "Refurbished", "Kasutatud", "Grade A", "A+" etc. in the
title. The title should name the product exactly as a new unit would:"Apple iPhone 15 Pro 256GB Titanium".
{
"title": "Apple iPhone 15 Pro 256GB Titanium",
"condition": "refurbished",
"attributes": { "color": "Titanium", "storage_gb": 256, "grade": "A", "battery": "98" }
}Your condition and grade are displayed next to your shop name on the listing page; the listing title, H1 and URL stay condition-free.
Server-side normalization: if condition or grade markers do appear in a product title, we strip them during import and move them to the offer (a title marker like "Refurbished" also upgrades a missing/new condition to refurbished). Bare grade letters (A, B, C, …) are only interpreted as grades when the item is declared used/refurbished or the title carries an explicit condition marker. Don't rely on this: clean titles match better across merchants.
4. Vertical extensions
The attributes blob carries vertical-specific fields. JSON Schemas live at static/vertical-schemas/{vertical}.schema.json.
| Vertical | Required attributes |
|---|---|
products | none beyond common |
apartments | transaction (sale/rent), property_type, area_m2, address |
vehicles | vehicle_type, make, model, year |
pets | species, transaction (sale/adoption/rehome/supply), location |
trips | trip_type, from, to, depart_at, nights |
tickets | event_name, event_date, venue |
jobs | location, employment_type |
services | service_type, location |
The table lists what a feed must carry. Each vertical accepts many more optional attributes; the authoritative list is the JSON Schema itself, which you can fetch and validate against: /vertical-schemas/vehicles.schema.json, and the same for every vertical above.
5. Identifiers and matching
We dedup the same real-world thing across merchants so users see one comparison page with N offers. Provide as many identifiers as you can:
For products:
gtin: strongest. EAN-13 / UPC-A / GTIN-14. We canonicalize to GTIN-14 and validate the check digit.(brand, mpn): strong.- Title + brand: heuristic, used only as fallback. Ambiguous matches are flagged for human review.
For other verticals:
vehicles:vin(17-char ISO 3779)apartments:cadastre_numbertickets:event_id
If you can't supply any, your listings still surface, but they won't merge with other merchants' offers and you'll appear as a standalone listing.
6. Validation rules
The validator enforces every rule below; full details: src/lib/feed/validate.ts.
Envelope
version="1.0".merchant.slugmatches the slug we have on file.verticalis in the supported enum.currencyis ISO 4217.languageis ISO 639-1.countryis ISO 3166-1 alpha-2.
Item
price > 0.original_price > pricefor sale display (warning, not error, if equal).urlandimages[*].urlstart withhttps://(configurable:FEED_ALLOW_HTTP=truefor dev).external_id1–200 chars;title5–250 chars.description≤ 50 KB after sanitize.gtinis 8/12/13/14 digits with valid GS1 check digit. Invalid GTINs are warnings: we ignore the GTIN for matching and fall back to other matching signals.attributesvalidated against the vertical's JSON Schema.
Caps
- Compressed file ≤ 200 MB.
- Uncompressed ≤ 1 GB.
- Items per feed ≤ 500,000. Beyond → require pagination.
HTTP
- Connect timeout 30s, total 5min per page.
- We honor
If-Modified-SinceandETag. 304 Not Modified→ no parsing, no item changes; just alast_pulled_atbump.- We accept
Content-Encoding: gzip, br, deflate.
7. Authentication
Pick one when registering your feed. Stored encrypted at rest (AES-256-GCM).
| Mode | What we send |
|---|---|
none | Nothing. Use an unguessable URL slug. |
basic | Authorization: Basic <base64(user:pass)> |
header | A header you specify, e.g. X-Feed-Token: <secret> |
bearer | Authorization: Bearer <token> |
Rotate your secret in the merchant portal anytime. Old secrets are wiped immediately.
8. Discovery
To minimize wasted pulls (and your bandwidth), implement these:
If-Modified-SinceandETag: the most common case. Apache and Nginx do it for free on static files.Cache-Control: public, max-age=…matching your update cadence; we respectmax-ageif it exceeds ourpull_interval_minutes.Content-Encoding: gzip. We always sendAccept-Encoding: gzip, br, deflate.page.nextfor catalogs over 50,000 items.
Frequent failures in our pull (HTTP 5xx, timeouts, parse errors) get you paused and we email you. Five consecutive failures pauses the feed.
9. Examples
Reference feeds are checked in as JSON and XML examples at:
static/feed-examples/products.json/.xmlstatic/feed-examples/apartments.json/.xmlstatic/feed-examples/vehicles.json/.xmlstatic/feed-examples/pets.json/.xmlstatic/feed-examples/trips.json/.xmlstatic/feed-examples/tickets.json/.xmlstatic/feed-examples/jobs.json/.xmlstatic/feed-examples/services.json/.xml
Each has at least one valid item end-to-end. The invalid-examples/ subfolder has small files designed to fail validation.
10. Versioning & deprecation policy
- Spec version is
MAJOR.MINOR(currently1.0). - Minor bumps are additive only; old feeds keep working.
- Major bumps are breaking; we publish at least 60 days' notice and run both versions side by side during the transition.
- Deprecated fields are marked in the docs and validator emits warnings before they become errors.
11. FAQ
Can I send my whole catalog every pull? Yes, that's the default; we diff against last run. For very large catalogs (>50k items), pagination via page.next is encouraged.
Can I send only deltas? Not in 1.0. The 1.1 spec will add an optional since query parameter. For now, a full snapshot every pull is the contract.
Can I push instead of being pulled? Live-quote feeds (e.g. flights, last-second tickets) can register a push endpoint where we POST you. See merchant portal → "Push mode".
I run WooCommerce / WordPress. Do I have to build this? No. Install the FindIt Connector plugin, press "Connect to find-it", and it generates a compliant feed for every vertical, registers it for you and keeps price and stock in sync within the minute. If you are writing an integration for another platform, the handshake and management API it uses are documented in the storefront plugin API; the feed format on this page is unchanged either way.
My category names don't match yours. Send your own category_path and category_id. We map merchant categories to our canonical taxonomy on first sight; admins approve mappings, then they're cached.
You're charging me per click. What counts as a click? A user pressing "Go to shop" on a listing that has your offer. Bot clicks, repeat clicks within a 30-minute window, and clicks failing fraud checks are not billable. Every click is listed in your portal under Clicks, each with the reason it was or wasn't charged, so you can check any individual one rather than taking the total on trust.
Check your feed right here
Runs the exact validation our importer runs, against the spec on this page. No account needed.