# Site files: product feeds and plain files on the storefront host

> **Резюме по-русски.** Файлы на хосте витрины — на корне или в разделе
> дерева: фиды Яндекса (YML) и Google (XML), CSV, `ads.txt`, верификация,
> простая html-страница без скриптов. Тело — шаблон Go на живом каталоге.
> Здесь — короткий вход; полный справочник по разделам —
> `/docs/feeds-reference?section=<slug>`.

**Status:** draft · **Verified:** 2026-09-24 against branch
`feature/site-files-feeds` · **Owner:** site files line (ФИДЫ-РЕКЛАМЫ)
**Serves:** `GET /docs/feeds` · full reference `GET /docs/feeds-reference`

## What site files are

A site file is `{category_id, name, body, is_published, in_sitemap}` served by
the shop's storefront host at the root (`category_id` `0`) or under any
category (`/sofas/feed.yml`); the full `path` is computed. The body is a Go
`text/template` (≤ 64 KB) rendered on the **live catalogue** as an anonymous
buyer sees it, at storefront listing prices: a Yandex YML or Google Merchant
feed, CSV/JSON for a partner, `ads.txt`, a verification file, a simple
`.html` page. Scopes `site-files:read` / `site-files:write`, owner or
administrator only, writes with a production key only.

## How it works

- **Serving:** `https://<host>/<path>` on the shop host and its custom
  domain; `Content-Type` by extension, ETag/304, `X-Robots-Tag: noindex`
  except `.html`, `Content-Security-Policy: sandbox` on every answer —
  scripts never run (interactive pages are HTML widgets).
- **Cache:** one built copy per file and host, up to 60 minutes; a file edit
  or a catalogue change is seen within ~15 s. A failed render of an unedited
  file serves the last good copy (≤ 7 days) — only preview shows the error.
- **Publish:** `is_published: false` — stored and previewable, 404 on the
  host; the site itself must be published (no PIN). `in_sitemap: true` lists
  a published `.html` file in `/sitemap.xml`.
- **Nesting:** the path follows the category's slugs — renaming a slug moves
  the URL; a deleted category leaves the file `orphaned` (404).
- **Permanent address:** `/_file/<id>` serves the same file wherever it lives
  now, with `Link: rel="canonical"` to the current path.

## Quick start: three requests

```bash
# 1. the reference body (yandex_yml, google_xml, empty)
curl -s "$API/site-files/templates" -H "$T"
# 2. render it on 20 live products without saving → errors: []
curl -s -X POST "$API/site-files/preview" -H "$T" -H "$J" \
  -d '{"path":"/yandex-feed.yml","body":"…","limit":20}'
# 3. create it published (add "category_id" to put it under a section)
curl -s -X POST "$API/site-files" -H "$T" -H "$J" \
  -d '{"name":"yandex-feed.yml","body":"…","is_published":true}'
```

Then fetch the returned `url`: 200 and the expected offer count. The full
walk-through with `jq` is `?section=3-quick-start-a-yandex-feed-in-three-requests`.

## Key rules

- **Refer by id, never by path.** Find ids with `GET /site-files?q=…`. Inside
  a site file link with `file_url ID`, `file_path ID`, `category_url ID`,
  `product_url ID`; anywhere else (widgets, components, menus) with
  `/_file/<id>`. Both follow a moved or renamed target.
- **Escaping is automatic** per format (XML, HTML, CSV, JSON): write only the
  constant markup; no CDATA, no quotes around `{{ }}` in CSV, never `html`,
  `js`, `urlquery`.
- **Preview after every edit:** saving checks syntax only
  (`SITE_FILE_TEMPLATE_INVALID` with the line); wrong fields, options and
  category slugs surface in `POST /site-files/preview` or on the host.
- **Safe edit:** send `row_version` back as `know_version`; a concurrent save
  answers `SITE_FILE_VERSION_CONFLICT`.
- **One product out of every feed:** `PUT /products/{id}/ad-participation
  {"mode":"exclude"}` (`catalog:write`); a category out of one feed:
  `exclude_category=` in that template.
- **Limits:** name `[A-Za-z0-9._-]{1,120}` + `.xml .yml .csv .txt .json
  .html`; body 64 KB; render 30 s / 50 MB; 50 000 products per `range`;
  preview 30 calls a minute. Tariff `site_files_max`: **Free 5, Pro 50** live
  files per shop (`SITE_FILE_LIMIT_REACHED`, 429). Machine block:
  `?section=11-limits`.

## Reference: sections

`GET /docs/feeds-reference?section=<slug>`:

| Slug | What |
|---|---|
| `1-what-you-can-do-here` · `2-decide-first-forks` | the object, names, reserved names, which path to take |
| `4-requests` (`4-1-objects`, `4-2-endpoints`, `4-3-preview`, `4-4-refusals`) | objects, endpoints, preview, refusal codes |
| `5-the-template-language` (`5-2-products-the-offers`, `5-4-functions`, `5-5-escaping-is-automatic-write-only-the-constant-markup`) | data, `products` options, `categories`, functions, escaping, errors |
| `6-recipes` (`6-1-yandex-yml-with-utm-tags-parameters-and-collections`, `6-2-google-merchant-center-xml`, `6-10-links-between-files-an-index-file`) | YML, Google XML, CSV, A/B, per category, pictures, `ads.txt`, verification, pages, links |
| `7-how-serving-works` (`7-1-the-url-and-its-answers`, `7-2-headers-and-caching`) | answers, headers, cache, price parity, sitemap |
| `8-ad-participation` · `9-silently-ignored` | excluding products; what is accepted and does nothing |
| `10-checklists-before-submitting` · `11-limits` · `12-how-this-was-verified` | checklists, the limits JSON, tests |
