Adds a JSON REST API at /api/v1/ (Bearer token auth via API_KEYS env var) exposing the full pipeline — email processing, log querying, sender profiles, purge rules, whitelist, and analysis — for external consumers like OpenClaw. Adds a /dbusers web UI for generating PostgreSQL roles with read_only, modify, or full permission levels; credentials shown once and never stored. Includes Alembic migration 0005 for the db_api_users tracking table and API.md with full endpoint documentation and integration examples. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6.5 KiB
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:
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
{ "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
{
"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
{
"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
{
"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.
{ "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
{
"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
{
"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
{
"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.
{
"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
- Navigate to DB Users in the sidebar
- Click + Create User
- Enter a username (3–31 chars, lowercase, letters/digits/underscores) — the role will be created as
howl_<username> - Select a permission level
- Optionally add a description (e.g. "OpenClaw integration")
- 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_<username>
Password: (shown at creation time)
Example: OpenClaw integration
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",
},
)