type Query { getAggregatedShotMetrics( aggregateInput: AggregateInputGQL! ): [AggregateResultGQL!]! getBucketSet(keyName: String!): BucketSetGQL getDeployedConfig: DeployedConfigGQL! waitFor(duration: Float!): Float! getVideoMakePercentageIntervals( videoId: ID! intervalDuration: Int! = 300 ): [MakePercentageIntervalGQL!]! getShotsWithMetadata( filterInput: FilterInput! ids: [Int!] = null shotsPagination: GetShotsPagination = null limit: Int! = 500 ): GetShotsResult! getShots( filterInput: FilterInput! shotsPagination: GetShotsPagination = null limit: Int! = 500 ): [ShotGQL!]! getShotsByIds(ids: [Int!]!): [ShotGQL!]! getShotAnnotationTypes(errorTypes: Boolean = false): [ShotAnnotationTypeGQL!]! getUser(userId: Int!): UserGQL getLoggedInUser: UserGQL getUsernames( matchString: String = null limit: Int = null after: String = null ): [String!]! getPlayTime(userId: Int!): UserPlayTimeGQL! getUserVideos( userId: Int = null limit: Int! = 5 after: String = null filters: VideoFilterInput = null ): VideoHistoryGQL! getUserTags: [TagGQL!]! getVideo(videoId: Int!, debuggingJson: JSON = null): VideoGQL! getVideos(videoIds: [Int!]!): [VideoGQL!]! } type AggregateResultGQL { aggregationIdentifiers: [AggregationIdentifierGQL!]! targetMetrics: TargetMetricsGQL! } type AggregationIdentifierGQL { featureName: String! groupName: String! } type TargetMetricsGQL { count: Int! makePercentage: Float averageDifficulty: Float spinTypeCounts: SpinTypeCountsGQL shotDirectionCounts: ShotDirectionCountsGQL } type SpinTypeCountsGQL { follow: Int! draw: Int! center: Int! unknown: Int! } type ShotDirectionCountsGQL { left: Int! right: Int! straight: Int! } input AggregateInputGQL { aggregations: [AggregationInput!]! filterInput: FilterInput } input AggregationInput @oneOf { bucketSet: BucketSetInputGQL enum: EnumAggregation datetimeRange: DatetimeRangeAggregationInput } input BucketSetInputGQL { feature: String! buckets: [BucketInputGQL!]! } input BucketInputGQL { rangeKey: String! lowerBound: Float! } input EnumAggregation { feature: String! } input DatetimeRangeAggregationInput { startDatetime: DateTime = null endDatetime: DateTime = null interval: TimeInterval! feature: String! = "created_at" } """ Date with time (isoformat) """ scalar DateTime input TimeInterval @oneOf { timedelta: TimeDeltaGQL aligned: AlignedIntervalEnum } input TimeDeltaGQL { days: Int = 0 weeks: Int = 0 months: Int = 0 years: Int = 0 } enum AlignedIntervalEnum { MONTH YEAR WEEK DAY } input FilterInput @oneOf { andFilters: [FilterInput!] orFilters: [FilterInput!] notFilter: FilterInput cueObjectDistance: FloatRangeFilter targetPocketDistance: FloatRangeFilter cueObjectAngle: FloatRangeFilter cueBallSpeed: FloatRangeFilter difficulty: FloatRangeFilter intendedPocketType: [PocketEnum!] shotDirection: [ShotDirectionEnum!] videoId: [Int!] userId: [Int!] username: [String!] make: [Boolean!] tags: [VideoTagInput!] annotations: [ShotAnnotationInput!] isStraight: [Boolean!] isRight: [Boolean!] isLeft: [Boolean!] isLeftMiss: [Boolean!] isRightMiss: [Boolean!] isDirect: [Boolean!] tableSize: FloatRangeFilter bankAngle: FloatRangeFilter bankDistance: FloatRangeFilter kickAngle: FloatRangeFilter kickDistance: FloatRangeFilter cueAngleAfterObject: FloatRangeFilter spinType: [SpinTypeEnum!] cueSpeedAfterObject: FloatRangeFilter falsePositiveScore: FloatRangeFilter backcut: [Boolean!] targetPocketAngleDirection: [ShotDirectionEnum!] targetPocketAngle: FloatRangeFilter missAngleInDegrees: FloatRangeFilter marginOfErrorInDegrees: FloatRangeFilter createdAt: DateRangeFilter } input FloatRangeFilter { lessThan: Float = null greaterThanEqualTo: Float = null greaterThan: Float = null includeOnNone: Boolean! = false lessThanInclusive: Boolean! = false greaterThanInclusive: Boolean! = true } enum PocketEnum { CORNER SIDE } enum ShotDirectionEnum { LEFT RIGHT STRAIGHT } input VideoTagInput { tagClasses: [VideoTagClassInput!]! = [] name: String! } input VideoTagClassInput { name: String! } input ShotAnnotationInput { name: String! } enum SpinTypeEnum { DRAW FOLLOW CENTER UNKNOWN } input DateRangeFilter { lessThan: Date = null greaterThanEqualTo: Date = null greaterThan: Date = null includeOnNone: Boolean! = false lessThanInclusive: Boolean! = false greaterThanInclusive: Boolean! = true } """ Date (isoformat) """ scalar Date type BucketSetGQL { keyName: String! feature: String! buckets: [BucketGQL!]! } type BucketGQL { rangeKey: String! lowerBound: Float! } type DeployedConfigGQL { allowNewUsers: Boolean! firebase: Boolean! devMode: Boolean! environment: String! minimumAllowedAppVersion: String! } type MakePercentageIntervalGQL { makePercentage: Float! elapsedTime: Float! } type GetShotsResult { shots: [ShotGQL!]! count: Int ids: [Int!]! } type ShotGQL { id: Int! videoId: Int! startFrame: Int! endFrame: Int! createdAt: DateTime updatedAt: DateTime cueObjectFeatures: CueObjectFeaturesGQL pocketingIntentionFeatures: PocketingIntentionFeaturesGQL pocketingIntentionInfo: PocketingIntentionInfoGQL bankFeatures: BankFeaturesGQL serializedShotPaths: SerializedShotPathsGQL user: UserGQL annotations: [ShotAnnotationGQL!]! falsePositiveScore: Float video: VideoGQL } type CueObjectFeaturesGQL { cueObjectDistance: Float cueObjectAngle: Float cueBallSpeed: Float shotDirection: ShotDirectionEnum spinType: SpinTypeEnum } type PocketingIntentionFeaturesGQL { targetPocketDistance: Float make: Boolean intendedPocketType: PocketEnum difficulty: Float targetPocketAngle: Float targetPocketAngleDirection: ShotDirectionEnum marginOfErrorInDegrees: Float backcut: Boolean } type PocketingIntentionInfoGQL { ballId: Int! pocketId: PocketIdentifier! pathMetadataIndex: Int! } enum PocketIdentifier { TOP_LEFT TOP_SIDE TOP_RIGHT BOTTOM_LEFT BOTTOM_SIDE BOTTOM_RIGHT } type BankFeaturesGQL { wallsHit: [WallTypeEnum!]! bankAngle: Float! distance: Float! } enum WallTypeEnum { LONG SHORT } type SerializedShotPathsGQL { b64EncodedBuffer: String } type UserGQL { id: Int! firebaseUid: String! username: String! isAdmin: Boolean! fargoRating: Int activeVideoId: Int profileImageUri: String createdAt: DateTime updatedAt: DateTime } type ShotAnnotationGQL { shotId: Int! type: ShotAnnotationTypeGQL! creator: UserGQL! notes: String! errorDefault: Boolean! createdAt: DateTime updatedAt: DateTime } type ShotAnnotationTypeGQL { id: Int! name: String! } type VideoGQL { id: Int! owner: UserGQL name: String screenshotUri: String totalShotsMade: Int! totalShots: Int! makePercentage: Float! medianRun: Float averageTimeBetweenShots: Float createdAt: DateTime updatedAt: DateTime shots: [ShotGQL!]! startTime: DateTime endTime: DateTime elapsedTime: Float framesPerSecond: Float! tableSize: Float! stream: UploadStreamGQL playlist: HLSPlaylistGQL tags: [VideoTag!]! currentHomography: HomographyInfoGQL homographyHistory: [HomographyInfoGQL!]! currentProcessing: VideoProcessingGQL } type UploadStreamGQL { id: ID! linksRequested: Int! uploadsCompleted: Int! segmentProcessingCursor: Int! lastIntendedSegmentBound: Int isCompleted: Boolean! initPlaylistUploadStatus: InitPlaylistUploadStatusEnum lowestUnuploadedSegmentIndex: Int! uploadCompletionCursor: Int! errors: [StreamErrorGQL!]! createdAt: DateTime! updatedAt: DateTime! segments: [UploadSegmentGQL!]! resolution: VideoResolutionGQL! streamSegmentType: StreamSegmentTypeEnum! } enum InitPlaylistUploadStatusEnum { NOT_APPLICABLE NOT_UPLOADED UPLOADED } type StreamErrorGQL { message: String! } type UploadSegmentGQL { segmentIndex: Int! uploaded: Boolean! valid: Boolean! endFrameIndex: Int framesPerSecond: Float durationInSeconds: Float linksRequested: Int! } type VideoResolutionGQL { width: Int height: Int } enum StreamSegmentTypeEnum { FRAGMENTED_MP4 RB_CHUNKED_MP4 } type HLSPlaylistGQL { videoId: Int! m3u8Text: String! segmentDurations: [Float!]! } type VideoTag { tagClasses: [VideoTagClass!]! name: String! } type VideoTagClass { name: String! } type HomographyInfoGQL { frameIndex: Int! crop: BoundingBoxGQL! pockets: [BoundingBoxGQL!]! sourcePoints: PocketPointsGQL! destPoints: PocketPointsGQL! } type BoundingBoxGQL { left: Float! top: Float! width: Float! height: Float! } type PocketPointsGQL { topLeft: IntPoint2D! topSide: IntPoint2D! topRight: IntPoint2D! bottomLeft: IntPoint2D! bottomSide: IntPoint2D! bottomRight: IntPoint2D! } type IntPoint2D { x: Int! y: Int! } type VideoProcessingGQL { errors: [VideoProcessingErrorGQL!]! status: ProcessingStatusEnum! statuses: [VideoProcessingStatusGQL!]! } type VideoProcessingErrorGQL { message: String! startSegmentIndex: Int endSegmentIndex: Int } enum ProcessingStatusEnum { STARTED FAILED SUCCEEDED SUSPENDED CREATED QUEUED RUNNING REEXTRACTING_FEATURES } type VideoProcessingStatusGQL { status: ProcessingStatusEnum! appVersion: String! sequenceId: Int! createdAt: DateTime updatedAt: DateTime } input GetShotsPagination { createdAfter: CreatedAfter! startFrameAfter: Int! } input CreatedAfter @oneOf { videoId: Int createdAt: DateTime } type UserPlayTimeGQL { totalSeconds: Float! } type VideoHistoryGQL { videos: [VideoGQL!]! pageInfo: PageInfoGQL! } type PageInfoGQL { hasNextPage: Boolean! endCursor: String } input VideoFilterInput { isStreamCompleted: Boolean = null requireCursorCompletion: Boolean! = true } type TagGQL { name: String! id: Int! group: String } """ The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf). """ scalar JSON @specifiedBy( url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf" ) type Mutation { createBucketSet(params: CreateBucketSetInput!): BucketSetGQL! setLoggerLevel(path: String!, level: String!): Boolean! addAnnotationToShot( shotId: Int! annotationName: String! notes: String = null ): AddShotAnnotationReturn! updateShotAnnotations( shotId: Int! annotations: [UpdateAnnotationInputGQL!]! ): UpdateShotAnnotationReturn! getProfileImageUploadLink( fileExt: String = ".png" ): GetProfileUploadLinkReturn! editProfileImageUri(profileImageUri: String!): UserGQL! editUser(input: EditUserInputGQL!): UserGQL! createUploadStream( videoMetadata: VideoMetadataInput! ): CreateUploadStreamReturn! getUploadLink(videoId: Int!, segmentIndex: Int!): GetUploadLinkReturn! getHlsInitUploadLink(videoId: Int!): GetUploadLinkReturn! setSegmentDuration( videoId: Int! segmentIndex: Int! duration: Float! ): Boolean! editUploadStream(videoId: Int!, videoMetadata: VideoMetadataInput!): Boolean! deleteVideo(videoId: Int!): Boolean! } input CreateBucketSetInput { keyName: String! feature: String! buckets: [BucketInputGQL!]! } type AddShotAnnotationReturn { value: SuccessfulAddAddShotAnnotationErrors! } union SuccessfulAddAddShotAnnotationErrors = SuccessfulAdd | AddShotAnnotationErrors type SuccessfulAdd { value: Boolean! } type AddShotAnnotationErrors { error: DoesNotOwnShotErrOtherErrorNeedsNote! } union DoesNotOwnShotErrOtherErrorNeedsNote = DoesNotOwnShotErr | OtherErrorNeedsNote type DoesNotOwnShotErr { shotId: Int! msg: String } type OtherErrorNeedsNote { msg: String } type UpdateShotAnnotationReturn { value: SuccessfulUpdateUpdateShotAnnotationErrors! } union SuccessfulUpdateUpdateShotAnnotationErrors = SuccessfulUpdate | UpdateShotAnnotationErrors type SuccessfulUpdate { value: Boolean! } type UpdateShotAnnotationErrors { error: DoesNotOwnShotErr } input UpdateAnnotationInputGQL { name: String! notes: String = null } type GetProfileUploadLinkReturn { value: UploadLinkGetProfileUploadLinkErrors! } union UploadLinkGetProfileUploadLinkErrors = UploadLink | GetProfileUploadLinkErrors type UploadLink { uploadUrl: String! headers: [Header]! } type Header { key: String! value: String! } type GetProfileUploadLinkErrors { error: TooManyProfileImageUploadsErr! } type TooManyProfileImageUploadsErr { linksRequested: Int! } input EditUserInputGQL { username: String = null fargoRating: Int = null } type CreateUploadStreamReturn { videoId: Int! } input VideoMetadataInput { videoName: String = null startTime: DateTime = null endTime: DateTime = null gameType: String = null tableSize: Float = null uploadStreamMetadataInput: UploadStreamMetadataInput = null lastIntendedSegmentBound: Int = null streamSegmentType: StreamSegmentTypeEnum = null endStream: Boolean! = false resolution: VideoResolution = null framesPerSecond: Float = null } input UploadStreamMetadataInput { deviceType: DeviceTypeEnum = null osVersion: String = null appVersion: String = null browserName: String = null browserVersion: String = null locale: String = null timezone: String = null networkType: String = null ipAddress: String = null } enum DeviceTypeEnum { IOS ANDROID BROWSER } input VideoResolution { width: Int! height: Int! } type GetUploadLinkReturn { value: UploadLinkGetUploadLinkErrors! stream: UploadStreamGQL } union UploadLinkGetUploadLinkErrors = UploadLink | GetUploadLinkErrors type GetUploadLinkErrors { error: MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErr! } union MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErr = MustHaveSetForUploadLinkErr | SegmentAlreadyUploadedErr | ProcessingFailedErr | NoInitForChunkedUploadErr | TooManyProfileImageUploadsErr | InitUploadAlreadyCompletedErr | TooManyInitUploadsErr type MustHaveSetForUploadLinkErr { resolution: Boolean framesPerSecond: Boolean } type SegmentAlreadyUploadedErr { segmentId: Int! } type ProcessingFailedErr { processing: VideoProcessingGQL! } type NoInitForChunkedUploadErr { segmentType: StreamSegmentTypeEnum! } type InitUploadAlreadyCompletedErr { segmentType: StreamSegmentTypeEnum! } type TooManyInitUploadsErr { linksRequested: Int! }