---
title: Device polling and errors
description: Handle Device API polling states, single-use sessions, and the
  security boundaries around deviceCode and userCode.
editUrl: true
head: []
template: doc
sidebar:
  order: 2
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

Device authorization clients spend most of their time waiting. A good implementation treats `/device-token` as a state machine: pending means keep polling, `slow_down` means back off, terminal errors stop the flow, and success consumes the session.

## Polling loop

After `POST /device-authorize`, store `deviceCode`, show `userCode`, and start polling no faster than the returned `interval`.

```js
let intervalSeconds = authorize.interval;

while (true) {
  await sleep(intervalSeconds * 1000);

  const res = await fetch("https://device-api.sudomimus.com/device-token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ deviceCode: authorize.deviceCode }),
  });

  const body = await res.json();

  if (res.ok) {
    return body; // { accessToken, refreshToken, claims, ... }
  }

  if (body.error === "authorization_pending") continue;
  if (body.error === "slow_down") {
    intervalSeconds = body.interval ?? intervalSeconds + 5;
    continue;
  }

  throw new Error(body.error);
}
```

The server may return `slow_down` if the client polls too quickly. When that happens, use the returned `interval` for subsequent polls.

## Polling errors

`POST /device-token` uses the [RFC 8628 device-flow error vocabulary](https://www.rfc-editor.org/rfc/rfc8628#section-3.5) for polling states. Branch on `error`; do not expect Sudomimus `{ "reason": "..." }` wire reasons from this endpoint's polling outcomes.

| Error | Meaning | Client behavior |
|---|---|---|
| `authorization_pending` | The user has not approved or denied yet. | Keep polling after the current interval. |
| `slow_down` | The client is polling too quickly. | Increase the interval; use `interval` if present. |
| `access_denied` | The user denied the request, approval failed, or policy no longer allows issuance. | Stop polling and show a denied/failed state. |
| `expired_token` | The device authorization session expired. | Stop polling; start again with `/device-authorize`. |
| `invalid_request` | The `deviceCode` is malformed, unknown, or already consumed. | Stop polling; start a new flow if appropriate. |
| `server_error` | Token issuance failed after approval. | Stop polling and report a retryable service failure. |

Successful responses use Sudomimus's normal camelCase JSON shape. Polling failures intentionally use the device-flow `error` vocabulary so public clients can follow the familiar device-code state machine.

## Expiry and single use

Each device authorization session is short lived. The production default is currently `expiresIn: 600` seconds, but clients should use the value returned by `/device-authorize` instead of hard-coding a lifetime.

Once `/device-token` succeeds, the session is consumed. Repeating the same `/device-token` request with the same `deviceCode` returns `invalid_request` and cannot mint another token pair.

## Code handling

Treat the two codes differently:

| Value | Who sees it | Purpose |
|---|---|---|
| `deviceCode` | Only the initiating client | High-entropy bearer secret for `/device-token` |
| `userCode` | The user and browser approval page | Short code the user compares and confirms |

Do not display `deviceCode`, put it in logs, embed it in a browser URL, or send it to your application backend unless that backend is the component polling `/device-token`. Losing `deviceCode` before expiry lets whoever holds it poll and consume the approved session.

`userCode` is safe to display, but it is not a token and cannot mint tokens by itself. It exists so the browser user can confirm they are approving the same client session shown in the terminal, launcher, or device UI.

## No client secret

Device authorization is for public clients. `/device-authorize` does not require a client-auth JWT because the client could not protect the signing key. Instead, Sudomimus gates the flow on application configuration:

- The `applicationAnchor` must resolve to an enabled application.
- The application must have an enabled Layer 3 `DEVICE_CODE` ReturnRule.
- Browser approval still runs through the application's Layer 1 authentication rules.
- The realized account is still checked by Layer 2 before approval can complete.

If you do have a confidential backend that can protect a client-auth private key, [Connect](/en-us/connect/flow/) or [Connect browser polling](/en-us/native/overview/#browser-polling) may be a better fit.

## Claims and token operations

`/device-token` returns the same kind of application access and refresh tokens as other Sudomimus flows. Claim sharing follows the application's claim policy and the user's grant state, just like Connect or Native direct-issue.

Device API has no refresh endpoint. After the initial exchange, use Connect:

- `connect-api POST /refresh`
- `connect-api POST /logout`
- `connect-api POST /introspect`
- `connect-api POST /revoke-all`

See [Managing sessions](/en-us/guides/managing-sessions/) for token lifecycle calls and the [Device API reference](/en-us/api/device/) for exact request and response schemas.