API v1.11.0

Raw Markdown: changelog.md

API contract · current version v1.11.0

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 and the project adheres to Semantic Versioning:

  • 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.