Your first approach would force the client to treat the same structure differently depending on whether it is an error or a normal result, while the second approach does not mix them up.
For examples of how the second approach is being used in API design, have a look at the OpenAPI spec regarding API responses (see https://swagger.io/docs/specification/describing-responses/ under "Default Responses", the section focuses on another aspect but shows the syntax nicely). The format allows you to specify different response content schemas for different HTTP codes. So the schema for the 200
code would be your normal response (which could be either {"products":[...]}
or simply [...]
) and the schema for an error response could be {"error_code":12345, "error_message":"Wrong phase of the moon"}
(actual schema definitions look different in OpenAPI, but that's beyond the scope of this answer.)
If you take that as a general direction for API design, your second approach would be better.