Python Examples for FinAutCRM API

This section provides Python code examples for interacting with the FinAutCRM API.

Prerequisites

Install the requests library:

pip install requests

Credentials Setup

Create a client-data.json file in the userdocs/examples/ directory with your credentials:

{
    "client_id": "your_client_id_here",
    "client_secret": "your_client_secret_here"
}

Warning

Never commit your client-data.json file to version control!

Examples

1. Get Access Token

01_get_access_token.py - Authenticate and obtain an access token using OAuth2 client credentials flow.

"""
Get an Access Token from the FinAutCRM API.

This script demonstrates how to authenticate with the CRM API using
OAuth2 client credentials flow and obtain an access token.

Prerequisites:
    pip install requests

Usage:
    python 01_get_access_token.py
"""
import sys, os
import json
from pathlib import Path

# The 'requests' library makes HTTP requests simple and readable.
# Install it with: pip install requests
import requests


# =============================================================================
# Configuration
# =============================================================================

# The base URL for the CRM API
API_BASE_URL_PROD = "https://crm.norsktest.no"
API_BASE_URL_DEV = "http://localhost:8000"
DEV = sys.platform == 'win32' and not os.environ.get('PROD')  # Use DEV on Windows unless PROD is set
API_BASE_URL = API_BASE_URL_DEV if DEV else API_BASE_URL_PROD

# Path to the JSON file containing your client credentials.
# This file should contain: {"client_id": "...", "client_secret": "..."}
# IMPORTANT: Never commit this file to version control!
CLIENT_DATA_FILE = Path(__file__).parent.parent / f"client-data-{'dev' if DEV else 'prod'}.json"


# =============================================================================
# Step 1: Load client credentials from file
# =============================================================================

def load_client_credentials(filepath: Path) -> dict:
    """
    Load client_id and client_secret from a JSON file.

    Storing credentials in a separate file keeps them out of your code
    and makes it easier to use different credentials for different environments.
    """
    with open(filepath, "r") as f:
        return json.load(f)


# =============================================================================
# Step 2: Request an access token
# =============================================================================

def get_access_token(client_id: str, client_secret: str) -> dict:
    """
    Exchange client credentials for an access token.

    This uses the OAuth2 "client credentials" grant type, which is designed
    for machine-to-machine authentication (no user interaction needed).

    Args:
        client_id: Your application's client ID
        client_secret: Your application's client secret

    Returns:
        The full token response as a dictionary containing:
        - access_token: The token to use for API requests
        - token_type: Always "Bearer"
        - expires_in: Token lifetime in seconds (typically 3600 = 1 hour)
        - scope: The permissions granted to this token
    """
    # The token endpoint URL
    token_url = f"{API_BASE_URL}/api/v1/auth/token"

    # The request body - this tells the API what we want
    payload = {
        "client_id": client_id,
        "client_secret": client_secret,
        "grant_type": "client_credentials",  # This is the OAuth2 flow type
        "scope": "read write"                 # Request both read and write access
    }

    # The Content-Type header tells the API we're sending JSON
    headers = {
        "Content-Type": "application/json"
    }

    # Make the POST request to get a token
    response = requests.post(token_url, json=payload, headers=headers)

    # Check if the request was successful (status code 200)
    # If not, this will raise an exception with details
    response.raise_for_status()

    # Parse and return the JSON response
    return response.json()


# =============================================================================
# Main script
# =============================================================================

def main():
    """Main function that ties everything together.
    """

    # Step 1: Load credentials from file
    print("Loading client credentials...")
    credentials = load_client_credentials(CLIENT_DATA_FILE)

    client_id = credentials["client_id"]
    client_secret = credentials["client_secret"]

    # Show which client we're using (but never print the secret!)
    print(f"Client ID: {client_id}")

    # Step 2: Get the access token
    print("\nRequesting access token...")
    token_response = get_access_token(client_id, client_secret)

    # Step 3: Display the results
    print("\nSuccess! Token response:")
    print("-" * 40)

    # Pretty-print the response
    print(json.dumps(token_response, indent=2))

    print("-" * 40)

    # Extract the access token for easy copying
    access_token = token_response["access_token"]
    expires_in = token_response["expires_in"]

    print(f"\nYour access token (valid for {expires_in} seconds):")
    print(access_token)

    print("\nUse this token in the Authorization header for API requests:")
    print(f'  Authorization: Bearer {access_token[:20]}...')


# This ensures the main() function only runs when the script is executed directly,
# not when it's imported as a module by another script.
if __name__ == "__main__":
    main()

2. List Ordninger (Verify Token)

02_list_ordninger.py - Fetch ordninger (schemes) to verify your token works. Demonstrates authenticated API requests with pagination.

"""
List Ordninger (Schemes) from the FinAutCRM API.

This script demonstrates how to:
1. Authenticate with the CRM API (get an access token)
2. Make an authenticated API request to fetch data
3. Handle paginated responses

This example builds on 01_get_access_token.py - see that file for
detailed explanations of the authentication process.

Prerequisites:
    pip install requests

Usage:
    python 02_list_ordninger.py
"""

import json
from pathlib import Path

import requests


# =============================================================================
# Configuration
# =============================================================================

API_BASE_URL = "https://crm.norsktest.no"
CLIENT_DATA_FILE = Path(__file__).parent.parent / "client-data.json"


# =============================================================================
# Authentication (see 01_get_access_token.py for detailed explanation)
# =============================================================================

def load_client_credentials(filepath: Path) -> dict:
    """Load client credentials from JSON file.
    """
    with open(filepath, "r") as f:
        return json.load(f)


def get_access_token(client_id: str, client_secret: str) -> str:
    """
    Get an access token using client credentials.
    Returns just the token string (not the full response).
    """
    response = requests.post(
        f"{API_BASE_URL}/api/v1/auth/token",
        json={
            "client_id": client_id,
            "client_secret": client_secret,
            "grant_type": "client_credentials",
            "scope": "read write"
        },
        headers={"Content-Type": "application/json"}
    )
    response.raise_for_status()
    return response.json()["access_token"]


# =============================================================================
# API Request: List Ordninger
# =============================================================================

def list_ordninger(access_token: str, page: int = 1, page_size: int = 20) -> dict:
    """
    Fetch a list of ordninger (schemes) from the API.

    This demonstrates how to make an authenticated GET request.
    The access token must be included in the Authorization header.

    Args:
        access_token: The OAuth2 access token obtained from authentication
        page: Which page of results to fetch (1-based)
        page_size: How many items per page (max usually 100)

    Returns:
        A dictionary containing:
        - items: List of ordning objects
        - total: Total number of ordninger in the database
        - page: Current page number
        - page_size: Items per page
        - pages: Total number of pages
    """
    # The endpoint URL for listing ordninger
    url = f"{API_BASE_URL}/api/v1/ordninger/"

    # Query parameters for pagination
    # These are appended to the URL as ?page=1&page_size=20
    params = {
        "page": page,
        "page_size": page_size
    }

    # The Authorization header authenticates our request.
    # Format: "Bearer <token>" - this is the OAuth2 standard.
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Accept": "application/json"  # We want JSON response
    }

    # Make the GET request
    response = requests.get(url, params=params, headers=headers)

    # Check for errors (401 = unauthorized, 403 = forbidden, etc.)
    response.raise_for_status()

    return response.json()


# =============================================================================
# Main script
# =============================================================================

def main():
    """Main function that demonstrates fetching ordninger.
    """

    # Step 1: Authenticate
    print("Authenticating...")
    credentials = load_client_credentials(CLIENT_DATA_FILE)
    access_token = get_access_token(
        credentials["client_id"],
        credentials["client_secret"]
    )
    print("Authentication successful!\n")

    # Step 2: Fetch ordninger
    print("Fetching ordninger...")
    result = list_ordninger(access_token, page=1, page_size=50)
    # import pprint
    # pprint.pprint(result)

    # Step 3: Display results
    total = result["total"]
    items = result["items"]

    print(f"Found {total} ordninger:\n")
    print("-" * 72)

    # Display each ordning
    # The format string aligns columns for readability
    print(f"{'ID':<6} {'Name':<40} {'Short':<10}")
    print("-" * 72)

    for ordning in items:
        # Each ordning has an id, name, and short_name
        ordning_id = ordning["name"]
        name = ordning["full_name"]
        short_name = ordning.get("short_name", "")  # .get() handles missing keys

        # Truncate long names to fit the column
        if len(name) > 38:
            name = name[:35] + "..."

        print(f"{ordning_id:<6} {name:<40} {short_name:<10}")

    print("-" * 60)
    print(f"\nShowing {len(items)} of {total} ordninger")

    # Show pagination info
    print(f"Page {result['page']} of {result['total_pages']}")


if __name__ == "__main__":
    main()

3. List Organization Units

03_list_orgunits.py - Fetch a paginated list of organization units with optional filtering by kind, active status, or search term.

"""
List Organization Units from the FinAutCRM API.

This script demonstrates how to:
1. Authenticate with the CRM API
2. Fetch a paginated list of organization units
3. Filter orgunits by various criteria (kind, active status, search)

Organization units (orgunits) represent the organizational hierarchy -
companies, departments, teams, etc. Each unit has a type (kind) and
can be filtered by various criteria.

Prerequisites:
    pip install requests

Usage:
    python 03_list_orgunits.py
"""

import json
from pathlib import Path

import requests


# =============================================================================
# Configuration
# =============================================================================

API_BASE_URL = "https://crm.norsktest.no"
CLIENT_DATA_FILE = Path(__file__).parent.parent / "client-data.json"


# =============================================================================
# Authentication (see 01_get_access_token.py for detailed explanation)
# =============================================================================

def load_client_credentials(filepath: Path) -> dict:
    """Load client credentials from JSON file.
    """
    with open(filepath, "r") as f:
        return json.load(f)


def get_access_token(client_id: str, client_secret: str) -> str:
    """
    Get an access token using client credentials.
    Returns just the token string (not the full response).
    """
    response = requests.post(
        f"{API_BASE_URL}/api/v1/auth/token",
        json={
            "client_id": client_id,
            "client_secret": client_secret,
            "grant_type": "client_credentials",
            "scope": "read write"
        },
        headers={"Content-Type": "application/json"}
    )
    response.raise_for_status()
    return response.json()["access_token"]


# =============================================================================
# API Request: List Organization Units
# =============================================================================

def list_orgunits(
    access_token: str,
    page: int = 1,
    page_size: int = 20,
    kind: str = None,
    active: bool = None,
    search: str = None
) -> dict:
    """
    Fetch a paginated list of organization units from the API.

    Each organization unit has:
    - id: Unique identifier
    - name: Display name of the unit
    - kind: Type of unit (e.g., 'company', 'department', 'team')
    - active: Whether the unit is currently active
    - depth: How deep in the tree (1 = root, 2 = child of root, etc.)
    - numchild: Number of direct children this unit has

    Args:
        access_token: The OAuth2 access token
        page: Which page of results to fetch (1-based)
        page_size: How many items per page
        kind: Filter by organization type (e.g., 'company', 'department')
        active: Filter by active status (True/False/None for all)
        search: Search in name and email fields

    Returns:
        A dictionary containing paginated results with 'items', 'total',
        'page', 'page_size', and 'total_pages'.
    """
    url = f"{API_BASE_URL}/api/v1/orgunits"

    # Query parameters for pagination and filtering
    params = {
        "page": page,
        "page_size": page_size
    }

    # Add optional filters only if provided
    if kind is not None:
        params["kind"] = kind
    if active is not None:
        params["active"] = str(active).lower()  # API expects 'true'/'false'
    if search is not None:
        params["search"] = search

    # Authorization header with Bearer token
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Accept": "application/json"
    }

    response = requests.get(url, params=params, headers=headers)
    response.raise_for_status()

    return response.json()


# =============================================================================
# Display Helper
# =============================================================================

def display_orgunit_list(orgunits: list):
    """
    Display a list of organization units in a table format.

    The 'depth' field indicates the hierarchy level:
    - depth=1: Root level (top of hierarchy)
    - depth=2: Child of root
    - etc.
    """
    print(f"{'ID':<6} {'Name':<40} {'Kind':<12} {'Active':<8} {'Depth'}")
    print("-" * 80)

    for unit in orgunits:
        name = unit["name"]
        if len(name) > 38:
            name = name[:35] + "..."

        active_str = "Yes" if unit["active"] else "No"
        kind = unit.get("kind", "")
        depth = unit.get("depth", 1)

        print(f"{unit['id']:<6} {name:<40} {kind:<12} {active_str:<8} {depth}")


# =============================================================================
# Main script
# =============================================================================

def main():
    """Main function demonstrating organization unit listing.
    """

    # Step 1: Authenticate
    print("Authenticating...")
    credentials = load_client_credentials(CLIENT_DATA_FILE)
    access_token = get_access_token(
        credentials["client_id"],
        credentials["client_secret"]
    )
    print("Authentication successful!\n")

    # Step 2: List all organization units
    print("Fetching organization units...\n")
    result = list_orgunits(access_token, page=1, page_size=25)

    # Display summary and table
    total = result["total"]
    items = result["items"]
    print(f"Found {total} organization units\n")

    display_orgunit_list(items)

    print("-" * 80)
    print(f"\nShowing {len(items)} of {total} orgunits")
    print(f"Page {result['page']} of {result['total_pages']}")

    # Step 3: Demonstrate filtering (optional)
    # Uncomment to filter by active units only:
    #
    # print("\n\nFiltering active units only...")
    # active_result = list_orgunits(access_token, active=True)
    # print(f"Found {active_result['total']} active units")
    #
    # Or search by name:
    # search_result = list_orgunits(access_token, search="Sales")
    # print(f"Found {search_result['total']} units matching 'Sales'")


if __name__ == "__main__":
    main()

4. Get Organization Unit Tree

04_get_orgunit_tree.py - Fetch a single organization unit with its full tree of descendants. Demonstrates hierarchical data traversal.

"""
Get Organization Unit Tree from the FinAutCRM API.

This script demonstrates how to:
1. Authenticate with the CRM API
2. Fetch a single organization unit with its full tree of descendants
3. Display a hierarchical tree structure

Organization units form a tree hierarchy. When you fetch a single unit
by ID, the API returns that unit along with all its descendants nested
in a 'children' array, allowing you to display the complete subtree.

Prerequisites:
    pip install requests

Usage:
    python 04_get_orgunit_tree.py
"""

import json
from pathlib import Path

import requests


# =============================================================================
# Configuration
# =============================================================================

# API_BASE_URL = "https://crm.norsktest.no"
API_BASE_URL = "http://localhost:8000"
CLIENT_DATA_FILE = Path(__file__).parent.parent / "client-data.json"

# The ID of the organization unit to fetch
# Change this to explore different parts of the hierarchy
ORGUNIT_ID = 169  # Example: root organization
# ORGUNIT_ID = 528  # Example: root organization


# =============================================================================
# Authentication (see 01_get_access_token.py for detailed explanation)
# =============================================================================

def load_client_credentials(filepath: Path) -> dict:
    """Load client credentials from JSON file.
    """
    with open(filepath, "r") as f:
        return json.load(f)


def get_access_token(client_id: str, client_secret: str) -> str:
    """
    Get an access token using client credentials.
    Returns just the token string (not the full response).
    """
    response = requests.post(
        f"{API_BASE_URL}/api/v1/auth/token",
        json={
            "client_id": client_id,
            "client_secret": client_secret,
            "grant_type": "client_credentials",
            "scope": "read write"
        },
        headers={"Content-Type": "application/json"}
    )
    response.raise_for_status()
    return response.json()["access_token"]


# =============================================================================
# API Request: Get Organization Unit Tree
# =============================================================================

def get_orgunit_tree(access_token: str, orgunit_id: int) -> dict:
    """
    Fetch a single organization unit with its full tree of descendants.

    This endpoint returns the requested unit along with all its children,
    grandchildren, etc., nested in a 'children' array. This allows you to
    display or process the complete organizational hierarchy below any node.

    Args:
        access_token: The OAuth2 access token
        orgunit_id: The ID of the organization unit to fetch

    Returns:
        The organization unit with nested 'children' containing all descendants.
        Each unit includes: id, name, kind, active, depth, and children array.
    """
    url = f"{API_BASE_URL}/api/v1/orgunits/{orgunit_id}"

    headers = {
        "Authorization": f"Bearer {access_token}",
        "Accept": "application/json"
    }

    response = requests.get(url, headers=headers)
    response.raise_for_status()

    # print response header for debugging (one on each line)
    for key, value in response.headers.items():
        print(f"{key}: {value}")

    return response.json()


# =============================================================================
# Display Helper
# =============================================================================

def display_tree(unit: dict, prefix: str = "", is_last: bool = True):
    """
    Recursively display an organization unit tree with visual connectors.

    Uses box-drawing characters to create a tree visualization:
    ├── for middle children
    └── for the last child
    │   for vertical continuation

    Args:
        unit: The organization unit dictionary
        prefix: Current line prefix for indentation
        is_last: Whether this is the last child of its parent
    """
    # Choose the connector based on position
    connector = "└── " if is_last else "├── "

    # Format unit info
    name = unit["name"]
    kind = unit.get("kind", "")
    active = "✓" if unit["active"] else "✗"
    unit_id = unit["id"]

    # Print this unit (root has no connector)
    if prefix == "":
        print(f"{name} ({kind}) [active: {active}] - ID: {unit_id}")
    else:
        print(f"{prefix}{connector}{name} ({kind}) [{active}] ID: {unit_id}")

    # Prepare prefix for children
    # If this is the last child, don't draw a vertical line below
    child_prefix = prefix + ("    " if is_last else "│   ")

    # Recursively display children
    children = unit.get("children", [])
    for i, child in enumerate(children):
        is_last_child = (i == len(children) - 1)
        display_tree(child, child_prefix, is_last_child)


def count_nodes(unit: dict) -> int:
    """Count total nodes in a tree (including the root).
    """
    count = 1
    for child in unit.get("children", []):
        count += count_nodes(child)
    return count


# =============================================================================
# Main script
# =============================================================================

def main():
    """Main function demonstrating organization unit tree retrieval.
    """

    # Step 1: Authenticate
    print("Authenticating...")
    credentials = load_client_credentials(CLIENT_DATA_FILE)
    access_token = get_access_token(
        credentials["client_id"],
        credentials["client_secret"]
    )
    print("Authentication successful!\n")

    # Step 2: Fetch the organization unit tree
    print(f"Fetching organization unit tree for ID {ORGUNIT_ID}...\n")
    tree = get_orgunit_tree(access_token, ORGUNIT_ID)

    # Step 3: Display tree info
    total_nodes = count_nodes(tree)
    print(f"Tree contains {total_nodes} organization units\n")
    print("=" * 70)

    # Step 4: Display the tree
    display_tree(tree)

    print("=" * 70)
    print(f"\nTotal units in tree: {total_nodes}")


if __name__ == "__main__":
    main()