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

# Authentication

> Sign in with device authorization to get an access token, send it as a bearer token, and refresh it.

Most API requests need an access token for your Itoflow account, sent in the `Authorization` header:

```http theme={null}
Authorization: Bearer ACCESS_TOKEN
```

Replace `ACCESS_TOKEN` with your access token.

Some routes need no token, such as `GET /auth/client-config`, `GET /pricing/catalog`, `GET /fx-rates`, `GET /strategies/public` and the public share routes. The [API coverage index](https://docs.itoflow.ai/api-reference/coverage.md) shows the authentication for every endpoint.

> [!TIP]
>
> **Most people don't need a token.** If you use Claude, Codex or another AI assistant, [connect Itoflow MCP](https://docs.itoflow.ai/mcp/install.md) and sign in once in your browser. Your client handles authentication for you.

## Get an access token

Itoflow uses the OAuth 2.0 device authorization flow. Your program asks for a short code, you approve that code in your browser, and the program receives an access token and a refresh token.

```mermaid theme={null}
sequenceDiagram
  participant C as Your program
  participant I as Itoflow sign-in
  participant U as You in a browser
  participant A as Itoflow API
  C->>A: GET /auth/client-config
  A-->>C: Client ID and sign-in endpoints
  C->>I: Request a device code
  I-->>C: Code and approval URL
  U->>I: Sign in and approve the code
  C->>I: Poll for the token
  I-->>C: Access token and refresh token
  C->>A: Request with bearer token
```

This Python script uses only the standard library. It prints the approval link to the terminal, waits while you approve, then prints the access token. It saves the refresh token to `~/.config/itoflow/credentials.json`, readable only by you, and never prints it.

```python get_token.py theme={null}
import json
import os
import sys
import time
from pathlib import Path
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, build_opener, install_opener, urlopen

CREDENTIALS = Path.home() / ".config" / "itoflow" / "credentials.json"

# The API rejects Python's default User-Agent, so send a named one.
opener = build_opener()
opener.addheaders = [("User-Agent", "itoflow-get-token/1.0")]
install_opener(opener)


def post(url, fields):
    request = Request(
        url,
        data=urlencode(fields).encode(),
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )
    try:
        with urlopen(request, timeout=30) as response:
            return json.load(response)
    except HTTPError as error:
        try:
            return json.load(error)
        except ValueError:
            sys.exit(f"Sign-in service error: HTTP {error.code}")


def save_and_print(config, token):
    # Keep the refresh token in a file only you can read; print only the access token.
    if "refresh_token" not in token:
        print("No refresh token returned; sign in again when this one expires.", file=sys.stderr)
        print(token["access_token"])
        return
    CREDENTIALS.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
    # Write a new owner-only file, then swap it in, so an older file's permissions never apply.
    temporary = CREDENTIALS.with_name(f".credentials-{os.getpid()}.tmp")
    fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
    with os.fdopen(fd, "w") as file:
        json.dump(
            {
                "refresh_token": token["refresh_token"],
                "client_id": config["clientId"],
                "token_endpoint": config["tokenEndpoint"],
            },
            file,
        )
    os.replace(temporary, CREDENTIALS)
    print(token["access_token"])


def refresh():
    if not CREDENTIALS.exists():
        sys.exit("No saved sign-in. Run the script without --refresh first.")
    saved = json.loads(CREDENTIALS.read_text())
    token = post(
        saved["token_endpoint"],
        {
            "grant_type": "refresh_token",
            "refresh_token": saved["refresh_token"],
            "client_id": saved["client_id"],
        },
    )
    if "access_token" not in token:
        sys.exit(f"Refresh failed: {token.get('error')}. Sign in again.")
    config = {"clientId": saved["client_id"], "tokenEndpoint": saved["token_endpoint"]}
    save_and_print(config, token)


def sign_in():
    with urlopen("https://api.itoflow.ai/auth/client-config", timeout=30) as response:
        config = json.load(response)

    fields = {"client_id": config["clientId"]}
    if config.get("scopes"):
        fields["scope"] = " ".join(config["scopes"])
    device = post(config["deviceAuthorizationEndpoint"], fields)
    link = device.get("verification_uri_complete", device["verification_uri"])
    print(f"Open {link} and approve code {device['user_code']}", file=sys.stderr)

    interval = device.get("interval", 5)
    deadline = time.time() + device["expires_in"]
    while time.time() < deadline:
        time.sleep(interval)
        token = post(
            config["tokenEndpoint"],
            {
                "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
                "device_code": device["device_code"],
                "client_id": config["clientId"],
            },
        )
        if "access_token" in token:
            save_and_print(config, token)
            return
        if token.get("error") == "slow_down":
            interval += 5
        elif token.get("error") != "authorization_pending":
            sys.exit(f"Sign-in failed: {token.get('error')}")
    sys.exit("The code expired before it was approved. Run the script again.")


if "--refresh" in sys.argv:
    refresh()
else:
    sign_in()
```

Put the token straight into an environment variable, so it stays out of your shell history:

```bash theme={null}
export ITOFLOW_ACCESS_TOKEN="$(python3 get_token.py)"
```

If sign-in fails, the script prints the reason and the variable is left empty, so check it before you continue.

The code expires after a few minutes. Poll no faster than the `interval` the sign-in service returns; if it answers `slow_down`, wait longer between polls.

## Refresh an expired token

Access tokens are short-lived. When a request returns `401`, get a new one without signing in again:

```bash theme={null}
export ITOFLOW_ACCESS_TOKEN="$(python3 get_token.py --refresh)"
```

The script sends the saved refresh token to the sign-in service, saves the new refresh token it gets back, and prints the new access token. Each refresh returns a new refresh token and the old one may stop working, so always use the newest. If the refresh fails, run `python3 get_token.py` again to sign in.

To refresh from your own code, send these form fields to the `tokenEndpoint` from `GET /auth/client-config`:

```text theme={null}
grant_type=refresh_token
refresh_token=REFRESH_TOKEN
client_id=CLIENT_ID
```

Replace the following:

* `REFRESH_TOKEN`: the newest refresh token you saved.
* `CLIENT_ID`: the `clientId` from `GET /auth/client-config`.

## Keep tokens safe

* Keep tokens out of source files, URLs, logs, screenshots and bug reports.
* Keep the refresh token inside the program that manages the session; the script stores it in `~/.config/itoflow/credentials.json` with owner-only permissions. Only the access token goes to the API.
* The client ID, an ID token and a refresh token are not API tokens. The API rejects them.
* Don't copy tokens out of browser cookies or another app's credential files.

The API checks the token's issuer, audience and expiry on every request, then checks that your account may use the resource you asked for. See [Security at Itoflow](https://itoflow.ai/security) for how we protect your data.

## Try requests in these docs

Read-only endpoints have a **Send** button in the API reference; the [reference overview](https://docs.itoflow.ai/api-reference/overview.md) lists them. [Get an access token](#get-an-access-token) and paste it into the bearer field. Requests go straight from your browser to `https://api.itoflow.ai`. The docs site sends no cookies and does not proxy the request. Clear the field when you finish, and don't use a shared browser.

## Errors

* `401`: the token is missing, expired or not an Itoflow access token. Refresh it or sign in again. Don't retry the same token in a loop.
* `403`: the token is valid, but your account can't use this resource, or your plan doesn't include it.


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