Skip to content

Authentication

pydoover handles authentication automatically for both the Data API and Control API clients. This page covers the two auth flows, profile-based configuration, and how token refresh works under the hood.

Information Circle

Profiles are created by running doover login from the Doover CLI. The CLI handles the device authorisation flow and saves your tokens to a named profile.

Auth Flows

pydoover supports two authentication flows, each designed for a different use case:

Doover2 (User Login)

The Doover2 flow is the standard authentication method for user accounts. It uses a refresh token to obtain and renew access tokens from the Doover auth server.

  • Used by: Both DataClient and ControlClient
  • Credentials: refresh token (obtained via doover login)
  • Token refresh: automatic, using the refresh token
  • Auth server: https://auth.doover.com (default)

This is the flow used when you authenticate via a profile.

DataService (Client Credentials)

The DataService flow is designed for service accounts and automated systems. It uses a client ID and client secret pair to obtain access tokens.

  • Used by: DataClient only
  • Credentials: client_id + client_secret
  • Token refresh: automatic, using the client credentials
  • Scope: Data API access only (not the Control API)
from pydoover.api import DataClient

# DataService auth is triggered by providing client_id and client_secret
client = DataClient(
    client_id="my-service-id",
    client_secret="my-service-secret",
)
Information Circle

The ControlClient does not support the DataService flow. Service account access to the Control API requires a Doover2 refresh token.

Profiles

Profiles store authentication credentials in ~/.doover/config so you do not need to pass tokens directly in your code. Profiles are managed by the doover CLI tool and the ConfigManager class.

Profile Structure

Each profile in the config file stores the following fields:

FieldDescription
TOKENCurrent access token (JWT)
TOKEN_EXPIRESToken expiry as a Unix timestamp
AGENT_IDDefault agent ID for this profile
BASE_URLControl API base URL
BASE_DATA_URLData API base URL
REFRESH_TOKENRefresh token for obtaining new access tokens
REFRESH_TOKEN_IDIdentifier for the refresh token
AUTH_SERVER_URLAuth server URL
AUTH_SERVER_CLIENT_IDClient ID for the auth server

Creating a Profile

Profiles are typically created by logging in with the CLI:

doover login

You can also manage profiles programmatically using ConfigManager:

from pydoover.api.auth import ConfigManager, AuthProfile

config = ConfigManager()

# Create a new profile
profile = AuthProfile(
    profile="production",
    token="eyJhbGciOiJSUzI1NiIs...",
    refresh_token="refresh_token_value",
    control_base_url="https://api.doover.com",
    data_base_url="https://data.doover.com/api",
    auth_server_url="https://auth.doover.com",
)

config.create(profile)
config.write()

Using a Profile

Pass the profile name to any client constructor:

from pydoover.api import DataClient, ControlClient

# Both clients can use the same profile
data_client = DataClient(profile="production")
control_client = ControlClient(profile="production")

You can also pass an AuthProfile object directly instead of a name:

from pydoover.api.auth import ConfigManager

config = ConfigManager()
profile = config.get("production")

client = DataClient(profile=profile)

Authentication Methods

Profile-Based

The recommended approach for interactive use and development. Credentials are loaded from ~/.doover/config:

client = DataClient(profile="default")

Explicit Token

Pass a bearer token directly. Useful for short-lived scripts or when tokens are provided externally:

from datetime import datetime, timezone

client = DataClient(
    token="eyJhbGciOiJSUzI1NiIs...",
    token_expires=datetime(2026, 6, 1, tzinfo=timezone.utc),
)

If you omit token_expires, the client extracts the exp claim from the JWT automatically. The token_expires parameter accepts a datetime, a Unix timestamp as float or int, or None.

Client Credentials

For service accounts on the Data API:

client = DataClient(
    client_id="my-service-id",
    client_secret="my-service-secret",
)

Pre-Built Auth Object

Share a single auth client across multiple API clients:

from pydoover.api.auth import Doover2AuthClient

auth = Doover2AuthClient.from_profile("default")

data_client = DataClient(auth=auth)
control_client = ControlClient(auth=auth)
Warning

When passing an auth object, do not combine it with other auth parameters (profile, token, client_id, etc.). The client raises a ValueError if conflicting auth configuration is detected.

Automatic Token Refresh

Both auth flows handle token refresh transparently. Before each API request, the client calls ensure_token(), which checks whether the current token needs refreshing.

The refresh logic works as follows:

  1. The JWT's exp claim is extracted (without cryptographic verification) to determine the expiry time.
  2. If the token expires within the next 30 seconds, a refresh is triggered.
  3. For Doover2 auth, the refresh token is sent to the auth server to obtain a new access token.
  4. For DataService auth, the client credentials are used to obtain a new token.
  5. The new token and its expiry are stored on the auth client.

If no expiry information is available (no exp claim and no token_expires provided), the token is assumed to be valid and no refresh is attempted.

The AuthProfile Dataclass

AuthProfile is a dataclass that represents a stored profile. It is used both by ConfigManager for persistence and by the auth clients for initialization:

from pydoover.api.auth import AuthProfile

profile = AuthProfile(
    profile="my-profile",
    token="eyJ...",
    token_expires=None,  # Extracted from JWT automatically
    agent_id="12345",
    control_base_url="https://api.doover.com",
    data_base_url="https://data.doover.com/api",
    refresh_token="refresh_token_value",
    refresh_token_id="refresh_token_id_value",
    auth_server_url="https://auth.doover.com",
    auth_server_client_id="client_id_value",
)

The ConfigManager

ConfigManager reads and writes the ~/.doover/config file. It parses profile blocks and provides methods for CRUD operations:

from pydoover.api.auth import ConfigManager

config = ConfigManager()

# List all profiles
for name, profile in config.entries.items():
    print(f"{name}: token={'set' if profile.token else 'unset'}")

# Get a specific profile
profile = config.get("default")

# Delete a profile
config.delete("old-profile")
config.write()

The config file format uses INI-style blocks separated by blank lines:

[profile=default]
TOKEN=eyJhbGciOiJSUzI1NiIs...
TOKEN_EXPIRES=1748880000.0
AGENT_ID=12345
BASE_URL=https://api.doover.com
REFRESH_TOKEN=refresh_token_value
BASE_DATA_URL=https://data.doover.com/api
AUTH_SERVER_URL=https://auth.doover.com

Default URLs

When no URL is specified, the clients use these defaults:

URLDefaultUsed By
Data APIhttps://data.doover.com/apiDataClient / AsyncDataClient
Control APIhttps://api.doover.comControlClient / AsyncControlClient
Auth Serverhttps://auth.doover.comToken refresh (Doover2 flow)

Related Pages