Authentication¶
PyPropertyMe uses OAuth 2.0 with the authorization code flow to authenticate with the PropertyMe API.
Overview¶
The authentication flow:
- User initiates authentication via CLI or programmatically
- A local callback server starts to receive the OAuth response
- Browser opens to PropertyMe's authorization page
- User logs in and authorizes the application
- PropertyMe redirects back with an authorization code
- The code is exchanged for access and refresh tokens
- Tokens are stored for future API calls
- Access tokens are automatically refreshed when expired
OAuth 2.0 Flow¶
sequenceDiagram
participant User
participant CLI
participant Browser
participant PropertyMe
User->>CLI: pypropertyme auth
CLI->>CLI: Start local callback server
CLI->>CLI: Generate state parameter
CLI->>Browser: Open authorization URL
Browser->>PropertyMe: User logs in
PropertyMe->>Browser: Redirect with auth code + state
Browser->>CLI: Callback with code
CLI->>CLI: Verify state matches
CLI->>PropertyMe: Exchange code for tokens
PropertyMe->>CLI: Access + refresh tokens
CLI->>CLI: Save to ~/.pypropertyme/tokens.json
CLI Authentication¶
The simplest way to authenticate:
pypropertyme auth
This command:
- Starts a local web server on the configured redirect URI port
- Opens your default browser to PropertyMe's login page
- Waits for the OAuth callback
- Exchanges the authorization code for tokens
- Stores tokens in
~/.pypropertyme/tokens.json
Configuration¶
Required Environment Variables¶
Set these in a .env file or your shell:
PROPERTYME_CLIENT_ID=your_client_id_here
PROPERTYME_CLIENT_SECRET=your_client_secret_here
PROPERTYME_REDIRECT_URI=http://localhost:65385/home/callback
PROPERTYME_SCOPES=["activity:read", "communication:read", "contact:read", "property:read", "transaction:read", "offline_access"]
| Variable | Required | Description |
|---|---|---|
PROPERTYME_CLIENT_ID |
Yes | OAuth client ID from PropertyMe |
PROPERTYME_CLIENT_SECRET |
Yes | OAuth client secret |
PROPERTYME_REDIRECT_URI |
No | Callback URL (default: http://localhost:65385/home/callback) |
PROPERTYME_SCOPES |
No | JSON array of OAuth scopes |
OAuth Scopes¶
Common scopes for read-only access:
activity:read- Read activity logscommunication:read- Read communicationscontact:read- Read contactsproperty:read- Read properties, tenancies, jobs, etc.transaction:read- Read financial transactionsoffline_access- Enables refresh tokens for persistent access
Token Storage¶
Tokens are stored in ~/.pypropertyme/tokens.json:
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "Bearer",
"expires_at": 1234567890,
"expires_in": 3600
}
Security
The token file contains sensitive credentials. Ensure it has appropriate file permissions and is not committed to version control.
Token Refresh¶
The PropertyMeAuthProvider handles token refresh automatically:
- Before each API request, it checks if the access token is expired
- If expired, it uses the refresh token to obtain a new access token
- The new tokens are saved to the token file
- The API request proceeds with the new access token
Programmatic Authentication¶
Using the Authenticator¶
from pypropertyme.auth import PropertyMeAuthenticator
# Create authenticator
auth = PropertyMeAuthenticator(
client_id="your_client_id",
client_secret="your_client_secret",
redirect_url="http://localhost:65385/home/callback",
scopes=["contact:read", "property:read", "offline_access"],
)
# Start the OAuth flow
# This will open the browser and wait for the callback
token = await auth.authenticate()
# Token dict contains access_token, refresh_token, etc.
print(token["access_token"])
Using Tokens with the Client¶
import json
from pathlib import Path
from pypropertyme.client import Client
# Load tokens from file
token_path = Path.home() / ".pypropertyme" / "tokens.json"
token = json.loads(token_path.read_text())
# Create client with token
client = Client.get_client(token)
# Make API calls
contacts = await client.contacts.all()
Custom Token Persistence¶
You can provide a callback to save tokens when they're refreshed:
from pypropertyme.client import Client
def save_token(token: dict):
"""Custom token saver - called when tokens are refreshed."""
# Save to database, secret manager, etc.
print(f"Token refreshed: {token['access_token'][:20]}...")
# Create client with token saver callback
client = Client.get_client(
token=initial_token,
token_saver=save_token,
)
Authentication Classes¶
PropertyMeAuthenticator¶
Handles the initial OAuth flow:
- Constructs the authorization URL
- Starts a local callback server
- Exchanges authorization code for tokens
from pypropertyme.auth import PropertyMeAuthenticator
auth = PropertyMeAuthenticator(
client_id="...",
client_secret="...",
redirect_url="...",
scopes=["..."],
)
# Get the authorization URL
url = auth.construct_auth_url(state="random-state")
# Exchange code for tokens
token = await auth.get_tokens_from_auth_code(code)
PropertyMeAuthProvider¶
Kiota authentication provider that:
- Adds Bearer token to API requests
- Automatically refreshes expired tokens
- Calls token saver callback when tokens change
from pypropertyme.auth import PropertyMeAuthProvider
provider = PropertyMeAuthProvider(
token=token,
client_id="...",
client_secret="...",
token_saver=save_callback,
)
Troubleshooting¶
"No token found"¶
Error: No token found. Please run 'pypropertyme auth' first.
Run pypropertyme auth to authenticate.
"Token expired"¶
The client should automatically refresh tokens. If you see this error, your refresh token may have expired. Run pypropertyme auth again.
"Invalid redirect URI"¶
Ensure the redirect URI in your .env matches exactly what's configured in the PropertyMe developer portal.
Browser doesn't open¶
If the browser doesn't open automatically, check the terminal for the authorization URL and open it manually.