# Changelog — RCS-Box API

All notable changes to the **API contract** (tenant API under `/v1`, public
endpoints under `/api/v1`, outbound webhook payloads) are documented here.
Admin UI, background jobs and infrastructure are out of scope unless they change
what an API consumer sees.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and the project adheres to [Semantic Versioning](https://semver.org/):

- **MAJOR** — a field or endpoint is removed, renamed or changes meaning. The `/v1`
  path prefix equals the major version. Announced as *Deprecated* at least one
  minor version ahead.
- **MINOR** — new endpoint, field, enum value or webhook event (backwards
  compatible). Clients must ignore unknown response fields.
- **PATCH** — bug fix or documentation-only change with no contract change.

The running version is reported by `GET /v1/health` as `api_version` and is the
`info.version` of the OpenAPI document (`/docs/openapi.json`). Versions before
1.0.0 were assigned retroactively from the git history on 2026-09-25; during the
0.x phase breaking changes shipped as minor bumps.

## [1.11.0] — 2026-11-08

### Added
- `PATCH /v1/agents/{id}` takes `phones`, `emails` and `websites`. Each list
  replaces the stored one at Google (omit it to keep it, `[]` to empty it); same
  format and limit checks as on create, and at least one contact must remain
  (`422` `contacts: missing`). Until now contacts could only be set at create.

## [1.10.1] — 2026-11-05

### Changed
- No contract change: new admin page "Rechnungsabgleich" (`/admin/carrier_invoices`)
  that checks carrier invoices against Box usage per agent, with five admin-MCP
  tools to record and map invoices (`record_carrier_invoice`, `map_invoice_agent`,
  `list_carrier_invoices`, `usage_for_reconciliation`, `carrier_reconciliation`);
  the usage pages take any period (`from`/`to`) and export carrier units per
  agent and day (`/admin/usage/units.csv`).

## [1.10.0] — 2026-11-05

### Added
- `GET /api/v1/public/status`: each metric carries `window` (`24h`, `7d` or
  `30d`). A metric with fewer than 20 samples in the last 24 hours now falls
  back to the last 7, then 30 days instead of being `null`; `null` only when
  even 30 days are too thin. The status page shows the window on each tile.

## [1.9.0] — 2026-11-05

### Added
- Public status page at `/status` and its data as `GET /api/v1/public/status`
  (no authentication, CORS for box.rcs.jetzt): live status of API, sending to
  Google, receiving from Google, forwards to your webhooks and background
  processing, 90 days of daily history and anonymous 24 h figures (delivery
  rate, median latencies). Only RCS-Box-side failures count.

### Changed
- No contract change: a request that crashes with an unhandled error is now
  recorded in the API log as a 500 (`internal_error`); the response itself is
  unchanged.

## [1.8.0] — 2026-11-04

### Added
- Every error response carries a machine-readable `code` (e.g. `agent_not_found`,
  `phone_not_allowed`, `recipient_not_tester`, `content_invalid`) next to the
  unchanged `error` label and HTTP status. Branch on `code`; the full list is in
  the API docs ("Error codes") and in the `ErrorCode` schema.
- Google rejections tell you why: `rcs_unreachable` (send failed because the
  recipient can't be reached via RCS — no RCS on the device, or the agent isn't
  launched on the recipient's network; fall back to e.g. SMS),
  `google_invalid_argument`, `google_permission_denied`, `google_not_found`,
  `google_rate_limited`, `google_unavailable`, `google_timeout`,
  `google_connection_failed`, `google_auth_failed`, `google_error` — plus
  `google_status` with Google's own status (e.g. `NOT_FOUND`).
- The 429 of the analytics ingestion endpoint carries `code: rate_limited`, a
  failed `submit_for_review` dry run `code: preflight_failed`, and a malformed
  JSON body now gets a JSON 400 with `code: invalid_json`.

## [1.7.1] — 2026-11-04

### Changed
- No contract change: new admin page "Fancy Analytics" (`/admin/explorer`) that
  cross-filters outbound messages by tenant, agent, message type, buttons,
  send time and outcome; the admin log pages (Webhooks, API-Logs, Messages,
  Weiterleitungen) get a timeline, clickable filters with counts, search and a
  live mode. API responses, status codes and webhook payloads are unchanged.

## [1.7.0] — 2026-11-03

### Added
- `PATCH /v1/agents/:id` accepts `track` (`newsletter` | `flex`) to switch a
  `NON_CONVERSATIONAL` draft agent between Community (MAU) and Flex (per
  message). Box-local, no Google call. Only while the agent is `DRAFT` and the
  autopilot is not working on it, otherwise `422` `track: locked_after_submit`;
  `track: not_allowed_for_billing_category` / `track: invalid_value` for
  tariffs the agent cannot run in.

### Changed
- `POST /v1/agents/:id/submit_for_review` requires `mau_interval` and
  `mau_expected_contacts` for every agent on the `newsletter` track, not only
  for tenants with automatic MAU requests: the carriers need the MAU approval
  for every newsletter agent, the tenant setting only decides whether RCS-Box
  sends it. The preflight error names the tariff and points to `track: flex`
  for billing per message. Flex and conversation agents are unaffected.

## [1.6.4] — 2026-11-03

### Changed
- No contract change: the Box now records every outbound HTTP call (webhook
  forwards to the tenant, calls to Google) per attempt, and the admin API log
  keeps the agent, phone number and request id of each call, so a message can be
  followed through all four legs in the admin timeline. Responses, status codes,
  headers and webhook payloads are unchanged.

## [1.6.3] — 2026-11-01

### Changed
- No contract change: the admin API log stores the request body as the client
  sent it, without the copy Rails' parameter wrapping adds (e.g. a duplicate
  `message` / `agent` object); GET requests no longer show an empty `{"agent": {}}`.

## [1.6.2] — 2026-11-01

### Changed
- The billing month is the German calendar month (Europe/Berlin): quota
  counters, the free-tier cap and the monthly usage report switch months at
  midnight German time instead of 01:00 (02:00 in summer). A send at 00:30 on
  the 1st now counts towards the new month.
- No format change: timestamps in API responses and webhook payloads stay UTC
  (`…Z`), as before.

## [1.6.1] — 2026-11-01

### Changed
- No contract change: the API-key middleware now keeps the (masked, size-capped)
  request and response bodies of failed calls for the admin API log. Responses,
  status codes and headers are unchanged.

## [1.6.0] — 2026-11-01

### Added
- Per-agent webhook: agents have an optional `webhook_url`. When set, the
  agent's callbacks (forwarded user messages incl. opt-out / opt-in, `DELIVERED`
  / `READ` events, Agent-Autopilot notifications) go to that URL instead of the
  tenant's `webhook_url`; `null` (the default for every existing agent) keeps
  the tenant webhook. Signature unchanged (HMAC with the tenant API key).
- `POST /v1/agents` and `PATCH /v1/agents/{id}` accept `webhook_url` (HTTPS
  only, otherwise `422` `webhook_url: invalid_format`). `PATCH` with `null` or
  `""` removes the override. Changing only `webhook_url` makes no call to
  Google.
- The Agent object returns `webhook_url`.

## [1.5.3] — 2026-10-28

### Changed
- No contract change: `config/routes.rb` gained the admin-only tariff
  explainer (`/admin/tariffs`). The tenant API and webhook payloads are
  unchanged.

## [1.5.2] — 2026-10-27

### Changed
- No contract change: `config/routes.rb` gained the admin-only usage report
  (`/admin/usage`, monthly usage per tenant and agent with CSV export). The
  tenant API and webhook payloads are unchanged.

## [1.5.1] — 2026-10-27

### Changed
- Documentation only, no contract change: the Agents section explains the
  three tariffs (`track`: `conversation`, `newsletter`, `flex`) and the MAU
  approval flow for newsletter agents; the German guide covers MAU approval
  and Flex (launch step, Flex checklist, glossary).

## [1.5.0] — 2026-10-27

### Added
- Flex tariff: agent `track` value `flex` for `NON_CONVERSATIONAL` agents
  billed per sent message, without the carriers' MAU approval. Flex agents
  never trigger the automatic MAU approval request, and the MAU preflight on
  `POST /v1/agents/:id/submit_for_review` applies to the `newsletter` track
  only.
- `POST /v1/agents` accepts an optional `track`: `newsletter` (default) or
  `flex` for `NON_CONVERSATIONAL`, `conversation` (default) for
  `CONVERSATIONAL`. Other combinations answer `422`
  `track: not_allowed_for_billing_category`. Previously a supplied `track`
  was silently ignored.
- `GET /api/v1/public/plans` lists the Flex plans (`flex_s`, `flex_m`,
  `flex_l`) with `track` `flex`, `metric` `message`, tagline `PER MESSAGE` and
  a `standard` quota of `component_type` `single` / `overage_unit`
  `per_message`. Clients grouping plans by `track` get a third group.

### Changed
- `GET /api/v1/public/plans`: the Conversation plans' `tagline` is now
  `PER CONVERSATION` (was `PER MESSAGE`; they are billed per 24-hour
  conversation).
- `billing_category` only knows Google's current categories `CONVERSATIONAL`
  and `NON_CONVERSATIONAL`. `POST /v1/agents` answers `422`
  `billing_category: invalid_value` for the legacy `BASIC_MESSAGE`,
  `SINGLE_MESSAGE`, `CONVERSATIONAL_MESSAGE` and `BASIC_MESSAGE_V2` — Google
  stopped accepting them on 2026-02-18, so such requests already failed with
  `Google API Error`. Agents stored with a legacy value are reported with the
  current one (Basic/Single → `NON_CONVERSATIONAL`, Conversational Message →
  `CONVERSATIONAL`).

## [1.4.0] — 2026-10-24

### Added
- `POST /v1/agents/:id/launch_questionnaire_draft` accepts `mau_interval`
  (`daily` | `weekly` | `biweekly` | `monthly`) and `mau_expected_contacts`
  (whole number ≥ 1) for newsletter (`NON_CONVERSATIONAL`) agents. Invalid
  values answer `422` with `invalid_value` / `must_be_positive_integer`.
- `GET /v1/agents/:id/launch_questionnaire_draft` returns
  `mau_request_sent_at` (`null` until sent).
- Automatic MAU approval request (per-tenant setting, off by default): once
  a newsletter agent's launch is submitted to all German carriers, RCS-Box
  sends the MAU request to the carriers' support. Launches restricted via
  `carriers` do not trigger it. With the setting on,
  `POST /v1/agents/:id/submit_for_review` requires both MAU fields for
  `NON_CONVERSATIONAL` agents (preflight error otherwise).

## [1.3.3] — 2026-10-22

### Changed
- Documentation only, no contract change: the explanatory prose of the former
  hand-written `/docs` page (authentication, errors, rate limits, phone format,
  operator block, message types, tester workflow, webhook event types and the
  opt-out / opt-in flow, per-endpoint notes) now lives in the OpenAPI document
  itself (`info.description`, tag and operation descriptions, `webhooks`) and is
  rendered by `/docs`. Operator-only and Google-vendor notes moved to the
  repository (`docs/INTERNAL-API.md`, `docs/VENDOR-GOOGLE-API.md`).

## [1.3.2] — 2026-10-22

### Changed
- Documentation only, no contract change: `/docs` is now an interactive API
  reference (Scalar) rendered from the OpenAPI document. `/docs/swagger`
  permanently redirects (`301`) to `/docs`. `/docs/openapi.json` and
  `/docs/changelog` are unchanged. The previous hand-written page stays
  reachable at `/docs/legacy` for now.

## [1.3.1] — 2026-10-22

### Fixed
- `PATCH /v1/agents/:id`: Google's patch response only echoes the fields it
  was sent. The Box no longer interprets a missing `isArchived` in that echo
  as "not archived", so an unrelated metadata edit cannot clear the Box-side
  archive marker of an agent archived in the Google console. No contract
  change; the archive marker is admin-only and not part of the agent payload.

## [1.3.0] — 2026-10-20

### Added
- **Box-side block** (operator decision): an agent or a whole tenant can be
  blocked in the RCS-Box without touching Google. While blocked, every send
  endpoint and every mutating `/v1` request (agent create/update, unlaunch,
  submit_for_review, tester invites, questionnaire drafts, assets, brand
  documents, brand contact) answers **`423 Locked`** with
  `{ "error": "Agent blocked" | "Tenant blocked", "message": <reason>, "block": { "scope", "reason", "since" } }`.
  Reads stay available. Nothing is forwarded to the tenant webhook while
  blocked (messages, delivery/read events, consent confirmations, autopilot
  status); STOP/START are still honored by the Box. Suppressed forwards are not
  replayed after unblocking.
- Agent payload: `blocked` (boolean) and `block` (object or `null`).
- `GET /v1/health`: `tenant: { name, blocked, block }`.

## [1.2.0] — 2026-10-17

### Added
- `autopilot.state` (agent payload) / webhook `autopilot_state` can now be `HOLDING_LAUNCH`: the
  brand verification is through, but the agent's operator-configured release
  schedule has not yet released the launch. Clients must treat it like
  `AWAITING_VERIFICATION` (active, waiting). The schedule itself (tester
  invites → verification → launch, with per-step timing) is admin-only and not
  exposed through the API.

## [1.1.1] — 2026-10-17

### Changed
- No API contract change. Admin-only routes were added (default tester list,
  one-click tester invites per agent, responsive admin UI); the version bump
  exists so the CI contract guard stays strict on `config/routes.rb`.

## [1.1.0] — 2026-10-14

### Added
- `POST /v1/assets`, `GET /v1/assets`, `DELETE /v1/assets/{id}`: tenants upload
  agent branding images (PNG/JPEG, max. 5 MB) and get back a permanent public
  HTTPS URL to pass as `logo_uri` / `hero_uri` to `POST /v1/agents` or
  `PATCH /v1/agents/{id}`. Until now the only way to obtain a Google-fetchable
  URL was a customer-hosted file or direct object-storage access.

## [1.0.1] — 2026-10-06

### Fixed
- `POST /v1/agents/{id}/capabilities/batch` no longer fails with `422 Google API
  Error … "Requested users amount shouldn't be less than 500"`. Google's bulk
  lookup (`users:batchGet`) requires 500–10,000 numbers per request and could
  never back a 1–100 batch; the endpoint now resolves reachability per number
  via the same lookup as the single-number endpoint. These per-number lookups now
  run concurrently (bounded fan-out), so a full 100-number batch resolves in a few
  round-trip waves instead of ~100 sequential ones. Request and response shapes
  are unchanged (#620).

## [1.0.0] — 2026-09-25

First stable contract. The OpenAPI document was audited against the code and now
describes the actual behaviour (#355); the `/docs` page was refreshed to match (#356).

### Fixed
- `X-RCS-Box-Signature` on tenant webhook forwards is now computed over the exact
  request body. Forwards whose user text contained `<`, `>` or `&` previously carried
  a signature that could not be verified (#357).

### Changed (documentation only, contract unchanged)
- `POST /v1/agents`: documented required fields (`logo_uri`, `hero_uri`, `color`,
  `privacy_uri`, `terms_uri`) and the "at least one contact" rule; `track` is not
  accepted and was removed from the request schema; `website_uri`/`website_label`
  documented; enums for `hosting_region` (incl. `ASIA_PACIFIC`), `billing_category`,
  `use_case`.
- Documented the full `Agent`, `MessageLog` and `Tester` response shapes, the
  `AgentStatus` enum (`DRAFT`, `PENDING`, `LAUNCHED`, `REJECTED`, `SUSPENDED`),
  402/403/404 on all message endpoints, the XL partial-success shape, the
  brand-document upload error codes and the outbound webhooks (`webhooks:` block).

## [0.22.0] — 2026-09-21

### Added
- `POST /v1/agents` accepts optional `google_brand_id` **or** `external_ref` to
  create the agent under a specific brand of the tenant; unknown/foreign brand →
  `404`, no agent created, no fallback (#354).

## [0.21.0] — 2026-09-20

### Added
- `POST /v1/agents/{id}/unlaunch` — withdraw a submitted/launched agent; stops an
  armed Agent-Autopilot in the same call (#351).

### Removed
- The rswag surface at `/api-docs` (a second, drifted OpenAPI document).
  `/docs/openapi.json` and `/docs/swagger` are the single source (#353).

## [0.20.0] — 2026-09-19

### Added
- `POST /v1/brands` — create a brand at Google and mirror it locally, idempotent per
  tenant via `external_ref` (or, best-effort, `display_name`); `external_ref` field
  on the `Brand` object (#350).

### Changed
- Agent-Autopilot state `LAUNCHED` renamed to `MONITORING` (`autopilot.state` in
  `GET /v1/agents/{id}/launch`, `autopilot_state` in the
  `agent.review_status_changed` webhook) (#349).

## [0.19.0] — 2026-09-17

### Added
- `agent.review_status_changed` webhook carries `launch_details[]` (region,
  launch_state, comment, update_time) mirroring `GET /v1/agents/{id}/launch` (#346).
- `error_kind: "suspended"` is emitted (once per suspension, recoverable) while
  `AWAITING_LAUNCH`/`MONITORING` (#345); `"unlaunched"` when a live launch is
  pulled outside the Box, with post-launch monitoring (#347).

## [0.18.0] — 2026-09-11

### Added
- `POST /v1/agents/{id}/submit_for_review`: optional `carriers[]` (carrier slugs or
  `google_region_name`) to scope the launch, and `dry_run=true` returning
  `{dry_run, ok, errors}` without arming the autopilot (#336).

## [0.17.0] — 2026-09-09

### Added
- Agent-Autopilot: `POST /v1/agents/{id}/submit_for_review` (202 / 422 preflight),
  `autopilot` block in `GET /v1/agents/{id}/launch`, and the outbound webhook event
  `agent.review_status_changed` (signed, 2xx acknowledges) (#331).

## [0.16.0] — 2026-07-31

### Added
- `PATCH /v1/agents/{id}` — update mutable agent profile fields (display name,
  description, logo/hero, color, privacy/terms).
- `PATCH /v1/brands/{google_brand_id}/verification_contact` — set the brand's
  verification-contact fields; response includes `verification_contact_missing` (#311).

## [0.15.0] — 2026-07-27

### Added
- `POST /v1/agents/{agent_id}/messages/typing` — RBM `IS_TYPING` indicator; not
  persisted, not billed, `202 {"status":"typing_sent"}` (#305).

## [0.14.0] — 2026-06-21

### Added
- `POST /api/v1/analytics/events` — public browser/device analytics ingestion,
  authenticated by a per-agent write-key in the body plus an Origin allow-list;
  rate-limited to 120 requests/min/IP (#293).
- `/docs/swagger` (Swagger UI) and `/docs/openapi.json` (#289).

## [0.13.0] — 2026-06-17

### Added
- `GET`/`POST`/`DELETE /v1/brands/{google_brand_id}/documents` — list, upload
  (PDF, ≤ 50 MB, max 5) and delete brand supporting documents (#281).

## [0.12.0] — 2026-06-13

### Added
- `POST /v1/agents`: contact lists `phones[]`, `emails[]`, `websites[]` (max 3
  each); `logo_uri`, `hero_uri`, `color`, `privacy_*`, `terms_*`, `websites` are
  persisted and returned on the agent (#269).

### Changed
- `POST /v1/agents` requires at least one contact method (`contacts: missing`
  otherwise). The legacy single `website_uri` still counts as one (#269).
- `GET /v1/carriers` returns only the German carriers (#270).

## [0.11.0] — 2026-06-10

### Changed
- Opt-in (`START`, …) removes the phone from the per-agent blacklist immediately on
  receipt; the tenant acknowledgement is audit-of-record only and no longer
  gates the de-blacklisting (#264).

## [0.10.0] — 2026-06-05

### Added
- `GET /v1/brands`, `GET /v1/brands/{google_brand_id}`,
  `GET /v1/brands/{google_brand_id}/verification` (#255).
- `GET /v1/agents/{id}/launch` — aggregated status plus per-carrier
  `launch_details[]` (#255).
- `GET /v1/carriers` (#255).
- `GET`/`POST /v1/agents/{id}/launch_questionnaire_draft` — customer-submitted
  launch questionnaire (#257).

## [0.9.0] — 2026-05-29

### Added
- `GET /v1/agents/{id}/capabilities?phone=` — RCS reachability with feature list
  (#250).
- `POST /v1/agents/{id}/capabilities/batch` — up to 100 phones, `skipped` reasons
  `invalid_phone` / `not_in_allowlist` (#251).

## [0.8.0] — 2026-05-24

### Added
- `POST /v1/agents/{agent_id}/messages/final` — one single-use post-opt-out
  message authorised by `consent_audit_log_id` (#227).
- Webhook: `DELIVERED` / `READ` delivery events are forwarded to the tenant
  `webhook_url` as-is (#226).
- Webhook: opt-out/opt-in forwards include `consent_audit_log_id` (#227).

### Changed
- Opt-out/opt-in keyword detection matches the **first word** of the user
  message (was: exact match), so `START <token>` is recognised (#226).

## [0.7.0] — 2026-05-14

### Added
- `402 Payment Required` with `error_key: quota_exceeded_free_tier` on message
  endpoints when a hard-capped plan is exhausted; `agent_status_at_send` on
  `MessageLog` (#170).
- `GET /api/v1/public/plans`: `meta.version`, `meta.schema_version`, `meta.count`
  and `track` / `metric` / `sort_order` per plan (#189, #192).
- `track` (`conversation` | `newsletter`) on the `Agent` object (#199).

## [0.6.0] — 2026-05-01

### Added
- `GET /api/v1/public/plans` — public, unauthenticated plan/pricing list with
  `ETag` / `Last-Modified` / 304 support (#160).

## [0.5.0] — 2026-04-24

### Added
- `POST /v1/agents/{agent_id}/messages/video`, `/image` and `/xl` (2–3 messages,
  `delay_seconds`, partial-success 422 with `failed_at`) (#135).
- Pre-launch sends to **accepted testers** are allowed; other recipients get
  `422 Agent not launched` (#132).
- `message_type`, `content`, `direction` on `MessageLog` (#132).

### Changed
- **Breaking (0.x):** `POST /v1/agents/{agent_id}/messages` takes
  `{phone, content_message}` (full Google `contentMessage`, validated with stable
  error codes) instead of `{phone, text}` (#132).

## [0.4.0] — 2026-04-20

### Added
- Testers: `GET`/`POST /v1/agents/{agent_id}/testers`,
  `DELETE /v1/agents/{agent_id}/testers/{id}`,
  `GET /v1/agents/{agent_id}/testers/{id}/status` (#113).

### Changed
- Tenant country allow-list (`allowed_countries`, default DE): recipients outside
  it are rejected with `400`; the message lists the allowed prefixes (#107).

## [0.3.0] — 2026-04-17

Rewrite of the public surface around tenant-owned agents.

### Added
- `POST /v1/agents` — create an agent at Google under the tenant brand (DRAFT).
- `POST /v1/agents/{agent_id}/messages` — send a text message (`{phone, text}`).
- Tenant webhook forwarding: user messages are POSTed to the tenant's
  `webhook_url`, signed with `X-RCS-Box-Signature` (HMAC-SHA256, API key);
  `STOP`/`START` keywords maintain a per-agent blacklist and are forwarded with
  `rcs_box_action` (`OPT_OUT` / `OPT_IN`) requiring `{"acknowledged": true}`.
- `403 Forbidden` on sends to blacklisted phones.

### Removed
- The 0.2.0 Google proxy endpoints under `/api/v1/brands/...`, `/api/v1/phones/...`,
  `/api/v1/files`, `/api/v1/users/batchGet`.

## [0.2.0] — 2026-01-20

### Added
- Google Business Communications proxy: `/api/v1/brands/{brand}/agents` (CRUD +
  `requestLaunch`), `.../integrations`, `.../testers`,
  `/api/v1/phones/{phone}/agentMessages`, `/api/v1/files`,
  `/api/v1/users/batchGet`, `/api/v1/phones/{phone}/capabilities` (#11–#16).

## [0.1.0] — 2026-01-19

### Added
- API-key authentication (`Authorization: Bearer` or `X-API-Key`), `401` for
  missing/invalid keys, `403` for inactive tenants (#7).
- `GET /health` (public), `GET /v1/health`, `GET /v1/agents`, `GET /v1/agents/{id}`.
- `POST /webhooks/google` — inbound Google callback endpoint.
