Перейти к основному содержимому

API-контракты

Endpoint coverage

api-map.md является endpoint registry. Этот файл не дублирует полный DTO для каждой CRUD-строки; все endpoints из api-map.md покрываются platform envelope, pagination, error и idempotency rules плюс DTO/feature docs из таблицы.

API map sectionEndpoint rows coveredContract source
Public/events*, /seasons/{seasonId}/venues*, /seasons/{seasonId}/public-results, /seasons/{seasonId}/training-links, /documents/verify/{code}Event, season, tour, Check and result, Publication and documents; features/events-seasons-tours.md, features/results.md, features/documents-awards.md
Admin event/season/tour/admin/events*, /admin/seasons*, /admin/tours*, /admin/tracksEvent, season, tour; features/admin.md
Registration and participants/seasons/{seasonId}/pre-registrations, /seasons/{seasonId}/registrations, /registrations*, /registrations/{registrationId}/official-data-*, /teacher/organizations*, /teacher/competition-groups*, /participants/{participantId}/claim-access, /admin/access-claims*Registration, Official data package, Teacher group projection, Participant and claim; features/participants.md, features/external-teachers.md
Teacher conduct and materials/teacher/conduct-applications*, /admin/conduct-applications*, /teacher/seasons/{seasonId}/materials, /teacher/tours/{tourId}/materials, /admin/materials*Teacher conduct application, Teacher material; features/external-teachers.md, features/delivery.md
Venues and organizations/venues*, /admin/venues*, /participants/{participantId}/venue-selection, /venue/me/participantsVenue application, Venue; features/organizations-venues.md
External teachers/external-teachers*External teacher; features/external-teachers.md
Activity runtime and delivery/admin/tour-activity-bindings*, /tours/{tourId}/submissions, /teacher/tours/{tourId}/group-submissions, /submissions*, /photo-reports*, /admin/submissions/{id}/lockTour activity binding, Submission ref, Photo report; features/task-bank-link.md, features/delivery.md
Checking, results, publications, documents/checker/submissions*, /admin/results*, /appeals*, /admin/arbitration-cases*, /admin/publications, /admin/documents/generateCheck and result, Publication and documents; features/results.md, features/documents-awards.md

Общие DTO

type SourceRef = {
domain: 'identity' | 'task-bank' | 'crm' | 'storefront' | 'lms' | 'learning-workspace' | 'management' | 'competitions';
type: string;
id: string;
};

type MoneyDto = {
amount: string;
currency: 'RUB';
};

type OfficialDataPackageStatus = 'not_required' | 'not_filled' | 'incomplete' | 'needs_verification' | 'confirmed' | 'rejected';

type AwardStatusKey =
| 'diploma_1'
| 'diploma_2'
| 'diploma_3'
| 'honorable_mention_1'
| 'honorable_mention_2'
| 'participation_certificate'
| 'no_award';

type ApiEnvelope<TData, TMeta = Record<string, unknown>> = {
data: TData | null;
meta?: TMeta;
error?: {
code: string;
message: string;
details?: Record<string, unknown>;
};
};

Event, season, tour

type CompetitionEventDto = {
id: string;
slug: string;
title: string;
type: 'olympiad' | 'contest' | 'tournament';
subjectKey?: string;
status: 'draft' | 'active' | 'archived';
};

type CompetitionSeasonDto = {
id: string;
eventId: string;
title: string;
status: 'draft' | 'registration_open' | 'running' | 'checking' | 'results_review' | 'published' | 'archived' | 'cancelled';
startsAt?: string;
endsAt?: string;
registrationPolicy: Record<string, unknown>;
feePolicy: Record<string, unknown>;
};

type CompetitionTourDto = {
id: string;
seasonId: string;
title: string;
position: number;
format: 'test' | 'written' | 'scans' | 'hybrid' | 'chess' | 'other';
status: string;
startsAt?: string;
endsAt?: string;
};

Registration

type CreateRegistrationRequest = {
participant: {
identityUserId?: string;
studentProfileId?: string;
organizationStudentId?: string;
identityOrganizationId?: string;
organizationMembershipId?: string;
organizationStudentSnapshot?: Record<string, unknown>;
displayName: string;
grade?: string;
childModeRequired?: boolean;
};
source: 'self' | 'parent' | 'teacher' | 'organization' | 'admin';
stage?: 'pre_registration' | 'details_required' | 'ready_for_review' | 'complete';
groupId?: string;
learningWorkspaceId?: string;
learningGroupId?: string;
deliveryPreferences?: Record<string, unknown>;
teacherNameText?: string;
officialDataPackageRequired?: boolean;
officialDataRef?: SourceRef;
};

type RegistrationDto = {
id: string;
seasonId: string;
participantId?: string;
groupId?: string;
learningWorkspaceId?: string;
learningGroupId?: string;
status: 'draft' | 'pre_submitted' | 'needs_completion' | 'submitted' | 'pending_review' | 'approved' | 'rejected' | 'cancelled' | 'duplicate_hold';
stage: 'pre_registration' | 'details_required' | 'ready_for_review' | 'complete';
source: 'self' | 'parent' | 'teacher' | 'organization' | 'admin';
requiredFieldsStatus?: Record<string, unknown>;
teacherNameText?: string;
officialDataPackage?: OfficialDataPackageDto;
};

type OfficialDataPackageDto = {
required: boolean;
status: OfficialDataPackageStatus;
officialDataRef?: SourceRef;
parentContactRef?: SourceRef;
parentInviteRef?: SourceRef;
missingFields?: Array<'child_snils' | 'child_birth_date' | 'parent_email' | 'parent_phone'>;
collectedByUserId?: string;
collectedAt?: string;
verifiedByUserId?: string;
verifiedAt?: string;
};

type CreateOfficialDataCollectionSessionRequest = {
participantId: string;
registrationId: string;
requestedFields: Array<'child_snils' | 'child_birth_date' | 'parent_email' | 'parent_phone'>;
collectorContext: 'self' | 'parent' | 'teacher' | 'admin';
returnUrl?: string;
};

Validation:

  • season must be registration_open;
  • two-step registration may return needs_completion until required fields are filled;
  • paid season may require CRM entitlement before approval;
  • raw official fields are not accepted by competitions API; collection goes through identity-owned protected flow;
  • official data package may be required only when registration policy marks the scenario as official;
  • official package readiness covers child SNILS, child birth date, parent email and parent phone;
  • teacherNameText is display-only and never creates teacher access;
  • parent registration sets child-mode participation for the ребёнок;
  • duplicate participant warning must not silently merge records and may put registration into duplicate_hold.

Teacher group projection

type CompetitionGroupDto = {
id: string;
seasonId: string;
competitionOrganizationId?: string;
learningWorkspaceId: string;
learningGroupId: string;
primaryTeacherUserId?: string;
titleSnapshot: string;
memberSnapshot: Array<Record<string, unknown>>;
status: 'active' | 'archived';
snapshotTakenAt: string;
};

type CreateCompetitionGroupFromLearningGroupRequest = {
seasonId: string;
competitionOrganizationId: string;
learningWorkspaceId: string;
learningGroupId: string;
selectedLearningGroupMemberIds?: string[];
};

type UpsertGroupHelperRequest = {
helperUserId: string;
role: 'answers_entry' | 'file_upload' | 'roster_view' | 'venue_staff';
};

Validation:

  • caller must be in teacher mode with selected organization;
  • Learning Group membership is read from Learning Workspace and snapshotted;
  • competition_group is not a canonical student list and must not be used outside season registration/delivery.

Participant and claim

type ParticipantDto = {
id: string;
seasonId: string;
studentProfileId?: string;
organizationStudentId?: string;
identityOrganizationId?: string;
organizationMembershipId?: string;
organizationStudentSnapshot?: Record<string, unknown>;
identityUserId?: string;
displayName: string;
grade?: string;
identityStatus: 'unlinked' | 'linked' | 'claimed';
duplicateStatus: 'not_checked' | 'no_duplicate' | 'possible_duplicate' | 'duplicate_confirmed' | 'merged';
};

type CreateAccessClaimRequest = {
participantId: string;
claimType: 'student' | 'parent' | 'family';
evidence?: Record<string, unknown>;
};

Venue application

type VenueRulesPayload = {
accessRules?: Record<string, unknown>;
requiredDocuments?: Array<Record<string, unknown>>;
guardianPolicy?: Record<string, unknown>;
facilityRules?: Record<string, unknown>;
technicalCapabilities?: Record<string, unknown>;
participantPublicComment?: string;
adminInternalComment?: string;
};

type CreateVenueApplicationRequest = VenueRulesPayload & {
seasonId: string;
competitionOrganizationId: string;
title: string;
address: Record<string, unknown>;
geoPoint?: Record<string, unknown>;
capacityTotal?: number;
acceptedGradeCategories?: string[];
contacts?: Array<Record<string, unknown>>;
};

type VenueDto = VenueRulesPayload & {
id: string;
seasonId: string;
competitionOrganizationId?: string;
venueApplicationId?: string;
title: string;
address: Record<string, unknown>;
geoPoint?: Record<string, unknown>;
capacity?: number;
publicVisibility: 'hidden' | 'list' | 'map' | 'list_and_map';
officialStatus: 'official' | 'not_official' | 'mixed';
status: string;
};

Validation:

  • public/student payload must omit technicalCapabilities and adminInternalComment;
  • capacity decrease below current active assignments returns competitions.venue.capacity_below_assignments.

Teacher conduct application

type TeacherConductApplicationDto = {
id: string;
seasonId: string;
tourId: string;
identityOrganizationId: string;
competitionOrganizationId?: string;
submittedByUserId: string;
locationText?: string;
expectedParticipantCount?: number;
plannedDateFrom?: string;
plannedDateTo?: string;
gradeCategories: string[];
deliveryMode: 'teacher_led' | 'online_supervised' | 'mixed';
contactSnapshot: Record<string, unknown>;
photoReportRequired: boolean;
status: 'draft' | 'submitted' | 'approved' | 'needs_changes' | 'rejected' | 'cancelled' | 'completed';
adminComment?: string;
};

type UpsertTeacherConductApplicationRequest = {
seasonId: string;
tourId: string;
identityOrganizationId: string;
competitionOrganizationId?: string;
locationText?: string;
expectedParticipantCount?: number;
plannedDateFrom?: string;
plannedDateTo?: string;
gradeCategories?: string[];
deliveryMode: 'teacher_led' | 'online_supervised' | 'mixed';
contactSnapshot?: Record<string, unknown>;
photoReportRequired?: boolean;
comment?: string;
};

type ReviewTeacherConductApplicationRequest = {
comment?: string;
};

Validation:

  • caller must be in teacher mode with selected organization;
  • conduct application does not replace student registrations or competition_group;
  • only draft and needs_changes are editable by the teacher.

Teacher material

type TeacherMaterialDto = {
id: string;
seasonId: string;
tourId?: string;
teacherConductApplicationId?: string;
materialType: 'poster' | 'banner' | 'announcement_text' | 'school_news_text' | 'teacher_instruction' | 'student_instruction' | 'parent_instruction' | 'tour_material' | 'participant_list' | 'report_template' | 'photo_report' | 'results_news' | 'gratitude';
title: string;
description?: string;
fileRef?: SourceRef;
textContent?: string;
visibility: 'teacher' | 'teacher_and_helpers' | 'organization' | 'public' | 'admin';
availableFrom?: string;
availableUntil?: string;
status: 'draft' | 'published' | 'archived';
generated: boolean;
sourceRef?: SourceRef;
};

type UpsertTeacherMaterialRequest = Omit<TeacherMaterialDto, 'id' | 'status' | 'generated'> & {
status?: 'draft' | 'published' | 'archived';
generated?: boolean;
};

Validation:

  • teacher read endpoints return only materials allowed by organization access, visibility and availability window;
  • closed material access is audited.

Tour activity binding

type TourActivityBindingDto = {
id: string;
seasonId: string;
tourId: string;
gradeCategoryId?: string;
trackId?: string;
mode: 'lms_activity' | 'temporary_adapter';
lmsActivityRef?: SourceRef;
adapterRef?: SourceRef;
taskBankSourceRef?: SourceRef;
status: 'draft' | 'review' | 'locked' | 'published' | 'archived';
activitySnapshotRef?: SourceRef;
taskStructure: TourTaskStructureDto;
};

type TourTaskStructureDto = {
id: string;
activityBindingId: string;
gradeCategoryId?: string;
taskCount: number;
maxTotalScore: number;
structureSnapshot?: Record<string, unknown>;
items: TourTaskItemDto[];
};

type TourTaskItemDto = {
id: string;
taskNumber: number;
displayTitle?: string;
maxScore: number;
lmsActivityItemRef?: SourceRef;
taskBankProblemVersionRef?: SourceRef;
scoringSnapshot?: Record<string, unknown>;
};

type TrainingPublicationDto = {
id: string;
sourceActivityBindingId: string;
sourceTourTaskStructureId?: string;
lmsRef: SourceRef;
storefrontRef?: SourceRef;
trainingMode: 'practice' | 'timed_practice' | 'review_mode' | 'mock_contest';
visibility: 'public' | 'registered' | 'participants' | 'teacher' | 'admin';
status: 'draft' | 'scheduled' | 'published' | 'paused' | 'archived';
scheduledAt?: string;
publishedAt?: string;
};

Validation:

  • mode = 'lms_activity' requires lmsActivityRef;
  • mode = 'temporary_adapter' requires adapterRef and locked activitySnapshotRef;
  • taskBankProblemVersionRef is a content/source ref, not runtime ref;
  • every item must have taskNumber, maxScore and lmsActivityItemRef or adapter item ref in snapshot;
  • training publication requires lmsRef; storefrontRef does not replace LMS ownership.

Submission

type SubmitCompetitionWorkRequest = {
tourParticipationId: string;
activityBindingId: string;
lmsActivityAttemptRef?: SourceRef;
adapterAttemptRef?: SourceRef;
files?: Array<{
type: 'file' | 'photo' | 'scan' | 'pdf' | 'image' | 'zip';
uploadId: string;
}>;
};

type SubmitGroupAnswersRequest = {
tourId: string;
competitionGroupId: string;
participantAnswers: Array<{
participantId: string;
lmsActivityAttemptRef?: SourceRef;
adapterAttemptRef?: SourceRef;
answerSnapshotRef?: SourceRef;
}>;
confirmLock: boolean;
allowMissingAnswers?: boolean;
};

type BulkFinalFilesUploadRequest = {
tourId: string;
files: Array<{
type: 'file' | 'photo' | 'scan' | 'pdf' | 'image' | 'zip';
uploadId: string;
participantId?: string;
}>;
lateReason?: string;
};

type CompetitionSubmissionDto = {
id: string;
tourParticipationId: string;
activityBindingId?: string;
lmsActivityAttemptRef?: SourceRef;
adapterAttemptRef?: SourceRef;
status: 'not_started' | 'started' | 'submitted' | 'locked' | 'checking' | 'checked' | 'void';
attemptStatusSnapshot?: Record<string, unknown>;
submittedAt?: string;
lockedAt?: string;
};

Validation:

  • tour must be open for participant delivery mode;
  • participant starts/continues task execution in LMS activity runtime or temporary compatible adapter;
  • raw answers are rejected by competitions API when execution is LMS-owned;
  • locked submission cannot be edited;
  • group answer submit requires explicit confirmLock = true after UI warning;
  • locked students in a first-tour group batch cannot be edited, but new students may be submitted before tour close when season policy allows it;
  • file uploads must pass malware/type/size validation.
  • final MVP bulk files may omit participantId; late uploads are marked and audited.

Photo report

type PhotoReportDto = {
id: string;
tourId: string;
venueId?: string;
competitionGroupId?: string;
teacherConductApplicationId?: string;
competitionOrganizationId?: string;
status: 'submitted' | 'confirmed' | 'unconfirmed';
fileRefs: SourceRef[];
comment?: string;
};

type ReviewPhotoReportRequest = {
status: 'confirmed' | 'unconfirmed';
comment?: string;
};

Validation:

  • unconfirmed requires a comment;
  • photo report status does not change score/result state.

Check and result

type CheckSubmissionRequest = {
score: number;
taskScores?: Array<{
tourTaskItemId?: string;
lmsActivityItemRef?: SourceRef;
score: number;
maxScore?: number;
comment?: string;
}>;
sourceRuntimeRef?: SourceRef;
reason?: string;
};

type CompetitionResultDto = {
id: string;
seasonId: string;
tourId?: string;
participantId: string;
trackId?: string;
score: number;
rank?: number;
awardStatusKey: AwardStatusKey;
status: 'draft' | 'calculated' | 'review' | 'finalized' | 'void';
publicationStatus: 'hidden' | 'personal' | 'public' | 'archived';
thresholdPolicySnapshot?: Record<string, unknown>;
isWithheld: boolean;
withheldReason?: string;
};

type CreateAppealRequest = {
resultId?: string;
submissionId?: string;
participantId: string;
reason: string;
};

type CreateArbitrationCaseRequest = {
submissionId?: string;
participantId?: string;
suspicionType: string;
evidenceRefs?: SourceRef[];
};

Validation:

  • appeals are for result/check disagreement in MVP;
  • result status and publication status are separate; public/personal visibility is controlled by publication actions;
  • UI label “Новая” maps to technical status submitted; no separate technical new status is introduced;
  • suspicious works use arbitration cases, not appeals;
  • withheld results are excluded from public publication until released.

Publication and documents

type CreatePublicationRequest = {
seasonId: string;
type: 'personal_results' | 'public_results' | 'works' | 'materials' | 'training' | 'venues';
gradeCategoryId?: string;
trackId?: string;
scheduledFor?: string;
settings?: Record<string, unknown>;
};

type GenerateDocumentsRequest = {
seasonId: string;
resultIds?: string[];
documentType: 'diploma' | 'certificate' | 'gratitude' | 'participation' | 'teacher_gratitude' | 'team_gratitude' | 'organization_gratitude' | 'venue_gratitude';
recipientRefs?: SourceRef[];
recipientSnapshots?: Array<Record<string, unknown>>;
};

External teacher

type ExternalTeacherProfileDto = {
id: string;
identityUserId: string;
displayNameSnapshot: string;
organizationName?: string;
verificationStatus: 'pending' | 'verified' | 'rejected' | 'revoked';
verificationSource: 'self_declaration' | 'organization' | 'admin' | 'migration';
allowedSeasonRefs: string[];
verifiedAt?: string;
};

type UpsertExternalTeacherProfileRequest = {
displayNameSnapshot: string;
organizationName?: string;
verificationSource: 'self_declaration' | 'organization';
};

type LinkExternalTeacherStudentRequest = {
participantId: string;
evidence?: Record<string, unknown>;
};

Endpoints:

MethodEndpointPermission
POST/api/v2/competitions/external-teachers/profilecompetitions.external_teachers.manage.own
POST/api/v2/competitions/external-teachers/profile/verifycompetitions.external_teachers.manage.own
POST/api/v2/competitions/external-teachers/studentscompetitions.participants.manage.assigned
DELETE/api/v2/competitions/external-teachers/students/{participantId}competitions.participants.manage.assigned
GET/api/v2/competitions/external-teachers/resultscompetitions.results.read.assigned