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

# invalid_token

> HTTP 401 — the access token is missing, malformed, expired, or revoked.

```http theme={null}
HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
WWW-Authenticate: Bearer error="invalid_token"
X-Request-Id: req_01HV9XK...

{
  "type": "https://docs.digitalseaservice.com/errors/invalid_token",
  "title": "Invalid or expired token",
  "status": 401,
  "detail": "Access token is expired or has been revoked.",
  "instance": "req_01HV9XK..."
}
```

## What it means

We couldn't authenticate the request. Either there's no `Authorization`
header, the bearer token we saw doesn't match anything we issued, it's
expired, or it's been revoked.

## Common causes & fixes

| Cause | Fix |
| - | - |
| Missing `Authorization` header | Add `Authorization: Bearer <access_token>`. |
| Header format wrong | Must be exactly `Bearer <token>`. Not `bearer`, no extra whitespace, no quoting. |
| Access token expired (1-hour TTL) | Refresh via `grant_type=refresh_token`. See [Authentication / refreshing](/authentication#refreshing-tokens). |
| Refresh token used after rotation | The previous refresh token is revoked the moment you refresh. Always store the new one immediately. |
| User disconnected | The user clicked Disconnect on their DSS dashboard. Reconsent flow is needed — see `user.consent.revoked`. |
| Partner suspended | Your client\_id has been suspended by DSS support. Contact us. |

## Distinguishing "expired" vs "revoked"

We don't expose this in the `detail` for security reasons (you shouldn't be
able to probe which case you're in to enumerate users). Treat them the same:

1. Try a refresh. If it succeeds, you had a stale access token — store the
   new pair and retry the original request.
2. If the refresh itself returns `invalid_grant`, the user has either
   disconnected or your refresh token has expired (90-day TTL). In either
   case, you need to send the user through the authorize flow again.

```python theme={null}
def request_with_auto_refresh(url, access_token, refresh_token):
    r = httpx.get(url, headers={"Authorization": f"Bearer {access_token}"})
    if r.status_code != 401:
        return r, access_token, refresh_token

    try:
        new_tokens = refresh(refresh_token)  # POST /oauth/token
    except httpx.HTTPStatusError as exc:
        if exc.response.status_code == 400:
            raise NeedsReconsent() from exc
        raise

    r = httpx.get(url, headers={"Authorization": f"Bearer {new_tokens['access_token']}"})
    return r, new_tokens["access_token"], new_tokens["refresh_token"]
```

## Related

* [Authentication / refreshing](/authentication#refreshing-tokens)
* [`user.consent.revoked` event](/webhooks/events#userconsentrevoked)
* [Errors overview](/errors)


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