> For the complete documentation index, see [llms.txt](https://docs.datasentinel.io/manual/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.datasentinel.io/manual/ai-connector/developers/refresh-user-token.md).

# Refresh the user token automatically

A standalone script that keeps a valid Datasentinel user token cached for scheduled and scripted runs.

A person using an assistant signs in once with OAuth and never handles a token. A script or a scheduled job has nobody to sign in, so it authenticates with the platform's [user token](https://app.gitbook.com/o/DaDQkGCM7r2TghFSBFLQ/s/yE16Xb3IemPxJWydtPOj/implementation/platform-usage/api-reference/access-token) instead, sent as an `Authorization: Bearer` header by the MCP client: the `--header` option of Claude Code, the `headers` entry of a Python agent, the equivalent field of any other client. See [How authentication works](/manual/ai-connector/server-setup/how-authentication-works.md#scripts-and-scheduled-jobs).

{% hint style="info" %}
The script on this page is an example of how to use Datasentinel AI Connector from your own code. Use it as a starting point. Review it and adapt it to your environment before you run it in production.
{% endhint %}

The platform issues these tokens with a **24-hour lifetime** and does not renew them. The script below keeps the cached token in `~/.datasentinel_token` permanently valid, so a job that reads the header value from that file never fails on an expired credential.

It reads the cached token, checks the expiry (`exp`) claim inside the JWT, and mints a fresh token from the platform whenever the cached one is missing, malformed, or about to expire. It then prints the valid token on stdout, so a caller can capture the output or read the cache file.

It is a standalone version of the token handling built into the agent from [Build a Python agent with deepagents](/manual/ai-connector/developers/build-a-python-agent-with-deepagents.md), and the companion to the scheduling guidance in [Run recurring analyses](/manual/ai-connector/analyze-and-tune-workloads/recurring-analyses.md).

Save this as `refresh_token.py`:

```python
#!/usr/bin/env python3
"""Print a valid Datasentinel user-token, minting a fresh one when the
cached token is missing or expired. Cache: ~/.datasentinel_token (0600)."""

import base64
import json
import os
import pathlib
import stat
import sys
import time

import httpx

TOKEN_PATH = pathlib.Path.home() / ".datasentinel_token"

# Refresh this many seconds before the real expiry so a token that is valid
# now but would lapse mid-run is treated as expired.
TOKEN_EXPIRY_MARGIN = 60


# The exp claim is a Unix timestamp; the platform issues tokens with a 24h
# life. A token that cannot be decoded or carries no exp is treated as
# expired so a fresh one is minted rather than sending a bad credential.
def token_expired(token: str) -> bool:
    try:
        payload = token.split(".")[1]
        # JWT uses base64url without padding; restore it before decoding.
        payload += "=" * (-len(payload) % 4)
        exp = json.loads(base64.urlsafe_b64decode(payload))["exp"]
    except (IndexError, ValueError, KeyError, TypeError):
        return True
    return time.time() >= exp - TOKEN_EXPIRY_MARGIN


def mint_token() -> str:
    host = os.environ.get("DATASENTINEL_HOST")
    user = os.environ.get("DATASENTINEL_USER")
    password = os.environ.get("DATASENTINEL_PASSWORD")
    if not (host and user and password):
        sys.exit(
            "Datasentinel user-token is missing or expired and cannot be "
            "renewed.\n"
            "Set DATASENTINEL_HOST, DATASENTINEL_USER and "
            "DATASENTINEL_PASSWORD so a new token can be minted."
        )
    # verify=False: the platform serves a self-signed certificate, so the
    # connection is encrypted but the server is not authenticated.
    resp = httpx.post(
        f"https://{host}/ds-api/user-token",
        auth=(user, password),
        verify=False,
        timeout=30.0,
    )
    resp.raise_for_status()
    token = resp.json()["user-token"]

    TOKEN_PATH.write_text(token)
    TOKEN_PATH.chmod(stat.S_IRUSR | stat.S_IWUSR)  # 0600, owner only
    print(f"Minted a new Datasentinel user-token in {TOKEN_PATH}", file=sys.stderr)
    return token


if __name__ == "__main__":
    token = TOKEN_PATH.read_text().strip() if TOKEN_PATH.exists() else ""
    if not token or token_expired(token):
        token = mint_token()
    print(token)
```

It needs Python 3.10 or later and the `httpx` package (`pip install httpx`), plus the three environment variables so it can mint:

```bash
export DATASENTINEL_HOST=<public-host>
export DATASENTINEL_USER=<platform-user>
export DATASENTINEL_PASSWORD=<platform-password>

python refresh_token.py
```

Run it at the start of each scheduled analysis, or on its own schedule shorter than the 24-hour lifetime (for example every 12 hours):

```bash
# crontab: refresh at 06:00 and 18:00, before any scheduled analysis fires
0 6,18 * * * /usr/bin/python3 /opt/datasentinel/refresh_token.py >/dev/null
```

{% hint style="info" %}
The scheduled job then reads the token from `~/.datasentinel_token` (or captures the script's stdout) and passes it to its MCP client as the `Authorization: Bearer` header, for example `--header "Authorization: Bearer $(python refresh_token.py)"` with Claude Code. Keep the minting credentials (`DATASENTINEL_USER` / `DATASENTINEL_PASSWORD`) in environment variables or your scheduler's secret store, never in the script itself.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.datasentinel.io/manual/ai-connector/developers/refresh-user-token.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
