Zum Inhalt springen

GraphQL Schema

This page documents the conventions every SwissMentor GraphQL endpoint follows. Once you understand them you can navigate the full schema without further guidance.

The complete, type-by-type listing — every query, mutation, object, input, enum and scalar — is in the Schema Reference.

The live schema is always available through introspection from any GraphQL IDE (Apollo Sandbox, Altair, GraphiQL) pointed at your SwissMentor endpoint. Introspection always reflects the current server, so it is the authoritative source for the exact fields available to you.

Every domain entity (Person, Course, Document, Registration, …) extends a base SMObject. This base carries the identity, type, status, validity window and ACL bindings.

type SMObject {
"Stable UID — use this everywhere in the API"
id: ID!
"Short name shown in lists and pickers"
name: String!
"Object type code, e.g. 'PERSON', 'COURSE', 'DOCUMENT'"
objectType: String!
"System classification; coarser than objectType"
sysType: SysType!
"Status (active / inactive / archived / …)"
statusID: Int!
"Validity window — object is hidden outside these dates"
validFrom: Date
validTo: Date
"Owner — typically a person responsible for the record"
owner: Person
remarks: String
customFields: [CustomField!]
}
enum SysType {
Person
Group
Course
CourseParticipant
CourseRegistration
Document
Company
Folder
# ... several dozen more
}

A Person, Course or CourseParticipant is always reachable through its .object field on the entity, and every SMObject can be narrowed back to its specific entity via .person, .course, .courseParticipant, …

The public API exclusively exposes the UID scalar (a GUID string). Internal integer IDs (personID, courseID, objectID) are never returned through GraphQL, even though they exist in the database.

Consequences:

  • Client code stores and compares UID strings.
  • parentID and other cross-entity references also take a UID.
  • Some resolvers accept a few special UID forms, e.g. special://PERSONFLDROOT to target the default person folder.

Singular, named after the entity, takes id: ID!.

query {
person(id: "8f3a1a8e-...-1c") {
id
lastName
preName
}
}

Plural, takes one options argument of type <Entity>ListOptions. All list-options types share the same base interface:

input <Entity>ListOptions {
id: UID # restrict to a single object
ids: [UID!] # restrict to a set
parentID: UID # children of this parent
recursiveLevel: Int # walk N folder levels deep
searchTerm: String # full-text search (name, code, …)
sysType: SysType
sysTypes: [SysType!]
onlyActive: Boolean
statusAll: Boolean
statusIncludes: [Int!]
statusExcludes: [Int!]
orderBy: String
orderDirection: OrderDirection # ASC / DESC
limit: Int
offset: Int
}

List queries return a result wrapper:

type <Entity>Results {
totalCount: Int!
nodes: [<Entity>!]!
}

Example:

query {
persons(options: { searchTerm: "Müller", onlyActive: true, limit: 25 }) {
totalCount
nodes {
id
lastName
preName
}
}
}

createBaseResolver emits a standard set of mutations for every base object:

type Mutation {
createPerson(person: PersonCreateInput!, parentID: ID, objectType: String!): CreatePersonResult!
updatePerson(id: ID!, person: PersonUpdateInput!): UpdatePersonResult!
updatePersonMultiple(ids: [ID!]!, person: PersonUpdateInput!): [UpdatePersonResult!]!
deletePerson(id: ID!): DeleteResult!
}
  • parentID subordinates the new object under an existing one; ACL is checked against it.
  • objectType falls back to the entity default when omitted.

Every mutation returns a structured result — not the entity directly:

type CreatePersonResult {
status: OperationResultStatus! # SUCCESS, WARNING, ERROR, DENIED
message: String
id: ID
person: Person
errorType: String # programmatic error code (e.g. 'PersonNumberDuplicateError')
errorData: JSON # structured data for the error
}

Always branch on status first, then show message to the user. Use errorType / errorData for programmatic handling (retry, linking to the conflicting record, …).

<Entity>CreateInput extends <Entity>UpdateInput, which extends BaseObjectInput. That means every create input inherits:

  • parentID, objectType, remarks, code, importID
  • ownerID, validFrom, validTo
  • customFields

plus all entity-specific fields.

Entities that declare customFieldsEnabled = true accept arbitrary customer-defined fields through the shared CustomFieldInput:

input CustomFieldInput {
customFieldID: UID!
value: String
}

On the read side, custom fields appear under entity.customFields with their configured types already parsed (date, number, select values, …).

Real-time updates are available for high-signal events. They are push-based and use the same auth as queries.

type Subscription {
objectChanged(objectID: ID!): ObjectChangeEvent!
registrationCreated(courseID: ID!): CourseRegistration!
}

Subscriptions are optional — polling a list query at a sensible interval is often simpler for integrations.

Two layers of errors:

  1. Transport-level (HTTP 4xx/5xx, malformed GraphQL): returned in the top-level errors array.
  2. Domain-level (validation failures, ACL denials, conflicts): returned inside the mutation’s result wrapper via status, message, errorType.
{
"data": {
"createPerson": {
"status": "ERROR",
"message": "Die Personennummer \"1234\" ist bereits vergeben.",
"errorType": "PersonNumberDuplicateError",
"errorData": { "personNumber": "1234", "existingPersonID": "8f3a..." }
}
}
}
  • Every resolver is @Authorized by default — requests without a valid JWT return HTTP 401.
  • A small, curated set is annotated @Authorized(auth('Public')) and accepts anonymous calls: login, systemInfo, publicEnrollToCourse, a handful of OTP/password-reset endpoints.
  • ACL is enforced inside the repository layer, not just the resolver. status: "DENIED" is the standard response for insufficient rights.
ConceptPattern
Public ID typeUID scalar (GUID)
List query<entity>s(options: <Entity>ListOptions)
Single query<entity>(id: ID!)
Create mutationcreate<Entity>(<entity>: <Entity>CreateInput!, ...)
Update mutationupdate<Entity>(id: ID!, <entity>: <Entity>UpdateInput!)
Delete mutationdelete<Entity>(id: ID!)
Mutation result{ status, message, id, <entity>, errorType, errorData }
Auth default@Authorized() — JWT required
Public auth@Authorized(auth('Public'))

When in doubt, open Apollo Sandbox against your endpoint or browse the Schema Reference — both always reflect the current schema.