message.comDevelopers

Sites Live

A site is a domain or subdomain where the widget is installed. A workspace can run many sites, each with its own widget appearance, welcome message, and allowed origin list. Most teams have one site and never touch this API; agencies and multi-brand operators rely on it.

List sites

GET/api/v1/sitesAuth: Bearer

Cursor-paginated list of sites in the workspace. Default limit 50, maximum 200. No filters; the list is small in practice.

200 OK
{
  "sites": [ /* up to 50 site objects */ ],
  "nextCursor": null
}

Code samples

cURL
curl 'https://app.message.com/api/v1/sites' \
  -H 'Authorization: Bearer YOUR_WORKSPACE_JWT'
JavaScript
const res = await fetch('https://app.message.com/api/v1/sites', {
  headers: { Authorization: 'Bearer ' + token }
});
const { sites } = await res.json();
Python
import requests
r = requests.get(
    "https://app.message.com/api/v1/sites",
    headers={"Authorization": f"Bearer {token}"},
)
sites = r.json()["sites"]
Ruby
require "net/http"
require "json"
uri = URI("https://app.message.com/api/v1/sites")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
  req = Net::HTTP::Get.new(uri)
  req["Authorization"] = "Bearer #{token}"
  http.request(req)
end
sites = JSON.parse(res.body)["sites"]
PHP
<?php
$ctx = stream_context_create([
  "http" => ["method" => "GET", "header" => "Authorization: Bearer $token"]
]);
$sites = json_decode(file_get_contents("https://app.message.com/api/v1/sites", false, $ctx), true)["sites"];

Get one site

GET/api/v1/sites/:idAuth: Bearer

Returns the full site record including the public embedId rendered in your install snippet.

200 OK
{
  "id": "uuid",
  "version": 3,
  "name": "Acme storefront",
  "domain": "acme.com",
  "embedId": "site_01HRX0...",
  "primaryColor": "#FF4D1F",
  "launcherPosition": "right",
  "welcomeMessage": "Hi! How can we help?",
  "businessHoursOverride": null,
  "allowedOrigins": ["https://acme.com", "https://www.acme.com"],
  "createdAt": "2026-01-04T12:00:00Z"
}

Create a site

POST/api/v1/sitesAuth: Bearer

Creates a new site. Returns the full site record including the generated embedId. Admin-only.

FieldTypeDescription
namerequiredstringHuman-readable name. Shown in the dashboard site picker.
domainrequiredstringPrimary domain. Used to derive the default allowed origins.
primaryColoroptionalstringHex colour. Overrides workspace primary for this site only.
launcherPositionoptionalenumright or left. Defaults to right.
welcomeMessageoptionalstringOverride workspace welcome message for this site.
allowedOriginsoptionalstring[]Strict-match origin allowlist. Defaults to the domain + www variant.
Body
{
  "name": "Acme storefront",
  "domain": "acme.com",
  "primaryColor": "#FF4D1F"
}

Update a site

PATCH/api/v1/sites/:idAuth: Bearer

Patch one or more site fields. Requires expectedVersion.

Delete a site

DELETE/api/v1/sites/:idAuth: Bearer

Removes the site and invalidates its embedId. Existing conversations are preserved (they still belong to the workspace); only the widget config goes away. The next page load on a deleted embedId renders nothing.

Embed IDs are not reusable. Deleting and recreating a site means updating every install snippet that referenced the old ID. Prefer PATCH for everyday edits.

Errors

CodeWhen
400 invalid_bodyPayload validation failed.
403 forbiddenAgent role is not admin.
404 not_foundSite does not exist or belongs to another workspace.
409 conflictA site already exists for that domain in this workspace.
409 stale_versionexpectedVersion mismatch.

Common pitfalls

  • Mixing site IDs and workspace IDs in install snippets. The snippet uses data-workspace for the workspace, and the bundle derives the site from the window.location match against allowed origins.
  • Overly tight allowedOrigins. A staging subdomain you forgot to add will load the widget but refuse to send messages. Add wildcards (https://*.staging.acme.com) sparingly.
  • Expecting per-site billing. Billing is workspace-level. Sites are a routing/appearance concept.