Every operation of POST /graphql, grouped by module, with the permission its resolver checks.
Permission lists what authorize() requires; a branch-scoped permission is checked against the
record's branch (see 06-auth-and-permissions.md). public means no
sign-in is needed; (signed in) means any signed-in user. The full SDL is schema.graphql,
and runnable examples are in examples/.
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | health: Health! |
public | |
| mutation | ping: String! |
public | Does nothing; lets clients check that mutations reach the server. |
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | myOrganizations: [OrganizationChoice!]! |
(signed in) | Every lab the signed-in person can switch to (same email, active account), the current one included. |
| query | mySessions: [Session!]! |
(signed in) | |
| query | permissions: [String!]! |
(signed in) | Every permission name the API checks. |
| query | roles: [Role!]! |
role:read |
|
| query | user(id: ID!): User |
user:read |
|
| query | users: [User!]! |
user:read |
|
| query | viewer: Viewer |
public | null when not signed in. |
| mutation | changePassword(currentPassword: String!, newPassword: String!): Boolean! |
(signed in) | |
| mutation | createUser(input: CreateUserInput!): User! |
user:create |
|
| mutation | disableUser(userId: ID!): User! |
user:disable |
|
| mutation | enableUser(userId: ID!): User! |
user:disable |
|
| mutation | login(input: LoginInput!): LoginResult! |
public | |
| mutation | logout(refreshToken: String): Boolean! |
public | Ends this session: the refresh token when given, else the bearer token (even an expired access token), else the session cookies (which are cleared). |
| mutation | logoutOtherSessions: Int! |
(signed in) | Ends every other session of the signed-in user; returns how many. |
| mutation | refreshSession(refreshToken: String, device: DeviceInput): LoginResult! |
public | Spends a refresh token and returns a new access token and the next refresh token: the one given, or for a cookie session (no argument) the refresh cookie, and the new tokens are set as cookies. A refresh token used twice ends its session (it was copied); within a few seconds (two tabs) the second use only fails. Send the same device as at login: a refresh from another device id ends the session (DEVICE_BINDING). INVALID_TOKEN when it is unknown, spent or expired: sign in again. |
| mutation | requestPasswordReset(email: String!, orgCode: String): Boolean! |
public | Always true (never reveals whether the email exists). Queued: the worker creates the single-use token and mails the link. |
| mutation | resetPassword(token: String!, newPassword: String!): Boolean! |
public | |
| mutation | revokeSession(id: ID!): Boolean! |
(signed in) | |
| mutation | setUserPassword(userId: ID!, newPassword: String!): Boolean! |
user:update |
|
| mutation | setUserRoles(userId: ID!, roles: [RoleAssignmentInput!]!): User! |
user:update |
|
| mutation | switchOrganization(orgCode: String!, device: DeviceInput): LoginResult! |
public | Moves this session to the signed-in person's account in another lab (same email, active there), without the password. This session ends; use the returned tokens (a cookie session gets them as cookies). Send the same device as at login, or the session ends (INVALID_TOKEN). ORG_UNAVAILABLE when there is no such account, or it is the current lab. |
| mutation | upsertRole(input: RoleInput!): Role! |
role:manage |
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | auditLog(entity: String, entityId: ID, limit: Int, offset: Int): [AuditEntry!]! |
audit:read |
|
| query | branch(id: ID!): Branch |
(signed in) | |
| query | branches: [Branch!]! |
(signed in) | |
| query | organization: Organization! |
(signed in) | |
| mutation | createBranch(input: BranchInput!): Branch! |
branch:create |
|
| mutation | updateBranch(id: ID!, input: BranchPatch!): Branch! |
branch:update |
|
| mutation | updateOrganization(input: OrganizationInput!): Organization! |
org:update |
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | catalogDepartments: [String!]! |
public | |
| query | catalogSampleTypes: [String!]! |
public | |
| query | catalogSearch(query: String!, limit: Int, department: String, sampleType: String, fuzzy: Boolean): [CatalogHit!]! |
public | Typo-tolerant search with lab shorthand (cbc, lft, vit d…). |
| query | catalogTest(id: ID, code: String): CatalogTest |
public | By MGPL id (MGPLT0001), old numeric id (10409) or code (CBC). |
| query | catalogTests(department: String, sampleType: String, limit: Int, offset: Int): [CatalogTest!]! |
public | |
| query | priceQuote(tests: [String!]!): PriceQuote! |
public | Total MRP for test codes, MGPL ids, or old numeric ids as strings. |
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | booking(id: ID!): Booking |
booking:read |
|
| query | bookings(branchId: ID, status: BookingStatus, patientId: ID, limit: Int, offset: Int): [Booking!]! |
booking:read |
|
| query | doctors(activeOnly: Boolean): [Doctor!]! |
doctor:read |
|
| query | patient(id: ID!): Patient |
patient:read |
|
| query | patients(search: String!, limit: Int): [Patient!]! |
patient:read |
Name, phone or UHID. |
| query | report(id: ID!): Report |
report:read |
|
| query | sample(id: ID!): Sample |
sample:read |
|
| query | sampleByBarcode(barcode: String!): Sample |
sample:read |
|
| mutation | amendReport(id: ID!): Report! |
report:amend |
|
| mutation | cancelBooking(id: ID!): Booking! |
booking:cancel |
|
| mutation | collectSample(id: ID!, barcode: String): Sample! |
sample:collect |
Marks the sample collected. barcode: the tube's own label, when it is not the generated one (unique in the organisation). |
| mutation | createBooking(input: CreateBookingInput!): CreateBookingResult! |
booking:create, invoice:create, invoice:discount, payment:create |
|
| mutation | createDoctor(input: DoctorInput!): Doctor! |
doctor:manage |
|
| mutation | createReport(bookingId: ID!, testCode: String!, testName: String): Report! |
report:enter |
|
| mutation | receiveSample(id: ID!): Sample! |
sample:receive |
|
| mutation | registerPatient(input: RegisterPatientInput!): Patient! |
patient:create |
|
| mutation | rejectSample(id: ID!, reason: String!): Sample! |
sample:reject |
|
| mutation | relabelSample(id: ID!, barcode: String!): Sample! |
sample:collect |
A new barcode for a sample that the lab has not received yet (BARCODE_TAKEN when another sample has it). |
| mutation | releaseReport(id: ID!): Report! |
report:release |
|
| mutation | saveResults(reportId: ID!, results: [ResultInput!]!): Report! |
report:enter |
|
| mutation | updateBooking(id: ID!, input: UpdateBookingInput!): UpdateBookingResult! |
booking:update, invoice:cancel, invoice:create, accounts:post |
Edits a booking (booking:update): doctor, collection details, notes, tests. Samples follow the tests. Billing follows too: an unpaid invoice is cancelled and issued again (invoice:cancel when tests are removed, invoice:create), a paid one gets a credit note for removed tests (accounts:post) and an invoice for added ones (invoice:create). One transaction, audited as booking.update. |
| mutation | updateDoctor(id: ID!, input: DoctorPatch!): Doctor! |
doctor:manage |
|
| mutation | updatePatient(id: ID!, input: UpdatePatientInput!): Patient! |
patient:update |
|
| mutation | verifyReport(id: ID!): Report! |
report:verify |
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | invoice(id: ID!): Invoice |
invoice:read |
|
| query | invoices(branchId: ID, status: InvoiceStatus, patientId: ID, customerId: ID, limit: Int, offset: Int): [Invoice!]! |
invoice:read |
|
| mutation | cancelInvoice(id: ID!, reason: String!): Invoice! |
invoice:cancel |
|
| mutation | issueInvoice(input: IssueInvoiceInput!): Invoice! |
invoice:create, invoice:discount, payment:create |
|
| mutation | recordPayment(invoiceId: ID!, payment: PaymentInput!): Invoice! |
payment:create |
|
| mutation | refundPayment(invoiceId: ID!, payment: PaymentInput!): Invoice! |
payment:refund |
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | accountGroups: [AccountGroup!]! |
accounts:read |
|
| query | balanceSheet(asOf: DateTime!, branchId: ID): BalanceSheet! |
accounts:read |
|
| query | cashBook(from: DateTime!, to: DateTime!, branchId: ID): LedgerStatement! |
accounts:read |
|
| query | dayBook(from: DateTime!, to: DateTime!, branchId: ID, type: VoucherType): [Voucher!]! |
accounts:read |
|
| query | gstSummary(from: DateTime!, to: DateTime!, branchId: ID): GstSummary! |
accounts:read |
|
| query | ledger(id: ID!): Ledger |
accounts:read |
|
| query | ledgers: [Ledger!]! |
accounts:read |
|
| query | ledgerStatement(ledgerId: ID!, from: DateTime!, to: DateTime!, branchId: ID): LedgerStatement! |
accounts:read |
|
| query | outstandingReceivables(asOf: DateTime, branchId: ID): Outstanding! |
accounts:read |
|
| query | profitAndLoss(from: DateTime!, to: DateTime!, branchId: ID): ProfitAndLoss! |
accounts:read |
|
| query | trialBalance(asOf: DateTime, branchId: ID): TrialBalance! |
accounts:read |
|
| query | voucher(id: ID!): Voucher |
accounts:read |
|
| mutation | cancelVoucher(id: ID!): Voucher! |
accounts:post |
|
| mutation | createLedger(input: LedgerInput!): Ledger! |
accounts:manage |
|
| mutation | postVoucher(input: PostVoucherInput!): Voucher! |
accounts:post |
|
| mutation | setupChartOfAccounts: [Ledger!]! |
accounts:manage |
Seeds the standard chart (28 groups, default ledgers) and finance-kit's ledgers. Idempotent. |
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | bankLines(ledgerId: ID, statementId: ID, status: BankLineStatus, limit: Int, offset: Int): [BankLine!]! |
accounts:read |
|
| query | bankReconciliation(ledgerId: ID!, asOf: DateTime!): BankReconciliation! |
accounts:read |
|
| query | bankStatements(ledgerId: ID, limit: Int, offset: Int): [BankStatement!]! |
accounts:read |
|
| query | cashClosings(branchId: ID!, limit: Int, offset: Int): [CashClosing!]! |
accounts:read |
|
| query | creditNote(id: ID!): CreditNote |
accounts:read |
|
| query | creditNotes(invoiceId: ID, customerId: ID, limit: Int, offset: Int): [CreditNote!]! |
accounts:read |
|
| query | customer(id: ID!): Customer |
accounts:read |
|
| query | customers(activeOnly: Boolean): [Customer!]! |
accounts:read |
|
| query | customerStatement(customerId: ID!, from: DateTime!, to: DateTime!): CustomerStatement! |
accounts:read |
|
| query | exportBalanceSheet(asOf: DateTime!, branchId: ID, bom: Boolean): CsvFile! |
accounts:read |
|
| query | exportDayBook(from: DateTime!, to: DateTime!, branchId: ID, bom: Boolean): CsvFile! |
accounts:read |
|
| query | exportGstr1(from: DateTime!, to: DateTime!, gstin: String, bom: Boolean): [CsvFile!]! |
accounts:read |
One file per section: b2b, b2cl, b2cs, cdnr, cdnur, nil, hsn, docs. |
| query | exportGstr3b(from: DateTime!, to: DateTime!, gstin: String, bom: Boolean): CsvFile! |
accounts:read |
|
| query | exportLedger(ledgerId: ID!, from: DateTime!, to: DateTime!, branchId: ID, bom: Boolean): CsvFile! |
accounts:read |
|
| query | exportProfitAndLoss(from: DateTime!, to: DateTime!, branchId: ID, bom: Boolean): CsvFile! |
accounts:read |
|
| query | exportSettlement(settlementId: ID!, bom: Boolean): CsvFile! |
accounts:read |
|
| query | exportTrialBalance(asOf: DateTime, branchId: ID, bom: Boolean): CsvFile! |
accounts:read |
|
| query | financeConfig: FinanceConfig! |
accounts:read |
|
| query | gstr1(from: DateTime!, to: DateTime!, gstin: String): Gstr1! |
accounts:read |
|
| query | gstr3b(from: DateTime!, to: DateTime!, gstin: String): Gstr3b! |
accounts:read |
|
| query | lockDates: [PeriodLock!]! |
accounts:read |
|
| query | partner(id: ID!): Partner |
accounts:read |
|
| query | partners(branchId: ID): [Partner!]! |
accounts:read |
|
| query | receivablesSummary(asOf: DateTime!): ReceivablesSummary! |
accounts:read |
|
| query | settlement(id: ID!): Settlement |
accounts:read |
|
| query | settlementPreview(partnerId: ID!, period: String!, deductions: [SettlementDeductionInput!]): SettlementPreview! |
accounts:read |
Computes a month's settlement without saving it. period: YYYY-MM. |
| query | settlements(partnerId: ID, status: SettlementStatus, branchId: ID, limit: Int, offset: Int): [Settlement!]! |
accounts:read |
|
| query | taxRules: [TaxRule!]! |
accounts:read |
|
| query | verifyWithCA: [String!]! |
(signed in) | Items the lab's CA must confirm before these values are relied on. |
| mutation | accrueSettlement(id: ID!): Settlement! |
accounts:post |
|
| mutation | addAgreement(input: AgreementInput!): PartnerAgreement! |
accounts:manage |
|
| mutation | addTaxRule(input: TaxRuleInput!): TaxRule! |
accounts:manage |
|
| mutation | cancelSettlement(id: ID!): Settlement! |
accounts:post |
|
| mutation | closeCashDay(input: CloseCashDayInput!): CashClosing! |
accounts:post |
|
| mutation | closeFinancialYear(fy: String!): YearEndResult! |
accounts:manage |
Closing vouchers on 31 March and a lock. fy: 2026-27. |
| mutation | createCustomer(input: CustomerInput!): Customer! |
accounts:manage |
|
| mutation | createPartner(input: PartnerInput!): Partner! |
accounts:manage |
|
| mutation | createSettlement(partnerId: ID!, period: String!, deductions: [SettlementDeductionInput!]): Settlement! |
accounts:post |
|
| mutation | importBankStatement(input: BankImportInput!): BankImportResult! |
accounts:post |
|
| mutation | issueCreditNote(input: CreditNoteInput!): CreditNoteResult! |
accounts:post |
|
| mutation | issueCustomerInvoice(input: CustomerInvoiceInput!): Invoice! |
accounts:post, invoice:create |
|
| mutation | matchBankLine(lineId: ID!, voucherIds: [ID!]!): BankLine! |
accounts:post |
|
| mutation | paySettlement(input: PaySettlementInput!): Settlement! |
accounts:post |
|
| mutation | reconcileBank(ledgerId: ID!, statementId: ID): ReconcileResult! |
accounts:post |
|
| mutation | seedTaxRules: [TaxRule!]! |
accounts:manage |
finance-kit's starting rules. Every rate must be verified with the lab's CA. |
| mutation | setBankLineStatus(lineId: ID!, status: BankLineManualStatus!): BankLine! |
accounts:post |
|
| mutation | setLockDate(input: LockDateInput!): PeriodLock! |
accounts:manage |
|
| mutation | updateCustomer(id: ID!, input: CustomerPatch!): Customer! |
accounts:manage |
|
| mutation | updatePartner(id: ID!, input: PartnerPatch!): Partner! |
accounts:manage |
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | bookingReportDocument(bookingId: ID!, reportIds: [ID!], format: DocumentFormat, variant: ReportVariant, design: ReportDesign): Document! |
report:read |
A booking's verified or released reports, printed together (or only reportIds). |
| query | documentSamples: [DocumentSample!]! |
(signed in) | Demo samples (empty unless the server enables them). |
| query | invoiceDocument(invoiceId: ID!, format: DocumentFormat, variant: ReportVariant): Document! |
invoice:read |
A stored invoice, printed. |
| query | invoiceSamples: [DocumentSample!]! |
(signed in) | |
| query | invoiceTotals(request: JSON!): JSON! |
invoice:read |
invoice-kit's calculation of an InvoiceRequest, without rendering (rupees). |
| query | job(id: ID!): Job |
(signed in) | A job you started (null when unknown, expired, or someone else's). |
| query | letterhead(format: DocumentFormat): Document! |
report:read |
|
| query | priceList: [PriceListEntry!]! |
(signed in) | invoice-kit's sample price list (codes = report-kit template and profile ids, services, consumables). |
| query | renderInvoice(request: JSON!, format: DocumentFormat, variant: ReportVariant, fileName: String): Document! |
invoice:read |
Any invoice: request = invoice-kit's InvoiceRequest JSON ({ invoice } or { sample }). |
| query | renderReport(request: JSON!, format: DocumentFormat, variant: ReportVariant, design: ReportDesign, fileName: String): Document! |
report:read |
Any report: request = report-kit's ReportRequest JSON (patient + tests | profile | report | sample). |
| query | reportTemplates: TemplateCatalog! |
(signed in) | |
| mutation | startDocumentJob(input: DocumentJobInput!): Job! |
invoice:read, report:read |
Renders a stored invoice or a booking's reports in the background; poll job(id) until completed or failed. |
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | externalReport(id: ID!): ExternalReport |
report:import |
|
| query | externalReports(branchId: ID, bookingId: ID, status: ExternalReportStatus, first: Int, after: String): ExternalReportConnection! |
report:import |
|
| query | externalSignatories(lab: ExternalLab): [ExternalSignatory!]! |
signatory:read |
The organisation's partner signatory registry. |
| mutation | applyExternalReport(id: ID!, mappings: [ExternalTestMappingInput!]!, confirmPatientMismatch: Boolean): [Report!]! |
report:enter, report:import |
Writes the mapped tests into our reports as entered results with provenance. Staff verify as usual. |
| mutation | importExternalReport(pdfBase64: String!, fileName: String, bookingId: ID, branchId: ID): ExternalReport! |
report:import |
Reads a partner lab's PDF (base64, up to IMPORT_MAX_BYTES). The same PDF again returns the same import. branchId defaults to the booking's branch. |
| mutation | matchExternalReport(id: ID!, bookingId: ID!): ExternalReport! |
report:import |
Ties an import to a booking; patient differences become warnings. |
| mutation | nameExternalSignature(input: ExternalSignatoryInput!): ExternalSignatory! |
signatory:manage |
Names a partner signature; the organisation's unapplied imports that carry it are read again. |
| mutation | rejectExternalReport(id: ID!, reason: String!): ExternalReport! |
report:import |
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | patientDocument(token: String!, kind: PatientDocumentKind!, invoiceId: ID): Document |
public | Public (no sign-in): a PDF from a patient link: one of its bills (invoiceId) or its released reports. Null when the link does not open. |
| query | patientLink(bookingId: ID!): PatientLinkInfo! |
booking:read, report:read |
The booking's patient link (made on first use). Needs booking:read and report:read on the booking's branch. |
| query | patientPage(token: String!): PatientPage |
public | Public (no sign-in): the page behind a patient link, or null. |
| mutation | revokePatientLinks(bookingId: ID!): Int! |
booking:update |
Stops every link of the booking (printed QRs stop opening); the next print makes a new one. Returns how many were stopped. Needs booking:update. |
| Kind | Operation | Permission | Notes |
|---|---|---|---|
| query | systemUpdates(dashboardVersion: String, refresh: Boolean): SystemUpdates! |
settings:manage |
Owner and admin (settings:manage): is a newer api or dashboard release out, what changed, and how to update. The dashboard passes its own version; refresh skips the cache (at most once a minute). |