November 3, 2026
[Breaking + API] Every
VALIDATION_ERROR answers 400 with a list detailA VALIDATION_ERROR now always answers 400, never 422, and its detail is always a list of {type, loc, msg}. loc[0] names the request part: body, query, path, header or form (tool on MCP). A multipart or form field is now at form, not body, and an unknown query parameter is one item per name at ["query", <name>]. A semantic refusal keeps its own code at 422, such as GT_INVALID, DATASET_INVALID_REQUEST or EMPTY_EVAL.Action required: branch on error_code, not on 422, and read field errors from the detail list. Code that read a string or a {field: [msgs]} map from detail reads item["msg"] and item["loc"] instead.[Breaking + API] An oversized upload answers
413 REQUEST_TOO_LARGEA file over the plan’s size limit now answers 413 REQUEST_TOO_LARGE with params.max_bytes, the smallest limit among the refused files, and detail.files naming each refused file. It was 400 UNSUPPORTED_FILE_TYPE. A request body over the server limit answers 413 too, where it was 400. A refusal for the file type alone stays 400 UNSUPPORTED_FILE_TYPE.Action required: handle 413 (or error_code == "REQUEST_TOO_LARGE") and send a file smaller than params.max_bytes. In the SDKs it is a plain APIError, no longer a BadRequest.[Breaking + API] A backend status with no code of its own gets a precise codeA backend answer with no
error_code now resolves by its status: 408 is GATEWAY_TIMEOUT (retryable), 409 is CONFLICT, 410 is GONE, 413 is REQUEST_TOO_LARGE, and any other 4xx is REQUEST_REJECTED, all but 408 not retryable. Each was INTERNAL_ERROR, and all but 409 were retryable. The status is unchanged.Action required: stop retrying 409, 410, 413 and the other 4xx, and branch on the new codes where you matched INTERNAL_ERROR on a 4xx.
