# RegCheck360 API Changelog

This file tracks every change to the **public** `/api/v1/*` surface. Internal
routes under `/api/` (used by the website) are not covered here; assume they
can change freely.

## Deprecation policy

- **Additive changes** (new endpoints, new optional fields on responses,
  new optional headers, new error codes) ship without warning. We aim
  for these never to break a well-behaved client.
- **Breaking changes** ship under a new path-versioned root (`/api/v2/*`).
  We do not break `/api/v1/*` shapes once they're in this changelog.
- **Sunsetting** an endpoint requires a **180-day notice** via the
  `Deprecation: true` + `Sunset: <RFC 1123 date>` response headers per
  [RFC 8594](https://datatracker.ietf.org/doc/html/rfc8594) and an entry
  in this changelog with the planned removal date. We will also email all
  active key holders.
- **Bug fixes** that change observable behavior are flagged here under
  the relevant date. If the previous behavior was load-bearing for your
  integration, flag it within 7 days for accommodation.

The OpenAPI 3.1 spec at `https://regcheck360.com/api/openapi.json` is the
source of truth for the current contract.

---

## 2026-05-23 — v1 public-API launch

Initial public release. Everything below shipped today.

### Endpoints — added

- `POST /api/v1/check` — single ingredient verdict (AU/BR/CA)
- `POST /api/v1/check/batch` — up to 100 ingredients per call, atomic
  quota gate, per-item errors inline
- `POST /api/v1/formula` — whole-formula verdict + cross-ingredient warnings
- `POST /api/v1/pathway` — regulatory pathway advisor
- `POST /api/v1/scan` — label image OCR + per-ingredient compliance
- `GET  /api/v1/ingredient/{slug}?region=AU` — cached lookup, no AI call,
  no monthly quota burn (populated by `POST /v1/check` calls)
- `GET  /api/v1/whoami` — key metadata + masked owner email
- `GET  /api/v1/usage` — current-month plan + used/limit per type
- `POST /api/v1/webhooks` — register a webhook (returns `whsec_<hex>` once)
- `GET  /api/v1/webhooks` — list user's webhooks
- `DELETE /api/v1/webhooks/{id}` — remove (cascades to deliveries)
- `GET  /api/v1/webhooks/{id}/deliveries` — last 50 delivery attempts
- `GET  /api/openapi.json` / `.yaml` — OpenAPI 3.1 spec

### Auth & rate limits — added

- Two auth headers accepted (equivalent): `X-API-Key: rc360_...` and
  `Authorization: Bearer rc360_...`. Keys issued at `/account/api-keys`.
- Per-key per-minute (default 30) and per-day (default 1000) rate limits
  via atomic Postgres RPCs.
- Per-user per-month plan quota (free: 5 checks / 2 formula / 1 pathway;
  Pro/Team/Trial unlimited) enforced against the key owner.
- Response headers: `X-RateLimit-{Limit,Remaining}-{Minute,Day}`,
  `X-RateLimit-Reset`, `Retry-After` (on 429).

### Request/response infrastructure — added

- `X-Request-Id` echo end-to-end (client-supplied or auto-generated).
- `Idempotency-Key` header honored on all v1 POSTs. 24h dedup. Same key
  + same body → cached replay with `X-Idempotent-Replayed: true`. Same
  key + different body → 409 `idempotency_conflict`. 5xx not cached.
- CORS preflight returns 204 with `Access-Control-Expose-Headers`
  listing all custom headers.

### Webhooks — added

Four event types, HMAC-SHA256 signed via `X-RegCheck360-Signature`,
delivered via cron worker (1-minute schedule) with 5-attempt retry
(60s, 5m, 30m, 4h, 12h):

- `regulatory_alert.created` — fires on new TGA/ANVISA/Health Canada
  alert detected by the daily scrub agents
- `ingredient.status_changed` — fires when permissibility flips on
  `tga_ingredients.permitted_use` / `anvisa_ingredients.status` /
  `nhpid_ingredients.status`
- `check.completed` — fires on successful `POST /v1/check` (per item
  for `POST /v1/check/batch`)
- `formula.completed` — fires on successful `POST /v1/formula`

### Error envelope — added (opt-in)

Clients can opt into the **v1.1 wrapped error envelope** by sending:

```
Accept: application/vnd.regcheck360.v1.1+json
```

Responses then take the structured shape:

```json
{
  "error": {
    "code": "invalid_api_key",
    "message": "...",
    "type": "authentication",
    "request_id": "req_...",
    "doc_url": "https://regcheck360.com/errors/invalid_api_key"
  },
  "meta": { ... }
}
```

Each error `code` has a public reference page at
`https://regcheck360.com/errors/{code}` — what it means, why it happens,
how to fix, an example. Index at `/errors`.

Without the Accept header, the flat error shape stays exactly as it is.
`Vary: Accept` is set on wrapped responses for cache correctness.

### Operational notes

- All v1 POSTs forward `X-Cron-Secret` to internal routes to bypass the
  internal anonymous per-IP gate. Required Vercel env: `CRON_SECRET`.
- Webhook signing secrets stored plaintext (matches Stripe/GitHub model).
- All migrations applied: `api_idempotency`, `webhooks` +
  `webhook_deliveries`, `enqueue_regulatory_alert_webhooks` trigger,
  `enqueue_ingredient_status_change_webhooks` + 3 triggers,
  `v1_ingredient_cache` + `upsert_v1_ingredient_cache` RPC.
