# API Specification

## Conventions

- Base path: `/api/v1`
- JSON request/response bodies
- Bearer tokens for scanners and external clients; secure session/CSRF
  protection for first-party Inertia requests
- Pagination metadata, allow-listed filtering/sorting, validation error
  objects, stable machine error codes, and correlation IDs
- `Idempotency-Key` required for scanner ingestion and CRM import mutations
- Rate limits by endpoint, actor, tenant, device, and token type

## Primary resources

- `/auth`, `/me`, `/sessions`, `/two-factor`
- `/organizations`, `/memberships`, `/invitations`, `/roles`, `/permissions`
- `/plans`, `/subscriptions`, `/entitlements`, `/licenses`, `/usage`
- `/campaigns`, `/keywords`, `/exclusions`, `/scoring-versions`
- `/sources`, `/devices`, `/scanners`, `/scanner-authorizations`
- `/scan-jobs`, `/scan-sessions`, `/scan-ingestion`, `/scan-metrics`
- `/content-items`, `/content-observations`, `/ai-analyses`
- `/opportunities`, `/assignments`, `/activities`, `/responses`
- `/alerts`, `/notification-deliveries`
- `/crm-connections`, `/crm-field-mappings`, `/crm-sync-jobs`,
  `/crm-record-mappings`, `/crm-sync-events`
- `/reports`, `/exports`, `/audit-logs`, `/security-events`
- `/owner/system-settings`, `/owner/kill-switches`
- `/health`, `/health/ready`, `/health/live`

## Scanner job acceptance

The implemented scanner endpoints are:

| Method | Path                                | Authentication                             | Purpose                                                            |
| ------ | ----------------------------------- | ------------------------------------------ | ------------------------------------------------------------------ |
| `POST` | `/api/v1/scanner/register`          | Sanctum user session/token                 | Register or refresh a browser device and rotate its scanner token. |
| `POST` | `/api/v1/scanner/register/code`     | Single-use registration code               | Exchange a short-lived code for a revocable scanner token.         |
| `GET`  | `/api/v1/scanner/status`            | Scanner bearer token                       | Read device, scanner, organization, and scanning-control state.    |
| `POST` | `/api/v1/scanner/heartbeat`         | Scanner bearer token                       | Record version, capabilities, health, and current-job state.       |
| `GET`  | `/api/v1/scanner/jobs/next`         | Scanner bearer token                       | Accept the scanner's active job or claim its next queued job.      |
| `POST` | `/api/v1/scanner/jobs/{job}/ingest` | Scanner bearer token and `Idempotency-Key` | Validate and finalize one scan result.                             |

Administrators can generate a short-lived, single-use code from the Devices &
scanners screen for the signed-in user. The extension exchanges that code
without receiving a user session or long-lived user API token. Authenticated
registration remains available for trusted first-party clients. Both paths
check active membership, scanner-device entitlement, hard limits, organization
state, and device ownership.

New devices are pending unless `SCANNER_AUTO_APPROVE_DEVICES=true`.
Re-registering the same authorized device rotates all prior active scanner
tokens. Registration-code TTL is configured with
`SCANNER_REGISTRATION_CODE_TTL_MINUTES`.
The administration dashboard compares extension versions against
`SCANNER_MINIMUM_VERSION` and flags scanners that require an update.

Before scheduling or accepting work, the API revalidates global and tenant
scanning state, organization writability, user and membership state, device,
scanner, license, subscription/plan entitlement and usage, campaign, source,
assignment, and token version. A successful job response contains only the
authorized source target, campaign context, and configured limits. It never
returns session cookies or social passwords.

## Ingestion

Ingestion accepts `completed`, `partial`, or `failed` result envelopes with an
explicit access outcome, exact stage metrics, structured posts/comments, and
safe warning/error objects. It validates schema, job ownership and expiry,
source limits, content types, timestamps, public HTTP(S) URLs, processed counts,
metric consistency, and metadata key safety.

The server prefers native platform identifiers and otherwise generates a stable
fingerprint. Content is upserted per tenant/source/type/fingerprint, each
observation is retained, and discovery/revisit/change counts are derived
server-side. The raw request body is hashed for idempotency but is not persisted;
only the restricted structured content and safe response summary are stored.

Repeating an `Idempotency-Key` with the identical request returns the original
response. Reusing the key with a different request returns
`IDEMPOTENCY_KEY_REUSED`.

Scanner endpoints are rate limited independently by authenticated user or
hashed bearer-token identity. Token TTL, heartbeat cadence, job TTL, registration
approval, and rate limits are server-controlled.

## AI qualification and opportunity review

After ingestion commits, one queued qualification job is created for each
content observation. Jobs use only stored, tenant-scoped content and campaign
configuration. They enforce the global and tenant AI kill switch, subscription
entitlement, monthly analysis limit, configured budget, campaign state, hard
exclusions, and strict output validation.

The OpenAI-compatible adapter uses the Responses API with a strict
`json_schema` text format. Provider output includes evidence-grounded intent,
urgency, problem, service, industry, location, company-size and decision-maker
indicators, recommendation request, existing solution, dissatisfaction, budget,
timeframe, exclusions, facts, inferences, response suggestions, and follow-up.
Unknown facts remain the literal `unknown`. The server recalculates the final
score and retains the scoring version independently of the provider's advisory
score.

Authenticated web routes support:

| Method  | Path                                                      | Permission             | Purpose                                                       |
| ------- | --------------------------------------------------------- | ---------------------- | ------------------------------------------------------------- |
| `PATCH` | `/opportunities/{id}/status`                              | `opportunities.manage` | Apply a validated pipeline transition and record history.     |
| `PATCH` | `/opportunities/{id}/assignment`                          | `opportunities.assign` | Assign an active tenant member and record assignment history. |
| `PATCH` | `/opportunities/{id}/suggestions/{suggestion}`            | `responses.review`     | Save or approve final human-controlled response text.         |
| `POST`  | `/opportunities/{id}/suggestions/{suggestion}/regenerate` | `responses.review`     | Regenerate a draft for the selected language and tone.        |

These routes never publish to a social platform. Approval means only that a
human confirmed text for manual use.

## Alerts and CRM

Alert rules support immediate, daily, and weekly frequency; in-app, email, and
WhatsApp channels; and score, campaign, source, platform, keyword, location,
industry, salesperson, active-hour, recipient, and timezone filters. Delivery
records track queued, sending, sent, delivered when supported, and failed
states. `radar:alert-summaries daily|weekly` generates idempotent summaries.

CRM web routes cover draft creation, encrypted credential rotation/removal,
connection testing, state changes, field mappings, import preview, manual
export, failed-job retry, and connection reconciliation. Automatic exports are
queued only for newly qualified opportunities whose campaigns opt in and when
every effective activation layer permits synchronization.

`POST /api/v1/crm/webhooks/ironhand` accepts provider status events only with a
valid `X-Ironhand-Signature` HMAC. The event identifies the public connection
and external record; unsigned events are rejected.

## Errors

Errors use:

```json
{
    "error": {
        "code": "SCANNER_AUTHORIZATION_REVOKED",
        "message": "The scanner authorization is no longer active.",
        "correlation_id": "..."
    }
}
```

General logs contain IDs and codes, not secrets or unrestricted content.
OpenAPI schemas will be generated from the implemented request/resource
contracts.

## Implemented reporting and export routes

- `GET /reports` renders permission-scoped exact metrics, breakdowns, filters,
  and qualified content matches.
- `GET /reports/opportunities.csv` streams a tenant-safe CSV export, applies a
  stricter export throttle, and records an immutable audit event.
- `POST /operations/exports` creates a private tenant JSON export that omits
  encrypted credentials, API/scanner tokens, passwords, and other secrets.
- `GET /operations/exports/{publicId}` downloads only a non-expired export
  owned by the active tenant.
