## TL;DR
DocuSign JWT access tokens expire after one hour, so any polling loop that runs longer dies mid-run with AUTHORIZATION_INVALID_TOKEN. The fix is not a longer token - it is a refresh-on-401 wrapper: catch the 401, generate a fresh JWT assertion, fetch a new access token, and resume polling from the last saved cursor. Persist the cursor so a resume never reprocesses or skips envelopes. Better yet, replace long polling with a Connect webhook and keep polling only as the backstop.

## The query
```text
DocuSign 'AUTHORIZATION_INVALID_TOKEN' after the access token expired mid-polling
```

## Use this when
- A long-running polling loop starts returning AUTHORIZATION_INVALID_TOKEN after about an hour
- The first requests succeed and only later ones fail with 401
- You poll envelope status on a schedule and the job dies overnight

## Not for
- 401s on the very first request - that is a bad key, bad assertion, or missing consent, not expiry
- USER_AUTHENTICATION_FAILED, which is a credentials problem rather than a token-lifetime problem
- Rate-limit (429) responses during polling, which need backoff rather than a new token

## Steps
### 1. Confirm the failure is expiry, not credentials
Decode the JWT (or check your token-issue timestamp): if the token is older than an hour when the 401s start, it is expiry. If the first request with a fresh token also 401s, stop and fix the integration key, consent grant, or assertion instead.
```python
import time, jwt
claims = jwt.decode(access_token, options={"verify_signature": False})
print("expires_at:", claims["exp"], "now:", int(time.time()))
```
Expected output: the exp timestamp is in the past exactly when the failures began.

### 2. Wrap the API client with refresh-on-401
Centralize token use in one client method: on a 401, regenerate the JWT assertion, call the token endpoint, store the new token, and retry the original request once. Never scatter token logic across call sites - one wrapper means one place to fix.
```python
def api_get(path, retries=1):
    r = requests.get(path, headers=auth_headers())
    if r.status_code == 401 and retries:
        refresh_access_token()  # new JWT assertion -> token endpoint
        return api_get(path, retries=0)
    return r
```
Expected output: polling continues past the one-hour mark with a single transparent token swap.

### 3. Persist the polling cursor before each page
Save the last processed envelopeId and timestamp to durable storage (database row, not process memory) before fetching the next page. A refresh that restarts the loop from scratch will reprocess envelopes; a saved cursor resumes exactly where it stopped.
Expected output: after a restart, the poller logs 'resuming from [CURSOR]' and processes only new envelopes.

### 4. Consider replacing polling with Connect
If the loop exists to learn about envelope events, a Connect webhook delivers them without any polling or token-lifetime pressure. Keep a short polling sweep as a reconciliation backstop (hourly, with refresh-on-401) rather than the primary mechanism.
Expected output: event latency drops from poll-interval to near real time, and the 401 class of failure disappears.

## Tool compatibility
DocuSign eSignature REST API v2.1 with JWT grant authentication. The one-hour access-token lifetime is the same on demo and production. Any HTTP client works; the pattern is client-side.

## Variant phrasings
- DocuSign AUTHORIZATION_INVALID_TOKEN access token expired polling
- DocuSign JWT token expires after one hour polling loop 401
- DocuSign polling returns 401 after an hour how to refresh token
- AUTHORIZATION_INVALID_TOKEN DocuSign long running job

## Why it happens
JWT access tokens are short-lived by design: a leaked token is only useful for an hour. DocuSign does not offer a refresh-token flow for the JWT grant - the intended pattern is to mint a fresh assertion whenever you need a token. Polling loops that treat the first token as permanent fight this design; the refresh-on-401 wrapper works with it instead.

## Edge cases / pitfalls
- Clock skew between your server and DocuSign can make a token look expired early - sync with NTP.
- Regenerating the assertion on every 401 without a retry cap can loop forever if the real problem is revoked consent; cap retries at one and alert.
- Multiple poller instances sharing one token can stampede the token endpoint on simultaneous 401s - serialize refreshes with a lock.
- The private key used to sign the assertion must stay readable only by the service account - a refresh wrapper is no excuse to loosen key permissions.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_pHm1f4mtqCDBGzG2JlrV2w
