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

# Errors

> How the SDK API reports errors, every code you can meet, and the codes in your app's result.

The SDK API answers every error with a problem details object ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)), with the content type `application/problem+json`. The Plugchoice API (`/v3`) is different: its errors carry a `message`.

```json theme={null}
{
  "type": "https://developer.plugchoice.com/sdk/errors/scope-required",
  "title": "A scope is required",
  "status": 422,
  "code": "scope-required",
  "detail": "Name the chargers, the sites, the address or a new site (`scope`) this client session may reach."
}
```

<ResponseField name="type" type="string" required>
  The code as a URL: its page in these docs, `https://developer.plugchoice.com/sdk/errors/{code}`.
</ResponseField>

<ResponseField name="title" type="string" required>
  A short summary, for people.
</ResponseField>

<ResponseField name="status" type="integer" required>
  The HTTP status code of the response.
</ResponseField>

<ResponseField name="code" type="string" required>
  The machine-readable code: the last part of `type`.
</ResponseField>

<ResponseField name="detail" type="string">
  What went wrong in this case, for people. Not always present.
</ResponseField>

Some codes add a member of their own:

| Member | With | What it holds |
| - | - | - |
| `errors` | [`validation-failed`](/sdk/errors/validation-failed) | Every field that failed, by path, with its messages |
| `site` | [`charger-at-other-site`](/sdk/errors/charger-at-other-site) | The site the charger is at now: `id` and `name` |
| `claimable` | [`identity-taken`](/sdk/errors/identity-taken) | Whether a claim can be started |
| `vendor_charger` | Problems connecting a Zaptec installation | Which charger of the installation the problem is about: `id` and `serial` |

<Note>
  Switch on `code`. Titles and details are written for people and may change. New codes may be added, so handle a code you don't know by its HTTP status.
</Note>

## Your server

The codes the SDK API answers your server with, on the endpoints in the [API reference](/sdk/api).

| Code | Status | When |
| - | - | - |
| [`unauthenticated`](/sdk/errors/unauthenticated) | 401 | The token is missing, invalid or expired |
| [`forbidden`](/sdk/errors/forbidden) | 403 | The token's client has no active SDK integration |
| [`not-found`](/sdk/errors/not-found) | 404 | A link session, charger or site that doesn't exist or isn't yours, also in a client session's `scope` |
| [`validation-failed`](/sdk/errors/validation-failed) | 422 | The body or query doesn't pass validation |
| [`scope-required`](/sdk/errors/scope-required) | 422 | A client session without a scope |
| [`unsupported-device-type`](/sdk/errors/unsupported-device-type) | 422 | A `device_type` other than `charger` |
| [`unknown-version`](/sdk/errors/unknown-version) | 400 | A `Plugchoice-Version` the API doesn't know |
| [`rate-limited`](/sdk/errors/rate-limited) | 429 | More than 120 requests a minute |
| [`bad-request`](/sdk/errors/bad-request) | 4xx | Another client error, such as a wrong method |
| [`server-error`](/sdk/errors/server-error) | 5xx | Something went wrong on Plugchoice's side |

## Your app's result

When the hosted flow can't start, it shows an error screen. When your user closes it, Link closes with the status `error`, and `error.code` is the problem's code:

| Code | When |
| - | - |
| [`not-found`](/sdk/errors/not-found) | The client secret is unknown; the action's charger or site is outside the client session's scope; or `add` was opened with a scope of chargers only |
| [`site-required`](/sdk/errors/site-required) | `add` without a site, while the scope has several sites |
| [`client-session-expired`](/sdk/errors/client-session-expired) | Every secret your app gave was expired, after the hosted flow asked three times |
| [`validation-failed`](/sdk/errors/validation-failed) | The action's ids are wrong: not a UUID, a missing charger, or a site with an action other than `add` |
| [`rate-limited`](/sdk/errors/rate-limited) | More than 30 link sessions started in a minute from one IP address |
| [`session-ended`](/sdk/errors/session-ended), [`unauthenticated`](/sdk/errors/unauthenticated) | The hosted flow reloaded after its link session ended |
| [`server-error`](/sdk/errors/server-error) | Something went wrong on Plugchoice's side |

## Only inside the hosted flow

These codes are answered to the hosted flow while your user goes through it. It handles them, mostly by telling your user what to do, so you don't need to handle them. They're here because every problem's `type` links to its page.

| Code | Status | When |
| - | - | - |
| [`consent-required`](/sdk/errors/consent-required) | 409 | Adding a charger before your user agreed to the consent screen |
| [`consent-not-needed`](/sdk/errors/consent-not-needed) | 409 | Recording consent in a link session that has no consent screen |
| [`location-required`](/sdk/errors/location-required) | 409 | Adding a charger before the new site's address is set |
| [`location-already-set`](/sdk/errors/location-already-set) | 409 | Setting an address when the link session already has a site |
| [`method-not-allowed`](/sdk/errors/method-not-allowed) | 422 | A brand, model or method this link session doesn't offer |
| [`identity-taken`](/sdk/errors/identity-taken) | 409 | The charger belongs to another account |
| [`identity-busy`](/sdk/errors/identity-busy) | 409 | Someone registered the charger moments ago |
| [`identity-not-taken`](/sdk/errors/identity-not-taken) | 409 | A claim on a charger nobody else uses |
| [`charger-at-other-site`](/sdk/errors/charger-at-other-site) | 409 | The charger is at another site of the same account |
| [`pincode-invalid`](/sdk/errors/pincode-invalid) | 422 | The identity and pincode don't match |
| [`claim-exists`](/sdk/errors/claim-exists) | 409 | There is already an open claim on the charger |
| [`claim-closed`](/sdk/errors/claim-closed) | 409 | Plugging in for a claim that is no longer open |
| [`charger-offline`](/sdk/errors/charger-offline) | 409 | Plugging in for a claim while the charger is offline |
| [`plug-in-not-available`](/sdk/errors/plug-in-not-available) | 409 | Plugging in can't prove this claim |
| [`nothing-to-complete`](/sdk/errors/nothing-to-complete) | 409 | Completing a link session that finished no charger |
| [`vendor-sign-in-failed`](/sdk/errors/vendor-sign-in-failed) | 422 | Zaptec didn't accept the email address and password |
| [`vendor-sign-in-required`](/sdk/errors/vendor-sign-in-required) | 409 | The Zaptec sign-in expired |
| [`vendor-not-permitted`](/sdk/errors/vendor-not-permitted) | 403 | The Zaptec account may not connect the installation |
| [`vendor-charger-offline`](/sdk/errors/vendor-charger-offline) | 409 | Zaptec reports the charger as offline |
| [`vendor-rate-limited`](/sdk/errors/vendor-rate-limited) | 429 | Zaptec's cloud is busy |
| [`vendor-unavailable`](/sdk/errors/vendor-unavailable) | 503 | Zaptec's cloud couldn't be reached |
| [`vendor-error`](/sdk/errors/vendor-error) | 502 | Zaptec's cloud refused the request |

## The SDK's own result codes

When Link closes with the status `error`, `LinkResult.error.code` says why. Besides the problem codes above, you can rely on these:

| Code | When |
| - | - |
| `clientSecretUnavailable` | Your `fetchClientSecret` callback threw, returned an empty string or took longer than 30 seconds, and your user left the error screen instead of trying again |
| `pageLoadFailed` | The hosted flow didn't load (no internet, or it couldn't be reached), and your user left the native "try again" screen. There is no `sessionId`. |
| `internal` | The SDK couldn't run the hosted flow. On Android: there is no usable WebView, or its renderer stopped. There is no `sessionId`. |

Any other code comes from the hosted flow, for example when setting up a charger failed. Those codes and `error.message` are for your logs; don't branch on them. Show your own message, and read the outcome on your server with `GET /link-sessions/{link_session_id}`: see [Link sessions](/sdk/concepts/link-sessions).

In React Native, `openLink` also rejects when Link can't be shown at all, for example when a Link screen is already open: see [React Native](/sdk/react-native).


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