Respect the Accept header from the client. If they need JSON, send JSON, otherwise don't.
Repeating an HTTP status code in the body is redundant and error prone. Never do it.
Error codes are great. Ensure to prefix yours and keep them unique.
Error messages can be helpful, but often lead developers to just display them in the frontend, breaking i18n. Some people supply error messages in multiple languages, depending on the Accept-Language header.