"This directive allows results to be deferred during execution" directive @defer( "Deferred behaviour is controlled by this argument" if: Boolean! = true, "A unique label that represents the fragment being deferred" label: String ) on FRAGMENT_SPREAD | INLINE_FRAGMENT "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" reason: String! = "No longer supported" ) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION "This directive disables error propagation when a non nullable field returns null for the given operation." directive @experimental_disableErrorPropagation on QUERY | MUTATION | SUBSCRIPTION "Directs the executor to include this field or fragment only when the `if` argument is true" directive @include( "Included when true." if: Boolean! ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT "Indicates an Input Object is a OneOf Input Object." directive @oneOf on INPUT_OBJECT "Directs the executor to skip this field or fragment when the `if` argument is true." directive @skip( "Skipped when true." if: Boolean! ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT "Exposes a URL that specifies the behaviour of this scalar." directive @specifiedBy( "The URL that specifies the behaviour of this scalar." url: String! ) on SCALAR type AdminUser { aiBannedAt: DateTime banReason: String bannedAt: DateTime createdAt: DateTime email: String! emailVerified: Boolean! firstName: String id: UUID! lastLoginAt: DateTime lastName: String lockedUntil: DateTime name: String roles: [String!]! } type AdminUserPage { content: [AdminUser!]! page: PageInfo! } """ AI assistant (CORE_SPEC §11) — the same services as REST /api/v1/ai/*. A streamed reply is REST only (POST /api/v1/ai/conversations/{id}/messages/stream, SSE); sendAiMessage waits. """ type AiConversation { createdAt: DateTime id: UUID! lastMessageAt: DateTime model: String "ACTIVE or ARCHIVED" status: String! title: String! updatedAt: DateTime } type AiConversationPage { content: [AiConversation!]! page: PageInfo! } type AiKnowledgeEntry { content: String! createdAt: DateTime "null until the text is embedded (and after every edit until it is again)" embeddedAt: DateTime embeddingModel: String enabled: Boolean! id: UUID! source: String! title: String! updatedAt: DateTime } type AiKnowledgeEntryPage { content: [AiKnowledgeEntry!]! page: PageInfo! } type AiKnowledgeReindex { embedded: Int! failed: Int! } type AiKnowledgeStatus { embeddingModel: String! embeddingProvider: String! entries: Long! pendingEmbedding: Long! "why retrieval is off, when it is" reason: String retrieval: Boolean! } type AiMessage { "Markdown" content: String! conversationId: UUID! createdAt: DateTime errorCode: String id: UUID! inputTokens: Int model: String outputTokens: Int provider: String "USER or ASSISTANT" role: String! "COMPLETE or FAILED" status: String! } type AiMessagePage { content: [AiMessage!]! page: PageInfo! } type AiModel { isDefault: Boolean! isFree: Boolean! model: String! provider: String! requiresOwnKey: Boolean! } type AiModelTotals { inputTokens: Long! model: String! outputTokens: Long! requests: Long! } type AiProviderConfig { byokEnabled: Boolean! defaultModel: String hasApiKey: Boolean! "Last four characters of the saved key; the key itself is never returned" keyHint: String platformModel: String! platformProvider: String! provider: String providers: [String!]! } type AiReply { conversationId: UUID! message: AiMessage! modelIsFree: Boolean! } " ── Admin (permission ai.manage) — the same services as REST /api/v1/admin/ai/* ──" type AiSettings { "false = new replies are refused with AI_DISABLED; history stays readable" enabled: Boolean! "the product's persona, scope and page links, layered under the guardrails" platformPrompt: String! } type AiUsageSummary { byModel: [AiModelTotals!]! "null when billing is off" freeDailyRemaining: Int from: DateTime! inputTokens: Long! outputTokens: Long! period: String! platformRepliesToday: Long! requests: Long! totalTokens: Long! } type AiUserInstructions { content: String enabled: Boolean! updatedAt: DateTime } type AppVersionInfo { forceUpdate: Boolean! latestVersion: String! links: [AppVersionLink!]! message: String! minVersion: String! platform: String! skip: Boolean! updateAvailable: Boolean updateSource: String! } type AppVersionLink { label: String! priority: Int! url: String! } type AuditEvent { action: String! actorId: UUID actorLabel: String after: JSON before: JSON category: String! context: JSON createdAt: DateTime id: UUID! ip: String requestId: String severity: String! subjectId: String subjectType: String } type AuditEventPage { content: [AuditEvent!]! page: PageInfo! } type AuthPayload { accessToken: String! expiresIn: Long! refreshExpiresAt: DateTime! refreshToken: String! tokenType: String! user: UserProfile! } type ClinicalAnswer { sources: [ClinicalSource!]! text: String! } type ClinicalChartContext { records: String! sources: [ClinicalSource!]! } type ClinicalChartOverview { allergies: [ClinicalHistory!]! conditions: [ClinicalHistory!]! medications: [ClinicalHistory!]! patient: ClinicalPatient! recentVisits: [ClinicalVisit!]! } type ClinicalDocument { batchId: UUID! createdAt: DateTime! doctorNote: String documentDate: String id: UUID! legibility: String ocrText: String pageNo: Int! sourceName: String visitId: UUID } type ClinicalDraft { assessment: String chiefComplaint: String plan: String symptoms: String unclearParts: String } type ClinicalHistory { active: Boolean! details: String id: UUID! kind: ClinicalHistoryKind! label: String! recordedAt: DateTime! version: Long! } type ClinicalHistoryPage { content: [ClinicalHistory!]! number: Int! size: Int! totalElements: Long! totalPages: Int! } type ClinicalLiveSession { consentVersion: UUID! expiresAt: DateTime! instructions: String! model: String! provider: ClinicalAiProvider! sessionId: UUID! token: String! } type ClinicalPatient { address: String age: Int allergyStatus: ClinicalAllergyStatus! chartNo: String! clinic: String fatherName: String fullName: String! id: UUID! note: String phone: String slug: String! version: Long! } type ClinicalPatientPage { content: [ClinicalPatientSummary!]! number: Int! size: Int! totalElements: Long! totalPages: Int! } type ClinicalPatientSummary { allergyStatus: ClinicalAllergyStatus! chartNo: String! clinic: String fatherName: String fullName: String! id: UUID! lastVisitAt: DateTime note: String phone: String slug: String! version: Long! visitCount: Long! } type ClinicalReading { legibility: ClinicalLegibility! text: String } type ClinicalRecentVisit { finalizedAt: DateTime id: UUID! notes: String patientId: UUID! patientName: String! patientSlug: String! visitedAt: DateTime! } type ClinicalRevision { changedAt: DateTime! priorNotes: String reason: String! } type ClinicalSource { id: UUID! kind: String! patientId: UUID! patientSlug: String! reference: String! } type ClinicalVisit { finalizedAt: DateTime id: UUID! notes: String patientId: UUID! version: Long! visitedAt: DateTime! } type ClinicalVisitPage { content: [ClinicalVisit!]! number: Int! size: Int! totalElements: Long! totalPages: Int! } type ClinicalVoiceNote { byteSize: Int! createdAt: DateTime! durationMs: Int! id: UUID! title: String! visitId: UUID! } type KeycloakLogout { "Open it to end the Keycloak browser session (RP-initiated logout)" logoutUrl: String! provider: String! "true when this API's session (the given refresh token's family) was ended" revoked: Boolean! success: Boolean! } type Media { alt: String collection: String! createdAt: DateTime deletedAt: DateTime height: Int isPublic: Boolean! kind: String! mimeType: String! originalName: String! ownerId: String! ownerType: String! protectionMode: String! publicId: UUID! sizeBytes: Long! sortOrder: Int! status: String! "Resolved at read time; signed for protected collections." url: String "null until generated — fall back to url." variants: MediaVariants! width: Int } type MediaPage { content: [Media!]! page: PageInfo! } type MediaVariants { large: String medium: String small: String thumbnail: String } type Message { message: String! } type Mutation { amendClinicalVisit(input: AmendClinicalVisit!, patientId: UUID!, visitId: UUID!): ClinicalVisit! archiveClinicalDocument(documentId: UUID!, patientId: UUID!): Boolean! archiveClinicalPatient(input: ArchiveClinicalRecord!, patientId: UUID!): Boolean! archiveClinicalVisit(input: ArchiveClinicalRecord!, patientId: UUID!, visitId: UUID!): Boolean! archiveClinicalVoiceNote(id: UUID!, patientId: UUID!, visitId: UUID!): Boolean! askClinicalChart(input: ClinicalQuestionInput!, patientId: UUID!): ClinicalAnswer! askClinicalWorkspace(input: ClinicalQuestionInput!): ClinicalAnswer! assignRoles(roles: [String!]!, userId: UUID!): AdminUser! banUser(id: UUID!, reason: String): AdminUser! changePassword(input: ChangePasswordInput!): AuthPayload! createAiKnowledgeEntry(input: AiKnowledgeEntryInput!): AiKnowledgeEntry! createClinicalHistory(input: CreateClinicalHistory!, patientId: UUID!): ClinicalHistory! createClinicalPatient(input: CreateClinicalPatient!): ClinicalPatient! createClinicalVisit(input: CreateClinicalVisit!, patientId: UUID!): ClinicalVisit! deleteAiConversation(id: UUID!): Boolean! deleteAiKnowledgeEntry(id: UUID!): Boolean! deleteAiProviderConfig: Boolean! deleteAiUserInstructions: Boolean! deleteMedia(publicId: UUID!): Boolean! "The one-time code from FRONTEND_URL/auth/callback?code= for the token pair" exchangeOAuthCode(code: String!): AuthPayload! finalizeClinicalVisit(input: FinalizeClinicalVisit!, patientId: UUID!, visitId: UUID!): ClinicalVisit! "Native apps: a Keycloak authorization code obtained with PKCE" keycloakExchange(code: String!, codeVerifier: String!, redirectUri: String): AuthPayload! keycloakLogout(refreshToken: String): KeycloakLogout! login(input: LoginInput!): AuthPayload! logout(refreshToken: String): Boolean! "No-op; lets modules extend the Mutation type." noop: Boolean readClinicalDocument(documentId: UUID!, patientId: UUID!): ClinicalReading! refreshTokens(refreshToken: String!): AuthPayload! register(input: RegisterInput!): AuthPayload! reindexAiKnowledge: AiKnowledgeReindex! renameClinicalVoiceNote(id: UUID!, input: ClinicalVoiceNoteMetadata!, patientId: UUID!, visitId: UUID!): ClinicalVoiceNote! requestPasswordReset(email: String!): Message! resetPassword(input: ResetPasswordInput!): Message! restoreMedia(publicId: UUID!): Media! revokeAllSessions: Boolean! revokeSession(id: UUID!): Boolean! "apiKey may be omitted to keep the saved key" saveAiProviderConfig(apiKey: String, defaultModel: String, provider: String!): AiProviderConfig! saveAiSettings(enabled: Boolean!, platformPrompt: String): AiSettings! saveAiUserInstructions(content: String!, enabled: Boolean): AiUserInstructions! saveClinicalLiveTranscript(input: ClinicalVoiceTranscriptInput!, patientId: UUID): Boolean! sendAiMessage(content: String!, conversationId: UUID!): AiReply! sendEmailVerificationCode: Message! speakClinicalVisit(language: ClinicalLanguage!, patientId: UUID!, visitId: UUID!): String! startAiConversation(title: String): AiConversation! startClinicalLive(language: ClinicalLanguage! = EN, patientId: UUID!, provider: ClinicalAiProvider! = GEMINI): ClinicalLiveSession! structureClinicalVisit(input: ClinicalNotesInput!, patientId: UUID!, visitId: UUID!): ClinicalDraft! unbanUser(id: UUID!): AdminUser! "status: ACTIVE or ARCHIVED" updateAiConversation(id: UUID!, status: String, title: String): AiConversation! updateAiKnowledgeEntry(id: UUID!, input: AiKnowledgeEntryInput!): AiKnowledgeEntry! updateClinicalDocument(documentId: UUID!, input: ClinicalDocumentMetadata!, patientId: UUID!): ClinicalDocument! updateClinicalHistory(entryId: UUID!, input: UpdateClinicalHistory!, patientId: UUID!): ClinicalHistory! updateClinicalPatient(input: UpdateClinicalPatient!, patientId: UUID!): ClinicalPatient! updateClinicalVisit(input: UpdateClinicalVisit!, patientId: UUID!, visitId: UUID!): ClinicalVisit! updateMedia(input: UpdateMediaInput!, publicId: UUID!): Media! updateMyProfile(input: UpdateProfileInput!): UserProfile! verifyEmailWithCode(code: String!): UserProfile! } type OAuthProviders { github: Boolean! google: Boolean! keycloak: Boolean! } type PageInfo { number: Int! size: Int! totalElements: Long! totalPages: Int! } type Query { aiConversation(id: UUID!): AiConversation! aiConversations(page: Int, search: String, size: Int, status: String): AiConversationPage! aiKnowledge(page: Int, search: String, size: Int, source: String): AiKnowledgeEntryPage! aiKnowledgeEntry(id: UUID!): AiKnowledgeEntry! aiKnowledgeStatus: AiKnowledgeStatus! aiMessages(conversationId: UUID!, page: Int, size: Int): AiMessagePage! aiModels: [AiModel!]! aiProviderConfig: AiProviderConfig! aiSettings: AiSettings! aiUsageSummary(period: String): AiUsageSummary! aiUserInstructions: AiUserInstructions! appVersion(appVersion: String, platform: String!): AppVersionInfo! auditEvents(action: String, actorId: UUID, category: String, page: Int, severity: String, size: Int): AuditEventPage! clinicalChartOverview(patientId: UUID!): ClinicalChartOverview! clinicalClinics: [String!]! clinicalDocument(documentId: UUID!, patientId: UUID!): ClinicalDocument! clinicalDocuments(page: Int! = 0, patientId: UUID!, size: Int! = 20): [ClinicalDocument!]! clinicalHistory(kind: ClinicalHistoryKind!, page: Int! = 0, patientId: UUID!, size: Int! = 20): ClinicalHistoryPage! clinicalHistoryEntry(entryId: UUID!, patientId: UUID!): ClinicalHistory! clinicalLiveContext(consentVersion: UUID!, patientId: UUID!, question: String!): ClinicalChartContext! clinicalLiveStatus(consentVersion: UUID!, patientId: UUID!): Boolean! clinicalPatient(patientId: UUID!): ClinicalPatient! clinicalPatients(clinic: String, page: Int! = 0, search: String, size: Int! = 20): ClinicalPatientPage! clinicalRecentVisits(limit: Int! = 8): [ClinicalRecentVisit!]! clinicalVisit(patientId: UUID!, visitId: UUID!): ClinicalVisit! clinicalVisitRevisions(patientId: UUID!, visitId: UUID!): [ClinicalRevision!]! clinicalVisits(page: Int! = 0, patientId: UUID!, size: Int! = 20): ClinicalVisitPage! clinicalVoiceNote(id: UUID!, patientId: UUID!, visitId: UUID!): ClinicalVoiceNote! clinicalVoiceNotes(patientId: UUID!, visitId: UUID!): [ClinicalVoiceNote!]! "The signed-in user. Same payload as GET /api/v1/auth/me." me: UserProfile! mediaByOwner(ownerId: String!, ownerType: String!): [Media!]! mediaItem(publicId: UUID!): Media! mediaSignedUrl(publicId: UUID!): SignedUrl! "The caller's media. Uploads go through REST (multipart, base64 or presigned)." myMedia(collection: String, page: Int, size: Int): MediaPage! myQuota: StorageQuota! "Configured sign-in providers. The browser flow starts at GET /api/v1/auth/oauth/{provider}/redirect." oauthProviders: OAuthProviders! "Liveness of the GraphQL endpoint." ping: String! "Public branding, media limits and feature flags — same projection as GET /api/v1/settings." publicSettings: JSON! roles: [Role!]! sessions: [Session!]! user(id: UUID!): AdminUser! users(page: Int, search: String, size: Int): AdminUserPage! } type Role { defaultRole: Boolean! description: String id: UUID! name: String! permissions: [String!]! superAdmin: Boolean! system: Boolean! } type Session { expiresAt: DateTime! id: UUID! ip: String lastActiveAt: DateTime userAgent: String } type SignedUrl { expiresInSeconds: Long! url: String! } type StorageQuota { maxBytes: Long! maxFiles: Int! totalFiles: Int! usedBytes: Long! } type UserProfile { avatarUrl: String createdAt: DateTime email: String! emailVerified: Boolean! firstName: String hasPassword: Boolean! id: UUID! isAdmin: Boolean! lastName: String name: String permissions: [String!]! roles: [String!]! twoFactorEnabled: Boolean! } enum ClinicalAiProvider { GEMINI OPENAI } enum ClinicalAllergyStatus { HAS_ALLERGIES NONE_KNOWN UNKNOWN } enum ClinicalHistoryKind { ALLERGY CONDITION MEDICATION } enum ClinicalLanguage { EN MY } enum ClinicalLegibility { CLEAR PARTIAL UNREADABLE } "A slightly refined version of RFC-3339 compliant DateTime Scalar" scalar DateTime "A JSON scalar" scalar JSON "A 64-bit signed integer" scalar Long "A universally unique identifier compliant UUID Scalar" scalar UUID input AiKnowledgeEntryInput { content: String! enabled: Boolean "default platform" source: String title: String! } input AmendClinicalVisit { notes: String! reason: String! version: Long! } input ArchiveClinicalRecord { version: Long! } input ChangePasswordInput { currentPassword: String! password: String! } input ClinicalDocumentMetadata { docDate: String doctorNote: String sourceName: String } input ClinicalNotesInput { notes: String! } input ClinicalQuestionInput { language: ClinicalLanguage provider: ClinicalAiProvider question: String! } input ClinicalVoiceLineInput { speaker: String! text: String! } input ClinicalVoiceNoteMetadata { title: String! } input ClinicalVoiceTranscriptInput { consentVersion: UUID sessionId: UUID! turns: [ClinicalVoiceLineInput!]! } input CreateClinicalHistory { details: String kind: ClinicalHistoryKind! label: String! } input CreateClinicalPatient { address: String age: Int clinic: String fatherName: String fullName: String! note: String phone: String! } input CreateClinicalVisit { notes: String } input FinalizeClinicalVisit { version: Long! } input LoginInput { email: String! password: String! } input RegisterInput { email: String! name: String password: String! } input ResetPasswordInput { code: String! email: String! password: String! } input UpdateClinicalHistory { active: Boolean! details: String label: String! version: Long! } input UpdateClinicalPatient { address: String age: Int allergyStatus: ClinicalAllergyStatus! clinic: String fatherName: String fullName: String! note: String phone: String! version: Long! } input UpdateClinicalVisit { notes: String version: Long! } input UpdateMediaInput { alt: String sortOrder: Int } input UpdateProfileInput { firstName: String lastName: String name: String }