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
/v1path 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}takesphones,emailsandwebsites. 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 (422contacts: 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 carrieswindow(24h,7dor30d). A metric with fewer than 20 samples in the last 24 hours now falls back to the last 7, then 30 days instead of beingnull;nullonly 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
/statusand its data asGET /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 unchangederrorlabel and HTTP status. Branch oncode; the full list is in the API docs ("Error codes") and in theErrorCodeschema. - 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— plusgoogle_statuswith Google's own status (e.g.NOT_FOUND). - The 429 of the analytics ingestion endpoint carries
code: rate_limited, a failedsubmit_for_reviewdry runcode: preflight_failed, and a malformed JSON body now gets a JSON 400 withcode: 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/:idacceptstrack(newsletter|flex) to switch aNON_CONVERSATIONALdraft agent between Community (MAU) and Flex (per message). Box-local, no Google call. Only while the agent isDRAFTand the autopilot is not working on it, otherwise422track: locked_after_submit;track: not_allowed_for_billing_category/track: invalid_valuefor tariffs the agent cannot run in.
Changed
POST /v1/agents/:id/submit_for_reviewrequiresmau_intervalandmau_expected_contactsfor every agent on thenewslettertrack, 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 totrack: flexfor 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/agentobject); 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/READevents, Agent-Autopilot notifications) go to that URL instead of the tenant'swebhook_url;null(the default for every existing agent) keeps the tenant webhook. Signature unchanged (HMAC with the tenant API key). POST /v1/agentsandPATCH /v1/agents/{id}acceptwebhook_url(HTTPS only, otherwise422webhook_url: invalid_format).PATCHwithnullor""removes the override. Changing onlywebhook_urlmakes no call to Google.- The Agent object returns
webhook_url.
[1.5.3] — 2026-10-28
Changed
- No contract change:
config/routes.rbgained 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.rbgained 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
trackvalueflexforNON_CONVERSATIONALagents billed per sent message, without the carriers' MAU approval. Flex agents never trigger the automatic MAU approval request, and the MAU preflight onPOST /v1/agents/:id/submit_for_reviewapplies to thenewslettertrack only. POST /v1/agentsaccepts an optionaltrack:newsletter(default) orflexforNON_CONVERSATIONAL,conversation(default) forCONVERSATIONAL. Other combinations answer422track: not_allowed_for_billing_category. Previously a suppliedtrackwas silently ignored.GET /api/v1/public/planslists the Flex plans (flex_s,flex_m,flex_l) withtrackflex,metricmessage, taglinePER MESSAGEand astandardquota ofcomponent_typesingle/overage_unitper_message. Clients grouping plans bytrackget a third group.
Changed
GET /api/v1/public/plans: the Conversation plans'taglineis nowPER CONVERSATION(wasPER MESSAGE; they are billed per 24-hour conversation).billing_categoryonly knows Google's current categoriesCONVERSATIONALandNON_CONVERSATIONAL.POST /v1/agentsanswers422billing_category: invalid_valuefor the legacyBASIC_MESSAGE,SINGLE_MESSAGE,CONVERSATIONAL_MESSAGEandBASIC_MESSAGE_V2— Google stopped accepting them on 2026-02-18, so such requests already failed withGoogle 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_draftacceptsmau_interval(daily|weekly|biweekly|monthly) andmau_expected_contacts(whole number ≥ 1) for newsletter (NON_CONVERSATIONAL) agents. Invalid values answer422withinvalid_value/must_be_positive_integer.GET /v1/agents/:id/launch_questionnaire_draftreturnsmau_request_sent_at(nulluntil 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
carriersdo not trigger it. With the setting on,POST /v1/agents/:id/submit_for_reviewrequires both MAU fields forNON_CONVERSATIONALagents (preflight error otherwise).
[1.3.3] — 2026-10-22
Changed
- Documentation only, no contract change: the explanatory prose of the former
hand-written
/docspage (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:
/docsis now an interactive API reference (Scalar) rendered from the OpenAPI document./docs/swaggerpermanently redirects (301) to/docs./docs/openapi.jsonand/docs/changelogare unchanged. The previous hand-written page stays reachable at/docs/legacyfor 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 missingisArchivedin 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
/v1request (agent create/update, unlaunch, submit_for_review, tester invites, questionnaire drafts, assets, brand documents, brand contact) answers423 Lockedwith{ "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) andblock(object ornull). GET /v1/health:tenant: { name, blocked, block }.
[1.2.0] — 2026-10-17
Added
autopilot.state(agent payload) / webhookautopilot_statecan now beHOLDING_LAUNCH: the brand verification is through, but the agent's operator-configured release schedule has not yet released the launch. Clients must treat it likeAWAITING_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 aslogo_uri/hero_uritoPOST /v1/agentsorPATCH /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/batchno longer fails with422 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-Signatureon 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;trackis not accepted and was removed from the request schema;website_uri/website_labeldocumented; enums forhosting_region(incl.ASIA_PACIFIC),billing_category,use_case.- Documented the full
Agent,MessageLogandTesterresponse shapes, theAgentStatusenum (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/agentsaccepts optionalgoogle_brand_idorexternal_refto 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.jsonand/docs/swaggerare 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 viaexternal_ref(or, best-effort,display_name);external_reffield on theBrandobject (#350).
Changed
- Agent-Autopilot state
LAUNCHEDrenamed toMONITORING(autopilot.stateinGET /v1/agents/{id}/launch,autopilot_statein theagent.review_status_changedwebhook) (#349).
[0.19.0] — 2026-09-17
Added
agent.review_status_changedwebhook carrieslaunch_details[](region, launch_state, comment, update_time) mirroringGET /v1/agents/{id}/launch(#346).error_kind: "suspended"is emitted (once per suspension, recoverable) whileAWAITING_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: optionalcarriers[](carrier slugs orgoogle_region_name) to scope the launch, anddry_run=truereturning{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),autopilotblock inGET /v1/agents/{id}/launch, and the outbound webhook eventagent.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 includesverification_contact_missing(#311).
[0.15.0] — 2026-07-27
Added
POST /v1/agents/{agent_id}/messages/typing— RBMIS_TYPINGindicator; 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 listsphones[],emails[],websites[](max 3 each);logo_uri,hero_uri,color,privacy_*,terms_*,websitesare persisted and returned on the agent (#269).
Changed
POST /v1/agentsrequires at least one contact method (contacts: missingotherwise). The legacy singlewebsite_uristill counts as one (#269).GET /v1/carriersreturns 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-carrierlaunch_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,skippedreasonsinvalid_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 byconsent_audit_log_id(#227).- Webhook:
DELIVERED/READdelivery events are forwarded to the tenantwebhook_urlas-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 Requiredwitherror_key: quota_exceeded_free_tieron message endpoints when a hard-capped plan is exhausted;agent_status_at_sendonMessageLog(#170).GET /api/v1/public/plans:meta.version,meta.schema_version,meta.countandtrack/metric/sort_orderper plan (#189, #192).track(conversation|newsletter) on theAgentobject (#199).
[0.6.0] — 2026-05-01
Added
GET /api/v1/public/plans— public, unauthenticated plan/pricing list withETag/Last-Modified/ 304 support (#160).
[0.5.0] — 2026-04-24
Added
POST /v1/agents/{agent_id}/messages/video,/imageand/xl(2–3 messages,delay_seconds, partial-success 422 withfailed_at) (#135).- Pre-launch sends to accepted testers are allowed; other recipients get
422 Agent not launched(#132). message_type,content,directiononMessageLog(#132).
Changed
- Breaking (0.x):
POST /v1/agents/{agent_id}/messagestakes{phone, content_message}(full GooglecontentMessage, 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 with400; 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 withX-RCS-Box-Signature(HMAC-SHA256, API key);STOP/STARTkeywords maintain a per-agent blacklist and are forwarded withrcs_box_action(OPT_OUT/OPT_IN) requiring{"acknowledged": true}. 403 Forbiddenon 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: BearerorX-API-Key),401for missing/invalid keys,403for inactive tenants (#7). GET /health(public),GET /v1/health,GET /v1/agents,GET /v1/agents/{id}.POST /webhooks/google— inbound Google callback endpoint.