# Howl REST API Howl exposes a JSON REST API at `/api/v1/` for programmatic access by external services such as OpenClaw. All endpoints require a Bearer token. ## Authentication Set one or more API keys in `.env`: ```env API_KEYS=your-key-here,another-key ``` Include the key in every request: ``` Authorization: Bearer your-key-here ``` Requests without a valid key return `401 Unauthorized`. --- ## Endpoints ### Email Processing #### `POST /api/v1/process` Trigger a pipeline poll — fetches unread email from the last 14 days and runs the full classification pipeline. **Response** ```json { "processed": 12 } ``` --- ### Email Log #### `GET /api/v1/log` Query the email processing log. **Query parameters** | Parameter | Type | Description | |---|---|---| | `status` | string | Filter by status: `pending`, `processing`, `completed`, `failed`, `skipped` | | `sender_type` | string | Filter by sender type: `customer`, `vendor`, `whitelist`, `unknown` | | `classification` | string | Filter by LLM classification (e.g. `vendor_invoice`) | | `q` | string | Search sender address or subject (case-insensitive) | | `limit` | int | Results per page, max 200 (default 50) | | `offset` | int | Pagination offset (default 0) | **Response** ```json { "total": 847, "items": [ { "id": "...", "sender_address": "billing@vendor.com", "subject": "Invoice #1042", "llm_classification": "vendor_invoice", "llm_confidence": 0.97, "final_action": "flag_follow_up", "status": "completed", ... } ] } ``` #### `GET /api/v1/log/{entry_id}` Retrieve a single log entry by UUID. --- ### Sender Profiles Sender profiles guide the LLM by providing context about known senders. #### `GET /api/v1/senders` List all active sender profiles. **Response**: `{ "items": [...] }` #### `POST /api/v1/senders` Create or update a sender profile (upserts by `email_address`). **Request body** ```json { "email_address": "billing@vendor.com", "display_name": "Vendor Billing", "sender_type": "vendor", "email_types": ["invoice", "statement"], "processing_instructions": "Always flag for follow-up", "notes": null } ``` `sender_type` values: `customer`, `vendor`, `whitelist`, `unknown` #### `GET /api/v1/senders/{profile_id}` Retrieve a single profile by UUID. #### `PUT /api/v1/senders/{profile_id}` Replace a profile's fields (same body as POST). #### `DELETE /api/v1/senders/{profile_id}` Deactivate a profile (soft delete). --- ### Purge Rules Purge rules define senders whose mail should be deleted after a retention period. #### `GET /api/v1/purge-rules` List all purge rules (active and inactive). #### `POST /api/v1/purge-rules` Create or reactivate a purge rule. **Request body** ```json { "email_address": "newsletter@bulk.com", "display_name": "Bulk Newsletter", "older_than_days": 14, "notes": null } ``` #### `PUT /api/v1/purge-rules/{rule_id}` Partially update a rule. All fields are optional. ```json { "older_than_days": 7, "is_active": false } ``` #### `DELETE /api/v1/purge-rules/{rule_id}` Deactivate a rule (soft delete). --- ### Purge Execution #### `POST /api/v1/purge/execute` Preview or execute a purge run against Microsoft 365. **Request body** ```json { "mode": "preview", "sender": "newsletter@bulk.com", "days": 30 } ``` | Field | Values | Description | |---|---|---| | `mode` | `preview` \| `execute` | `preview` reports what would be deleted without deleting | | `sender` | string or `null` | Specific sender address; `null` uses all active rules | | `days` | int | Retention threshold in days (only applies when `sender` is set) | **Response** ```json { "mode": "preview", "total": 47, "rows": [ { "address": "newsletter@bulk.com", "label": "newsletter@bulk.com (>30d)", "count": 47, "subjects": ["Weekly digest", "Monthly digest", "Special offer"] } ] } ``` --- ### Analysis #### `GET /api/v1/analysis/senders` Aggregated sender statistics from the email log (top 200 by volume). **Response** ```json { "items": [ { "sender_address": "billing@vendor.com", "count": 143, "first_seen": "2025-01-03T10:22:00+00:00", "last_seen": "2026-03-28T14:05:00+00:00" } ] } ``` --- ### Whitelist #### `GET /api/v1/whitelist` List all active whitelist entries. #### `POST /api/v1/whitelist` Add a whitelist entry. At least one of `email_address` or `domain` is required. ```json { "email_address": "ceo@partner.com", "domain": null, "description": "Executive contact", "added_by": "openclaw", "expires_at": null } ``` #### `DELETE /api/v1/whitelist/{entry_id}` Deactivate a whitelist entry. --- ## DB Users The `/dbusers` web UI (available in the Howl dashboard sidebar) lets you create PostgreSQL roles for direct database access. This is intended for services that need to query Howl's database directly rather than going through the API. ### Permission Levels | Level | Permissions | |---|---| | **Read Only** | `SELECT` on all tables | | **Modify** | `SELECT` + `INSERT`/`UPDATE` on `customers`, `vendors`, `whitelist`, `sender_profiles`, `purge_rules` | | **Full** | `SELECT` + `INSERT`/`UPDATE`/`DELETE` on all tables | ### Usage 1. Navigate to **DB Users** in the sidebar 2. Click **+ Create User** 3. Enter a username (3–31 chars, lowercase, letters/digits/underscores) — the role will be created as `howl_` 4. Select a permission level 5. Optionally add a description (e.g. "OpenClaw integration") 6. Click **Create User** — credentials are shown **once** and never stored To revoke access, click **Revoke** next to the user. This sets the user inactive in Howl and drops the PostgreSQL role. ### Connection details ``` Host: localhost (or the host running Postgres) Port: 5434 Database: howl Username: howl_ Password: (shown at creation time) ``` --- ## Example: OpenClaw integration ```python import httpx HOWL_BASE = "https://howl.wulfconsulting.cloud" HOWL_KEY = "your-api-key" headers = {"Authorization": f"Bearer {HOWL_KEY}"} # Trigger a mail fetch httpx.post(f"{HOWL_BASE}/api/v1/process", headers=headers) # Pull recent completed entries resp = httpx.get( f"{HOWL_BASE}/api/v1/log", headers=headers, params={"status": "completed", "limit": 100}, ) entries = resp.json()["items"] # Add a sender profile httpx.post( f"{HOWL_BASE}/api/v1/senders", headers=headers, json={ "email_address": "alerts@vendor.com", "sender_type": "vendor", "email_types": ["notification"], "processing_instructions": "Route to Infosec if subject contains CVE", }, ) ```