openapi: 3.1.0 info: title: Mixmaker API version: 0.1.0 description: API for Overwatch community scrims. servers: - url: / security: - cookieAuth: [] tags: - name: Auth - name: Players - name: Events - name: Teams - name: Series - name: Tournaments paths: /healthz: get: security: [] operationId: health responses: "200": description: Healthy /readyz: get: security: [] operationId: ready responses: "200": description: Ready "503": description: Not ready /api/auth/discord: get: security: [] tags: [Auth] operationId: startDiscordLogin responses: "302": description: Redirect to Discord /api/auth/discord/callback: get: security: [] tags: [Auth] operationId: finishDiscordLogin parameters: - name: code in: query required: true schema: type: string - name: state in: query required: true schema: type: string responses: "302": description: Session created /api/auth/me: get: tags: [Auth] operationId: getCurrentAccount responses: "200": description: Current account content: application/json: schema: $ref: "#/components/schemas/Session" "401": $ref: "#/components/responses/Unauthorized" /api/auth/logout: post: tags: [Auth] operationId: logout responses: "204": description: Logged out /api/me/player: patch: tags: [Players] operationId: updateMyPlayer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/PlayerUpdate" responses: "200": description: Updated profile content: application/json: schema: $ref: "#/components/schemas/Player" "422": $ref: "#/components/responses/ValidationError" /api/players: get: tags: [Players] operationId: listPlayers responses: "200": description: Players content: application/json: schema: type: array items: $ref: "#/components/schemas/Player" /api/accounts: get: tags: [Admin] operationId: listAccounts responses: "200": description: Accounts visible to administrators content: application/json: schema: type: array items: { $ref: "#/components/schemas/Account" } "403": { $ref: "#/components/responses/Forbidden" } /api/accounts/{accountId}/moderator: patch: tags: [Admin] operationId: setModerator parameters: - name: accountId in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [moderator] properties: moderator: { type: boolean } responses: "200": { description: Updated account, content: { application/json: { schema: { $ref: "#/components/schemas/Account" } } } } "403": { $ref: "#/components/responses/Forbidden" } /api/events: get: tags: [Events] operationId: listEvents parameters: - name: from in: query schema: type: string format: date-time responses: "200": description: Events content: application/json: schema: type: array items: $ref: "#/components/schemas/Event" post: tags: [Events] operationId: createEvent requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/EventInput" responses: "201": description: Event created content: application/json: schema: $ref: "#/components/schemas/Event" "403": $ref: "#/components/responses/Forbidden" /api/events/{eventId}: get: tags: [Events] operationId: getEvent parameters: - $ref: "#/components/parameters/EventId" responses: "200": description: Event details content: application/json: schema: $ref: "#/components/schemas/Event" "404": $ref: "#/components/responses/NotFound" put: tags: [Events] operationId: updateEvent parameters: - $ref: "#/components/parameters/EventId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/EventInput" responses: "200": description: Event updated content: application/json: schema: $ref: "#/components/schemas/Event" delete: tags: [Events] operationId: deleteEvent description: Permanently removes the event and all event-scoped data. parameters: - $ref: "#/components/parameters/EventId" responses: "204": { description: Event deleted } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } /api/events/{eventId}/cancel: post: tags: [Events] operationId: cancelEvent parameters: - $ref: "#/components/parameters/EventId" requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/VersionCommand" } } } responses: "200": { description: Event cancelled, content: { application/json: { schema: { $ref: "#/components/schemas/Event" } } } } "409": { $ref: "#/components/responses/Conflict" } /api/events/{eventId}/rsvps: get: tags: [Events] operationId: listRegistrations parameters: - $ref: "#/components/parameters/EventId" responses: "200": description: Registrations content: application/json: schema: type: array items: $ref: "#/components/schemas/EventRegistration" /api/events/{eventId}/participants: post: tags: [Events] operationId: createGuestParticipant parameters: - $ref: "#/components/parameters/EventId" requestBody: required: true content: application/json: schema: type: object required: [displayName, ratings, status] properties: displayName: type: string minLength: 1 ratings: $ref: "#/components/schemas/Ratings" status: type: string enum: [Going, Maybe, NotGoing] responses: "201": description: Guest participant created and registered content: application/json: schema: type: object required: [player, rsvp] properties: player: $ref: "#/components/schemas/Player" rsvp: $ref: "#/components/schemas/EventRegistration" "403": $ref: "#/components/responses/Forbidden" /api/events/{eventId}/rsvps/{playerId}: put: tags: [Events] operationId: adminSetRegistration parameters: - $ref: "#/components/parameters/EventId" - $ref: "#/components/parameters/PlayerId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RegistrationInput" responses: "200": description: Registration updated by admin content: application/json: schema: $ref: "#/components/schemas/EventRegistration" "403": $ref: "#/components/responses/Forbidden" delete: tags: [Events] operationId: removeParticipantFromEvent parameters: - $ref: "#/components/parameters/EventId" - $ref: "#/components/parameters/PlayerId" responses: "204": description: Participant removed from the event "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" /api/events/{eventId}/balance: post: tags: [Teams] operationId: generateBalanceCandidates parameters: - $ref: "#/components/parameters/EventId" requestBody: required: false content: application/json: schema: type: object properties: seed: type: integer format: int64 responses: "200": description: Three best candidates content: application/json: schema: type: array items: $ref: "#/components/schemas/BalanceCandidate" /api/events/{eventId}/registration/close: post: tags: [Events] operationId: closeRegistration parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CloseRegistrationCommand" responses: "200": { description: Registration closed, content: { application/json: { schema: { $ref: "#/components/schemas/Event" } } } } "409": { $ref: "#/components/responses/Conflict" } /api/events/{eventId}/workflow/back: post: tags: [Events] operationId: revertEventWorkflowStage parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/VersionCommand" } } } responses: "200": { description: Workflow moved back one stage, content: { application/json: { schema: { $ref: "#/components/schemas/Event" } } } } "409": { $ref: "#/components/responses/Conflict" } /api/events/{eventId}/balance/generate: post: tags: [Teams] operationId: generateWorkflowBalance parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/VersionCommand" } } } responses: "200": description: Event and generated candidates content: application/json: schema: type: object required: [event, candidates] properties: event: { $ref: "#/components/schemas/Event" } candidates: { type: array, items: { $ref: "#/components/schemas/BalanceCandidate" } } "409": { $ref: "#/components/responses/Conflict" } /api/events/{eventId}/roster: get: tags: [Teams] operationId: getRosterDraft parameters: [{ $ref: "#/components/parameters/EventId" }] responses: "200": { description: Roster draft, content: { application/json: { schema: { $ref: "#/components/schemas/RosterDraft" } } } } put: tags: [Teams] operationId: selectWorkflowBalance parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: application/json: schema: type: object required: [candidate, expectedVersion] properties: candidate: { $ref: "#/components/schemas/BalanceCandidate" } expectedVersion: { type: integer } responses: "200": { description: Roster draft created, content: { application/json: { schema: { $ref: "#/components/schemas/RosterDraft" } } } } "409": { $ref: "#/components/responses/Conflict" } /api/events/{eventId}/roster/swap: post: tags: [Teams] operationId: swapRosterPlayers parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/RosterSwapCommand" } } } responses: "200": { description: Updated roster, content: { application/json: { schema: { $ref: "#/components/schemas/RosterDraft" } } } } "409": { $ref: "#/components/responses/Conflict" } /api/events/{eventId}/roster/move: post: tags: [Teams] operationId: moveRosterPlayer parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/RosterMoveCommand" } } } responses: "200": { description: Updated roster, content: { application/json: { schema: { $ref: "#/components/schemas/RosterDraft" } } } } /api/events/{eventId}/roster/place-reserve: post: tags: [Teams] operationId: placeReserveRosterPlayer parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/RosterPlaceReserveCommand" } } } responses: "200": { description: Updated roster, content: { application/json: { schema: { $ref: "#/components/schemas/RosterDraft" } } } } /api/events/{eventId}/roster/remove: post: tags: [Teams] operationId: removeRosterPlayer parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/RosterRemoveCommand" } } } responses: "200": { description: Updated roster, content: { application/json: { schema: { $ref: "#/components/schemas/RosterDraft" } } } } /api/events/{eventId}/roster/substitute: post: tags: [Teams] operationId: substituteRosterPlayer parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/RosterSubstitutionCommand" } } } responses: "200": { description: Updated roster, content: { application/json: { schema: { $ref: "#/components/schemas/RosterDraft" } } } } /api/events/{eventId}/roster/emergency-substitute: post: tags: [Teams] operationId: emergencySubstituteRosterPlayer parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/RosterSubstitutionCommand" } } } responses: "200": { description: Updated live roster, content: { application/json: { schema: { $ref: "#/components/schemas/RosterDraft" } } } } /api/events/{eventId}/roster/captain: put: tags: [Teams] operationId: setRosterCaptain parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: application/json: schema: type: object required: [teamId, playerId, expectedVersion] properties: teamId: { type: string } playerId: { type: string } expectedVersion: { type: integer } responses: "200": { description: Updated roster, content: { application/json: { schema: { $ref: "#/components/schemas/RosterDraft" } } } } /api/events/{eventId}/roster/confirm: post: tags: [Teams] operationId: confirmRosters parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: application/json: schema: type: object required: [expectedVersion, expectedRosterVersion] properties: expectedVersion: { type: integer } expectedRosterVersion: { type: integer } responses: "200": { description: Rosters confirmed, content: { application/json: { schema: { $ref: "#/components/schemas/Event" } } } } /api/events/{eventId}/start: post: tags: [Events] operationId: startScrim parameters: [{ $ref: "#/components/parameters/EventId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/VersionCommand" } } } responses: "201": description: Scrim started content: application/json: schema: type: object required: [event] properties: event: { $ref: "#/components/schemas/Event" } series: { $ref: "#/components/schemas/Series" } tournament: { $ref: "#/components/schemas/Tournament" } /api/teams/{teamId}/captain: put: tags: [Teams] operationId: assignCaptain parameters: - $ref: "#/components/parameters/TeamId" requestBody: required: true content: application/json: schema: type: object required: [playerId] properties: playerId: type: string format: uuid responses: "200": description: Captain assigned content: application/json: schema: $ref: "#/components/schemas/Team" "422": $ref: "#/components/responses/ValidationError" /api/teams/{teamId}/name: put: tags: [Teams] operationId: renameLiveTeam parameters: - $ref: "#/components/parameters/TeamId" requestBody: required: true content: application/json: schema: type: object required: [name] properties: name: { type: string, minLength: 2, maxLength: 32 } responses: "200": { description: Team renamed, content: { application/json: { schema: { $ref: "#/components/schemas/Team" } } } } "403": { $ref: "#/components/responses/Forbidden" } "409": { $ref: "#/components/responses/Conflict" } /api/coin-toss: post: tags: [Series] operationId: tossCoin requestBody: required: true content: application/json: schema: type: object required: [teamAId, teamBId, seed, eventId, actingTeamId] properties: teamAId: { type: string } teamBId: { type: string } seed: { type: string } eventId: { type: string } actingTeamId: { type: string } responses: "200": description: Coin toss result content: application/json: schema: type: object /api/drafts/{seriesId}/map-bans: post: tags: [Series] operationId: banMap parameters: - $ref: "#/components/parameters/SeriesId" requestBody: required: true content: application/json: schema: type: object required: [teamId, map, eventId, version] properties: teamId: { type: string } map: { type: string } eventId: { type: string } version: type: integer responses: "200": description: Updated series content: application/json: schema: $ref: "#/components/schemas/Series" "409": $ref: "#/components/responses/Conflict" /api/drafts/{seriesId}/hero-bans: post: tags: [Series] operationId: banHero parameters: - $ref: "#/components/parameters/SeriesId" requestBody: required: true content: application/json: schema: type: object required: [teamId, hero, eventId, version] properties: teamId: { type: string } hero: { type: string } eventId: { type: string } version: type: integer responses: "200": description: Updated series content: application/json: schema: $ref: "#/components/schemas/Series" "409": $ref: "#/components/responses/Conflict" /api/series/{seriesId}: get: tags: [Series] operationId: getSeries parameters: [{ $ref: "#/components/parameters/SeriesId" }] responses: "200": { description: Full live series state, content: { application/json: { schema: { $ref: "#/components/schemas/Series" } } } } /api/series/{seriesId}/coin-toss: post: tags: [Series] operationId: tossSeriesCoin parameters: [{ $ref: "#/components/parameters/SeriesId" }] requestBody: required: true content: application/json: schema: type: object required: [seed, actingTeamId, expectedVersion] properties: seed: { type: string } actingTeamId: { type: string } expectedVersion: { type: integer } responses: "200": { description: Updated series, content: { application/json: { schema: { $ref: "#/components/schemas/Series" } } } } "409": { $ref: "#/components/responses/Conflict" } /api/series/{seriesId}/map-bans: post: tags: [Series] operationId: banSeriesMap parameters: [{ $ref: "#/components/parameters/SeriesId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/SeriesBanCommand" } } } responses: "200": { description: Updated series, content: { application/json: { schema: { $ref: "#/components/schemas/Series" } } } } "409": { $ref: "#/components/responses/Conflict" } /api/series/{seriesId}/map-pick: post: tags: [Series] operationId: pickSeriesMap description: The previous map loser picks the next map directly. parameters: [{ $ref: "#/components/parameters/SeriesId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/SeriesBanCommand" } } } responses: "200": { description: Updated series with hero bans started, content: { application/json: { schema: { $ref: "#/components/schemas/Series" } } } } "409": { $ref: "#/components/responses/Conflict" } /api/series/{seriesId}/hero-bans: post: tags: [Series] operationId: banSeriesHero parameters: [{ $ref: "#/components/parameters/SeriesId" }] requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/SeriesBanCommand" } } } responses: "200": { description: Updated series, content: { application/json: { schema: { $ref: "#/components/schemas/Series" } } } } "409": { $ref: "#/components/responses/Conflict" } /api/series/{seriesId}/map-result: post: tags: [Series] operationId: recordSeriesMapResult parameters: [{ $ref: "#/components/parameters/SeriesId" }] requestBody: required: true content: application/json: schema: type: object required: [outcome, actingTeamId, expectedVersion] properties: outcome: { type: string, enum: [TeamAWin, TeamBWin, Draw] } actingTeamId: { type: string } expectedVersion: { type: integer } responses: "200": { description: Updated series, content: { application/json: { schema: { $ref: "#/components/schemas/Series" } } } } /api/series/{seriesId}/results: post: tags: [Series] operationId: recordMapResult parameters: - $ref: "#/components/parameters/SeriesId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/MapResultInput" responses: "200": description: Result recorded content: application/json: schema: $ref: "#/components/schemas/Series" /api/events/stream: get: tags: [Series] operationId: subscribeSeries parameters: - name: topic in: query schema: type: string responses: "200": description: Server-sent event stream content: text/event-stream: schema: type: string /api/tournaments/{tournamentId}: get: tags: [Tournaments] operationId: getTournament parameters: - $ref: "#/components/parameters/TournamentId" responses: "200": description: Tournament bracket content: application/json: schema: $ref: "#/components/schemas/Tournament" components: securitySchemes: cookieAuth: type: apiKey in: cookie name: mixmaker_session parameters: EventId: name: eventId in: path required: true schema: type: string format: uuid PlayerId: name: playerId in: path required: true schema: type: string format: uuid TeamId: name: teamId in: path required: true schema: type: string format: uuid SeriesId: name: seriesId in: path required: true schema: type: string format: uuid TournamentId: name: tournamentId in: path required: true schema: type: string format: uuid responses: Unauthorized: description: Authentication required content: application/json: schema: $ref: "#/components/schemas/Problem" Forbidden: description: Insufficient permission content: application/json: schema: $ref: "#/components/schemas/Problem" NotFound: description: Resource not found content: application/json: schema: $ref: "#/components/schemas/Problem" Conflict: description: State version conflict content: application/json: schema: $ref: "#/components/schemas/Problem" ValidationError: description: Invalid command content: application/json: schema: $ref: "#/components/schemas/Problem" schemas: Session: type: object required: [account, player] properties: account: $ref: "#/components/schemas/Account" player: $ref: "#/components/schemas/Player" Account: type: object required: [id, discordId, username, role, createdAt] properties: id: type: string format: uuid discordId: type: string username: type: string avatarUrl: type: [string, "null"] format: uri role: type: string enum: [player, moderator, admin] createdAt: type: string format: date-time Ratings: type: object required: [tank, damage, support] properties: tank: type: integer minimum: 1 maximum: 40 description: Bronze 5 = 1, Champion 1 = 40 damage: type: integer minimum: 1 maximum: 40 description: Bronze 5 = 1, Champion 1 = 40 support: type: integer minimum: 1 maximum: 40 description: Bronze 5 = 1, Champion 1 = 40 Player: type: object required: [id, accountId, displayName, ratings, preferredRoles, preferredPlayerIds, avoidedPlayerIds, updatedAt] properties: id: type: string format: uuid accountId: type: string format: uuid displayName: type: string ratings: $ref: "#/components/schemas/Ratings" preferredRoles: type: array items: type: string enum: [Tank, Damage, Support] preferredPlayerIds: type: array maxItems: 3 items: type: string avoidedPlayerIds: type: array maxItems: 3 description: Soft preference to place these players on another team. items: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time PlayerUpdate: type: object required: [ratings] properties: displayName: type: string ratings: $ref: "#/components/schemas/Ratings" preferredRoles: type: array items: type: string enum: [Tank, Damage, Support] preferredPlayerIds: type: array maxItems: 3 items: type: string avoidedPlayerIds: type: array maxItems: 3 items: type: string EventInput: type: object required: [name, startsAt, endsAt, registrationDeadline] properties: name: type: string minLength: 1 description: type: string startsAt: type: string format: date-time endsAt: type: string format: date-time registrationDeadline: type: string format: date-time Event: allOf: - $ref: "#/components/schemas/EventInput" - type: object required: [id, createdBy, createdAt, updatedAt, state, version, rulesetId, activeSeriesId, tournamentId] properties: id: type: string format: uuid createdBy: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time state: type: string enum: [RegistrationOpen, RegistrationClosed, Balancing, RostersDraft, RostersConfirmed, Live, Completed, Cancelled] version: { type: integer } rulesetId: { type: string } activeSeriesId: { type: string } tournamentId: { type: string } RegistrationInput: type: object required: [status] properties: status: type: string enum: [Going, Maybe, NotGoing] EventRegistration: type: object required: [eventId, playerId, status, actorAccountId, source, updatedAt] properties: eventId: type: string format: uuid playerId: type: string format: uuid status: type: string enum: [Going, Maybe, NotGoing] actorAccountId: type: string format: uuid source: type: string enum: [player, admin] updatedAt: type: string format: date-time TeamSlot: type: object required: [playerId, role, rating] properties: playerId: type: string role: type: string enum: [Tank, Damage, Support] rating: type: integer Team: type: object required: [id, eventId, name, captainPlayerId, slots] properties: id: type: string eventId: type: string name: type: string captainPlayerId: type: string slots: type: array minItems: 5 maxItems: 5 items: $ref: "#/components/schemas/TeamSlot" BalanceCandidate: type: object required: [score, teams, reserve] properties: score: type: integer teams: type: array items: $ref: "#/components/schemas/Team" reserve: type: array items: type: string VersionCommand: type: object required: [expectedVersion] properties: expectedVersion: { type: integer } CloseRegistrationCommand: type: object required: [rulesetId, expectedVersion] properties: rulesetId: { type: string } expectedVersion: { type: integer } RosterDraft: type: object required: [eventId, teams, reserve, version, confirmed] properties: eventId: { type: string } teams: { type: array, minItems: 2, items: { $ref: "#/components/schemas/Team" } } reserve: { type: array, items: { type: string } } version: { type: integer } confirmed: { type: boolean } RosterSwapCommand: type: object required: [teamAId, playerAId, teamBId, playerBId, expectedVersion] properties: teamAId: { type: string } playerAId: { type: string } teamBId: { type: string } playerBId: { type: string } expectedVersion: { type: integer } RosterMoveCommand: type: object required: [fromTeamId, playerId, toTeamId, role, expectedVersion] properties: fromTeamId: { type: string } playerId: { type: string } toTeamId: { type: string } role: { type: string, enum: [Tank, Damage, Support] } expectedVersion: { type: integer } RosterPlaceReserveCommand: type: object required: [teamId, reservePlayerId, role, expectedVersion] properties: teamId: { type: string } reservePlayerId: { type: string } role: { type: string, enum: [Tank, Damage, Support] } expectedVersion: { type: integer } RosterRemoveCommand: type: object required: [teamId, playerId, expectedVersion] properties: teamId: { type: string } playerId: { type: string } expectedVersion: { type: integer } RosterSubstitutionCommand: type: object required: [teamId, outgoingPlayerId, reservePlayerId, expectedVersion] properties: teamId: { type: string } outgoingPlayerId: { type: string } reservePlayerId: { type: string } expectedVersion: { type: integer } SeriesBanCommand: type: object required: [teamId, expectedVersion] properties: teamId: { type: string } map: { type: string } hero: { type: string } expectedVersion: { type: integer } MapResultInput: type: object required: [mapName, outcome, expectedVersion] properties: mapName: type: string outcome: type: string enum: [TeamAWin, TeamBWin, Draw] expectedVersion: type: integer Series: type: object required: [id, eventId, tournamentId, teamAId, teamBId, winnerTeamId, bestOf, rulesetId, phase, playedMaps, maps, audit, results, version] properties: id: type: string eventId: type: string tournamentId: type: string teamAId: type: string teamBId: type: string winnerTeamId: type: string bestOf: type: integer rulesetId: { type: string } phase: type: string enum: [CoinTossPending, MapBan, MapPick, HeroBan, Playing, Completed] coinToss: { type: [object, "null"] } mapDraft: { type: [object, "null"] } heroDraft: { type: [object, "null"] } currentMap: { type: string } playedMaps: { type: array, items: { type: string } } nextMapPickerId: { type: string } availableMaps: { type: array, items: { type: string } } maps: { type: array, items: { type: object } } audit: { type: array, items: { type: object } } results: type: array items: type: object version: type: integer Tournament: type: object required: [id, eventId, name, winnerTeamId, teamIds, rounds] properties: id: type: string eventId: type: string name: type: string winnerTeamId: type: string teamIds: type: array items: type: string rounds: type: array items: type: array items: $ref: "#/components/schemas/Series" Problem: type: object required: [error] properties: error: type: string