GraphQL Channel
Query IdentityScribe via GraphQL with dynamic schema generation, cursor pagination, and an interactive GraphiQL IDE.
Overview
Section titled “Overview”The GraphQL channel provides:
- Dynamic schema generated from your entry type configuration
- Collection and lookup queries with filtering, sorting, and Relay-style pagination
- Change history API — global feed and per-entry history with temporal lookup
- GraphiQL IDE — interactive query builder with schema explorer
- Automatic Persisted Queries (APQ) — reduce bandwidth with query caching
Related:
- Configuration Reference — all settings, env vars, and defaults
- Channels Overview — general channel concepts
- REST Channel — alternative HTTP API with OpenAPI spec
- Failures — error handling
Quick start
Section titled “Quick start”Enable the GraphQL channel:
SCRIBE_GRAPHQL_ENABLED=trueAccess points:
| URL | Description |
|---|---|
/graphql | Query endpoint (POST) and GraphiQL UI (GET) |
/graphql.sdl | Download schema in SDL format (application/graphql) |
/graphql.json | Download schema as JSON introspection |
/graphql/schema.graphql | Alias for /graphql.sdl |
/graphql/schema.json | Alias for /graphql.json |
Schema exploration
Section titled “Schema exploration”Use GraphiQL at /graphql to explore the schema interactively. The UI provides:
- Schema browser with type definitions and field descriptions
- Query autocompletion and validation
- Query history and variables editor
- Documentation for all types, fields, and arguments
For programmatic access, download the schema via /graphql.sdl (or /graphql/schema.graphql) or /graphql.json. See API Discovery for programmatic channel and schema discovery.
Query patterns
Section titled “Query patterns”Collection queries
Section titled “Collection queries”Fetch entries with filtering, sorting, and pagination:
query { users(filter: "department=Engineering", first: 25, sort: "-modifyTimestamp") { nodes { entryDN cn mail } pageInfo { hasNextPage endCursor } }}Arguments: filter, sort, first/after, last/before, limit
Notes:
limitmirrors REST semantics and resolves tofirstorlastbased on cursor directionlimitis mutually exclusive withfirst/last;lastrequiresbefore
Single entry lookup
Section titled “Single entry lookup”Look up a single entry by ID, UUID, UOID, or DN:
query { user(id: "dXNlcjo5aXg") { entryDN cn mail memberOf }}Polymorphic lookup
Section titled “Polymorphic lookup”Look up any entry type via the node query:
query { node(id: "dXNlcjo5aXg") { ... on User { cn mail } ... on Group { cn member } }}ID formats
Section titled “ID formats”All lookup queries accept GlobalId, UUID, UOID, or DN. GlobalId is fastest since the type is encoded. See Entry identifiers for details.
Filtering
Section titled “Filtering”Filter syntax matches the REST API — FleX, JSON, SCIM, or LDAP:
# FleX syntaxusers(filter: "department=Engineering AND active=true")
# LDAP syntaxusers(filter: "(&(department=Engineering)(active=TRUE))")See Filters Reference for complete syntax.
Pagination
Section titled “Pagination”Relay-style cursor pagination with first/after (forward) or last/before (backward). limit is a REST-style alias that resolves based on cursor direction.
# First pagequery { users(first: 25) { nodes { cn } pageInfo { hasNextPage endCursor } }}
# Next pagequery { users(first: 25, after: "Y3Vyc29yOjI1") { nodes { cn } pageInfo { hasNextPage endCursor } }}Connection counts
Section titled “Connection counts”Connections expose nodes, edges, pageInfo, and count.
- Select
nodesoredges(not both) count(mode: ESTIMATED)is fast and approximate (default)count(mode: EXACT)is precise but slower- For count-only queries, request only
count
query { users(filter: "department=Engineering", first: 25) { count(mode: ESTIMATED) pageInfo { hasNextPage endCursor } nodes { cn } }}Change connections (changes) expose the same count field and modes.
Timestamp formatting
Section titled “Timestamp formatting”Operational timestamp fields accept a format argument:
- Fields:
createTimestamp,modifyTimestamp,verifiedTimestamp - Presets:
ISO(default),ISO_DATE,ISO_TIME,RFC_1123,UNIX,UNIX_MS,LDAP - Custom
DateTimeFormatterpatterns are also supported
query { user(id: "dXNlcjo5aXg") { modifyTimestamp(format: "UNIX_MS") }}History queries
Section titled “History queries”Query change history via the global changes feed or per-entry changes field.
Temporal lookup
Section titled “Temporal lookup”View an entry at a specific point in time using the at argument:
query { user(id: "dXNlcjo5aXg", at: "2024-01-15T10:00:00Z") { cn mail department }}The at argument accepts:
- Cursor: Event cursor from a previous change (e.g.,
AAFntW9XAAAAAAApQwAA) - ISO timestamp:
2024-01-15T10:00:00Z - Partial date:
2024,2024-01 - ISO duration:
P30D,PT1H - HOCON duration:
30d,2 weeks - Relative expressions:
now,now-1h
Future timestamps are clamped to now.
Global change feed
Section titled “Global change feed”Query changes across all entry types:
query { changes(range: "24h", type: [MODIFY], first: 100) { nodes { id type timestamp entryUOID entryType data { ... on ModifyData { diff { attribute operation value } } } } pageInfo { hasNextPage endCursor } }}Arguments:
| Argument | Description |
|---|---|
range | ISO interval or relative range (1h, now-1h/now, yesterday, this week, last 3 days) |
since / until | Cursor or timestamp bounds (ISO timestamp, partial date, duration, yesterday, this week, last 3 days). until defaults to now |
type | Filter by ADD, MODIFY, MOVE, DELETE |
entryType | Filter by entry type names |
affects | Filter MODIFY events by changed attributes |
sort | timestamp (oldest first) or -timestamp (newest first) |
Per-entry history
Section titled “Per-entry history”Each entry has a changes field for its history:
query { user(id: "dXNlcjo5aXg") { cn changes(first: 10, sort: "-timestamp") { nodes { type timestamp data { ... on ModifyData { diff { attribute operation value } } } } } }}Change data formats
Section titled “Change data formats”MODIFY events provide three equivalent representations (all delta semantics):
| Format | Use Case |
|---|---|
diff | Internal format with attribute-level operations (SET, ADD, REMOVE) |
patch | JSON Patch-inspired with /attribute/- for array additions |
merge | JSON Merge Patch-inspired with changed values only |
data { ... on ModifyData { diff { attribute operation value } patch { operation path value } merge { attribute values } }}Incremental sync pattern
Section titled “Incremental sync pattern”Use cursors for resumable synchronization:
# Initial syncquery { changes(first: 1000, sort: "timestamp") { nodes { id type entryUOID data { ... } } pageInfo { hasNextPage endCursor } }}
# Resume from cursorquery { changes(after: "AAFntW9XAAAAAAApQwAA", first: 1000, sort: "timestamp") { nodes { ... } pageInfo { ... } }}Automatic persisted queries
Section titled “Automatic persisted queries”APQ reduces request payload size by caching query strings server-side. After registering a query with its SHA256 hash, subsequent requests send only the hash.
- First request: Send query + hash → server caches and executes
- Subsequent requests: Send hash only → server retrieves from cache
- Cache miss: Server returns
PersistedQueryNotFound→ client retries with full query
Apollo Client and similar libraries handle this flow automatically.
See Configuration Reference for cache size and TTL settings.
Query security
Section titled “Query security”Three layers protect against denial-of-service:
| Layer | Protection |
|---|---|
| Parser limits | Reject oversized payloads before parsing |
| Depth limits | Reject deeply nested queries |
| Complexity limits | Reject queries selecting too many fields |
Defaults are suitable for most deployments. See Configuration Reference for tuning.
Error handling
Section titled “Error handling”GraphQL errors include rich metadata in extensions:
{ "errors": [{ "message": "Query exceeds maximum depth of 12", "extensions": { "id": "err-a1b2c3", "timestamp": "2024-01-15T10:00:00Z", "code": "GRAPHQL_QUERY_DEPTH_EXCEEDED", "graphqlCode": "BAD_USER_INPUT", "kind": "INVALID_ARGUMENT", "status": 400 } }]}| Field | Description |
|---|---|
code | IdentityScribe-specific error code for precise troubleshooting |
graphqlCode | Standard GraphQL error code for client compatibility |
kind | Error classification (e.g., NOT_FOUND, INVALID_ARGUMENT) |
status | HTTP status that would apply |
Standard GraphQL codes:
| graphqlCode | Used For |
|---|---|
BAD_USER_INPUT | Invalid arguments, out of range, failed preconditions |
FORBIDDEN | Permission denied |
UNAUTHENTICATED | Authentication required |
NOT_FOUND | Entry not found |
INTERNAL_SERVER_ERROR | Server errors, timeouts, unimplemented features |
Use graphqlCode for standard error handling, code for detailed troubleshooting and support.
Observability
Section titled “Observability”GraphQL operations emit OpenTelemetry traces:
| Span | Description |
|---|---|
GRAPHQL.Search | Collection queries |
GRAPHQL.Lookup | Single entry lookups |
GRAPHQL.Node | Polymorphic node queries |
GRAPHQL.History | Change history queries |
Spans include operation name, query complexity, and error status.
Related:
- Observability Guide — traces, metrics, and logs
- Monitoring Guide — dashboards and alerting