> ## Documentation Index
> Fetch the complete documentation index at: https://docs.baselayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Handling Failures

> Know why a credential was refused and what to tell the agent.

> [!NOTE]
>
> The KYA SDK is coming soon. The packages aren't published yet, so the helpers named here won't run until they are.

When a check fails, `report.failure` holds one `VerificationFailure` code and `report.checks` shows every check that ran. The code is the same string in TypeScript and Python.

Only two failures are retryable. Both mean Baselayer couldn't be reached from your side, not that anything is wrong with the credential. Every other failure is final: the same presentation will fail again.

## Responding to the agent

Put the code in `kya_error` in the JSON body so the agent can tell what went wrong. The agent side of the SDK reads it from there. `toOAuthError(failure)` (`failure.to_oauth_error()` in Python) builds the whole answer for you.

| Kind | HTTP status | `error` |
| - | - | - |
| Retryable | `503` | none |
| `UNKNOWN_DISCLOSURE_SCOPE` | `400` | `invalid_scope` |
| Every other failure | `400` | `invalid_request` |

The agent side treats any `5xx` as worth retrying and any `4xx` as final. The `oauthError` helper in the [SDK quickstart](https://docs.baselayer.com/docs/kya-sdk-quickstart.md#the-two-helpers) follows this table.

## Your application creates the response

`verify` never returns these two. Your own route logic produces them before it calls `verify`.

| Code | Meaning | Action |
| - | - | - |
| `NO_CREDENTIAL_PRESENTED` | The request had no credential. | Respond `401` with your `KYA-Disclosure-Scope` header so the agent knows what to present. |
| `UNKNOWN_DISCLOSURE_SCOPE` | The scope the agent sent isn't in your published profile. | Check that your `kya-profile.json` is published and the agent used a scope from it. |

## Try again later

These are the only two `503` cases. Nothing is wrong with the credential. Your server couldn't fetch one of Baselayer's public files.

You choose whether to fail open or closed. If you fail closed, return `503` so the agent tries again later. You can pass a `Retry-After` time in seconds as the second argument to `toOAuthError` (`retry_after` in Python).

| Code | Meaning | Action |
| - | - | - |
| `ISSUER_RESOLUTION_FAILED` | Baselayer's signing keys couldn't be fetched. | Fail open, or return `503`. |
| `STATUS_LIST_RESOLUTION_FAILED` | Baselayer's revocation list couldn't be fetched. | Fail open, or return `503`. |

## Present a corrected or fresh credential

These are final for this presentation. The agent or its builder can often fix the input and present again.

| Code | Meaning | Action |
| - | - | - |
| `MALFORMED_PRESENTATION` | The credential couldn't be read. | Tell the agent the credential is malformed. |
| `UNSUPPORTED_CREDENTIAL_TYPE` | This isn't a KYA credential type the SDK accepts. | Tell the agent to present a KYA credential. |
| `SANDBOX_ISSUER` | A sandbox (test) credential, and sandbox credentials are off. | In testing, turn on `allowSandbox`. In production, refuse. |
| `CREDENTIAL_EXPIRED` | The credential has expired. | Tell the agent to present a fresh credential. |
| `CREDENTIAL_NOT_YET_VALID` | The credential's start time is in the future. | Tell the agent to present a valid credential. If this happens often, check your server clock. |
| `KEY_BINDING_MISSING` | The agent didn't sign the presentation. | Tell the agent to sign with its key and your nonce. |
| `KEY_BINDING_INVALID` | The agent's signature is wrong, or doesn't cover this presentation. On a business pair, the two credentials use different keys or nonces. | Tell the agent to fetch a new nonce and present again. |
| `NONCE_NOT_ISSUED` | You never issued this nonce, or it expired (10 minutes by default). | Tell the agent to fetch a new nonce. On many servers, check that they share one `NonceStore`. |
| `NONCE_REPLAYED` | This nonce was already used. | Tell the agent to fetch a new nonce. If you didn't expect a second use, this may be a replay. |
| `AUDIENCE_MISMATCH` | The credential was issued for another site, or signed for another audience. | Check that your verifier's `audience` is your domain exactly as credentials are minted for you. Otherwise refuse. |
| `ACTOR_MISMATCH` | The business and person credentials don't belong together, or a business L3 credential came without its person. | Tell the agent to present the business credential with the acting person's credential. |
| `SCOPE_VIOLATION` | The credential shares fields outside the scope you asked for. The whole credential is refused. | Tell the agent to get a credential minted for exactly that scope. |

## Refuse and investigate

These point to a credential that can't be trusted. Refuse, and log the failure with its checks.

| Code | Meaning | Action |
| - | - | - |
| `UNTRUSTED_ISSUER` | Baselayer didn't issue this credential. | Refuse. |
| `UNKNOWN_SIGNING_KEY` | The credential names a signing key Baselayer doesn't publish. | Refuse. |
| `ISSUER_SIGNATURE_INVALID` | The signature doesn't match. The credential was changed or forged. | Refuse. |
| `STATUS_LIST_INVALID` | The revocation list couldn't be trusted. | Refuse. |
| `CREDENTIAL_REVOKED` | The credential has been revoked. | Refuse. Treat it the same as an expired credential. |
| `UNSUPPORTED_DIGEST_ALGORITHM` | The credential uses a hashing method the SDK doesn't accept. | Refuse. |
| `DISCLOSURE_DIGEST_MISMATCH` | A shared field doesn't match what Baselayer signed. A value was changed. | Refuse. |
| `ACTOR_NOT_AUTHORIZED` | Baselayer hasn't verified that this person may act for the business. | Refuse. |
| `VERIFICATION_LEVEL_MISMATCH` | The credential's verification summary does not match the checks it lists. | Refuse. |

## Using the checks list

`report.checks` lists every check that ran with a name, whether it passed and a short detail. Log it with the failure. It shows exactly where a credential stopped, which helps when an agent builder asks why they were refused.

## Next steps

* [Start in watch-only mode](https://docs.baselayer.com/docs/kya-where-to-run-it.md#start-in-watch-only-mode): verify and log every credential before you change anyone's access.
* [KYA Sandbox and Testing](https://docs.baselayer.com/docs/kya-sandbox-and-testing.md): test the full flow with test data.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.