RCS-Box API

Multi-Tenant API Gateway for Google RCS Business Messaging. Send text messages via your brand's agents, list your agents, and receive user-message callbacks — all through a single, authenticated API.

Multi-Tenant

Brand-level isolation with dedicated API keys per tenant.

Tenant allow-list

Each tenant has a configurable country allow-list (default: +49). The send endpoint rejects numbers outside it.

Opt-out Management

Built-in STOP/START blacklist handling per agent.

Base URL

All API requests are made to the following base URL:

https://api.box.rcs.jetzt

All endpoints use JSON for request and response bodies.

Authentication

All /v1/* endpoints require an API key. Each tenant receives a unique 64-character hex key. You can authenticate using either method:

Option 1: Authorization Header (recommended)

curl -H "Authorization: Bearer $API_KEY" \
     https://api.box.rcs.jetzt/v1/agents

Option 2: X-API-Key Header

curl -H "X-API-Key: $API_KEY" \
     https://api.box.rcs.jetzt/v1/agents

Important: Keep your API key secret. Do not expose it in client-side code or public repositories. If compromised, contact your administrator to regenerate the key.

Endpoints

Health

GET /health No auth required

Public health check endpoint. Use this to verify the API is running.

Response

{
  "status": "ok",
  "timestamp": "2026-03-26T12:00:00Z"
}
GET /v1/health Auth required

Authenticated health check. Verifies both API availability and API key validity.

Response

{
  "status": "ok",
  "version": "v1",
  "timestamp": "2026-03-26T12:00:00Z"
}

Agents

GET /v1/agents Auth required

Returns all RCS agents belonging to your tenant's brand. Only agents associated with your brand are visible.

Example Request

curl -H "Authorization: Bearer $API_KEY" \
     https://api.box.rcs.jetzt/v1/agents

Response

{
  "agents": [
    {
      "id": 1,
      "tenant_id": 1,
      "google_agent_id": "brands/acme-corp/agents/my-agent",
      "brand_id": "acme-corp",
      "name": "ACME Support Bot",
      "display_name": "ACME Support Bot",
      "description": "ACME customer support agent for order questions.",
      "status": "LAUNCHED",
      "hosting_region": "EUROPE",
      "billing_category": "CONVERSATIONAL",
      "use_case": "PROMOTIONAL",
      "synced_at": "2026-03-25T10:30:00Z",
      "created_at": "2026-01-20T14:00:00Z",
      "updated_at": "2026-03-25T10:30:00Z"
    }
  ]
}

Agent Statuses

Status Description
DRAFTAgent created but not yet submitted for launch
LAUNCHINGAgent is under review by Google
LAUNCHEDAgent is live and can send messages
GET /v1/agents/:id Auth required

Returns details for a specific agent. The agent must belong to your tenant.

Path Parameters

Parameter Type Description
idintegerThe agent's internal ID

Example Request

curl -H "Authorization: Bearer YOUR_API_KEY" \
     https://rcs-box.example.com/v1/agents/1

Response

{
  "agent": {
    "id": 1,
    "tenant_id": 1,
    "google_agent_id": "brands/acme-corp/agents/my-agent",
    "brand_id": "acme-corp",
    "name": "ACME Support Bot",
    "display_name": "ACME Support Bot",
    "description": "ACME customer support agent for order questions.",
    "status": "LAUNCHED",
    "hosting_region": "EUROPE",
    "billing_category": "CONVERSATIONAL",
    "use_case": "PROMOTIONAL",
    "synced_at": "2026-03-25T10:30:00Z",
    "created_at": "2026-01-20T14:00:00Z",
    "updated_at": "2026-03-25T10:30:00Z"
  }
}

Error Response (404)

{
  "error": "Not Found",
  "message": "Agent not found"
}
POST /v1/agents Auth required

Create a new RCS agent under your tenant's brand. The agent starts in DRAFT status and must be launched by an administrator before it can send messages.

Image URIs must be reachable by Google

When you call this endpoint, Google fetches logo_uri and hero_uri server-side and stores the binary in its own CDN. The URLs must be publicly reachable HTTPS at request time — localhost, private networks and unresolved hosts produce a Google URL_ERROR / INVALID_ARGUMENT and the agent is not created.

End-user devices cache these images aggressively — once a phone has displayed an agent's logo/hero, updates via PATCH (updateMask=logoUri) may not be reflected until the cache expires or the app data is cleared. Upload your production-quality assets before launching; don't rely on being able to swap a placeholder later.

Request Body

Field Type Required Description
display_namestringYesHuman-readable name shown to end users (max. 40 chars)
descriptionstringYesShort description of the agent (max. 100 chars)
logo_uristringYesPublicly reachable HTTPS URL — PNG/JPG, 224×224 px, max. 50 KB
hero_uristringYesPublicly reachable HTTPS URL — PNG/JPG, 1440×448 px, max. 200 KB
colorstringYesBrand color as hex (e.g. #1DA1F2)
privacy_uristringYesHTTPS URL to your privacy policy
privacy_labelstringNoDisplay label for the privacy link (default: Datenschutz)
terms_uristringYesHTTPS URL to your terms & conditions / Impressum
terms_labelstringNoDisplay label for the terms link (default: Impressum)
phonesarrayNo*Up to 3 entries of {"number", "label"} — number in E.164 format (e.g. +4922199999701), label defaults to Telefon
emailsarrayNo*Up to 3 entries of {"address", "label"} — label defaults to E-Mail
websitesarrayNo*Up to 3 entries of {"uri", "label"} — HTTPS only, label defaults to Webseite
website_uristringNo*Legacy single-website shorthand — HTTPS only, counts as the first websites entry
website_labelstringNoDisplay label for website_uri (default: Webseite)
hosting_regionstringYesOne of EUROPE, NORTH_AMERICA, ASIA_PACIFIC
billing_categorystringYesOne of CONVERSATIONAL, NON_CONVERSATIONAL, BASIC_MESSAGE, SINGLE_MESSAGE, CONVERSATIONAL_MESSAGE, BASIC_MESSAGE_V2
use_casestringNoOne of TRANSACTIONAL, PROMOTIONAL, OTP, MULTI_USE (default: PROMOTIONAL)

* At least one contact method is required

Google shows the agent's contact details on its info sheet and requires at least one. Provide any combination of phones, emails and websites (max. 3 entries each) — or the legacy website_uri. Requests without any contact are rejected with {"errors": {"contacts": "missing"}}.

Example Request

curl -X POST \
     -H "Authorization: Bearer $API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "display_name": "ACME Support Bot",
       "description": "ACME customer support agent for order questions.",
       "logo_uri": "https://acme.example.com/logo-224.png",
       "hero_uri": "https://acme.example.com/hero-1440x448.png",
       "color": "#1DA1F2",
       "privacy_uri": "https://acme.example.com/privacy",
       "terms_uri": "https://acme.example.com/terms",
       "phones": [{"number": "+4922199999701", "label": "Hotline"}],
       "emails": [{"address": "support@acme.example.com"}],
       "websites": [{"uri": "https://acme.example.com", "label": "Webseite"}],
       "hosting_region": "EUROPE",
       "billing_category": "CONVERSATIONAL",
       "use_case": "PROMOTIONAL"
     }' \
     https://api.box.rcs.jetzt/v1/agents

Smoke testing without real brand assets

For pure end-to-end smoke tests, dummyimage.com returns a real PNG at the exact dimensions and is publicly reachable by Google. Use it only for throwaway test agents — do not ship to production with placeholder images, because end-user phones will cache them.

"logo_uri": "https://dummyimage.com/224x224/1da1f2/ffffff.png",
"hero_uri": "https://dummyimage.com/1440x448/1da1f2/ffffff.png"

Error Response (422 — local validation)

When required fields are missing or invalid, you get a field-keyed hash of stable error codes before any call to Google. Codes are language-neutral so you can map them to your own UX:

{
  "error": "Validation failed",
  "errors": {
    "description": "missing",
    "hero_uri": "missing",
    "color": "invalid_format"
  }
}

Possible codes: missing, too_long, invalid_format, invalid_value, too_many (contact lists over 3 entries). A request without any contact method fails with {"errors": {"contacts": "missing"}}.

Error Response (422 — Google rejected payload)

If local validation passes but Google rejects the request (e.g. an image URL it can't fetch, a permission problem, or a constraint it enforces server-side), the upstream message is forwarded:

{
  "error": "Google API Error",
  "message": "Unexpected response: 400 - The supplied file URL https://example.com/logo.png is invalid, status: URL_ERROR"
}

Response (201)

{
  "agent": {
    "id": 7,
    "tenant_id": 1,
    "google_agent_id": "brands/acme-corp/agents/support-bot-xyz",
    "brand_id": "acme-corp",
    "name": "ACME Support Bot",
    "display_name": "ACME Support Bot",
    "description": "ACME customer support agent for order questions.",
    "status": "DRAFT",
    "hosting_region": "EUROPE",
    "billing_category": "CONVERSATIONAL",
    "use_case": "PROMOTIONAL",
    "synced_at": "2026-04-20T09:00:00Z",
    "created_at": "2026-04-20T09:00:00Z",
    "updated_at": "2026-04-20T09:00:00Z"
  }
}

Messages

POST /v1/agents/:agent_id/messages Auth required

Send any RCS message type via a specific agent — text, rich card, carousel, file, or text with interactive suggestions. The agent must belong to your tenant.

Pre-launch sends are restricted to accepted testers

If the agent is in LAUNCHED status, it can send to any recipient (subject to the tenant's country allow-list and per-agent blacklist). If the agent is still in DRAFT or any other pre-launch status, the recipient phone must be registered as a tester on that agent and have accepted the invitation (tester status = "ACCEPTED"). Otherwise the request returns 422 Agent not launched.

This mirrors Google's RCS tester workflow: register testers via POST /v1/agents/:id/testers, the user receives an SMS invite from Google, and once they accept the invite the tester's status flips to ACCEPTED. Phone matching is done by normalized digits — spaces and dashes are ignored.

Request Body

Field Type Required Description
phonestringYesRecipient phone number in E.164 format. Must match one of the tenant's configured country prefixes (default: +49).
content_messageobjectYesThe Google RCS contentMessage payload. Must contain exactly one of text, richCard, contentInfo, fileName, or uploadedRbmFile, optionally plus suggestions (max 11).

The content_message shape mirrors Google's RCS Business Messaging AgentContentMessage — see the sub-sections below for each supported type.

Response (201)

{
  "message": {
    "id": 42,
    "tenant_id": 3,
    "agent_id": 7,
    "google_message_id": "phones/+491701234567/agentMessages/msg-abc-123",
    "phone": "+491701234567",
    "status": "SENT",
    "message_type": "text",
    "content": { "text": "Hello!" },
    "direction": "outbound",
    "sent_at": "2026-04-24T12:00:00Z",
    "delivered_at": null,
    "error_message": null,
    "created_at": "2026-04-24T12:00:00Z",
    "updated_at": "2026-04-24T12:00:00Z"
  }
}

message_type is one of text, rich_card, carousel, file.

Text message

The simplest message type. Max 3072 characters.

curl -X POST \
     -H "Authorization: Bearer $API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "phone": "+491701234567",
       "content_message": {
         "text": "Hallo! Willkommen bei ACME."
       }
     }' \
     https://api.box.rcs.jetzt/v1/agents/7/messages

Text with suggestions

Suggestions are interactive chips shown below the message. You can mix any of the four action types per message — up to 11 suggestions total.

Suggestion types

  • reply — tapping sends a reply back (you receive postbackData via webhook)
  • action.openUrlAction — opens an external URL
  • action.dialAction — starts a phone call
  • action.viewLocationAction — opens a location on a map
curl -X POST \
     -H "Authorization: Bearer $API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "phone": "+491701234567",
       "content_message": {
         "text": "Wie können wir helfen?",
         "suggestions": [
           {
             "reply": {
               "text": "Bestellung verfolgen",
               "postbackData": "track_order"
             }
           },
           {
             "action": {
               "text": "Website öffnen",
               "postbackData": "open_site",
               "openUrlAction": { "url": "https://acme.example.com" }
             }
           },
           {
             "action": {
               "text": "Hotline anrufen",
               "postbackData": "call_hotline",
               "dialAction": { "phoneNumber": "+498000000000" }
             }
           },
           {
             "action": {
               "text": "Filiale auf Karte",
               "postbackData": "show_store",
               "viewLocationAction": {
                 "latLong": { "latitude": 50.9413, "longitude": 6.9583 },
                 "label": "ACME Köln"
               }
             }
           }
         ]
       }
     }' \
     https://api.box.rcs.jetzt/v1/agents/7/messages

Standalone Rich Card

A card with title, description, media and up to 4 actions. Use cardOrientation: VERTICAL for full-width media at the top, HORIZONTAL for side-by-side. Media height: SHORT, MEDIUM, or TALL.

curl -X POST \
     -H "Authorization: Bearer $API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "phone": "+491701234567",
       "content_message": {
         "richCard": {
           "standaloneCard": {
             "cardOrientation": "VERTICAL",
             "cardContent": {
               "title": "Neuer Hoodie 🔥",
               "description": "Limited Edition — jetzt im Shop.",
               "media": {
                 "height": "MEDIUM",
                 "contentInfo": {
                   "fileUrl": "https://cdn.example.com/hoodie.jpg",
                   "thumbnailUrl": "https://cdn.example.com/hoodie-thumb.jpg"
                 }
               },
               "suggestions": [
                 {
                   "action": {
                     "text": "Jetzt kaufen",
                     "postbackData": "buy_hoodie",
                     "openUrlAction": { "url": "https://shop.example.com/hoodie" }
                   }
                 },
                 {
                   "reply": { "text": "Mehr Infos", "postbackData": "info_hoodie" }
                 }
               ]
             }
           }
         }
       }
     }' \
     https://api.box.rcs.jetzt/v1/agents/7/messages

File / Media

Send a file (image, PDF, video, audio) either by URL or by referencing a file previously uploaded to Google's RBM file store.

By URL

Use contentInfo on its own — it is its own member of Google's contentMessage oneof, not a companion to fileName. Combining them returns Google 400 INVALID_ARGUMENT (“oneof field 'content' is already set”).

curl -X POST \
     -H "Authorization: Bearer $API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "phone": "+491701234567",
       "content_message": {
         "contentInfo": {
           "fileUrl": "https://cdn.example.com/rechnungen/12345.pdf",
           "thumbnailUrl": "https://cdn.example.com/rechnungen/12345-thumb.jpg"
         }
       }
     }' \
     https://api.box.rcs.jetzt/v1/agents/7/messages

Via uploaded RBM file

curl -X POST \
     -H "Authorization: Bearer $API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "phone": "+491701234567",
       "content_message": {
         "uploadedRbmFile": {
           "fileName": "files/abc123",
           "thumbnailName": "files/abc123-thumb"
         }
       }
     }' \
     https://api.box.rcs.jetzt/v1/agents/7/messages

Common errors

Invalid content_message (422)

{
  "error": "Invalid content_message",
  "errors": { "text": "too_long" }
}

Stable error codes per field:

  • missing — required field absent or empty
  • too_long — string exceeds max length (text: 3072)
  • too_many — array exceeds limit (suggestions: 11, card suggestions: 4)
  • invalid_value — value not in the allowed enum (e.g. cardOrientation)
  • invalid_size — carousel needs 2–10 cards
  • invalid_structure — wrong shape for the given type
  • missing_primary — need one of text, richCard, contentInfo, fileName, uploadedRbmFile
  • multiple_primary — only one primary key allowed per message
  • unknown_keyscontent_message contains unsupported fields

Phone not in tenant allow-list (400)

The Allowed prefixes list reflects your tenant's configured allowed_countries at request time. Example for a tenant configured with Germany and Austria:

{
  "error": "Bad Request",
  "message": "Phone number is not in the tenant's allow-list. Allowed prefixes: +49, +43"
}

Agent not launched (422)

Returned when the agent is pre-launch and the recipient is not an accepted tester. Register the number as a tester and have them accept the invite before retrying.

{
  "error": "Agent not launched",
  "message": "Agent '…' is in status DRAFT. Before launch, only accepted testers may receive messages — register +491701234567 as a tester and have them accept the invitation."
}

Phone blacklisted (403)

Returned when the recipient is on this agent's blacklist. Two distinct cases share this response: (1) the user sent a STOP/opt-out keyword — clears once they send START/opt-in; and (2) an operator added the number manually via the admin blacklist — clears only when an operator removes it. The error body is identical in both cases.

{
  "error": "Forbidden",
  "message": "Phone number has opted out for this agent"
}

Note: STOP/START handling is managed internally — when a user replies "STOP" (or German equivalents) the phone number is added to the agent's blacklist. The original payload is still forwarded to your webhook_url, but enriched with an rcs_box_action field (OPT_OUT / OPT_IN) so you can mirror the state in your own system — see the Opt-out / Opt-in section. The send endpoint rejects blacklisted numbers with 403 Forbidden.

Quota exceeded (402)

Returned when the tenant's plan has a hard cap and the next send would exceed it (today: the free plan). Paid plans never return 402; they accept the send and accrue overage usage. The error_key field is the machine-readable reason — branch on it rather than parsing message.

{
  "error": "Quota exceeded",
  "error_key": "quota_exceeded_free_tier",
  "message": "Quota exceeded for plan 'free': 100/100 session(s) this period"
}

Note: Pre-launch sends to accepted testers do not count toward the quota — only sends made while the agent is LAUNCHED are billable. Quotas reset at the start of each calendar month.

POST /v1/agents/:agent_id/messages/video

Convenience wrapper: sends a URL inside a plain text message so the RCS client auto-unfurls it into a native preview. Works for YouTube, Vimeo and direct video links — the client renders a tappable thumbnail without you having to upload or host the media yourself. An optional caption is prepended on its own two lines.

Request Body

Field Type Description
phonestringRecipient phone in E.164 format (e.g. +491791234567).
video_urlstringAny URL the RCS client will unfurl (YouTube, Vimeo, direct MP4, …).
captionstring, optionalText shown above the preview. Blank = URL only.

Example

curl -X POST \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "phone": "+491791234567",
       "video_url": "https://youtu.be/kg9bFxsJXlE",
       "caption": "🎥 RCS in 90 Sekunden"
     }' \
     https://api.box.rcs.jetzt/v1/agents/7/messages/video

Internally this sends a plain {"text": "caption\n\nurl"} contentMessage. The response shape is identical to Send Message.

POST /v1/agents/:agent_id/messages/image

Convenience wrapper that sends a standalone image by URL — the RCS client downloads and renders it inline. Accepts an optional thumbnail_url for a lighter preview before the full image loads.

Request Body

Field Type Description
phonestringRecipient phone in E.164 format.
image_urlstringPublicly reachable HTTPS URL to the full-size image.
thumbnail_urlstring, optionalLightweight preview loaded first.

Example

curl -X POST \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "phone": "+491791234567",
       "image_url": "https://dummyimage.com/1080x720/1DA1F2/ffffff.png&text=Hallo",
       "thumbnail_url": "https://dummyimage.com/270x180/1DA1F2/ffffff.png&text=Hallo"
     }' \
     https://api.box.rcs.jetzt/v1/agents/7/messages/image

Internally this sends a {"contentInfo": {"fileUrl": "…", "thumbnailUrl": "…"}} contentMessage. The response shape is identical to Send Message.

POST /v1/agents/:agent_id/messages/xl

Sends 2–3 content messages in sequence to the same phone. Maps to Telekom's XL Message billing concept: up to three messages delivered within 60 seconds are consolidated into a single billing event, so you can split a richer payload (e.g. text + image + text) across multiple bubbles without paying three times.

Fail-fast with partial success

All messages are validated up-front. If one is invalid, nothing is sent and a 422 is returned with failed_at pointing at the offending index. If Google rejects a message mid-sequence, the endpoint returns 422 with the successfully-sent logs in messages plus failed_at and the error on the first failure. Already-sent messages remain delivered — there is no rollback.

Request Body

Field Type Description
phonestringRecipient phone in E.164 format.
messagesarray (2–3)Each item is a full content_message object — text, richCard, contentInfo etc.
delay_secondsnumber, optionalPause between sends so the handset receives them in order. Defaults to 2.0, clamped to [0, 10]. Google delivers sequential sends async to the handset — without a buffer, messages can arrive out of order.

Example

curl -X POST \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "phone": "+491791234567",
       "messages": [
         { "text": "🎉 Unsere neue Kollektion ist da!" },
         { "contentInfo": { "fileUrl": "https://dummyimage.com/1080x720" } },
         { "text": "Jetzt im Shop: https://example.com/shop" }
       ]
     }' \
     https://api.box.rcs.jetzt/v1/agents/7/messages/xl

Success (201)

{
  "messages": [
    { "id": 101, "google_message_id": "…/msg-1", "message_type": "text",  … },
    { "id": 102, "google_message_id": "…/msg-2", "message_type": "file",  … },
    { "id": 103, "google_message_id": "…/msg-3", "message_type": "text",  … }
  ]
}

Partial success — Google rejects item #1 (422)

{
  "messages":   [ { "id": 101, …, "message_type": "text" } ],
  "failed_at":  1,
  "error":      "Google API Error",
  "message":    "…"
}
POST /v1/agents/:agent_id/messages/final

Single-use "goodbye" send that bypasses the per-agent blacklist for ONE message immediately after a user opts out. Authorized via the consent_audit_log_id that RCS-Box passed in the OPT_OUT forward — see the Opt-out / Opt-in section. Use it for the polite "Tschüss + STOP-Hinweis-Wiederholung"-Pattern; every subsequent send to that phone still hits the normal 403 Forbidden.

Tightly scoped exception, not a removal

The DSGVO fail-safe stays intact: the phone is blacklisted synchronously when the user sent STOP, and that doesn't change. The bypass is consumed by the first call within 5 minutes after the tenant acked the OPT_OUT and is then dead. A second call returns 403.

Request Body

Field Type Description
phonestringE.164 phone of the user who opted out. Must match the audit log's normalized phone.
consent_audit_log_idintegerThe ID RCS-Box added to the OPT_OUT forward payload. Must belong to your tenant, target this agent and phone, be acked, and not yet consumed.
content_messageobjectSame shape as POST /messages.

Example

curl -X POST \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "phone": "+491791234567",
       "consent_audit_log_id": 12345,
       "content_message": {
         "text": "👋 Du bist abgemeldet. Falls du's anders überlegst: einfach START zurück."
       }
     }' \
     https://api.box.rcs.jetzt/v1/agents/7/messages/final

Error responses

HTTP When
404The consent_audit_log_id is unknown to your tenant.
403Audit log belongs to a different agent or phone, is not OPT_OUT, isn't acked, has already been consumed, or the 5-min window has expired.
400Phone is outside the tenant's allowed country prefixes.
422Content message validation failed (same shape as POST /messages).

Testers

While an agent is in DRAFT, Google only delivers messages to registered tester phone numbers. Use these endpoints to manage the tester allow-list for each of your agents. Phones must be in the tenant's allowed country prefixes (V1: +49 only).

GET /v1/agents/:agent_id/testers Auth required

Lists testers registered locally for the given agent.

Example Request

curl -H "Authorization: Bearer $API_KEY" \
     https://api.box.rcs.jetzt/v1/agents/7/testers

Response (200)

{
  "testers": [
    {
      "id": 12,
      "agent_id": 7,
      "phone": "+491791234567",
      "status": "ACCEPTED",
      "google_resource_name": "brands/acme-corp/agents/my-agent/testers/+491791234567",
      "created_at": "2026-04-20T10:00:00Z",
      "updated_at": "2026-04-20T10:05:00Z"
    }
  ]
}
POST /v1/agents/:agent_id/testers Auth required

Registers a phone number as a tester for the given agent. Forwards to Google Business Communications and stores the result locally. Google triggers an RCS invitation on the device.

Request Body

Field Type Required Description
phonestringYesE.164 phone number (e.g. +491791234567). Must match tenant's allowed prefixes.

Example Request

curl -X POST \
     -H "Authorization: Bearer $API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"phone":"+491791234567"}' \
     https://api.box.rcs.jetzt/v1/agents/7/testers

Response (201)

{
  "tester": {
    "id": 12,
    "agent_id": 7,
    "phone": "+491791234567",
    "status": "INVITED",
    "google_resource_name": "brands/acme-corp/agents/my-agent/testers/+491791234567",
    "created_at": "2026-04-20T10:00:00Z",
    "updated_at": "2026-04-20T10:00:00Z"
  }
}

Error — Phone not in allow-list (400)

Prefixes reflect the tenant's allowed_countries at request time (example shown for DE + AT):

{
  "error": "Bad Request",
  "message": "Phone number is not in the tenant's allow-list. Allowed prefixes: +49, +43"
}
DELETE /v1/agents/:agent_id/testers/:id Auth required

Removes a tester both at Google and locally. If Google reports the tester as already gone (404), the local record is still removed.

Example Request

curl -X DELETE \
     -H "Authorization: Bearer $API_KEY" \
     https://api.box.rcs.jetzt/v1/agents/7/testers/12

Response (204)

Empty response body.

GET /v1/agents/:agent_id/testers/:id/status Auth required

Pulls the current invite status from Google for the tester and updates the local record. Use this to check whether a user accepted the invitation on their device.

Example Request

curl -H "Authorization: Bearer $API_KEY" \
     https://api.box.rcs.jetzt/v1/agents/7/testers/12/status

Response (200)

{
  "tester": {
    "id": 12,
    "agent_id": 7,
    "phone": "+491791234567",
    "status": "ACCEPTED",
    "google_resource_name": "brands/acme-corp/agents/my-agent/testers/+491791234567",
    "created_at": "2026-04-20T10:00:00Z",
    "updated_at": "2026-04-20T10:15:00Z"
  }
}

Status values: INVITED, PENDING, ACCEPTED, DECLINED, UNKNOWN, NOT_FOUND.

Brands

A brand represents the legal entity sending RCS messages (e.g. Acme Ltd.). Each tenant maps to one Google brand; agents live underneath it. Public endpoints let customers read brand state. Mutating actions (sync, document upload, vendor calls) are performed by Railslove operators via the admin UI.

Operator- und Vendor-Endpunkte siehe Internal API bzw. Vendor API am Seitenende.

GET /v1/brands Auth required

Lists brands visible to the authenticated tenant.

Example Request

curl -H "Authorization: Bearer $API_KEY" \
     https://api.box.rcs.jetzt/v1/brands

Response (200)

{
  "brands": [
    {
      "google_brand_id": "brands/abc",
      "display_name": "Acme Ltd.",
      "verification_state": "VERIFIED",
      "synced_at": "2026-06-02T12:00:00Z"
    }
  ]
}
GET /v1/brands/:google_brand_id Auth required

Returns a single brand by its Google brand id (e.g. brands/abc).

Example Request

curl -H "Authorization: Bearer $API_KEY" \
     https://api.box.rcs.jetzt/v1/brands/brands/abc

Response (200)

{
  "brand": {
    "google_brand_id": "brands/abc",
    "display_name": "Acme Ltd.",
    "verification_state": "VERIFIED",
    "synced_at": "2026-06-02T12:00:00Z"
  }
}
GET /v1/brands/:google_brand_id/verification Auth required

Returns the current brand-level verification state mirrored from Google.

Response (200)

{
  "google_brand_id": "brands/abc",
  "verification_state": "VERIFIED",
  "verification_synced_at": "2026-06-02T12:00:00Z"
}

Launch

Launch is the per-carrier rollout. Once an agent is VERIFIED, Railslove submits a launch request listing the target carriers and answering a questionnaire about message volume, opt-in flow, and use case. Google then forwards the request to each carrier; carrier states transition independently to LAUNCHED.

Operator- und Vendor-Endpunkte siehe Internal API bzw. Vendor API am Seitenende.

Operator checklist

[ ] Brand exists & synced from Google (GET /v1/brands)
[ ] Brand verification contact filled (partner + brand + website)
[ ] Supporting PDFs uploaded (1–5 PDFs, ≤50MB each)
[ ] Agent created at Google with all required fields
    (logo, hero, color, privacy, terms, website, billing, region)
[ ] At least one Tester registered + accepted invite
[ ] STOP flow tested with tester (operator confirms)
[ ] Agent asset URLs HTTPS-reachable (UI button runs HEAD probe)
[ ] Verification submitted (requestVerification) → state VERIFIED
[ ] Carriers selected
[ ] Launch questionnaire complete
[ ] Launch submitted (requestLaunch) → poll getLaunch until LAUNCHED
GET /v1/agents/:id/launch Auth required

Returns the agent's overall launch state plus a per-carrier breakdown. :id is the local Agent ID (Rails bigint primary key). Use this to monitor rollout progress.

Response (200)

{
  "status": "LAUNCHED",
  "launch_details": [
    {
      "region": "regions/dt-germany",
      "launch_state": "LAUNCH_STATE_LAUNCHED",
      "update_time": "2026-06-01T08:30:00Z",
      "comment": null
    }
  ]
}
POST /v1/agents/:id/launch_questionnaire_draft Auth required

Reicht die Launch-Questionnaire-Antworten als Customer ein. Jeder Aufruf überschreibt den vorherigen Draft. Railslove sichtet, übernimmt und sendet die Daten anschließend an Google.

Example Request

curl -X POST -H "Authorization: Bearer $API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "primary_contact_name": "Joe",
       "primary_contact_email": "joe@example.com",
       "opt_in_description": "User signs up on our website",
       "opt_out_description": "Reply STOP",
       "trigger_description": "Order confirmation",
       "interaction_description": "Customer service",
       "video_url": "https://example.com/demo.mp4",
       "screenshot_url": "https://example.com/screenshot.png"
     }' \
     https://api.box.rcs.jetzt/v1/agents/<agent_id>/launch_questionnaire_draft

Response (201)

{
  "agent_id": 19,
  "google_agent_id": "brands/acme/agents/local-1",
  "submitted_at": "2026-06-03T01:00:00Z",
  "draft": {
    "primary_contact_name": "Joe",
    "primary_contact_email": "joe@example.com",
    ...
  }
}

Validation errors (422)

primary_contact_email is required and must be a valid email. video_url and screenshot_url are optional but must begin with http:// or https:// when provided.

{
  "error": "Validation failed",
  "errors": { "primary_contact_email": "invalid_format" }
}
GET /v1/agents/:id/launch_questionnaire_draft Auth required

Gibt den aktuell eingereichten (aber noch nicht von Railslove übernommenen) Draft zurück. draft ist null, wenn nichts eingereicht wurde oder Railslove den Draft bereits übernommen oder verworfen hat.

Response (200)

{
  "agent_id": 19,
  "google_agent_id": "brands/acme/agents/local-1",
  "submitted_at": "2026-06-03T01:00:00Z",
  "draft": {
    "primary_contact_email": "joe@example.com",
    "trigger_description": "Order confirmation"
  }
}

Carriers

GET /v1/carriers Auth required

Lists the German carriers RCS-Box has contracts with (mirrored from Google's regions catalog). Used when selecting targets for launch.

Example Request

curl -H "Authorization: Bearer $API_KEY" \
     https://api.box.rcs.jetzt/v1/carriers

Response (200)

{
  "carriers": [
    {
      "slug": "dt-germany",
      "name": "Germany: DT",
      "google_region_name": "regions/dt-germany",
      "management_type": "GOOGLE_MANAGED"
    }
  ]
}

Capabilities

GET /v1/agents/:agent_id/capabilities Auth required

Checks whether a phone number is reachable via RCS for the given agent. Returns the list of RCS features that Google reports for the device. Use this before sending to avoid wasted billable sends to non-RCS numbers.

Query parameters

Param Description
phoneE.164 phone number to check, e.g. +491791234567. Must match the tenant's country allow-list.

Example Request

curl -H "Authorization: Bearer $API_KEY" \
     "https://api.box.rcs.jetzt/v1/agents/7/capabilities?phone=%2B491791234567"

Response — RCS-capable (200)

{
  "rcs_enabled": true,
  "features": [
    "RICHCARD_STANDALONE",
    "RICHCARD_CAROUSEL",
    "ACTION_CREATE_CALENDAR_EVENT",
    "ACTION_DIAL"
  ]
}

Response — not RCS-capable (200)

{
  "rcs_enabled": false,
  "features": []
}

Don't cache the result. RCS capability can change at any time (device swap, carrier change, user disabling RCS). Google recommends running the check immediately before sending. RCS-Box does not persist the response.

POST /v1/agents/:agent_id/capabilities/batch Auth required

Checks reachability for up to 100 phone numbers in a single call. Useful for pre-filtering large recipient lists. Returns one result per input phone, in the same order as the request.

What this endpoint does NOT return: the per-number feature list. Google's batch lookup only reports reachability (true/false). If you need the supported feature set for a number, call the single-number endpoint.

Request body

Field Description
phonesNon-empty array of E.164 phone numbers (max 100). Numbers outside the tenant's country allow-list are reported as skipped without contacting Google.

Example Request

curl -X POST \
     -H "Authorization: Bearer $API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"phones":["+491791234567","+491791234568","+12025550100"]}' \
     https://api.box.rcs.jetzt/v1/agents/7/capabilities/batch

Response (200)

{
  "results": [
    { "phone": "+491791234567", "rcs_enabled": true },
    { "phone": "+491791234568", "rcs_enabled": false },
    { "phone": "+12025550100",  "rcs_enabled": false, "skipped": "not_in_allowlist" }
  ]
}

When skipped is set, rcs_enabled is always false — we did not query Google for that number.

  • not_in_allowlist — the number is well-formed E.164 but outside the tenant's country allow-list.
  • invalid_phone — the entry is not a non-empty string, is missing a leading +, or contains no digits. Use this to flag malformed input on the client side.

Error responses

Status When
400phones missing, empty, not an array, or longer than 100
401Missing or invalid API key
404Agent not found in your tenant
422Google API error (rate-limited, brand misconfigured, etc.)

Webhooks

Each tenant may configure a webhook_url (set by your administrator). When Google sends an incoming user message for one of your agents, RCS-Box forwards the payload to that URL. The shape matches Google's RCS Business Messaging webhook format. Opt-out / opt-in messages are also forwarded, with an extra rcs_box_action field; these forwards require an acknowledgement from your endpoint — see the Opt-out / Opt-in section. Delivery events (DELIVERED, READ) are also forwarded as-is — without an rcs_box_action field — so you can match them against your outbound-message log via event.messageId.

Request format

RCS-Box forwards every callback as an HTTP POST to your configured webhook_url. The body is the raw JSON payload — no form-encoding, no wrapper. The connection timeout is 30 seconds; slower responses are retried via Sidekiq (up to 3× for ack'd flows; see Opt-out / Opt-in).

Property Value
MethodPOST
URLYour tenant's webhook_url
Content-Typeapplication/json
X-RCS-Box-SignatureHex HMAC-SHA256 over the body, keyed with your API key
BodyRaw JSON (the original Google payload, optionally enriched with rcs_box_action)
Timeout30 s
Expected response2xx for regular forwards; 200 + {"acknowledged": true} for opt-out/opt-in

Example forwarded request

POST /your/webhook/path HTTP/1.1
Host: your-domain.example.com
Content-Type: application/json
X-RCS-Box-Signature: 5e7c9a…b3f1

{
  "agent": "brands/acme-corp/agents/my-agent",
  "senderPhoneNumber": "+491701234567",
  "message": { "text": "Hello!" }
}

Signature header

Every forwarded request carries an X-RCS-Box-Signature header. The value is a lowercase hex-encoded HMAC-SHA256 computed over the request body (the JSON payload as serialized by RCS-Box), keyed with your API key. Verify it before trusting the payload.

# Ruby
require "openssl"
expected = OpenSSL::HMAC.hexdigest("SHA256", api_key, request.raw_post)
actual   = request.headers["X-RCS-Box-Signature"]
reject unless ActiveSupport::SecurityUtils.secure_compare(expected, actual)

Event types

Google sends three callback shapes. You can distinguish them by the top-level keys present in the payload. Only user messages are forwarded to your webhook_url; delivery events stay internal.

Top-level key Meaning Forwarded?
messageUser reply containing text, suggestion postback, or mediaYes
userMessageAlternate shape Google uses for the same thingYes
eventDelivery status (DELIVERED, READ) — updates the internal MessageLog onlyNo

Opt-out / Opt-in

When a user replies with a message whose first word (case-insensitive, after trimming whitespace) matches one of the keywords below, RCS-Box automatically updates the per-agent blacklist and forwards the original payload to your webhook_url with an extra rcs_box_action field set to "OPT_OUT" or "OPT_IN".

Action Recognized keywords Effect
OPT_OUT STOP, STOPP, ABMELDEN, ENDE, QUIT, UNSUBSCRIBE, NEIN Phone added to blacklist immediately (fail-safe), then forwarded for tenant ack
OPT_IN START, ANMELDEN, SUBSCRIBE, JA Phone removed from blacklist immediately on START; tenant ack is audit-of-record

Example forward (opt-out)

{
  "agent": "brands/acme-corp/agents/my-agent",
  "senderPhoneNumber": "+491701234567",
  "userMessage": { "text": "STOP" },
  "rcs_box_action": "OPT_OUT",
  "consent_audit_log_id": 12345
}

The consent_audit_log_id is your handle for a one-time "goodbye" send via POST /messages/final — useful for the "Tschüss + STOP-Hinweis-Wiederholung"-Pattern that the per-agent blacklist would otherwise block.

Matching rules: Only the first word of the user's message is matched against the keyword list (after trimming whitespace, case-insensitively). The full original text — including anything after the keyword — is preserved in the forwarded payload, so QR-code deeplinks of the form "START <scan_token>" are recognized as opt-in and the token reaches your endpoint via userMessage.text. Messages that contain a keyword later in the sentence (e.g. "please stop spamming") are forwarded as regular user replies without the rcs_box_action field. Repeated STOPs from the same number are idempotent.

Tenant acknowledgement (required)

For every forward with rcs_box_action set, your endpoint must respond 200 OK with the JSON body {"acknowledged": true} within 30 seconds. The acknowledgement is what proves to RCS-Box (and to your DSGVO audit trail) that you have processed the consent change in your own system.

HTTP/1.1 200 OK
Content-Type: application/json

{ "acknowledged": true }
Tenant response OPT_OUT result OPT_IN result
200 + {"acknowledged": true} Audit log marked acked. Phone stays blacklisted (already was). Audit log marked acked. Phone was already removed from the blacklist when the START webhook arrived — the ack is the audit-of-record, not the trigger.
Missing/false ack body, 5xx, or timeout (> 30 s) Phone stays blacklisted (fail-safe). Job retries up to 3× total (MAX_ATTEMPTS). Audit log marked failed. Phone stays de-blacklisted — the user already pressed START, that's the consent of record. Use the failed audit row to reconcile your CRM. Job retries up to 3× total (MAX_ATTEMPTS).

Why we still require the ack: Your {"acknowledged": true} is what tells RCS-Box that the consent change reached your CRM / email-list / downstream system. For OPT_OUT this is critical — without the ack we retry, and the phone stays blacklisted as a fail-safe. For OPT_IN the blacklist is removed as soon as the START webhook arrives, regardless of ack: the user actively re-engaged, and a fragile tenant endpoint shouldn't keep them blocked. The ack is still required and still retried; a missing ack leaves a consent_audit_logs row with ack_received: false (plus populated error and incremented attempts), which is your signal to reconcile with your own system.

First-time OPT_IN (no prior blacklist entry)

OPT_IN handling is symmetric: even when the user has never been on your blacklist, a START message still produces a consent_audit_logs row, still gets forwarded with "rcs_box_action": "OPT_IN", and still requires your {"acknowledged": true} response. The internal remove-from-blacklist step runs unconditionally when the START webhook arrives and is a no-op when there's no prior entry; the audit log is the part that captures the explicit consent. Use the forwarded event to register a first-time opt-in in your own system — e.g. flag the contact as "consent given by RCS reply" in your CRM.

How to test as a tenant

End-to-end you need three things: a public URL that captures incoming HTTP requests, a configured webhook_url on your tenant, and an accepted tester number that can send real RCS messages to one of your agents.

1. Set up a capture URL

Open webhook.site — you get a unique URL like https://webhook.site/<uuid>. Keep the tab open; every request the URL receives appears live with full body and headers.

2. Configure your tenant webhook_url

Ask your administrator to set the webhook_url on your tenant to the webhook.site URL from step 1. The setting lives in the admin tenant edit form.

3. Add and accept a tester

Pick an agent, register a tester phone number you control, and accept the invitation on that device. You can do this via POST /v1/agents/:agent_id/testers or in the admin UI.

4. Trigger callbacks

From the tester device, send the following messages to your agent:

Send from tester Expected at webhook.site
"hello there" Forward without rcs_box_action
"STOP" (or ENDE, NEIN, ABMELDEN, …) Forward with "rcs_box_action": "OPT_OUT"
"START" (or JA, ANMELDEN, …) Forward with "rcs_box_action": "OPT_IN"
"please stop spamming me" Forward without rcs_box_action — the substring is ignored

Note about webhook.site & opt-in/opt-out: webhook.site captures requests but does not return {"acknowledged": true}. STOP/START forwards are therefore retried up to 3 times total before the job gives up, and the matching consent_audit_logs row ends with ack_received: false (populated error, incremented attempts). The blacklist mutation itself still happens (OPT_OUT adds, OPT_IN removes) regardless of ack — only the audit-of-record is affected by webhook.site's missing ack. To exercise the full ack flow end-to-end, point webhook_url at an endpoint you control that responds 200 with {"acknowledged": true}.

5. Verify the signature

Each forward carries an X-RCS-Box-Signature header. Recompute it locally with your API key over the raw request body (see signature header). If the values match you know the payload is from RCS-Box and untampered.

RCS testing constraint: Google only delivers user messages from accepted tester numbers while an agent is in DRAFT/PENDING state. A regular phone you have not registered as a tester will not produce any callback — you will see no request hit webhook.site. Always test from the tester device.

Error Handling

All errors return a consistent JSON format with an error and message field.

{
  "error": "Unauthorized",
  "message": "API key required"
}

HTTP Status Codes

Code Meaning Common Cause
200OKRequest succeeded
201CreatedAgent created or message accepted
400Bad RequestMissing/invalid parameters, non-German phone number
401UnauthorizedMissing or invalid API key
402Payment RequiredPlan quota exceeded — see error_key for the specific reason (e.g. quota_exceeded_free_tier)
403ForbiddenTenant is inactive or recipient is blacklisted (STOP-opt-out or admin-blocked)
404Not FoundAgent or resource does not exist — also returned when accessing another tenant's resources
422UnprocessableAgent not launched, Google API rejection
429Rate LimitedToo many requests, retry after backoff
500Server ErrorInternal error, contact support

Rate Limits

Requests to the Google RCS API are subject to rate limiting. RCS-Box automatically retries rate-limited requests (HTTP 429) with exponential backoff up to 3 times.

Retry strategy: Initial interval of 0.5s with a backoff factor of 2x and 50% randomization. Retries also apply to server errors (500, 502, 503, 504).

Phone Number Format

All phone numbers must be provided in E.164 format. Each tenant has an allow-list of country prefixes; V1 defaults to Germany only (+49). Formatting characters (spaces, dashes) are stripped before validation; numbers outside the tenant's allow-list are rejected with HTTP 400.

Input Normalized Accepted?
+49 170 123 4567+491701234567Yes
+49-170-123-4567+491701234567Yes
+43 677 1234567+436771234567No (AT)
+1 202 555 0100+12025550100No (US)

Internal API — Operated by Railslove

Endpunkte in diesem Block werden ausschließlich von Railslove im Auftrag des Kunden aufgerufen. Sie sind hier zur Transparenz dokumentiert; Kunden haben darauf keinen direkten Zugriff. Wende dich an support@railslove.com, um eine dieser Operationen auszulösen.

Brand admin

POST /admin/brands/:id/sync Internal (Railslove only)

Re-fetches the brand from Google Business Communications and refreshes the local mirror. Triggered by Railslove operators after Google updates the brand contact or verification status.

PATCH /admin/brands/:id Internal (Railslove only)

Updates local brand metadata (display name, partner contact, website). Does not write to Google; use the vendor endpoints for that.

POST /admin/brands/:id/documents Internal (Railslove only)

Uploads a supporting PDF (1–5 per brand, ≤50 MB each) used by Google to verify the legal entity. Stored in encrypted blob storage and attached to subsequent verification requests.

DELETE /admin/brands/:id/documents/:doc_id Internal (Railslove only)

Removes an uploaded brand PDF.

Verification admin

Verification is the process where Google checks that an agent legitimately belongs to its brand. It requires the brand contact, supporting documents, and an agent in a complete state (logo, hero, color, privacy URL, terms URL, website, billing, region). The endpoints below are driven by Railslove on the customer's behalf.

POST /admin/brands/:id/verification/submit Internal (Railslove only)

Submits the brand for verification. Internally picks the brand's first agent (Google caches verification per-brand), calls vendor :requestVerification first, then uploads the brand's PDF attachments onto the verification record. Operator-only; once-per-brand.

POST /admin/brands/:id/verification/refresh Internal (Railslove only)

Polls Google for the latest verification status of the brand and updates the local mirror. Refresh ist manuell — Operator klickt im Admin auf "Refresh from Google". Eine Background-Polling-Variante ist als Follow-up geplant.

Launch admin

POST /admin/agents/:id/launch/submit Internal (Railslove only)

Submits the launch request to Google. Gated by readiness (verified + carriers selected + questionnaire complete + assets reachable). Calls vendor :requestLaunch.

POST /admin/agents/:id/launch/add_carriers Internal (Railslove only)

Adds additional carriers to an already-launched agent. Calls vendor PATCH to amend the launch document with the new carrier list.

POST /admin/agents/:id/launch/refresh Internal (Railslove only)

Polls Google for the latest per-carrier launch state and updates the local mirror.

PATCH /admin/agents/:id/launch/questionnaire Internal (Railslove only)

Updates the launch questionnaire fields (use case, opt-in flow, monthly volume, sample messages) stored locally and sent with the launch request.

PATCH /admin/agents/:id/launch/readiness Internal (Railslove only)

Records operator confirmation of manual checklist items (STOP flow tested, tester accepted, etc.) before submitting the launch.

POST /admin/agents/:id/launch/probe_assets Internal (Railslove only)

Runs a HEAD probe against the agent's asset URLs (logo, hero, privacy, terms, website) to confirm HTTPS reachability and reasonable content types. Surfaces failures inline in the admin UI.

Carriers admin

POST /admin/carriers/sync Internal (Railslove only)

Re-fetches the carrier/regions catalog from Google and refreshes the local mirror. Run manually after Google announces new carriers.

Analytics-Ingestion (Device/Browser)

Auf deiner Landing-/Redirect-Page kannst du Device- und Browser-Daten clientseitig erfassen und an RCS-Box senden. Wir parsen den User-Agent server-seitig und speichern nur aggregierte, anonyme Felder (kein IP, kein roher User-Agent). Den Write-Key und die erlaubten Origins findest du im Admin auf der Agent-Seite. Rufe das Snippet erst nach Einwilligung auf deiner eigenen Seite auf.

<script>
  navigator.sendBeacon(
    "https://<deine-rcs-box-domain>/api/v1/analytics/events",
    new Blob([JSON.stringify({
      key: "pk_DEIN_WRITE_KEY",
      campaign: "summer-2026",      // optional
      event_type: "page_view",      // z. B. page_view, cta_click
      screen_w: screen.width,
      screen_h: screen.height,
      viewport_w: window.innerWidth,
      viewport_h: window.innerHeight,
      language: navigator.language,
      timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
      referrer: document.referrer
    })], { type: "text/plain" })
  );
</script>

Wir senden als text/plain, damit der Beacon eine CORS-safelisted Simple-Request ist (kein Preflight); der Body ist trotzdem JSON und wird server-seitig geparst.

Antwort: 202 Accepted. Origin muss in der Agent-Allowlist stehen, sonst 403; ungültiger/deaktivierter Key ⇒ 401.

Vendor API — Google Business Communications

Die folgenden Endpunkte ruft Railslove gegen Google's Business Communications API auf — sie laufen NICHT durch die RCS-Box. Sie sind hier dokumentiert, damit Kunden sehen, was im Hintergrund passiert. Vollständige Referenz: developers.google.com.

Brand vendor

GET businesscommunications/v1/brands Vendor (Google)

Underlying Google Business Communications call used by /admin/brands/:id/sync. Not callable by customers; documented for transparency.

GET businesscommunications/v1/{brand} Vendor (Google)

Fetches a single Google brand resource. Used by sync to populate display_name and verification state.

POST businesscommunications/v1/brands Vendor (Google)

Manual one-off creation of a new brand at Google. Performed by a Railslove operator during tenant onboarding; the resulting google_brand_id is then stored on the tenant.

Verification vendor

POST businesscommunications/v1/{agent}:requestVerification Vendor (Google)

Underlying Google call that asks Google to verify the agent.

GET businesscommunications/v1/{agent}/verification Vendor (Google)

Reads the current verification document at Google. Used by refresh.

POST businesscommunications/upload/v1/{agent}/attachments Vendor (Google)

Uploads a single PDF attachment to Google. Called per document during submission, after :requestVerification has created the verification record — the upload docks the BAL onto that record.

DELETE businesscommunications/v1/{agent}/attachments/:id Vendor (Google)

Removes a previously uploaded attachment at Google.

Launch vendor

POST businesscommunications/v1/{agent}:requestLaunch Vendor (Google)

Underlying Google call to launch an agent across the listed carriers. Body includes the questionnaire payload.

GET businesscommunications/v1/{agent}/launch Vendor (Google)

Reads the current launch document at Google, including per-carrier state. Used by refresh.

PATCH businesscommunications/v1/{agent}/launch Vendor (Google)

Amends an existing launch document (e.g. to add carriers). Called by add_carriers.

Carriers vendor

GET businesscommunications/v1/regions Vendor (Google)

Underlying Google call returning the carriers/regions catalog.