Introduction
In the Cloud era APIs are an essential building block in connecting a company’s cloud-based and on-premise systems. SAP API Management as an important part of the SAP Integration Suite offers a solution to centrally manage, integrate, monitor and standardize APIs. API standardization covers many aspects like applying shared access through one API Gateway, a common behavior (authorization, quotas, etc.) and also an alignment on naming and structure.
In a recent project, we at p36 supported a client in the rollout of SAP API Management as the central API platform. One aspect was to also align the integrated APIs with the internal API Guideline, which uses the SAP Enterprise API Best Practice document as its foundation.
One problem we faced was the handling of errors (faults) in SAP API Management: While there is a pretty fine grained set of error codes when something goes wrong, the default response structure and http status codes sadly did not match the API Guideline.
The default error response structure of SAP API Management looks like this:
{
"fault": {
"faultstring":"Failed to resolve API Key variable request.header.apikey",
"detail": {
"errorcode":"steps.oauth.v2.FailedToResolveAPIKey"
}
}
}
The expected error response structure in alignment with the API Guideline should look like this:
{
"error": {
"code": "<some internal code or text like btp.apmgmt.auth.MissingApiKey>",
"message": "Missing APIKey header. Please provide an APIKey in the request header for authentication."
}
}
Sadly, there is no quick and easy way to modify the error response structure for all errors in SAP API Management. In the process of identifying the best solution, we looked at different approaches, but neither did we find a straight forward solution nor a best practice provided by SAP.
There are some great blog posts in the SAP community around the topic of error handling, but both describe a different solution:
- SAP API Management – Handling Faults using FaultRules and DefaultFaultRule by Santhosh Kumar Vellingiri
- SAP API Management – Creating your own status codes by Sven Huberti
The core of SAP API Management is based on Apigee, a former standalone API Management tool, which has been acquired by Google. In the community around Apigee, you can find various blog posts and best practices to handle errors:
- The official apigee documentation – Handling faults
- An error handling pattern for Apigee proxies from the apigee community
- A pattern for Apigee error handling
While all the Apigee best practices share a similar approach using FaultRules and the DefaultFaultRule, it seems that SAP’s vision to handle errors in SAP API Management is different, because FaultRules and the DefaultFaultRule are not easily maintainable in the API Portal and the best practice Policy Templates in the SAP API Business Hub are mainly using RaiseFault Policies.
In this blog post I am going to look into the various options of handling errors in SAP API Management and discuss pros & cons of the different approaches in more detail. In the upcoming second blog post, I will also give some insights into our approach to standardize the error handling.
So, if you are in the likely position to have the same requirements, here is your one-stop-shop documentation.
Background: Fault handling in SAP API Management
SAP API Management (Apigee) at its core distinguishes between different types of errors, that can be classified in two major groups:
- Automatic errors that are triggered by the platform
- Custom errors manually triggered in a flow, that can be used for your own purposes
Automatic errors
Automatic errors are thrown by an API Proxy in SAP API Management in situations, where technical problems occur on the platform, but mainly when a Policy throws an error as part of the Policies behavior.
An example regarding the VerifyAPIKey Policy:
If a Policy is throwing one of those errors, the message flow will immediately enter an error state and all subsequent steps in the flow will no more be executed.
By raising the error, a policy also sets some internal variables: The variable fault.name will be filled with the name of the Policy error (e.g. “FailedToResolveAPIKey”) and the variable [policy_namespace].[policy_name].failed will be set to true (e.g. oauthV2.VK-VerifyAPIKey.failed = true). Again, the name and values of those variables are documented for each Policy in the SAP API Management documentation (e.g. VerifyApiKey).
When entering the error state, the API Proxy checks for the presence of FaultRules, that have conditions to handle this kind of error.
The following example shows a more complex FaultRule, that is triggered, when the VerifyAPIKey Policy fails. The FaultRules` steps contain checks for the name of the fault and the flow is delegated to a defined Assign Message Policy.
<faultRule>
<name>APIKeyErrors</name>
<condition>(oauthV2.VK-VerifyAPIKey.failed = true)</condition>
<steps>
<step>
<policy_name>AM-FaultRuleNoAPIKey</policy_name>
<condition>(fault.name = "FailedToResolveAPIKey")</condition>
</step>
<step>
<policy_name>AM-FaultRuleInvalidAPIKey</policy_name>
<condition>(fault.name = "InvalidApiKey")</condition>
</step>
</steps>
</faultRule>
An example Assign Message Policy (AM-FaultRuleInvalidAPIKey) could look like this and return a specific error back to the client:
<AssignMessage async="false" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
<Set>
<Payload contentType="application/json" variablePrefix="@" variableSuffix="#">
{
"error": {
"code": "btp.apimgmt.auth.InvalidApiKey",
"message": "Invalid ApiKey specified. Please verify the correctness of the ApiKey in the request header."
}
}
</Payload>
<StatusCode>401</StatusCode>
<ReasonPhrase>Unauthorized</ReasonPhrase>
</Set>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
<AssignTo createNew="false" transport="http" type="request"/>
</AssignMessage>
If no FaultRule can be executed, then the DefaultFaultRule will be called. If the API Proxy contains neither FaultRules nor a DefaultFaultRule, the proxy sends the default error message in the default format back to the client.
Skipping Automatic Errors
It is possible to omit entering the error state in case of a Policy error by setting the continueOnError flag in the configuration of a Policy to true. In this case, the error information is still stored in the variables, but the flow continues as if nothing happed and the next step in the flow will be executed.
An example:
<!-- Skip the default error behavior -->
<VerifyAPIKey async='true' continueOnError='true' enabled='true'
xmlns='http://www.sap.com/apimgmt'>
<APIKey ref='request.header.APIKey'/>
</VerifyAPIKey>
In this case, no error will pop up and you either have to take care of raising an error by yourself or the error will stay unnoticed.
Custom Errors
In situations where no automatic errors are triggered by the system or errors may be skipped, it is possible to throw a custom error by using the RaiseFault Policy. RaiseFault Policies include a FaultResponse, which consists of HTTP headers, query parameters and a message payload, that can send an arbitrary error message back to the client. RaiseFault Policies can be used in the API flow like any other Policy and if the RaiseFault policy is triggered and executed, it will immediately throw an error and set the flow in an error state and not call any subsequent steps.
The following example shows a more complex oAuth2 authentication flow based on the JWT Token Verification Policy template by SAP, which first reads an oAuth token from Cloud Foundry and then uses this token to authenticate the request.
The RaiseFault Policy has a condition, that checks if the token response was valid and returned a correct status code. If not, the RaiseFault Policy will be triggered.
lookupcache.LC-ReadCachedToken.cachehit = false and sapapim.tokenresponse.status.code != 200
The RaiseFault Policy itself will setup the payload (in this case using earlier set variables) and return the error back to the client.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RaiseFault async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
<FaultResponse>
<Set>
<Payload contentType="application/json" variablePrefix="@" variableSuffix="#">sapapim.tokenresponse.content</Payload>
<StatusCode>{sapapim.tokenresponse.status.code}</StatusCode>
</Set>
</FaultResponse>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</RaiseFault>
FaultRules vs. the RaiseFault Policy
While by the above definition FaultRules and the RaiseFault policy have different purposes, it can be confusing, when to use which. Especially when you look at the Policy Templates offered by SAP and the not existing support for FaultRules in the API Portal.
Pros & Cons for using RaiseFault Policies
Since RaiseFault Policies can be used as part of the flow modeling, they are a convenient and easy way to send errors back to the client. It’s also possible to add them to Policy Templates and thus they can be easily adopted in the creation of an API Proxy. While the sharing via Template is primarily a good thing, it may also be dangerous:
The Policy Template Quota With 429 Status Code by SAP for example is providing three Policies in a pre flow: VerifyApiKey, Quota and RaiseFault.
The VerifyApiKey and Quota Policies are set to continueOnError=true, meaning, that even when a Policy error occurs, no error will be automatically returned. Instead in the last step, the RaiseFault Policy is triggered based on the condition, that the Quota Policy failed.
(ratelimit.setquota.failed = "true")
The RaiseFault Policy then sends an error message back to the client:
<RaiseFault async="true" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
<!-- Defines the response message returned to the requesting client-->
<FaultResponse>
<Set>
<!-- Sets or overwrites HTTP headers in the respone message -->
<Headers>
<Header name="Retry-After">{ratelimit.setquota.expiry.time}</Header>
</Headers>
<Payload contentType="text/plain">Your quota exceeded </Payload> <StatusCode>429</StatusCode>
<!-- sets the reason phrase of the response -->
<ReasonPhrase>Too many Requests</ReasonPhrase>
</Set>
</FaultResponse>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</RaiseFault>
With the Policy Template approach this is easily shareable and this approach kind of works for sending a customized error back to the client, but it does have some serious drawbacks:
- All possible ApiKey related errors are completely skipped and will not show up for the client.
- The RaiseFault response is very specific and telling the client, that too many requests have been sent. But since the Quota Policy may contain different kind of errors (it actually does!), this error may just be wrong.
Some of those problems can be fixed by either changing the condition (maybe check for the VerifyApiKey errors as well) or by adding multiple RaiseFault Policies in the flow. But this also does not really feel right, since you would end up with many different RaiseFault Policies and a complex flow. And since a RaiseFault Policy is very limited (it cannot just set variables for example), it is also not possible to build something clever (yes, that’s a hint for the second blog post).
To sum things up regarding RaiseFault Policies:
A special RaiseFault Policy: defaultRaiseFaultPolicy
Whenever you create an API Proxy in SAP API Management and hit “save”, you will automatically create an RaiseFault Policy in your proxy called defaultRaiseFaultPolicy, which is not part of any flow, but just does exists in your project. And there is no way to rename or remove it later on.
While this is not documented anywhere, it seems that SAP API Management is using this as a DefaultFaultRule replacement to handle unresolved errors. The default implementation just sets the status code to 405 and returns a Method not allowed error:
<RaiseFault async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
<FaultResponse>
<Set>
<StatusCode>405</StatusCode>
<ReasonPhrase>Method Not Allowed</ReasonPhrase>
</Set>
</FaultResponse>
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
</RaiseFault>
Pros & Cons for using FaultRules and the DefaultFaultRule
Okay then, why does SAP introduce such a defaultRaiseFaultPolicy when there is a DefaultFaultRule and why doesn’t the SAP Quota Policy Template contain a set of FaultRules to handle the VerifyApiKey and Quota errors? It’s pretty simple, because:
- FaultRules and the DefaultFaultRule are not shareable through Policy Templates
- and FaultRules and the DefaultFaultRule are not directly maintainable through the API Portal User Interface.
I would assume that the first issue is leading to the second one: Since SAP may want to provide best practices though Policy Templates, FaultRules and the DefaultFaultRule are not a first class citizen in SAP API Management. They cannot easily be shared and thus there is also lacking support to edit them in the API Portal UI.
You can find references in the official documentation (scroll down to FaultRule) of SAP API Management and FaultRules are also part of the OData API, but there is no real documentation on how to use them in your API Proxy.
While there is not supporting User Interface, both can be maintained as described in the blog post by Santhosh Kumar Vellingiri:
- Export the API Proxy.
- Extract the zip file
- Modify the APITargetEndPoint/default.xml or APIProxyEndPoint/default.xml
- Create a zip file again
- Import the zip into SAP API Management
While this process is a little cumbersome, it does work and even after making changes to the API Proxy in the API Portal, the inserted rules will not be overridden.
Sadly, there is no way to share a set of rules between multiple API Proxies. The current version of Apigee has support for reusable shared flows that add some kind of inheritance to API proxies, but this feature currently does not exist in SAP API Management and also does not seem to be part of the roadmap.
To sum things up for FaultRules and the DefaultFaultRule:
Summary
By closely looking at the best practices around Apigee, you can see that FaultRules and DefaultFaultRules are the preferred way to handle automatic errors and RaiseFault Policies should only be used in cases of a real custom error.
SAP seems to follow a different approach by including RaiseFault Policies in their best practices, by including a (sadly undocumented) defaultRaiseFaultPolicy in every API Proxy and by providing no User Interface to manage FaultRules and the DefaultFaultRule.
From my perspective, this leads to confusion and makes things more complex, then they should be, because you either have to work around the automatic error handling or things get more complicated, because you cannot maintain important stuff via UI.
In the upcoming blog post I am going to share an approach, how we tackled the requirement of having a standardized error response handling and dealing with the choices and restrictions in SAP API Management.