openapi: 3.0.3 info: title: GameTime Server API version: 0.1.0 description: Headless API for GameTime authentication, incremental sync and targeted sharing. servers: - url: http://localhost:8080 description: Local Docker or development server tags: - name: Health - name: Auth - name: Sync - name: Shares paths: /health: get: tags: [Health] summary: Healthcheck responses: '200': description: Server is reachable. content: application/json: schema: $ref: '#/components/schemas/HealthResponse' /auth/register: post: tags: [Auth] summary: Register a user account requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterRequest' responses: '201': description: User created. content: application/json: schema: $ref: '#/components/schemas/RegisterResponse' '400': $ref: '#/components/responses/BadRequest' '409': description: Email already taken. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /auth/login: post: tags: [Auth] summary: Login and create an API token session requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LoginRequest' responses: '200': description: Login accepted. The clear token is returned once. content: application/json: schema: $ref: '#/components/schemas/LoginResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /auth/logout: post: tags: [Auth] summary: Revoke the current API token session security: - bearerAuth: [] responses: '204': description: Session revoked. '401': $ref: '#/components/responses/Unauthorized' /sync/push: post: tags: [Sync] summary: Push client-side resource mutations security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SyncPushRequest' responses: '200': description: Batch processed. Item-level validation errors are returned in results. content: application/json: schema: $ref: '#/components/schemas/SyncPushResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /sync/pull: get: tags: [Sync] summary: Pull resources updated after a server cursor security: - bearerAuth: [] parameters: - name: since in: query required: false schema: type: string format: date-time description: ISO8601 UTC server cursor. If omitted, returns all resources. responses: '200': description: Resources for the authenticated user. content: application/json: schema: $ref: '#/components/schemas/SyncPullResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /sync/exchange: post: tags: [Sync] summary: Push then pull in one request security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SyncExchangeRequest' responses: '200': description: Pull response after applying the push batch. content: application/json: schema: $ref: '#/components/schemas/SyncExchangeResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /shares: post: tags: [Shares] summary: Create a targeted share security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateShareRequest' responses: '201': description: Share created. Unknown recipient emails are reported but do not fail the request. content: application/json: schema: $ref: '#/components/schemas/CreateShareResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /shares/inbox: get: tags: [Shares] summary: List shares received by the authenticated user security: - bearerAuth: [] responses: '200': description: Inbox sorted from newest to oldest. content: application/json: schema: $ref: '#/components/schemas/ShareInboxResponse' '401': $ref: '#/components/responses/Unauthorized' /shares/{id}/accept: post: tags: [Shares] summary: Accept a received share security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/ShareId' responses: '200': description: Share accepted and copied into synced resources. content: application/json: schema: $ref: '#/components/schemas/AcceptShareResponse' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' /shares/{id}/decline: post: tags: [Shares] summary: Decline a received share security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/ShareId' responses: '204': description: Share declined. '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' /shares/{id}/revoke: post: tags: [Shares] summary: Revoke a share sent by the authenticated user security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/ShareId' responses: '204': description: Share revoked. '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: opaque parameters: ShareId: name: id in: path required: true schema: type: string description: Share identifier. responses: BadRequest: description: Invalid JSON body, invalid parameter, or validation failure. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Unauthorized: description: Missing, malformed, expired, revoked or unknown bearer token. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' NotFound: description: Resource not found or not accessible by the authenticated user. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Conflict: description: Current resource state rejects the requested transition. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' schemas: ErrorResponse: type: object required: [error] properties: error: type: string HealthResponse: type: object required: [status] properties: status: type: string enum: [ok] RegisterRequest: type: object required: [email, password] properties: email: type: string format: email password: type: string minLength: 8 displayName: type: string nullable: true RegisterResponse: type: object required: [userId, email] properties: userId: type: string email: type: string format: email LoginRequest: type: object required: [email, password] properties: email: type: string format: email password: type: string deviceLabel: type: string nullable: true LoginResponse: type: object required: [token, expiresAt] properties: token: type: string expiresAt: type: string format: date-time ResourceType: type: string enum: [exercise, program, workoutTemplate, workoutHistory, mediaAsset] ShareResourceType: type: string enum: [program, workoutTemplate] ShareKind: type: string enum: [single, pack] ShareRecipientStatus: type: string enum: [pending, accepted, declined, revoked] JsonObject: type: object additionalProperties: true SyncPushRequest: type: object required: [items] properties: deviceId: type: string nullable: true description: Required by the application use case for each valid item. items: type: array items: $ref: '#/components/schemas/SyncPushItem' SyncPushItem: type: object required: [resourceType, clientId, schemaVersion, clientUpdatedAt, payload] properties: resourceType: $ref: '#/components/schemas/ResourceType' clientId: type: string schemaVersion: type: integer minimum: 1 clientUpdatedAt: type: string format: date-time deletedAt: type: string format: date-time nullable: true payload: $ref: '#/components/schemas/JsonObject' SyncPushResponse: type: object required: [serverCursor, results] properties: serverCursor: type: string format: date-time results: type: array items: $ref: '#/components/schemas/SyncPushResultItem' SyncPushResultItem: type: object required: [status] properties: resourceType: type: string nullable: true clientId: type: string nullable: true serverId: type: string nullable: true status: type: string enum: [accepted, ignoredOlder, error] serverUpdatedAt: type: string format: date-time nullable: true message: type: string SyncPullResponse: type: object required: [serverCursor, items] properties: serverCursor: type: string format: date-time items: type: array items: $ref: '#/components/schemas/SyncedResourceItem' SyncExchangeRequest: allOf: - $ref: '#/components/schemas/SyncPushRequest' - type: object properties: since: type: string format: date-time nullable: true SyncExchangeResponse: type: object required: [serverCursor, pushResults, items] properties: serverCursor: type: string format: date-time pushResults: type: array items: $ref: '#/components/schemas/SyncPushResultItem' items: type: array items: $ref: '#/components/schemas/SyncedResourceItem' SyncedResourceItem: type: object required: - resourceType - clientId - serverId - schemaVersion - clientUpdatedAt - serverUpdatedAt - payload properties: resourceType: $ref: '#/components/schemas/ResourceType' clientId: type: string serverId: type: string schemaVersion: type: integer minimum: 1 clientUpdatedAt: type: string format: date-time serverUpdatedAt: type: string format: date-time deletedAt: type: string format: date-time nullable: true payload: $ref: '#/components/schemas/JsonObject' CreateShareRequest: type: object required: [recipientEmails] properties: shareKind: description: Defaults to single when omitted. allOf: - $ref: '#/components/schemas/ShareKind' packName: type: string nullable: true resourceType: nullable: true allOf: - $ref: '#/components/schemas/ShareResourceType' payload: $ref: '#/components/schemas/JsonObject' nullable: true items: type: array items: $ref: '#/components/schemas/SharePackItem' recipientEmails: type: array minItems: 1 items: type: string format: email SharePackItem: type: object required: [resourceType, payload] properties: resourceType: $ref: '#/components/schemas/ShareResourceType' payload: $ref: '#/components/schemas/JsonObject' CreateShareResponse: type: object required: [shareId, shareKind, recipientUserIds, unresolvedEmails] properties: shareId: type: string shareKind: $ref: '#/components/schemas/ShareKind' recipientUserIds: type: array items: type: string unresolvedEmails: type: array items: type: string format: email ShareInboxResponse: type: object required: [items] properties: items: type: array items: $ref: '#/components/schemas/ShareInboxItem' ShareInboxItem: type: object required: - shareId - senderUserId - shareKind - payload - status - createdAt properties: shareId: type: string senderUserId: type: string shareKind: $ref: '#/components/schemas/ShareKind' packName: type: string nullable: true resourceType: nullable: true allOf: - $ref: '#/components/schemas/ShareResourceType' payload: $ref: '#/components/schemas/JsonObject' status: $ref: '#/components/schemas/ShareRecipientStatus' createdAt: type: string format: date-time respondedAt: type: string format: date-time nullable: true AcceptShareResponse: type: object required: [createdResources] properties: createdResource: $ref: '#/components/schemas/SyncedResourceItem' description: Present only for single-resource shares. createdResources: type: array items: $ref: '#/components/schemas/SyncedResourceItem'