Skip to main content

Error Handling

This chapter describes how errors are returned in GraphQL responses and how to handle them.

Error response structure​

GraphQL errors are returned in the errors array of the response. Each error contains:

{
"errors": [
{
"message": "Entity not found",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": ["runtime", "industryEnergyEnergyMeter", "items"],
"extensions": {
"code": "ENTITY_NOT_FOUND",
"details": "No entity found with rtId: 65dc6d24cc529cdc46c84fcc"
}
}
],
"data": null
}

Error fields​

FieldDescription
messageHuman-readable error description
locationsPosition in the GraphQL query where the error occurred
pathPath to the field that caused the error
extensions.codeMachine-readable error code
extensions.detailsAdditional error details

Partial responses​

GraphQL can return partial data alongside errors. If some parts of a query succeed while others fail, you may receive both data and errors:

{
"errors": [
{
"message": "Access denied to field 'configuration'",
"path": ["runtime", "systemCommunicationAdapter", "items", 0, "configuration"]
}
],
"data": {
"runtime": {
"systemCommunicationAdapter": {
"items": [
{
"rtId": "65d5c447b420da3fb12381bc",
"name": "My Adapter",
"configuration": null
}
]
}
}
}
}

Common error codes​

Query errors​

CodeDescription
ENTITY_NOT_FOUNDThe requested entity does not exist
TYPE_NOT_FOUNDThe specified ckTypeId is not valid
INVALID_FILTERFilter parameters are invalid
INVALID_SORTSort parameters are invalid
INVALID_CURSORPagination cursor is invalid or expired

Mutation errors​

CodeDescription
VALIDATION_ERRORInput data failed validation
REQUIRED_FIELD_MISSINGA required field was not provided
INVALID_FIELD_VALUEField value does not match expected type or format
ENTITY_ALREADY_EXISTSEntity with given identifier already exists
REFERENCE_NOT_FOUNDReferenced entity (e.g., in association) not found
CONSTRAINT_VIOLATIONOperation violates a constraint (e.g., multiplicity)

Authorization errors​

CodeDescription
UNAUTHORIZEDAuthentication required
FORBIDDENInsufficient permissions for the operation
ACCESS_DENIEDAccess to specific resource denied
BLUEPRINT_LOCKEDThe entity is locked by a blueprint and cannot be changed or deleted by users, see Blueprint-locked entities

Blueprint-locked entities​

A blueprint can ship product-owned entities, for example the categorization rules an accounting blueprint relies on. For a CK type that is protected by a data policy with ProtectBlueprintLocked (see Read-only for users), a user can no longer change or delete such an entity. The write is refused with the stable error code BLUEPRINT_LOCKED and the engine message number 6384. The entity is unchanged; reads are never affected.

GraphQL​

The error is returned for create, update and delete mutations. The extensions carry everything a client needs to show "locked by the product" instead of a generic failure:

{
"errors": [
{
"message": "Access denied: entity 'Accounting-1.0.0/CategorizationRule-1@65dc6d24cc529cdc46c84fcc' is locked by blueprint and cannot be changed.",
"path": ["runtime", "runtimeEntities", "update"],
"extensions": {
"code": "BLUEPRINT_LOCKED",
"messageNumber": 6384,
"ckTypeId": "Accounting-1.0.0/CategorizationRule-1",
"rtId": "65dc6d24cc529cdc46c84fcc",
"reason": "EntityLocked",
"items": [
{
"ckTypeId": "Accounting-1.0.0/CategorizationRule-1",
"rtId": "65dc6d24cc529cdc46c84fcc",
"reason": "EntityLocked"
}
]
}
}
],
"data": null
}
ExtensionDescription
codeAlways BLUEPRINT_LOCKED. Match on this value, not on the message.
messageNumberThe engine message number, 6384.
ckTypeId, rtIdThe first refused entity. rtId is the id the engine assigned to a refused insert.
reasonEntityLocked: the stored entity has rtBlueprintLocked = true and cannot be updated, replaced or deleted. ProtectedAttributes: the change sets or changes rtBlueprintLocked, rtBlueprintSource or rtBlueprintAppliedAt, which only blueprints may manage on protected types.
itemsOne entry (ckTypeId, rtId, reason) for every refused entity of the request.

A mutation is applied as a whole: if one entity of a batch is locked, nothing of the batch is written, including the entities that are not locked. Remove the locked entities from the request and send it again.

REST​

A REST action that surfaces the refusal answers with HTTP status 403 Forbidden and application/problem+json (RFC 9457) that carries the same stable code:

{
"type": "https://schemas.meshmakers.cloud/errors/blueprint-locked",
"title": "Entity is locked by a blueprint",
"status": 403,
"detail": "Access denied: entity 'Accounting-1.0.0/CategorizationRule-1@65dc6d24cc529cdc46c84fcc' is locked by blueprint and cannot be changed.",
"code": "BLUEPRINT_LOCKED",
"messageNumber": 6384,
"ckTypeId": "Accounting-1.0.0/CategorizationRule-1",
"rtId": "65dc6d24cc529cdc46c84fcc",
"reason": "EntityLocked",
"items": [
{ "ckTypeId": "Accounting-1.0.0/CategorizationRule-1", "rtId": "65dc6d24cc529cdc46c84fcc", "reason": "EntityLocked" }
]
}

Runtime entities are written through GraphQL; the REST surface of the asset repository has no direct entity write. The import job (POST {tenantId}/v1/Models/ImportRt) applies the same rule with the same message number 6384 when it runs on behalf of the initiating user: a file that would overwrite a locked entity fails as a whole, the job result lists every offending ckTypeId@rtId, and nothing is written (AB#6392). The initiating caller travels with the import job, so no separate restriction of ImportRt to administrators is needed for this protection.

Who is affected​

CallerLocked entity of a protected type
Tenant user, including a user with a write grant for the typeRefused with BLUEPRINT_LOCKED
Pipeline node with Identity: ServiceAccountRefused: a configured service account is guarded like a user. A pipeline that must write product-owned entities has to use Identity: System
System context: blueprint install, update and forced re-apply, Identity: System pipelines, internal servicesAllowed
Unlocked entity (rtBlueprintLocked is false or absent), or a type that is not protectedAllowed, nothing changes

If the data policy runs in AuditOnly mode, the change succeeds and an audit event DataPermissions.BlueprintLockViolation is published instead of the error.

Handling errors in code​

TypeScript/JavaScript example​

const response = await client.query({ query: GET_ENERGY_METERS });

if (response.errors) {
for (const error of response.errors) {
console.error(`Error: ${error.message}`);

if (error.extensions?.code === 'ENTITY_NOT_FOUND') {
// Handle missing entity
} else if (error.extensions?.code === 'FORBIDDEN') {
// Handle permission error
} else if (error.extensions?.code === 'BLUEPRINT_LOCKED') {
// The entity is owned by the product: show it read-only
console.warn(`Locked by blueprint: ${error.extensions.rtId}`);
}
}
}

if (response.data) {
// Process successful data
}

Checking for partial success​

const response = await client.mutate({
mutation: CREATE_ENTITIES,
variables: { entities: [...] }
});

// Check if any entities were created despite errors
const createdEntities = response.data?.runtime?.myEntities?.create ?? [];
const failedCount = entities.length - createdEntities.length;

if (response.errors && failedCount > 0) {
console.warn(`${failedCount} entities failed to create`);
}

Validation errors​

Validation errors typically include details about which field failed and why:

{
"errors": [
{
"message": "Validation failed",
"extensions": {
"code": "VALIDATION_ERROR",
"validationErrors": [
{
"field": "voltage",
"message": "Value must be between 0 and 500"
},
{
"field": "name",
"message": "Name must not be empty"
}
]
}
}
]
}

Best practices​

  1. Always check for errors: Even successful-looking responses may contain partial errors
  2. Use error codes: Match on extensions.code rather than parsing error messages
  3. Handle partial data: Process any valid data returned alongside errors
  4. Log error details: Include path and extensions in logs for debugging
  5. User-friendly messages: Map error codes to localized user-facing messages
  6. Retry transient errors: Network or timeout errors may succeed on retry