For validation errors, the adapter constructs an AuthnError with the code VALIDATION_ERROR, and then adds AuthnErrorDetail objects for each of the errors that occurred. The userMessage field of the AuthnErrorDetail object provides the user-facing text. Like states and actions, you can define errors up front using an AuthnErrorSpec or an AuthnErrorDetailSpec. Then an instance of the error is constructed from the specification on demand.

The following example shows how you can define the specification for an invalid OTP error.

public final static AuthnErrorDetailSpec INVALID_OTP = new AuthnErrorDetailSpec.Builder()
	.code("INVALID_OTP")
	.message("An invalid or expired OTP code was provided.")
	.userMessage("This code is invalid or has expired.")
	.parentCode(CommonErrorSpec.VALIDATION_ERROR.getCode())
	.build();

The following example shows how you can use that specification to send an error response to the API client.

AuthnErrorDetail errorDetail = ErrorDetailSpec.INVALID_OTP.makeInstanceBuilder().build();
AuthnError authnError = CommonErrorSpec.VALIDATION_ERROR.makeInstanceBuilder().detail(errorDetail).build();
apiSupport.writeErrorResponse(req, resp, authnError);

To localize the error message using a properties file for your adapter, you can use LocaleUtil and LanguagePackMessages from the standard PingFederate SDK.

LanguagePackMessages messages = new LanguagePackMessages("my-adapter-messages", LocaleUtil.getUserLocale(req));
String errorMessage = messages.getMessage("invalid.otp.key", new String[]{});
AuthnErrorDetail errorDetail = ErrorDetailSpec.INVALID_OTP.makeInstanceBuilder().userMessage(errorMessage).build();
AuthnError authnError = CommonErrorSpec.VALIDATION_ERROR.makeInstanceBuilder().detail(errorDetail).build();
apiSupport.writeErrorResponse(req, resp, authnError);

For more information about how PingFederate determines the user's locale at runtime, see Locale overrides by cookies.

Some errors reflect problems with API client programming rather than with end user input. If you think an error will not be shown to an end user, then you do not need to populate the userMessage field.