Referral API — реестр операций и общие контрактные модели
Индекс HTTP-операций сервиса referral (public / admin / internal) и единственный дом общих
контрактных моделей: формат ответа/ошибок, paging-модели, FullName-проекция. Файлы операций описывают
только свою дельту и ссылаются сюда.
Реестр операций
Public — self-service
Маршрутизация: статические сегменты default, lookup, registrations имеют приоритет над
route-параметром {id} того же уровня (правило приоритета статических сегментов маршрута) — конфликтов
/{id} ↔ /registrations ↔ /default ↔ /lookup нет.
Admin — администратор платформы
| # | Операция | HTTP | Route | Актор | Permission | Документ |
|---|
| 14 | getReferralLinks | GET | /api/admin/referrals/links | Operator(Admin) | referral.registrations.view-all | get-referral-links.md |
| 15 | getReferralLinkById | GET | /api/admin/referrals/links/{linkId} | Operator(Admin) | referral.registrations.view-all | get-referral-link-by-id.md |
| 16 | getLinkRegistrations (admin) | GET | /api/admin/referrals/links/{linkId}/registrations | Operator(Admin) | referral.registrations.view-all | get-link-registrations.md |
| 17 | getInvestorReferrals | GET | /api/admin/referrals/investors/{investorId}/referrals | Operator(Admin) | referral.registrations.view-all | get-investor-referrals.md |
| 18 | adminRebindBond | POST | /api/admin/referrals/bonds/{bondId}/rebind | Operator(Admin) | referral.registrations.view-all | rebind-bond.md |
| 19 | adminRevokeBond | POST | /api/admin/referrals/bonds/{bondId}/revoke | Operator(Admin) | referral.registrations.view-all | revoke-bond.md |
Internal — m2m
| # | Операция | HTTP | Route | Актор | Permission | Документ |
|---|
| 20 | resolveReferralLink | GET | /internal/v1/referral-links/resolve | System | policy internal-services | resolve-referral-link.md |
Межконтекстные мутации остаются событийными; internal-area — единственный синхронный read-only
резолв slug для identity/crm (владелец, тип/статус, lead-eligible; PII не отдаёт).
Формат ответа и ошибок
- Успех: тело = типизированный результат операции (
<Operation>Result); статус — 200/201/204
по HTTP-контракту операции.
- Ошибка валидации:
400 с перечнем полей.
- Доступ:
401 (нет/невалиден токен), 403 (нет требуемого permission).
- Корреляция — сквозной trace через заголовок
traceparent.
Кросс-сервисные (общие) коды ошибок
Коды, повторяющиеся в нескольких операциях; доменно-специфические (REFERRAL_LINK_*, REFERRAL_BOND_*)
— в файлах операций-владельцев. Статусы фиксированы контрактом и одинаковы во всех операциях, где код
применим.
| Код | HTTP | Условие | Где применяется |
|---|
ValidationError | 400 | Формат/диапазон входных полей нарушен (slug, paging, enum, длины) | все операции |
UNAUTHORIZED | 401 | Нет/просрочен/невалиден токен | все аутентифицированные операции |
FORBIDDEN / REFERRAL_ADMIN_FORBIDDEN / REFERRAL_FORBIDDEN | 403 | Нет требуемого permission (self или referral.registrations.view-all) | self- и admin-операции |
REFERRAL_LINK_NOT_FOUND | 404 (public delete — 400) | Ссылка не существует / чужая / удалена (без раскрытия чужого id) | операции по linkId |
REFERRAL_BOND_NOT_FOUND | 404 | Связь по bondId не существует | admin-коррекции связей |
CONCURRENCY_CONFLICT / REFERRAL_BOND_CONCURRENCY_CONFLICT | 409 (public delete — 400) | Конкурентная правка (потеря обновления); проигравший перечитывает и повторяет | мутации ссылок/связей |
IDEMPOTENCY_CONFLICT | 409 | Тот же Idempotency-Key, иной payload / операция в процессе | привилегированные мутации связей (rebind/revoke) |
Paging-модели (дом; используются списочными операциями 5, 8, 10, 11, 14, 16, 17)
Все списочные выдачи — offset-paging контейнером <Item>PagingResult. Коллекция всегда живёт в
именованной container-модели.
Модель PagingResult (базовая)
| Поле | Тип | Обязательность | Описание |
|---|
meta | PagingResultMeta | Да | Метаданные страницы. |
| Поле | Тип | Обязательность | Ограничения | Описание |
|---|
total | number (int64) | Да | >= 0 | Полное число записей под фильтром. |
take | number (int32) | Да | 0..1000 | Фактический размер страницы (эхо запроса). |
offset | number (int32) | Да | 0..2147483647 | Фактическое смещение (эхо запроса). |
Query-параметры paging (единые ограничения)
| Параметр | Тип | Обязательность | Ограничения | Описание |
|---|
Offset | int | Да | min 0, max 2147483647 | Смещение от начала выборки. |
Take | int | Public: Да / Admin: Нет | min 1, max 1000 | Размер страницы. В admin-области параметр опционален (клиент передаёт Take явно). |
Контейнеры
| Контейнер | Item | Операции |
|---|
ReferralLinkListItemPagingResult | ReferralLinkListItem | getMyReferralLinks |
ReferralLinkLookupEntryPagingResult | ReferralLinkLookupEntry | lookupReferralLinks |
ReferralRegistrationItemPagingResult | ReferralRegistrationItem | getLinkRegistrations, getAllRegistrations |
AdminReferralLinkEntryPagingResult | AdminReferralLinkEntry | admin getReferralLinks |
AdminReferralRegistrationEntryPagingResult | AdminReferralRegistrationEntry | admin getLinkRegistrations |
AdminReferredInvestorEntryPagingResult | AdminReferredInvestorEntry | admin getInvestorReferrals |
Item-модели — дома в файлах операций (первая операция-владелец), проекции полей — из сущностей
referral-link.md, referral-registration.md,
referral-bond.md.
Консистентность и стабильность страницы (единое правило списочных операций)
Правило для всех списочных выдач (5, 8, 10, 11, 14, 16, 17); операции ссылаются сюда и не переописывают.
- Детерминированный порядок. Сортировка — по основному ключу (дата:
RegisteredAt для журналов,
BoundAt для связей, CreatedAt для ссылок — desc, свежие сверху), при равных значениях даты —
вторичный ключ Id (стабильный tie-break). Без вторичного ключа записи с одинаковой датой могли бы
«перепрыгивать» между страницами; с ним постраничный обход детерминирован.
- Снимок offset-paging. Между запросами соседних страниц выборка не транзакционно-снимочна:
конкурентная вставка (новая регистрация/ссылка) или смена статуса связи
(
Active → Replaced/Revoked) сдвигает offset — на границе страниц запись может продублироваться или
быть пропущена. Для append-only журналов это затрагивает только «голову» ленты (новые записи сверху);
исторические страницы стабильны. Клиенту, которому нужна точная лента, следует дочитывать до
meta.total за один непрерывный проход.
- Свежесть. Выдача отражает только зафиксированные (committed) данные
referral. PII-поля из реплики
ReferralUserReplica (ФИО/телефон) могут отставать на лаг consumer-а
pin.identity.users.v1: при отсутствии реплики запись всё равно возвращается с пустым FullName и
phoneNumber = null (graceful degradation, не ошибка).
meta.total. Полное число записей под фильтром на момент запроса; при активной конкурентной записи
может незначительно отличаться от фактически перечисленных на всех страницах (то же окно, что и выше).
Модель FullName (admin-проекции)
Проекция имени владельца/приглашённого из реплики ReferralUserReplica
(исходный дом — identity/domain/user.md):
| Поле | Тип | Обязательность | Ограничения | Описание |
|---|
firstName | string | Да | max 128 | Имя (может быть пустой строкой — lazy-регистрация). |
lastName | string | Да | max 128 | Фамилия (может быть пустой строкой). |
middleName | string? | Нет | max 128 | Отчество. |
Общие правила безопасности (все операции)
| Правило | Значение |
|---|
| Record-level self | Все me-операции фильтруют по OwnerUserId/ReferrerUserId/ReferredUserId = UserId из claim sub; чужая/несуществующая запись неотличима от отсутствующей (нет user enumeration по id ссылок) |
| Tenant isolation | Инвесторы обслуживаются с TenantId = null; изоляция self-выдач — record-level; admin — платформенный permission |
| PII | Public-выдачи журнала обезличены (только referredInvestorId); ФИО/телефон/email — только admin-выдачи (permission referral.registrations.view-all); телефоны в логах маскируются +7***1234 |
| Untrusted input | Slug из route и форм — untrusted: нормализация lowercase + формат ^[a-z0-9][a-z0-9-]{2,63}$ (INV-REF-L3) до обращения к данным |
| Audit | Мутации ссылок — actor-stamping + доменные события; чтения с PII (admin) — access-audit |
Связанные документы