Error handling

GraphQL API error handling - status codes, error messages, syntax errors, multiple request outcomes, execution tracing for troubleshooting, and detailed error property explanations.

A GraphQL query is an HTTP POST request with the content type application/json and the body having the form:

{
  "query" : "...",
  "variables" : "..."
}

If the authorization for a request fails (no authorization header, expired token, etc.) the return status code is 401 (Unauthorized) and the response body is the following:

<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx</center>
</body>
</html>

However, for most requests, whether they are successful or they failed, the return status code is 200. When a successful query is executed, the JSON object that is returned contains a property called data whose value is an object representing the returned data. Here is an example:

{
  "data": {
    "useCompany": {
      "generalLedgerAccount": {
        "items": [
          {
            "accountNo": 9001,
            "name": "Special account",
          }
        ]
      }
    }
  }
}

When an error occurs during the execution of the request, the return JSON object contains both the data property, as well as a property called errors, which is an array of objects containing information about the error(s) that occurred. The following is an example:

{
  "errors": [
    {
      "message": "Error: Could not get authorize user using VismaNetCompanyId: 1234567. Description: External integration error. Status: 18."
    }
  ],
  "data": {
    "useCompany": {
      "generalLedgerAccount": null
    }
  }
}

If the GraphQL query has a syntax error, the returned information contains not just a message but also detains about the location of the error within the query. This is exemplified below:

Query

query read($cid : Int!)
{
  useCompany(no : $cid) {
    generalLedgerAccount {
      totalRows
    }
  }
}
Result

{
  "errors": [
    {
      "message": "GraphQL.Validation.Errors.FieldsOnCorrectTypeError: Cannot query field 'totalRows' on type 'Query_UseCompany_GeneralLedgerAccount_Connection'. Did you mean 'totalCount'?",
      "locations": [
        {
          "line": 4,
          "column": 7
        }
      ],
      "extensions": {
        "code": "FIELDS_ON_CORRECT_TYPE",
        "codes": [
          "FIELDS_ON_CORRECT_TYPE"
        ],
        "number": "5.3.1"
      }
    }
  ]
}

A GraphQL query may contain multiple requests (such as reading from different tables). It is possible that some may be successful, while other will fail. The result will contain data that was fetched successfully but also information about the errors that occurred for the other requests. Here is an example:

Query

query read($cid : Int!)
{
  useCompany(no : $cid) {
    generalLedgerAccount(first: 2) {
      totalCount
      items {
        accountNo
        name
      }
    }

    generalLedgerBalance(last: 10) {
      totalCount
      items {
        accountNo
        sumDebitObDomestic
        sumCreditObDomestic
      }      
    }    
  }
}
Result

{
  "errors": [
    {
      "message": "The cursor 'before' must have a value.",
      "path": [
        "useCompany",
        "generalLedgerBalance"
      ]
    }
  ],
  "data": {
    "useCompany": {
      "generalLedgerAccount": {
        "totalCount": 340,
        "items": [
          {
            "accountNo": 1000,
            "name": "Forskning og utvikling"
          },
          {
            "accountNo": 1020,
            "name": "Konsesjoner"
          }
        ]
      },
      "generalLedgerBalance": null
    }
  }
}

On the other hand, an operation may be successful (such as inserting a new row) although sub-operations (such as assigning a value to a column) may fail. GraphQL returns the result as well as all the errors messages from executing the request.

Query

mutation create_batch($cid : Int!)
{
  useCompany(no: $cid)
  {
    batch_create(values:
      [
        {
          valueDate: 20211122
          voucherSeriesNo: 3
          orgUnit1 : 0
          orgUnit2 : 0
          orgUnit3 : 0
          orgUnit4 : 0
          orgUnit5 : 0
          period :11
          year :2021
        }
      ])
    {
      affectedRows
      items
      {
        batchNo
        valueDate
        voucherSeriesNo
      }
    }
  }
}
Result

{
  "errors": [
    {
      "message": "Error: Illegal value date 11/22/2021. Check suspension date and the accounting periods and VAT periods tables..",
      "path": [
        "useCompany",
        "batch_create",
        "values/0"
      ],
      "extensions": {
        "data": {
          "status": 0
        }
      }
    },
    {
      "message": "Error: Org unit class not named. Description: Not read access to destination column. Column: OrgUnit3.",
      "path": [
        "useCompany",
        "batch_create",
        "values/0"
      ],
      "extensions": {
        "data": {
          "status": 3,
          "status_name" : "NotReadAccessToDestinationColumn"
        }
      }
    },
    {
      "message": "Error: Org unit class not named. Description: Not read access to destination column. Column: OrgUnit4.",
      "path": [
        "useCompany",
        "batch_create",
        "values/0"
      ],
      "extensions": {
        "data": {
          "status": 3,
          "status_name" : "NotReadAccessToDestinationColumn"
        }
      }
    },
    {
      "message": "Error: Org unit class not named. Description: Not read access to destination column. Column: OrgUnit5.",
      "path": [
        "useCompany",
        "batch_create",
        "values/0"
      ],
      "extensions": {
        "data": {
          "status": 3,
          "status_name" : "NotReadAccessToDestinationColumn"
        }
      }
    }
  ],
  "data": {
    "useCompany": {
      "batch_create": {
        "affectedRows": 1,
        "items": [
          {
            "batchNo": 26,
            "valueDate": 20211122,
            "voucherSeriesNo": 3
          }
        ]
      }
    }
  }
}

Understanding error information

When an error occurs during the execution of the query, you may see the following information for each returned error:

  • message: always present, contains a description of the error
  • path: contains the path in the query (schema) of the field that produced the error
  • extensions: additional information about the error. An example is data:status that contains an internal error code that could be useful for troubleshouting a failed execution. In addition, data:status_name provides a symbolic name of the status code, such as NotReadAccessToDestinationColumn.

Another example of an error message from attempting to create an order with a duplicate ID is shown below. You can see that status is 3 but status_name is set to PrimaryKeyAssignmentFailed to give you a better understanding of what the status code 3 means in this context.

{
  "errors": [
    {
      "message": "A record with the same primary key already exists.",
      "path": [
        "useCompany",
        "order_create",
        "values/0"
      ],
      "extensions": {
        "data": {
          "status": 3,
          "status_name": "PrimaryKeyAssignmentFailed"
        }
      }
    }
  ],
  "data": {
    "useCompany": {
      "order_create": {
        "items": null
      }
    }
  }
}

However, it is important to note that these status codes (and their names) are not unique. A request is composed of multiple operations, such as selecting a table, assigning a value to a field, etc. There are various status codes for each such contextual operation. Therefore, an operation may return status code 3 that means NotReadAccessToDestinationColumn, or status code 3 that means NotInsertAccessToTable. That is why the status_name field is useful to help you better understand the problem.

Tip

For more information about the GraphQL engine errors (such as schema errors, input errors and processing errors) see GraphQL.NET Error Handling.

Tracing query execution

Each GraphQL query that executes is assigned a unique identifier. This is used to trace the execution of the query and can be used for identifying problems with the execution. If you need to contact the Business NXT support for help to investigate a problem, you need to provide this unique identifier.

You can find this unique ID in the response of a GraphQL request. Although this was skipped in the examples shown in this tutorial (for simplicity), each response has a field called extensions that contains an object named vbnxt-trace-id.

Query

query read($cid : Int, $pagesize : Int) {
  useCompany(no: $cid) {
    generalLedgerAccount(first: $pagesize) {
      totalCount
      items {
        accountNo
        name
      }
    }
  }
}
Result

{
  "data": {
    "useCompany": {
      "generalLedgerAccount": {
        "totalCount": 340,
        "items": [
          {
            "accountNo": 1000,
            "name": "Forskning og utvikling"
          },
          {
            "accountNo": 1020,
            "name": "Konsesjoner"
          },
          ...
        ]
      }
    }
  },
  "extensions": {
    "vbnxt-trace-id": "00000000000000000196b4ea383242fa"
  }
}

Use the value of the vbnxt-trace-id when contacting the Business NXT support.

Tip

The same trace identifier can be retrieved from the response headers. GraphQL responses have a custom x-vbc-traceid header containing the value of the trace identifier.

Last modified September 24, 2024