Типы эндпоинтов 1С ODATA
Зачем эта классификация
endpoints.md устроен «по операциям» — read, create, update, delete, actions. Это удобно искать «как сделать X», но скрывает закономерность: в OData форма URL (плюс метод) однозначно определяет, какие query-параметры и тела имеют смысл. Шесть семейств ниже — карта семантики, по которой потом легко предсказывать поведение нового, ещё не встречавшегося эндпоинта.
Зная семейство, отвечаешь сразу:
- работает ли тут
$filter/$expand/If-Match; - какой HTTP-метод осмыслен и что класть в тело;
- чего ждать в ответе (json / xml / text);
- какие коды ошибок типичны.
Карта семейств
| Семейство | Форма URL | Метод(ы) | Семантика |
|---|---|---|---|
| service-root | B/, B/$metadata | GET | discovery |
| entity-set | B/<Prefix>_<Имя> (+ _<TP>, /$count) | GET, POST¹ | коллекция: чтение / создание |
| single-entity | B/<Set>(<key>) (+ _RowType, _RecordType, dot-traversal) | GET, PATCH, PUT, DELETE | экземпляр: чтение / обновление / удаление |
| bound-function | B/<Set>/<Func>(<params>) | GET | вычисление, без побочного эффекта |
| bound-action | B/<Set>(<key>)/<Verb> | POST | команда над экземпляром |
| service-operation | B/<Verb>?<params> | POST | корневая операция (action import в терминах OData) |
¹ Внутри entity-set GET и POST формально делят URL, но это семантически разные операции: GET читает существующие элементы и поддерживает все OData-$-параметры; POST добавляет элемент в коллекцию и возвращает один созданный экземпляр с Location. См. §2.
1. service-root
Discovery. Два эндпоинта корневого уровня, без query-параметров и тела, всегда XML.
| URL | B/, B/$metadata |
| Метод | GET |
| Тело | — |
| Query | игнорируется ($format=json семантически = код 3, фактически 1С молча отдаёт XML) |
| Ответ | atom service document или CSDL XML |
| Ошибки | редко; 4XX/5XX без специфики |
Что выделяет семейство: единственное место, где json не предусмотрен в принципе, и единственное, где URL описывает не доменную сущность, а сам сервис.
Подробнее: endpoints.md §4.
2. entity-set
Коллекция сущностей одного типа. По форме URL — одно семейство, но GET и POST на одном пути несут разные семантики. Внутри живут все ходовые $-параметры OData (только на GET) — больше нигде их нет.
2.1. GET — чтение коллекции
| URL | B/<Prefix>_<Имя>, B/<Prefix>_<Имя>_<TP>, B/<Prefix>_<Имя>/$count |
| Метод | GET |
| Query | $filter, $select, $expand, $orderby, $top, $skip, $inlinecount, $format, allowedOnly |
| Ответ | atom-feed или {"value": [...]} (+ m:count / odata.count при $inlinecount) |
| Ошибки | 8 (тип не найден), 14 ($filter/$orderby), 20/401 (RLS), 21 (нереализованные функции) |
$count как часть URL (/<Set>/$count) — отдельная под-форма entity-set GET с ответом text/plain; из query доступен только $filter.
Подробнее: endpoints.md §5.
2.2. POST — создание (action over a set)
POST по URL коллекции — формально action on a set в терминах OData: команда «добавь элемент», направленная на множество, возвращающая один созданный объект. URL общий с GET, но контракт другой: query-параметры OData здесь бессмысленны, нужно тело, в ответ — единичная сущность с Location.
| URL | B/<Prefix>_<Имя> (без ключа в пути) |
| Метод | POST |
| Headers | Content-Type: application/json или application/atom+xml; (опц.) 1C_OData-DataLoadMode: true |
| Body | объект в json/atom; ссылочные поля через <Реквизит>_Key; составные — пара <Реквизит> + <Реквизит>_Type |
| Query | $format (формат ответа). OData $-параметры бессмысленны. |
| Ответ | 201 Created; Location: с каноническим URL созданной сущности; тело — сама сущность с заполненными Ref_Key, DataVersion |
| Ошибки | 4, 5 (нет обязательного поля), 13 (POST на табличную часть), 15 (ключ уже занят), 16, 17 (DataLoadMode не поддерживается), 19, 20 |
Семантическая ловушка: эндпоинт _<TP> (табличная часть) — читаемая коллекция, но POST на него запрещён (код 13). Табличная часть создаётся/меняется только через PATCH родителя.
Подробнее: endpoints.md §7.
3. single-entity
Один экземпляр по ключу. Здесь живут DataVersion, If-Match и dot-traversal.
| URL | B/<Set>(<key>), B/<Set>_<TP>(Ref_Key=..., LineNumber=N), B/InformationRegister_<Имя>_RecordType(<dims>), dot-path B/<Set>(<key>)/Реквизит/Свойство |
| Методы | GET, PATCH, PUT, DELETE. RowType/RecordType — только GET. |
| Ключ | простой (guid'...') или составной (Name=val, Name2=val2); для составного — все компоненты обязательны, иначе код 7 |
| Body (PATCH) | частичный объект; ссылочные поля через _Key |
| Body (PUT) | все поля; ссылочные поля только через <Реквизит>@odata.bind |
| Query | $select, $format. $expand тут не работает. $filter бессмыслен — выбран конкретный экземпляр. allowedOnly запрещён. |
| Headers | If-Match: <DataVersion> для PATCH/DELETE |
| Ответ (PATCH/PUT) | обновлённая сущность либо 204 |
| Ответ (DELETE) | 204; объект удаляется физически |
| Ошибки | 7, 9, 10, 19, 20; 412 при несовпадении If-Match |
Невидимая граница: _RowType и _RecordType синтаксически выглядят как single-entity (есть ключ, канонический GET), но семантически — только чтение. PATCH/DELETE на строку табличной части или запись регистра напрямую невозможны; изменения идут через PATCH родителя или набора записей.
Dot-traversal — частный случай single-entity: B/<Set>(<key>)/Реквизит/Свойство возвращает значение свойства связанной сущности. Для стандартных реквизитов в цепочке используются только английские имена (Description, Code, Date, Posted).
Подробнее: endpoints.md §6, §8, §9.
4. bound-function
Параметризованное чтение, привязанное к ресурсу (обычно — регистру). Это не действие — побочных эффектов нет. По OData это формально function: GET-only, идемпотентно, можно кешировать.
| URL | B/<Set>/<Func>(<key=value>, ...) |
| Метод | GET |
| Параметры функции | в скобках, пары Key=Value через запятую; типизированные литералы (datetime'...', guid'...'); если параметр — отбор, синтаксис $filter в одинарных кавычках |
| Query | $select, $expand*, $format |
| Ответ | коллекция строк (как у entity-set), но вычисленных, не хранимых |
| Ошибки | 11 (функции нет на этом ресурсе), 12 (нет обязательного аргумента), 14 (синтаксис Condition=), 20, 21 |
*$expand на bound-function отклоняется от OData v3: для измерений — раскрывается рядом с _Key; для составных типов результат уезжает в свойство с суффиксом _Expanded. Клиентский код должен это знать.
Концептуально важно: параметры функции — это не query-параметры. Их нельзя пропустить произвольно, нельзя заменить на $filter. Внутри Condition=... живёт второй уровень DSL — фильтр в одинарных кавычках с экранированием:
.../SliceLast(Period=datetime'2024-01-01T00:00:00',Condition='Валюта_Key eq guid''aaa-bbb-...''')
Документированные функции:
InformationRegister_<Имя>/SliceLast,SliceFirstInformationRegister_<Имя>_RecordType/SliceLast(для подчинённых регистратору)AccumulationRegister_<Имя>/Balance,Turnovers,BalanceAndTurnoversAccountingRegister_<Имя>/Balance(cAccountCondition,Condition,ExtraDimensions)
Конкретный набор функций для CalculationRegister зависит от типа регистра — сверять с $metadata целевой конфигурации.
Подробнее: endpoints.md §11.
5. bound-action
Команда над конкретным экземпляром. POST с глаголом в пути, тело обычно пустое, параметры команды — в query. По OData это bound action: связана с ресурсом через сегмент пути.
| URL | B/<Set>(<key>)/<Verb> — у 1С это /Post, /Unpost |
| Метод | POST |
| Body | — (тело не передаётся) |
| Query | параметры команды (PostingModeOperational) — не OData $-параметры |
| Ответ | обновлённый экземпляр (после команды) |
| Ошибки | 9 (не найден), 11 (команда не определена на этом ресурсе), 19, 20 |
Семантика «команда, а не флаг»: Post нельзя заменить на PATCH ... {"Posted": true}. Поле Posted — состояние, его выставляет именно команда. PATCH Posted молча проигнорируется или вернёт ошибку 16. Аналогично с Unpost.
Подробнее: endpoints.md §10.
6. service-operation
Корневая операция, не привязанная к экземпляру через путь. В терминах OData v3 это action import — действие на уровне сервиса. У 1С таких ровно две: SelectChanges и NotifyChangesReceived, обе относятся к плану обмена.
| URL | B/<Verb>?<params> — корневой эндпоинт, без сущности в пути |
| Метод | POST |
| Body | — |
| Query | параметры операции; для плана обмена — DataExchangePoint='<URL узла>', MessageNo=<N> |
| Ответ | SelectChanges → только atom-feed, $format=json игнорируется; NotifyChangesReceived → 200 без тела |
| Ошибки | 8 (узел не найден через DataExchangePoint), 9, 14 (синтаксис параметров), 19, 20 |
Чем отличается от bound-action: binding явный, не через путь — целью операции выступает значение query-параметра (DataExchangePoint), а не сегмент URL. Отсюда же отличается и формат ответа: bound-action возвращает обновлённый экземпляр (json в общем случае), SelectChanges — фиксированно atom-feed. Из-за этих различий имеет смысл держать категорию отдельно.
Подробнее: endpoints.md §12.
Как определить семейство по URL
Алгоритм:
- Путь пустой или
$metadata? → service-root. - Корневой эндпоинт-глагол (
SelectChanges,NotifyChangesReceived)? → service-operation. - Есть
(<params>)после имени функции (не после имени набора с ключом)? → bound-function. - Есть
(<key>)в пути, а за ним/<Verb>без скобок (/Post,/Unpost)? → bound-action. - Есть
(<key>)в пути без последующего/<Verb>? → single-entity (включая_RowType,_RecordType, dot-path). - Иначе — плоский путь к набору (с
/$countили без)? → entity-set:- метод GET → чтение коллекции;
- метод POST → создание (action over a set).
Что меняется между семействами
| Признак | service-root | entity-set GET | entity-set POST | single-entity | bound-function | bound-action | service-operation |
|---|---|---|---|---|---|---|---|
$filter / $top / $skip | — | ✓ | — | — | — (через Condition=) | — | — |
$select | — | ✓ | — | ✓ | ✓ | — | — |
$expand | — | ✓ | — | — | ✓ (с оговоркой) | — | — |
$inlinecount / $orderby | — | ✓ | — | — | — | — | — |
allowedOnly | — | ✓ | — | — | — | — | — |
If-Match | — | — | — | ✓ (PATCH/DELETE) | — | — | — |
Location в ответе | — | — | ✓ | — | — | — | — |
| Body запроса | — | — | json/atom | json/atom (PATCH/PUT) | — | — (обычно) | — |
| Тело ответа | XML | json/atom | json/atom (created) | json/atom | json/atom | json/atom | atom-only (SelectChanges) или пусто |
См. также: Префиксы объектов — какие семейства поддерживает каждый префикс; Подводные камни и рецепты — типовые ошибки в разрезе семейств; Ошибки — матрица семейство × типичные коды.