# Signal Trade CRM API Python helpers

The dependency-free [Python client](./signal_trade_stock_sync.py) supports CRM reads, change-feed sync, safe record creation and version-checked updates. Keep the `crm:manage` key on a trusted server and out of browser code and source control.

## List task assignees

The directory returns current member IDs, display names and roles, without email addresses:

```python
import os

from signal_trade_stock_sync import get_crm_assignees

api_key = os.environ["SIGNAL_TRADE_API_KEY"]
base_url = os.getenv("SIGNAL_TRADE_BASE_URL", "https://signalautomotive.uk")
assignees = get_crm_assignees(api_key, base_url)
print(assignees)
```

## Create a customer, opportunity and task

Use a fresh UUID v4 for each new record. Keep the UUID and exact request details together so an uncertain create can be retried idempotently:

```python
import os
from datetime import date, timedelta
from uuid import uuid4

from signal_trade_stock_sync import create_crm_record, create_crm_task

api_key = os.environ["SIGNAL_TRADE_API_KEY"]
base_url = os.getenv("SIGNAL_TRADE_BASE_URL", "https://signalautomotive.uk")
customer = create_crm_record(
    api_key, "customers", str(uuid4()),
    {"name": "Example seller", "role": "seller", "phone": "02890000000"},
    base_url,
)
opportunity = create_crm_record(
    api_key, "opportunities", str(uuid4()),
    {
        "title": "AB12CDE · vehicle purchase",
        "direction": "buy",
        "customerId": customer["id"],
        "stage": "new",
        "nextAction": "Arrange a viewing",
    },
    base_url,
)
task = create_crm_task(
    api_key,
    str(uuid4()),
    {
        "title": "Call seller about service history",
        "customerId": customer["id"],
        "opportunityId": opportunity["id"],
        "ownerUserId": None,
        "dueDate": (date.today() + timedelta(days=1)).isoformat(),
    },
    base_url,
)
```

`ownerUserId` can be set to a member's `userId` from `get_crm_assignees`, or to `None` to leave the task unassigned. Creating a customer or opportunity does not send an email or text.

## Update with the current version

Updates send the version returned by the latest record read. Only mutable customers, opportunities and tasks can be changed; activities are append-only.

```python
from signal_trade_stock_sync import get_crm_assignees, get_crm_record, update_crm_record, update_crm_task

assignees = get_crm_assignees(api_key, base_url)
current_task = get_crm_record(api_key, "tasks", task["id"], base_url)
updated_task = update_crm_task(
    api_key,
    task["id"],
    current_task["version"],
    {"ownerUserId": assignees[0]["userId"] if assignees else None},
    base_url,
)
current_opportunity = get_crm_record(api_key, "opportunities", opportunity["id"], base_url)
updated_opportunity = update_crm_record(
    api_key,
    "opportunities",
    opportunity["id"],
    current_opportunity["version"],
    {"stage": "contacted"},
    base_url,
)
```

After HTTP 409, fetch the current record and reconcile with its latest version. Writes are never retried automatically. If a write times out, loses its connection, or returns a server error, its outcome may be uncertain: read the record before deciding what to do. For a create, retry only the exact same record details with the same UUID `createId`; never reuse that ID for a different create.
