> For the complete documentation index, see [llms.txt](https://sdk.metamarket.tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sdk.metamarket.tech/evaluate-and-integrate/troubleshooting.md).

# Troubleshooting

The failures you are most likely to hit during integration, what causes each, and how to confirm the fix. Most come down to one of three things: the session, the action's configuration, or which lane an action was sent on.

## Sign-in and sessions

A call returns `401` after working earlier. The access token has expired (they last 15 minutes) and the refresh did not run or did not succeed. The SDK refreshes transparently; a raw HTTP client refreshes itself. Confirm the device still holds its key and that the refresh token has not passed its 30-day life. A known device that presents the wrong key, or no proof of possession, is rejected: re-check that the device's key pair is the one registered. The full flow is in [Authentication](/evaluate-and-integrate/authentication.md).

## A configuration says no

An action returns `400` with `code: business_logic` and a message like "Integrity Violation" or "Strict Mode". A declared action tried to write a stat or resource whose key is not declared with a scope, and the engine rejects an undeclared key rather than creating it. Confirm the resource key exists in `Mechanics.Resources` (or the stat's scope in `AllowedActions`), set up during onboarding; see [App Configuration](/evaluate-and-integrate/app-configuration.md). A separate case: an action *verb* that is not in `AllowedActions` at all is not a `400`; it is counted in the response's `skipped`, covered below.

A `use` on a pool item returns `400 business_logic` "Bundle exhausted". The quota is spent and has not regenerated yet. This is expected; the allowance refills on its cycle. A write that would drive a resource below its floor is rejected the same way.

A `scope: target` action returns `400 target_entity_required`. The action needs an `entityId` naming which entity it applies to, and none was sent.

## An action seemed to do nothing

**Symptom.** The call returned HTTP 200, but `skipped` is greater than zero. The action was counted in `skipped` rather than `processed`. Transport success is not business success, so treat this as a failure.

**Two common causes:**

* The `actionType` is not declared in your `allowedActions`.
* It was sent on the wrong lane, `/track` against `/execute`, for how it is configured.

Both are configuration or wiring mistakes, so resending the same request skips again. [Sending Actions](/evaluate-and-integrate/events-and-directives.md#treat-a-non-zero-skipped-as-a-failure) lists the rest, including a duplicate client key, which means the work already happened.

**To fix it:**

1. Check each `actionType` and its configured lane against your `allowedActions`. Do not resend the batch to find the offender: actions accepted the first time would run again, and one configured `idempotency: none` would double-apply.
2. Correct the configuration or the lane.
3. For any action that does not deduplicate, read the player's state to confirm the first attempt did not land.
4. Resend only the actions that failed.

If you cannot resolve it, stop and report it rather than treating the batch as done.

State did not change the way you expected. First read the action's own response: the `effects` and `mutations` channels already carry what changed, so a mismatch there is the real signal, not a stale state read. If you changed configuration and the old behaviour is still showing, allow for the refresh window before it takes effect. To see the current authoritative state directly, read `GET /api/player/state` ([Reading State](/evaluate-and-integrate/reading-state.md)).

## Retrying safely

On a network failure, how you recover depends on the action's configuration. For an action configured to deduplicate, resend with the same client key; the engine will not double-apply it within its window. For a `daily` action that window is the UTC day, so a resend after UTC midnight is a new window and can apply again: keep the retry within the same day, or read state first. For an action configured without dedup, resending could apply it twice, so instead read the player's state (see [Reading State](/evaluate-and-integrate/reading-state.md)) to check whether the first call landed before sending again.

## Information to include in a support request

When you reach the team, this is what lets them find the request quickly:

* Environment you are pointing at.
* App id.
* UTC timestamp of the call.
* Endpoint or SDK method used.
* The client key you sent with the action (your own correlation handle for the call).
* The error `code`, if the response carried one.
* The affected test player id.
* What you expected to happen, and what happened instead.

[Get in touch](https://metamarket.tech/#contact) with that, and [Production Operations](/architecture-and-trust/production-operations.md) covers how support and incidents are handled.
