Idiomatic way of parsing JSON variants with runtime guarantee

Say I’m calling an api and get back some JSON which can have different forms:

{"ok": true, "data": {"users": [...]}} // success case
{"ok": false, "error_code": "not_authorized", "message": "..."}

Or it has multiple success forms (like parsing an agents output):

{"type": "agent_start"}
{"type": "message_start", "message": {...}}
{"type": "message_end", "message": {...}}

What’s the idiomatic way of defining the return type for a call then? Do you use variants with @tag("type") as @as("agent_start")? Or is there another way?

And how can I check during runtime that the shape actually matches the expected variant? Do I just use sury and redefine the type as a schema or has ReScript built in mechanisms to do that for me?

I’d say Sury or any other encoding/decoding library is the idiomatic way in any programming language, not only ReScript. You don’t want to depend on an API response structure for your domain types anyway.