Assume I have two different error cases that logically have the same HTTP status code. What's the canonical way of providing data dependent on the error case in the response so that clients can respond differently depending on the error? Today I parse the error message--is there a better way?
TL;DR: It depends on the status code itself. Sometimes using headers is correct, sometimes you don't need to include anything and sometimes it's correct to use a payload.
This typically depends a lot on the status code you're using but most likely I guess you're thinking of the 4XX line of status codes for which you should read RFC 7231.
6.5. Client Error 4xx
The 4xx (Client Error) class of status code indicates that the client seems to have erred. Except when responding to a HEAD request, the server SHOULD send a representation containing an explanation of the error situation, and whether it is a temporary or permanent condition. These status codes are applicable to any request method. User agents SHOULD display any included representation to the user.
Then you can also drill down deeper depending on the code you're using obviously, but that's the gist of it. For an example, looking at the 409 status code the following is stated:
6.5.8. 409 Conflict
The 409 (Conflict) status code indicates that the request could not be completed due to a conflict with the current state of the target resource. This code is used in situations where the user might be able to resolve the conflict and resubmit the request. The server SHOULD generate a payload that includes enough information for a user to recognize the source of the conflict.
Here it is explicitly stated that you should include a payload (body) describing the issue. So for this case you would be working in accordance to the one canonical way. However, what they speak of as a representation can differ and it typically depends on the request being made. For a 403 Forbidden it's explicitly specified that you can include an error message but whether or not you wish to do so is up to you. Here is really comes down to what kind of information you're willing to give away.
For a 400 Bad Request it's not specified but I consider it a quite common practice to return a payload indicating any issues, here it all comes down to preference though.
The RFC will likely help you much more than any brief summary of mine.
It might also be good to read this answer to a similar question on how to provide error information when status codes aren't sufficient (it gets to relevant points a bit in).