# Compliance

This API cannot be used to bypass the compliance controls in the product, and that is enforced
structurally rather than by a check somebody has to remember: `POST /api/v1/messages` calls the same
send functions the composer and the AI call, so the pre-send gate runs. There is no second code path.

## What runs on every send

1. **Account status** — a suspended workspace sends nothing on any channel.
2. **Suppression / opt-out** — STOP means STOP, on replies too.
3. **A2P registration** (SMS) — the sending number must be carrier-approved.
4. **Quiet hours** (proactive SMS) — the recipient's local time, resolved from their area code. If we
   cannot resolve a timezone we fail **closed**.
5. **Consent** (proactive SMS) — a non-expired consent artifact for that number, or the send is
   refused. This is TCPA, and it is the one that catches importers by surprise.

Email is opt-out-based: the hard gate is the suppression list.

## What a refusal looks like

```json
{
  "error": {
    "code": "compliance_blocked",
    "message": "Blocked (no_consent): No valid consent on file for +1••••••0142",
    "request_id": "req_…",
    "details": { "message_id": "clx…", "status": "FAILED", "recorded": true }
  }
}
```

**422, not a cheerful 201.** `recorded: true` means the attempt exists in the workspace's own inbox
as a refused message — it is on file, not lost, so do not retry it in a loop.

## Creating leads

`POST /api/v1/leads` without a `consent` object creates a lead carrying **no consent artifact**. The
response says so:

```json
{ "id": "clx…", "ai_outbound_allowed": false }
```

The AI will not dial or text that lead until consent is captured. This is a feature. If you have real
capture evidence — a form submission with a URL, an IP and a timestamp — pass it:

```json
{
  "phone": "+15125550142",
  "source": "your-integration",
  "consent": {
    "captured_at": "2026-08-13T12:00:00Z",
    "source_url": "https://yoursite.example/contact",
    "ip": "203.0.113.10",
    "form_snapshot": { "checkbox": "I agree to be contacted" }
  }
}
```

Only send this if it is true. It is the record that gets produced if anybody asks.

## MLS listings

`GET /api/v1/listings` returns the workspace's **own** listings, not MLS inventory. MLS data is
governed by per-board display rules — attribution, permitted fields, refresh windows — that a
general-purpose REST feed cannot honour, because we would have no idea where the data ended up.
