Skip to content

Handle and recover from validation errors

By the end of this guide your client will react correctly to every failure this API can produce, without parsing a body it was not expecting.

  • A working call from the Quickstart.
  • A read of Errors — this guide sequences the handling; that page is the catalogue.

This is the whole design. The API emits three different error bodies — Problem Details, the empty 401, and the operation-specific 409 on cost centres — so the body cannot be the thing you dispatch on.

switch (response.status) {
case 400: → the request is wrong. Do not retry it unchanged.
case 401: → re-authenticate, then retry once.
case 403: → a permissions matter. Stop; a human has to change something.
case 404: → not visible to you. Stop.
case 409: → a conflict with a soft-deleted record. Recoverable, see step 4.
default: → 5xx. Retry once with backoff, then surface it.
}

Only after you have branched should you attempt to read the body, and reading it must never be required to decide what to do.

Treat the body as diagnostic detail, not as control flow.

  • A 401 has no body at all. Do not attempt to parse it.
  • Most errors are Problem Details, where errors maps field names to messages on a validation failure and is absent otherwise. Check for the key rather than assuming it.
  • A 409 on cost centres is the exception: it carries an operation-specific body naming the soft-deleted record, not Problem Details.

A parser that throws when the shape is unfamiliar turns a handled 400 into an unhandled exception in your own code. Fall back to logging the raw body and the status.

3. Surface field errors without guessing at them

Section titled “3. Surface field errors without guessing at them”

When errors is present, each key is the name of a field in the request you sent and each value is an array of messages. Map them back onto your own form or payload by name.

Resist normalising these messages into your own error codes. They are human-readable text, not a stable machine contract, and the field names are the part you can rely on.

4. Recover from a conflict rather than renaming blindly

Section titled “4. Recover from a conflict rather than renaming blindly”

A 409 means the name you asked for is held by a soft-deleted record that still occupies the underlying unique constraint. The response tells you which record.

You have exactly two moves, and picking the right one is a business decision rather than a technical one:

  1. Restore the existing record, if it is the same thing coming back — its history and its links survive.
  2. Choose a different name, if it is genuinely a new record that happens to clash.

Creating a near-duplicate with a suffixed name to get past the error is the wrong move. It leaves two records competing for the same meaning.

Retrying a deterministic failure is the most common way an integration turns one problem into a rate problem.

  • Never retry a 400, 403, 404 or 409. The identical request produces the identical failure. Fix it, or stop and report it.
  • Retry a 401 once, after obtaining a fresh token — not with the token that just failed.
  • Retry a 500 once with backoff. If it persists it is a defect on the Recruso side.

Cap retries and give up loudly. An integration that silently retries forever looks healthy from the outside while doing nothing.

Exercise each branch deliberately rather than waiting to meet it in production:

  • Send a request with a required field omitted — a 400 with errors populated.
  • Send a request with no Authorization header — a 401 with an empty body.
  • Request a record id that does not belong to you — a 404, not a 403.

If your client handles those three without throwing, the remaining cases are variations on them.

What you see Why What to do
Your parser throws on an error response The body was not the shape you expected — most likely the empty 401, or the cost-centre 409 Branch on status before parsing; treat an unparseable body as loggable, not fatal
A 400 repeats forever It is being retried Validation failures are deterministic. Stop retrying and fix the payload
A 401 storm after a token expires Every queued request retries and each one re-authenticates Re-authenticate once, then release the queue
A 404 on an id you just received The id came from different credentials, or the record is soft-deleted Confirm the id came from a list call made with the same token

Errors — the full catalogue behind every row above.