{
  "openapi": "3.1.0",
  "info": {
    "title": "Public API",
    "version": "1",
    "description": "The routes a customer's own code calls: the guest API (/guest/v1/), which an app or a website calls for its signed-in user with the app key and the user's token; the server API (/server/v1/), which a backend calls with a server key; and the public routes (/public/v1/), which take no credential. Every refusal is problem JSON (application/problem+json) with a code to switch on. From a browser: a page on one of the app's web origins (Settings › Connect › Install, set for Live and Test apart; https, or http://localhost while developing) may call the guest API and read the app's public face (GET /public/v1/apps/{key}) across origins. The CRM answers its preflight (OPTIONS) and echoes the origin in Access-Control-Allow-Origin, never with credentials, and varies by Origin; the page may send Authorization, X-CRM-App-Key, Content-Type, X-Crm-Sdk and Idempotency-Key, and may read Retry-After. A page on any other origin gets no CORS, and the server API never does: keep server keys on your servers."
  },
  "servers": [
    {
      "url": "https://app.purrcrm.com",
      "description": "PurrCRM's API"
    }
  ],
  "tags": [
    {
      "name": "Messenger",
      "description": "Conversations with your team, the messenger's home, pictures and ratings.",
      "x-surface": "guest"
    },
    {
      "name": "Tickets",
      "description": "The user's support tickets, and writing on one.",
      "x-surface": "guest"
    },
    {
      "name": "Help center",
      "description": "Your articles and collections, read and searched by a signed-in user.",
      "x-surface": "guest"
    },
    {
      "name": "In-app messages",
      "description": "The check-in that brings in-app messages, receipts, and survey answers.",
      "x-surface": "guest"
    },
    {
      "name": "Events",
      "description": "Product events from the app or website, in batches.",
      "x-surface": "guest"
    },
    {
      "name": "Push and notices",
      "description": "Registering a device for pushes, and the inbox of notices the CRM keeps.",
      "x-surface": "guest"
    },
    {
      "name": "Preferences",
      "description": "The user's topics and channels, and their consent to offers.",
      "x-surface": "guest"
    },
    {
      "name": "Contacts",
      "description": "Who a person is: Identify, erase and export, by your own id for them.",
      "x-surface": "server"
    },
    {
      "name": "Server events",
      "description": "What people did, as your servers saw it, and the events your app defines.",
      "x-surface": "server"
    },
    {
      "name": "App",
      "description": "What an app shows before anyone signs in.",
      "x-surface": "public"
    },
    {
      "name": "Public help center",
      "description": "The help center's public articles, for a web page with no signed-in user.",
      "x-surface": "public"
    },
    {
      "name": "Sign-up",
      "description": "A new customer's sign-up, posted from your home page.",
      "x-surface": "public"
    },
    {
      "name": "Email links",
      "description": "The links in the CRM's own emails: clicks, opens, unsubscribing and the preference centre. The CRM writes these; you do not call them.",
      "x-surface": "public"
    },
    {
      "name": "Email hooks",
      "description": "Where your mail provider posts replies, bounces and complaints, signed with the app's inbound secret.",
      "x-surface": "public"
    },
    {
      "name": "Links",
      "description": "What a link's landing page shows.",
      "x-surface": "public"
    }
  ],
  "paths": {
    "/guest/v1/app-links": {
      "put": {
        "operationId": "putGuestAppLinks",
        "summary": "Declare the app's places",
        "description": "The places in the app a link may go, sent once a build when a check-in asks for them, so your team can pick them in the link picker.",
        "tags": [
          "In-app messages"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppLinksReport"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done, with nothing to say"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/articles": {
      "get": {
        "operationId": "getGuestArticles",
        "summary": "Search the help center",
        "description": "Articles whose words match q, best first; with no q, the most read.",
        "tags": [
          "Help center"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "A language to read in (a BCP 47 tag), instead of the user's own.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many to answer, at most.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Words to search for.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Articles"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/articles/{slug}": {
      "get": {
        "operationId": "getGuestArticlesSlug",
        "summary": "Read an article",
        "tags": [
          "Help center"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The article's or collection's slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "A language to read in (a BCP 47 tag), instead of the user's own.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Article"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/articles/{slug}/rating": {
      "post": {
        "operationId": "postGuestArticlesSlugRating",
        "summary": "Say whether an article helped",
        "tags": [
          "Help center"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The article's or collection's slug.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ArticleRatingBody"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done, with nothing to say"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/attachments": {
      "post": {
        "operationId": "postGuestAttachments",
        "summary": "Upload a picture",
        "description": "Asks for somewhere to upload one picture (JPEG, PNG, GIF or WebP) before sending it in a message: put the bytes at the answer's upload address, then name its id in the message.",
        "tags": [
          "Messenger"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttachmentBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Made",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AttachmentUpload"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/checkin": {
      "post": {
        "operationId": "postGuestCheckin",
        "summary": "Check in",
        "description": "Says the app is open (its screen, version and time zone) and answers the in-app messages waiting for this user, with their unread counts. Check in when the user signs in, when the app comes to the front, and after sending events; one sooner than 20 seconds after the last is answered from it.",
        "tags": [
          "In-app messages"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckinBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkin"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/collections": {
      "get": {
        "operationId": "getGuestCollections",
        "summary": "List the help center's collections",
        "tags": [
          "Help center"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "A language to read in (a BCP 47 tag), instead of the user's own.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Collections"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/collections/{slug}": {
      "get": {
        "operationId": "getGuestCollectionsSlug",
        "summary": "Read a collection",
        "description": "A collection and its articles, in the user's language.",
        "tags": [
          "Help center"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The article's or collection's slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "A language to read in (a BCP 47 tag), instead of the user's own.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionArticles"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/consent": {
      "get": {
        "operationId": "getGuestConsent",
        "summary": "Read the user's consent to offers by push",
        "tags": [
          "Preferences"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Consent"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "put": {
        "operationId": "putGuestConsent",
        "summary": "Set the user's consent to offers by push",
        "tags": [
          "Preferences"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConsentBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Consent"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/conversations": {
      "get": {
        "operationId": "getGuestConversations",
        "summary": "List the user's conversations",
        "description": "Newest first, a page at a time: pass the page's next_before as before for the next one.",
        "tags": [
          "Messenger"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "Where the page starts: the next_before (or before) of the page before it.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many to answer, at most.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "post": {
        "operationId": "postGuestConversations",
        "summary": "Start a conversation",
        "description": "Starts a conversation with the user's first message, or with the help bot's opening that the messenger's home offered.",
        "tags": [
          "Messenger"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Your own id for this send (at most 100 characters): sent again after a timeout, it is kept once.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StartBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Made",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Conversation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/conversations/{conversation_id}": {
      "get": {
        "operationId": "getGuestConversationsConversationId",
        "summary": "Read a conversation",
        "description": "The conversation with every part the user may see.",
        "tags": [
          "Messenger"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "The conversation's id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Conversation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/conversations/{conversation_id}/bot": {
      "post": {
        "operationId": "postGuestConversationsConversationIdBot",
        "summary": "Answer the help bot",
        "description": "The user's answer to the help bot's question: a button, or what it asked them to type.",
        "tags": [
          "Messenger"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "The conversation's id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BotReplyBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Made",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Part"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/conversations/{conversation_id}/messages": {
      "post": {
        "operationId": "postGuestConversationsConversationIdMessages",
        "summary": "Write in a conversation",
        "description": "Adds the user's message, with up to four pictures uploaded first.",
        "tags": [
          "Messenger"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "The conversation's id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Your own id for this send (at most 100 characters): sent again after a timeout, it is kept once.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Made",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Part"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/conversations/{conversation_id}/rating": {
      "post": {
        "operationId": "postGuestConversationsConversationIdRating",
        "summary": "Rate a conversation",
        "description": "The user's rating of a closed conversation that asked for one, once.",
        "tags": [
          "Messenger"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "The conversation's id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RatingBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Part"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/conversations/{conversation_id}/read": {
      "post": {
        "operationId": "postGuestConversationsConversationIdRead",
        "summary": "Mark a conversation read",
        "description": "Marks the conversation read up to a part, and answers the user's unread count.",
        "tags": [
          "Messenger"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "The conversation's id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReadBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnreadCount"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/conversations/{conversation_id}/wait": {
      "get": {
        "operationId": "getGuestConversationsConversationIdWait",
        "summary": "Wait for a reply",
        "description": "A long poll: answers as soon as the conversation has something newer than after, or after about 25 seconds with nothing new. Ask again straight away while the conversation is on screen.",
        "tags": [
          "Messenger"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "conversation_id",
            "in": "path",
            "required": true,
            "description": "The conversation's id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "description": "The version already held: an answer comes only once there is something newer.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationUpdate"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/devices": {
      "delete": {
        "operationId": "deleteGuestDevices",
        "summary": "Forget a device",
        "description": "Stops pushes to this device for this user: call it before your own sign-out, while the user's token still works.",
        "tags": [
          "Push and notices"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForgetDeviceBody"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done, with nothing to say"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "put": {
        "operationId": "putGuestDevices",
        "summary": "Register a device for pushes",
        "description": "The device's push token (from Firebase), for the user signed in. Register on every start and whenever the token changes.",
        "tags": [
          "Push and notices"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceBody"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done, with nothing to say"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/events": {
      "post": {
        "operationId": "postGuestEvents",
        "summary": "Send product events",
        "description": "A batch of up to 50 events. Each is kept or refused on its own; an event's id makes a retry safe. Send the standard events and the ones your app defines on the Data page.",
        "tags": [
          "Events"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventsBody"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventsAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/messages/{message_id}/open": {
      "post": {
        "operationId": "postGuestMessagesMessageIdOpen",
        "summary": "Open a message by its link",
        "description": "The in-app message a link names, to show over the page, whatever its audience says: the user asked for it.",
        "tags": [
          "In-app messages"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "message_id",
            "in": "path",
            "required": true,
            "description": "The in-app message's id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenMessageBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InAppMessage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/messenger": {
      "get": {
        "operationId": "getGuestMessenger",
        "summary": "The messenger's home",
        "description": "What the messenger's first screen shows this user: the greeting, the team's faces and reply time, the cards your team published, open conversations, a few articles and tickets, and the help bot's opening when one would start.",
        "tags": [
          "Messenger"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessengerHome"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/notices": {
      "get": {
        "operationId": "getGuestNotices",
        "summary": "List the user's notices",
        "description": "The inbox of every push sent to the user, newest first, a page at a time, with the unread count.",
        "tags": [
          "Push and notices"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "Where the page starts: the next_before (or before) of the page before it.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many to answer, at most.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoticePage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/notices/read": {
      "post": {
        "operationId": "postGuestNoticesRead",
        "summary": "Mark notices read",
        "tags": [
          "Push and notices"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NoticesReadBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnreadCount"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/preferences": {
      "get": {
        "operationId": "getGuestPreferences",
        "summary": "Read the user's preferences",
        "description": "The app's topics, the channels each goes by, and whether the user hears of each by each channel.",
        "tags": [
          "Preferences"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Preferences"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "put": {
        "operationId": "putGuestPreferences",
        "summary": "Change the user's preferences",
        "description": "The user's choices, topic by channel, as they make them; or unsubscribe from all offers and news.",
        "tags": [
          "Preferences"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PreferencesBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Preferences"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/receipts": {
      "post": {
        "operationId": "postGuestReceipts",
        "summary": "Say what the user did with a message",
        "description": "Seen, tapped, dismissed, a carousel's screens and buttons, or a goal: one receipt per thing the user did.",
        "tags": [
          "In-app messages"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReceiptBody"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done, with nothing to say"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/surveys/{message_id}/answers": {
      "post": {
        "operationId": "postGuestSurveysMessageIdAnswers",
        "summary": "Answer a survey's question",
        "description": "Keeps one answer as it is given, so a survey left half way still counts.",
        "tags": [
          "In-app messages"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "message_id",
            "in": "path",
            "required": true,
            "description": "The in-app message's id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SurveyAnswerBody"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done, with nothing to say"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/surveys/{message_id}/submit": {
      "post": {
        "operationId": "postGuestSurveysMessageIdSubmit",
        "summary": "Finish a survey",
        "tags": [
          "In-app messages"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "message_id",
            "in": "path",
            "required": true,
            "description": "The in-app message's id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SurveySubmit"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done, with nothing to say"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/tickets": {
      "get": {
        "operationId": "getGuestTickets",
        "summary": "List the user's tickets",
        "tags": [
          "Tickets"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SupportTicketPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/tickets/{ticket_id}": {
      "get": {
        "operationId": "getGuestTicketsTicketId",
        "summary": "Read a ticket",
        "description": "The ticket, where it has got to, and its conversation.",
        "tags": [
          "Tickets"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "ticket_id",
            "in": "path",
            "required": true,
            "description": "The ticket's id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SupportTicket"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/guest/v1/tickets/{ticket_id}/messages": {
      "post": {
        "operationId": "postGuestTicketsTicketIdMessages",
        "summary": "Write on a ticket",
        "tags": [
          "Tickets"
        ],
        "security": [
          {
            "appKey": [],
            "userToken": []
          },
          {
            "appKey": [],
            "unverifiedUserId": []
          }
        ],
        "parameters": [
          {
            "name": "ticket_id",
            "in": "path",
            "required": true,
            "description": "The ticket's id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Your own id for this send (at most 100 characters): sent again after a timeout, it is kept once.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Made",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Part"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/apps/{key}": {
      "get": {
        "operationId": "getPublicAppsKey",
        "summary": "Read an app's public face",
        "description": "What an app shows before anyone signs in: its brand, which parts are on, its currency and languages, and whether it is a live or test app. Cached for a minute. A page on one of the app's web origins may read it from a browser.",
        "tags": [
          "App"
        ],
        "security": [],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "The app's public key (app_…).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicApp"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/apps/{key}/email-events": {
      "post": {
        "operationId": "postPublicAppsKeyEmailEvents",
        "summary": "Bounces and complaints, for one app",
        "description": "Your mail provider's bounces, complaints and deliveries, signed the same way. A hard bounce or a complaint puts the address on the app's suppression list.",
        "tags": [
          "Email hooks"
        ],
        "security": [],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "The app's public key (app_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-CRM-Signature",
            "in": "header",
            "required": false,
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\"> under the inbound secret; refused more than five minutes from now.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailEvent"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "413": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/apps/{key}/inbound-email": {
      "post": {
        "operationId": "postPublicAppsKeyInboundEmail",
        "summary": "A reply by email, for one app",
        "description": "Your mail provider's inbound hook, through whatever turns its post into this shape and signs it with the app's inbound secret (X-CRM-Signature). The reply is threaded into the user's conversation.",
        "tags": [
          "Email hooks"
        ],
        "security": [],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "The app's public key (app_…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-CRM-Signature",
            "in": "header",
            "required": false,
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\"> under the inbound secret; refused more than five minutes from now.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InboundEmail"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "413": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/email-events": {
      "post": {
        "operationId": "postPublicEmailEvents",
        "summary": "Bounces and complaints",
        "description": "The same events, signed with the deployment's inbound secret.",
        "tags": [
          "Email hooks"
        ],
        "security": [],
        "parameters": [
          {
            "name": "X-CRM-Signature",
            "in": "header",
            "required": false,
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\"> under the inbound secret; refused more than five minutes from now.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailEvent"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "413": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/email/c/{token}": {
      "get": {
        "operationId": "getPublicEmailCToken",
        "summary": "Follow a link in an email",
        "description": "Counts the click and redirects to the link the email's own version holds.",
        "tags": [
          "Email links"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The signed token from the email's link.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "A redirect"
          }
        }
      },
      "post": {
        "operationId": "postPublicEmailCToken",
        "summary": "Count a click the app opened",
        "description": "The same click, when the app opened the email's link itself.",
        "tags": [
          "Email links"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The signed token from the email's link.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done, with nothing to say"
          }
        }
      }
    },
    "/public/v1/email/lists/{token}": {
      "get": {
        "operationId": "getPublicEmailListsToken",
        "summary": "Read an email's unsubscribe page",
        "tags": [
          "Email links"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The signed token from the email's link.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/email/o/{token}": {
      "get": {
        "operationId": "getPublicEmailOToken",
        "summary": "An email's open pixel",
        "description": "Always a 1×1 GIF; the first fetch counts the open.",
        "tags": [
          "Email links"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The signed token from the email's link.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/gif": {}
            }
          }
        }
      }
    },
    "/public/v1/email/unsubscribe/{token}": {
      "get": {
        "operationId": "getPublicEmailUnsubscribeToken",
        "summary": "Open the unsubscribe link",
        "description": "Redirects to the unsubscribe page, which asks first.",
        "tags": [
          "Email links"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The signed token from the email's link.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "A redirect"
          }
        }
      },
      "post": {
        "operationId": "postPublicEmailUnsubscribeToken",
        "summary": "Unsubscribe in one click",
        "description": "The one-click unsubscribe (RFC 8058) a mail app posts, and the unsubscribe page's confirm: the person leaves that email's topic by email.",
        "tags": [
          "Email links"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The signed token from the email's link.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "303": {
            "description": "A redirect"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/help/articles": {
      "get": {
        "operationId": "getPublicHelpArticles",
        "summary": "Search the public articles",
        "tags": [
          "Public help center"
        ],
        "security": [
          {
            "appKey": []
          }
        ],
        "parameters": [
          {
            "name": "app",
            "in": "query",
            "required": false,
            "description": "The app's public key, for a request that cannot send the X-CRM-App-Key header.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "A language to read in (a BCP 47 tag), instead of the user's own.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many to answer, at most.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Words to search for.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Articles"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/help/articles/{slug}": {
      "get": {
        "operationId": "getPublicHelpArticlesSlug",
        "summary": "Read a public article",
        "tags": [
          "Public help center"
        ],
        "security": [
          {
            "appKey": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The article's or collection's slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "app",
            "in": "query",
            "required": false,
            "description": "The app's public key, for a request that cannot send the X-CRM-App-Key header.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "A language to read in (a BCP 47 tag), instead of the user's own.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Article"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/help/articles/{slug}/rating": {
      "post": {
        "operationId": "postPublicHelpArticlesSlugRating",
        "summary": "Say whether a public article helped",
        "description": "From a reader with no account: their own id for the page, kept apart from every user's.",
        "tags": [
          "Public help center"
        ],
        "security": [
          {
            "appKey": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The article's or collection's slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "app",
            "in": "query",
            "required": false,
            "description": "The app's public key, for a request that cannot send the X-CRM-App-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicRatingBody"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done, with nothing to say"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/help/assets/{asset_id}": {
      "get": {
        "operationId": "getPublicHelpAssetsAssetId",
        "summary": "Fetch an article's picture",
        "description": "A redirect to the picture's short-lived address.",
        "tags": [
          "Public help center"
        ],
        "security": [],
        "parameters": [
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "The picture's id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "A redirect"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/help/collections": {
      "get": {
        "operationId": "getPublicHelpCollections",
        "summary": "List the public collections",
        "description": "Only articles marked public and meant for everyone. Name the app with the X-CRM-App-Key header, or with ?app= where a header cannot be sent.",
        "tags": [
          "Public help center"
        ],
        "security": [
          {
            "appKey": []
          }
        ],
        "parameters": [
          {
            "name": "app",
            "in": "query",
            "required": false,
            "description": "The app's public key, for a request that cannot send the X-CRM-App-Key header.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "A language to read in (a BCP 47 tag), instead of the user's own.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Collections"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/help/collections/{slug}": {
      "get": {
        "operationId": "getPublicHelpCollectionsSlug",
        "summary": "Read a public collection",
        "tags": [
          "Public help center"
        ],
        "security": [
          {
            "appKey": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The article's or collection's slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "app",
            "in": "query",
            "required": false,
            "description": "The app's public key, for a request that cannot send the X-CRM-App-Key header.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "A language to read in (a BCP 47 tag), instead of the user's own.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionArticles"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/inbound-email": {
      "post": {
        "operationId": "postPublicInboundEmail",
        "summary": "A reply by email",
        "description": "The same hook for a deployment whose reply addresses name the app themselves, signed with the deployment's inbound secret.",
        "tags": [
          "Email hooks"
        ],
        "security": [],
        "parameters": [
          {
            "name": "X-CRM-Signature",
            "in": "header",
            "required": false,
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\"> under the inbound secret; refused more than five minutes from now.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InboundEmail"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "413": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/links/apps/{name}": {
      "get": {
        "operationId": "getPublicLinksAppsName",
        "summary": "Read a link's landing page",
        "description": "What the landing page of an app's link shows a phone without the app: its name, brand, link base and store pages.",
        "tags": [
          "Links"
        ],
        "security": [],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "The app's link name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicLinkApp"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/preferences/{token}": {
      "get": {
        "operationId": "getPublicPreferencesToken",
        "summary": "Read the preference centre",
        "tags": [
          "Email links"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The signed token from the email's link.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicPreferences"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "post": {
        "operationId": "postPublicPreferencesToken",
        "summary": "Save the preference centre",
        "description": "Takes JSON, or the page's plain form (which is then redirected back to the page).",
        "tags": [
          "Email links"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The signed token from the email's link.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PreferencesBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicPreferences"
                }
              }
            }
          },
          "303": {
            "description": "A redirect"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/public/v1/signup": {
      "post": {
        "operationId": "postPublicSignup",
        "summary": "Sign up",
        "description": "A new customer's sign-up form. Nothing is made until the link emailed to the address is used, and the answer is the same whether or not the address has an account. Allowed from the home page's origin only, which reads every answer, a refusal and a 429's Retry-After among them; any other site reads none.",
        "tags": [
          "Sign-up"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignupBody"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignupAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/server/v1/contacts/{external_id}": {
      "delete": {
        "operationId": "deleteServerContactsExternalId",
        "summary": "Erase a person",
        "description": "Queues the erase of everything kept about the person, done within minutes. For 30 days afterwards nothing new is kept about that id (409 erased).",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "serverKey": []
          }
        ],
        "parameters": [
          {
            "name": "external_id",
            "in": "path",
            "required": true,
            "description": "Your id for the person: 1 to 200 characters, the same id your users' tokens carry as their subject.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Queued"
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "put": {
        "operationId": "putServerContactsExternalId",
        "summary": "Identify a person",
        "description": "Makes or updates the contact with your id for them. Only what you send changes: a field left out keeps its value, and one sent empty (or an attribute sent null) clears it. Safe to send again.",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "serverKey": []
          }
        ],
        "parameters": [
          {
            "name": "external_id",
            "in": "path",
            "required": true,
            "description": "Your id for the person: 1 to 200 characters, the same id your users' tokens carry as their subject.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Identify"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentifyAnswer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/server/v1/contacts/{external_id}/export": {
      "get": {
        "operationId": "getServerContactsExternalIdExport",
        "summary": "Export a person",
        "description": "Everything kept about the person, for your own subject-access export.",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "serverKey": []
          }
        ],
        "parameters": [
          {
            "name": "external_id",
            "in": "path",
            "required": true,
            "description": "Your id for the person: 1 to 200 characters, the same id your users' tokens carry as their subject.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportContact"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/server/v1/event-definitions": {
      "put": {
        "operationId": "putServerEventDefinitions",
        "summary": "Declare your events",
        "description": "Adds the events your app sends, and their props, so the list lives with your code. It only adds: nothing is deleted or changed, and the same declaration again changes nothing.",
        "tags": [
          "Server events"
        ],
        "security": [
          {
            "serverKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventDefinitions"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeclaredEvents"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/server/v1/events": {
      "post": {
        "operationId": "postServerEvents",
        "summary": "Send events",
        "description": "Up to 500 events a request, each kept or refused on its own. An event's id is your idempotency key: the same id sent again is kept once.",
        "tags": [
          "Server events"
        ],
        "security": [
          {
            "serverKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ServerEvents"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServerAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "413": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "appKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-CRM-App-Key",
        "description": "The app's public key (app_…, or app_test_… for its test app), from Settings › Connect › Install. It is not a secret: it names the app."
      },
      "serverKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "One of the app's server keys (crm_sk_…, or crm_sk_test_… for its test app), from Settings › Connect › Keys. Keep it on your servers."
      },
      "unverifiedUserId": {
        "type": "apiKey",
        "in": "header",
        "name": "X-CRM-User-Id",
        "description": "The user's id with no token, for an app whose staff turned on unverified sign-in. Prototypes only: anyone who knows an id can act as that user."
      },
      "userToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The signed-in user's token: signed by your backend with the app's identity secret (HS256), or by your own sign-in's key set."
      }
    },
    "schemas": {
      "Align": {
        "type": "string",
        "description": "Where a line of content sits across its width. A carousel screen's alignment is start or center; a block's may be end too.",
        "enum": [
          "start",
          "center",
          "end"
        ]
      },
      "AppEvent": {
        "type": "string",
        "description": "The name of something a person did in the app that no server sees for itself. These are the standard ones, which the CRM's package records for every app; an app's own events are sent beside them by name, and the CRM takes each that the app defines. What the backend already sees (a request, a claim, an order, a sign-in) is recorded by the server that sees it, never sent by the app.",
        "enum": [
          "app_opened",
          "session_ended",
          "screen_viewed",
          "app_updated",
          "push_permission_changed",
          "location_permission_changed",
          "push_opened",
          "link_opened"
        ]
      },
      "AppLinksReport": {
        "type": "object",
        "description": "PUT /guest/v1/app-links: what a build of the app declares, with the digest the SDK sent on its check-ins (LinksDigest), which the CRM checks against the links.",
        "properties": {
          "app_version": {
            "type": "string"
          },
          "digest": {
            "type": "string"
          },
          "links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeclaredLink"
            }
          }
        },
        "required": [
          "app_version",
          "digest",
          "links"
        ]
      },
      "AppMode": {
        "type": "string",
        "enum": [
          "live",
          "test"
        ]
      },
      "Article": {
        "type": "object",
        "description": "One published article, whole.",
        "properties": {
          "blocks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          },
          "collection": {
            "$ref": "#/components/schemas/Collection",
            "description": "The collection it is in, when it is in one."
          },
          "id": {
            "type": "string"
          },
          "language": {
            "type": "string",
            "description": "The language it is in: the one asked for when it has a translation into it, else the app's default. Languages are every language it can be read in, the default first (the web's hreflang)."
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "out_of_date": {
            "type": "boolean",
            "description": "Says the translation it is read in was written from a default that has changed since: it may be out of date."
          },
          "related": {
            "type": "array",
            "description": "Other articles in the same collection, at most three.",
            "items": {
              "$ref": "#/components/schemas/ArticleSummary"
            }
          },
          "slug": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "blocks",
          "id",
          "related",
          "slug",
          "title",
          "updated_at"
        ]
      },
      "ArticleRatingBody": {
        "type": "object",
        "description": "The guest saying whether an article helped. Asked again, the newer answer is kept.",
        "properties": {
          "helpful": {
            "type": "boolean"
          }
        },
        "required": [
          "helpful"
        ]
      },
      "ArticleSummary": {
        "type": "object",
        "description": "An article in a list.",
        "properties": {
          "collection": {
            "type": "string",
            "description": "The slug of the collection it is in; empty for none."
          },
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "slug",
          "title",
          "updated_at"
        ]
      },
      "Articles": {
        "type": "object",
        "description": "Answers a search: the best matches first.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArticleSummary"
            }
          },
          "language": {
            "type": "string",
            "description": "As Collections' is: a search looks at the articles' words in it, and at the default's where an article has none."
          }
        },
        "required": [
          "items"
        ]
      },
      "Attachment": {
        "type": "object",
        "description": "A picture in a part.",
        "properties": {
          "bytes": {
            "type": "integer"
          },
          "content_type": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Where to fetch it, until ExpiresAt; ask for the conversation again for a new one after that."
          }
        },
        "required": [
          "bytes",
          "content_type",
          "expires_at",
          "id",
          "url"
        ]
      },
      "AttachmentBody": {
        "type": "object",
        "description": "Asks for somewhere to upload a picture.",
        "properties": {
          "bytes": {
            "type": "integer",
            "description": "The picture's size, at most MaxAttachmentBytes."
          },
          "content_type": {
            "type": "string",
            "description": "One of AttachmentTypes."
          }
        },
        "required": [
          "bytes",
          "content_type"
        ]
      },
      "AttachmentUpload": {
        "type": "object",
        "description": "Where to upload a picture: a form POST to URL with Fields first and the file last, as a field named \"file\". The picture is sent by naming ID in a MessageBody before ExpiresAt.",
        "properties": {
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "id": {
            "type": "string"
          },
          "max_bytes": {
            "type": "integer"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "expires_at",
          "fields",
          "id",
          "max_bytes",
          "url"
        ]
      },
      "Author": {
        "type": "object",
        "description": "Who wrote a part, as the guest is shown it: a member of staff by their first name, never their account or their address.",
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/AuthorKind"
          },
          "name": {
            "type": "string",
            "description": "A teammate's first name or the bot's name; empty for the guest and the messenger."
          }
        },
        "required": [
          "kind"
        ]
      },
      "AuthorKind": {
        "type": "string",
        "description": "Who wrote a part.",
        "enum": [
          "guest",
          "teammate",
          "bot",
          "system"
        ]
      },
      "BackgroundKind": {
        "type": "string",
        "description": "What a screen's background is.",
        "enum": [
          "plain",
          "color",
          "gradient",
          "image"
        ]
      },
      "Banner": {
        "type": "object",
        "description": "A short message over a screen.",
        "properties": {
          "blocks": {
            "type": "array",
            "description": "Paragraphs and headings only, at most MaxBannerBlocks.",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          },
          "dismissible": {
            "type": "boolean",
            "description": "Dismissible shows a close button."
          },
          "link": {
            "type": "string",
            "description": "Where tapping the banner goes, as Block.Link; empty for nowhere."
          },
          "style": {
            "$ref": "#/components/schemas/BannerStyle"
          }
        },
        "required": [
          "blocks",
          "dismissible",
          "style"
        ]
      },
      "BannerStyle": {
        "type": "string",
        "description": "Where a banner is drawn.",
        "enum": [
          "inline",
          "floating"
        ]
      },
      "Block": {
        "type": "object",
        "description": "One block of content. Only the fields its Type names are set.",
        "properties": {
          "align": {
            "$ref": "#/components/schemas/Align",
            "description": "A text block's, or an emoji's; empty is the screen's."
          },
          "alt": {
            "type": "string",
            "description": "What a picture shows, for a guest who cannot see it. Required with ImageURL."
          },
          "aspect": {
            "$ref": "#/components/schemas/ImageAspect",
            "description": "Aspect, Fit and Shape are a picture's, as CarouselImage's are."
          },
          "color": {
            "type": "string",
            "description": "A text block's colour, or a callout's tint: empty is the screen's text colour (ink, or white or near-black over a background of the screen's own, by contrast)."
          },
          "emoji": {
            "type": "string",
            "description": "An emoji block's, or the one before a callout's words."
          },
          "fit": {
            "$ref": "#/components/schemas/ImageFit"
          },
          "image_url": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ListItem"
            }
          },
          "label": {
            "type": "string"
          },
          "level": {
            "type": "integer",
            "description": "A heading's, 1 to MaxHeadingLevel."
          },
          "link": {
            "type": "string",
            "description": "Where a button or a picture goes: a path in the app, which opens only if the app's allow-list names it, or an https URL, which opens in the browser."
          },
          "shape": {
            "$ref": "#/components/schemas/ImageShape"
          },
          "size": {
            "type": "string",
            "description": "A word of the block's own: a TextSize for a paragraph, a heading or a list, an ImageSize for a picture, a SpacerSize for a spacer. Empty is the default carousel_style.json gives the block: a heading's follows its level, a paragraph's is body."
          },
          "spans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Span"
            }
          },
          "style": {
            "$ref": "#/components/schemas/ButtonStyle",
            "description": "A button's; empty is primary."
          },
          "tone": {
            "$ref": "#/components/schemas/CalloutTone",
            "description": "A callout's tint when it has no Color; empty is info."
          },
          "type": {
            "$ref": "#/components/schemas/BlockType"
          },
          "weight": {
            "$ref": "#/components/schemas/TextWeight",
            "description": "A paragraph's or a heading's; empty is regular for a paragraph and bold for a heading."
          }
        },
        "required": [
          "type"
        ]
      },
      "BlockType": {
        "type": "string",
        "description": "What one block of content is. Every message, post, banner, carousel screen, article and email is a list of blocks, never HTML: the app draws each natively, and crm renders email from the same list. A block type this app does not know is not drawn.",
        "enum": [
          "paragraph",
          "heading",
          "bullets",
          "numbers",
          "image",
          "button",
          "divider",
          "eyebrow",
          "callout",
          "checklist",
          "spacer",
          "emoji"
        ]
      },
      "BotButton": {
        "type": "object",
        "description": "One reply button: tapping it answers with its ID, and shows the guest's answer as Label.",
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "label"
        ]
      },
      "BotKind": {
        "type": "string",
        "description": "What a help-bot part asks of the guest.",
        "enum": [
          "says",
          "asks",
          "collects",
          "suggests",
          "hands_off",
          "resolves"
        ]
      },
      "BotPart": {
        "type": "object",
        "description": "What a help-bot part asks of the guest, by Kind.",
        "properties": {
          "articles": {
            "type": "array",
            "description": "A suggestion's, each opened in the help center.",
            "items": {
              "$ref": "#/components/schemas/ArticleSummary"
            }
          },
          "buttons": {
            "type": "array",
            "description": "An ask's, and a suggestion's yes and no.",
            "items": {
              "$ref": "#/components/schemas/BotButton"
            }
          },
          "collect": {
            "$ref": "#/components/schemas/CollectField",
            "description": "The data a collecting part asks for."
          },
          "kind": {
            "$ref": "#/components/schemas/BotKind"
          },
          "order_ref": {
            "$ref": "#/components/schemas/OrderRef",
            "description": "What a collected order reference looks like, the app's own: with Collect order_ref, so that the app checks an answer as the CRM will."
          },
          "reply": {
            "$ref": "#/components/schemas/ReplyExpectation",
            "description": "A hand-off's: how soon a person answers."
          }
        },
        "required": [
          "kind"
        ]
      },
      "BotReplyBody": {
        "type": "object",
        "description": "Answers the bot part PartID: with one of its buttons, by Button, or with the data it collects, as Value.",
        "properties": {
          "button": {
            "type": "string"
          },
          "part_id": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        },
        "required": [
          "part_id"
        ]
      },
      "ButtonAction": {
        "type": "string",
        "description": "What a carousel's button does.",
        "enum": [
          "next",
          "screen",
          "back",
          "end",
          "link",
          "permission"
        ]
      },
      "ButtonShape": {
        "type": "string",
        "description": "A carousel's buttons' shape.",
        "enum": [
          "pill",
          "rounded"
        ]
      },
      "ButtonStyle": {
        "type": "string",
        "description": "How a button is drawn: primary is filled with the accent, secondary is outlined, text is the words alone.",
        "enum": [
          "primary",
          "secondary",
          "text"
        ]
      },
      "CalloutTone": {
        "type": "string",
        "description": "A callout's tint by what it says.",
        "enum": [
          "info",
          "good",
          "warn"
        ]
      },
      "Carousel": {
        "type": "object",
        "description": "Screens the guest moves through, which may branch: each button and each answer of a choice says where it goes (a screen, or the end), and a screen's Next says where Next and a forward swipe go. Screens may loop back (\"Try again\"); every screen the guest can reach has a way to the end, and a screen nothing leads to is kept but never shown.",
        "properties": {
          "locked": {
            "type": "boolean",
            "description": "A carousel the guest leaves only through its own buttons, for onboarding they must finish: no close button and no Skip, and a drag down, a tap outside and the system's back do nothing. Left out (false) is the usual carousel, which the guest may close at any time: a carousel is dismissible unless it says otherwise."
          },
          "presentation": {
            "$ref": "#/components/schemas/CarouselPresentation",
            "description": "How it is shown; empty is a sheet."
          },
          "progress": {
            "$ref": "#/components/schemas/CarouselProgress",
            "description": "What the top bar shows of how far the guest is; empty is dots for a carousel of one line of at most seven screens, with no choice or button that branches, and a bar otherwise."
          },
          "screens": {
            "type": "array",
            "description": "1 to MaxCarouselScreens, in the editor's order, which is also where a screen's Next goes when it names none.",
            "items": {
              "$ref": "#/components/schemas/CarouselScreen"
            }
          },
          "skip": {
            "type": "boolean",
            "description": "Skip shows \"Skip\" in the top bar in place of the close button: it closes the carousel as the close button does. A locked carousel has none."
          },
          "start": {
            "type": "string",
            "description": "The id of the screen it opens on; empty is the first."
          },
          "style": {
            "$ref": "#/components/schemas/CarouselStyle",
            "description": "The carousel's look, which a screen's own Style overrides field by field; left out is the defaults."
          }
        },
        "required": [
          "screens"
        ]
      },
      "CarouselBackground": {
        "type": "object",
        "description": "What is behind a screen. Text over a background of colour, gradient or image is drawn white or near-black, whichever reads better against it, unless a block sets its own colour.",
        "properties": {
          "angle": {
            "type": "integer",
            "description": "A gradient's direction in degrees, 0 to 359: 0 runs from the bottom up, 90 from left to right, as CSS's linear-gradient."
          },
          "color": {
            "type": "string",
            "description": "A colour's, and a gradient's first stop."
          },
          "color2": {
            "type": "string",
            "description": "A gradient's last stop."
          },
          "image_url": {
            "type": "string",
            "description": "An image background's, an https address. It is a decoration, with no alt text: what it says belongs in the blocks."
          },
          "kind": {
            "$ref": "#/components/schemas/BackgroundKind"
          },
          "scrim": {
            "$ref": "#/components/schemas/Scrim",
            "description": "Scrim darkens or lightens an image under the words; empty is none."
          }
        },
        "required": [
          "kind"
        ]
      },
      "CarouselButton": {
        "type": "object",
        "description": "One of a screen's buttons.",
        "properties": {
          "do": {
            "$ref": "#/components/schemas/ButtonAction",
            "description": "What it does, and says which of the rest it has."
          },
          "id": {
            "type": "string",
            "description": "Names it in receipts and in staff's Records: letters, digits, - and _, at most 40, unique among the carousel's buttons and options."
          },
          "label": {
            "type": "string"
          },
          "link": {
            "type": "string",
            "description": "What link opens, as Block.Link."
          },
          "permission": {
            "$ref": "#/components/schemas/CarouselPermission",
            "description": "What permission asks the phone for. The app asks the way it always does, and a guest who refused before is sent to the phone's settings instead."
          },
          "screen": {
            "type": "string",
            "description": "Where screen goes (a screen's id or \"end\"), and where permission goes once the phone has answered (empty is the screen's Next)."
          },
          "style": {
            "$ref": "#/components/schemas/ButtonStyle",
            "description": "Empty for primary."
          }
        },
        "required": [
          "do",
          "id",
          "label"
        ]
      },
      "CarouselChoice": {
        "type": "object",
        "description": "A question on a screen, answered by tapping options.",
        "properties": {
          "multiple": {
            "type": "boolean",
            "description": "Multiple lets the guest pick several, then Continue to the screen's Next; a single choice goes on as soon as one is tapped, to that option's Next."
          },
          "options": {
            "type": "array",
            "description": "2 to MaxCarouselOptions.",
            "items": {
              "$ref": "#/components/schemas/CarouselOption"
            }
          }
        },
        "required": [
          "options"
        ]
      },
      "CarouselImage": {
        "type": "object",
        "description": "A screen's hero picture.",
        "properties": {
          "alt": {
            "type": "string",
            "description": "What it shows, for a guest who cannot see it."
          },
          "aspect": {
            "$ref": "#/components/schemas/ImageAspect",
            "description": "Aspect, Fit and Shape are empty for auto, cover and rounded."
          },
          "fit": {
            "$ref": "#/components/schemas/ImageFit"
          },
          "shape": {
            "$ref": "#/components/schemas/ImageShape"
          },
          "size": {
            "$ref": "#/components/schemas/ImageSize",
            "description": "How tall it is (carousel_style.json's hero heights); empty is medium, and full for image_full."
          },
          "url": {
            "type": "string",
            "description": "An https address."
          }
        },
        "required": [
          "alt",
          "url"
        ]
      },
      "CarouselLayout": {
        "type": "string",
        "description": "How a screen is laid out.",
        "enum": [
          "centred",
          "image_top",
          "image_full",
          "text"
        ]
      },
      "CarouselOption": {
        "type": "object",
        "description": "One answer of a choice.",
        "properties": {
          "emoji": {
            "type": "string",
            "description": "Drawn before the label; empty for none."
          },
          "id": {
            "type": "string",
            "description": "Names it in receipts and in staff's Records, as a button's does, and is unique among the carousel's buttons and options."
          },
          "label": {
            "type": "string"
          },
          "next": {
            "type": "string",
            "description": "Where tapping it goes, in a single choice: a screen's id, or \"end\"; empty is the screen's Next. A multiple choice's options have none."
          }
        },
        "required": [
          "id",
          "label"
        ]
      },
      "CarouselPermission": {
        "type": "string",
        "description": "What a carousel's screen may ask the guest to allow. The app asks the way it always does, and a guest who refused before is sent to the phone's settings instead.",
        "enum": [
          "notifications",
          "location"
        ]
      },
      "CarouselPresentation": {
        "type": "string",
        "description": "How a carousel is shown.",
        "enum": [
          "sheet",
          "card",
          "full"
        ]
      },
      "CarouselProgress": {
        "type": "string",
        "description": "What a carousel's top bar shows of how far the guest is.",
        "enum": [
          "dots",
          "bar",
          "none"
        ]
      },
      "CarouselScreen": {
        "type": "object",
        "description": "One screen of a carousel.",
        "properties": {
          "align": {
            "$ref": "#/components/schemas/Align",
            "description": "Start or center; empty is center, and start on a text screen."
          },
          "background": {
            "$ref": "#/components/schemas/CarouselBackground",
            "description": "Behind the screen; left out is the sheet's own."
          },
          "blocks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          },
          "buttons": {
            "type": "array",
            "description": "0 to MaxCarouselButtons, stacked: the first is the main one. None gives the screen the app's Next (or Done).",
            "items": {
              "$ref": "#/components/schemas/CarouselButton"
            }
          },
          "choice": {
            "$ref": "#/components/schemas/CarouselChoice",
            "description": "A question the screen asks; left out for none."
          },
          "id": {
            "type": "string",
            "description": "Names the screen to the targets that go to it, and in receipts: letters, digits, - and _, at most 40, and never \"end\". Unique among the carousel's screens."
          },
          "image": {
            "$ref": "#/components/schemas/CarouselImage",
            "description": "The screen's hero picture, apart from its blocks: above them for image_top, behind them for image_full (which need one), and a smaller one above the words for centred. A text screen has none."
          },
          "layout": {
            "$ref": "#/components/schemas/CarouselLayout",
            "description": "How the screen is laid out; empty is centred."
          },
          "name": {
            "type": "string",
            "description": "Staff's own word for the screen, in the editor and the report. It is never sent to the app."
          },
          "next": {
            "type": "string",
            "description": "Where Next, a forward swipe, a multiple choice's Continue and a single choice's options with no Next of their own go: a screen's id, or \"end\". Empty is the screen after this one in the list, or the end after the last."
          },
          "style": {
            "$ref": "#/components/schemas/CarouselStyle",
            "description": "Style overrides the carousel's Style for this screen, field by field; left out is the carousel's."
          },
          "valign": {
            "$ref": "#/components/schemas/VAlign",
            "description": "Where the content sits in the screen's height; empty is center for centred, bottom for image_full, and top otherwise."
          }
        },
        "required": [
          "blocks",
          "id"
        ]
      },
      "CarouselStyle": {
        "type": "object",
        "description": "A carousel's look. Every field is optional: empty is the default (carousel_style.json's \"defaults\"), and on a screen, the carousel's.",
        "properties": {
          "accent": {
            "type": "string",
            "description": "Accent colours the buttons, the progress and a choice's ticks; empty is the app's brand accent."
          },
          "button_shape": {
            "$ref": "#/components/schemas/ButtonShape"
          },
          "corner": {
            "$ref": "#/components/schemas/Corner"
          },
          "text_scale": {
            "$ref": "#/components/schemas/TextScale"
          }
        }
      },
      "Category": {
        "type": "string",
        "description": "Category groups events on the CRM site's dashboard and in its pickers. The standard events have the standard categories below; an app's own event names its own (lower-case letters, digits and underscores), which may be account, app or a word of the app's, never crm.",
        "enum": [
          "account",
          "app",
          "crm"
        ]
      },
      "Checkin": {
        "type": "object",
        "description": "Answers a check-in: what to show now, in the order to show it.",
        "properties": {
          "messages": {
            "type": "array",
            "description": "The in-app messages to show, the most important first: at most one large one (MessageKind.Large) a session, and at most one banner. A message answered again, not yet seen, is the same message: the app shows it once.",
            "items": {
              "$ref": "#/components/schemas/InAppMessage"
            }
          },
          "next_checkin_seconds": {
            "type": "integer",
            "description": "The soonest the app should check in again."
          },
          "send_links": {
            "type": "boolean",
            "description": "Asks the app for the destinations it declares (PUT /guest/v1/app-links), once: the check-in's LinksDigest is one the CRM has not been sent."
          },
          "test_phone": {
            "type": "boolean",
            "description": "A guest staff chose as a test phone: the app offers a way to check for tests at once, rather than at the next check-in."
          },
          "throttled": {
            "type": "boolean",
            "description": "A check-in sooner than CheckinEvery after the last: nothing new is in it, and what the app shows stays as it is. A test phone with tests waiting is never throttled."
          },
          "unread": {
            "type": "integer",
            "description": "The messenger's unread count, a chat staff started included."
          },
          "unread_notices": {
            "type": "integer",
            "description": "The inbox's unread count: the notices the CRM pushed the guest and they have not read, for the bell."
          }
        },
        "required": [
          "messages",
          "next_checkin_seconds",
          "unread",
          "unread_notices"
        ]
      },
      "CheckinBody": {
        "type": "object",
        "description": "The app asking what to show: when it opens or comes back to the front, and after it sends a batch of events.",
        "properties": {
          "app_version": {
            "type": "string"
          },
          "holds_for_screens": {
            "type": "boolean",
            "description": "Says the app holds a message meant for some screens (InAppMessage.Screens) until one of them is on: an SDK that knows how, whose host has named a screen this run. A message for some screens is never answered to an app that does not say so."
          },
          "links_digest": {
            "type": "string",
            "description": "LinksDigest of the destinations the app declares, when it declares any: a check-in answers SendLinks for one the CRM has not been sent."
          },
          "platform": {
            "$ref": "#/components/schemas/DevicePlatform"
          },
          "screen": {
            "type": "string",
            "description": "The named route on screen, when the app knows it."
          },
          "session_id": {
            "type": "string",
            "description": "The app's id for this time in front, drawn when it opened or came back: a guest is shown one large message a session."
          },
          "time_zone": {
            "type": "string",
            "description": "The phone's IANA zone, for quiet hours and sending windows; left out when the phone does not say."
          }
        },
        "required": [
          "app_version",
          "platform",
          "session_id"
        ]
      },
      "CollectField": {
        "type": "string",
        "description": "The kind of data the bot asks for.",
        "enum": [
          "text",
          "email",
          "order_ref"
        ]
      },
      "Collection": {
        "type": "object",
        "description": "A group of help articles.",
        "properties": {
          "articles": {
            "type": "integer",
            "description": "How many published articles the guest can read in it."
          },
          "description": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "articles",
          "id",
          "slug",
          "title"
        ]
      },
      "CollectionArticles": {
        "type": "object",
        "description": "One collection and its articles, in staff's order.",
        "properties": {
          "articles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArticleSummary"
            }
          },
          "collection": {
            "$ref": "#/components/schemas/Collection"
          },
          "language": {
            "type": "string",
            "description": "As Collections' is."
          }
        },
        "required": [
          "articles",
          "collection"
        ]
      },
      "Collections": {
        "type": "object",
        "description": "Every collection with an article in it, in staff's order.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Collection"
            }
          },
          "language": {
            "type": "string"
          }
        },
        "required": [
          "items"
        ]
      },
      "Consent": {
        "type": "object",
        "description": "What the CRM keeps of the guest's choices about its pushes.",
        "properties": {
          "offers_by_push": {
            "type": "boolean"
          }
        },
        "required": [
          "offers_by_push"
        ]
      },
      "ConsentBody": {
        "type": "object",
        "description": "PUT consent: what the guest chose in the host's own screens, when the host lets them choose there. Only what is sent changes.",
        "properties": {
          "offers_by_push": {
            "type": "boolean",
            "description": "The guest agreeing to offers by push: a marketing push goes only to a guest who has."
          }
        }
      },
      "Conversation": {
        "type": "object",
        "description": "One conversation, with every part the guest sees, oldest first.",
        "properties": {
          "assignee": {
            "$ref": "#/components/schemas/Author",
            "description": "The member of staff answering, when one is."
          },
          "awaiting": {
            "type": "string",
            "description": "The id of the bot part the help bot is waiting on an answer to; empty when it waits on none."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "id": {
            "type": "string"
          },
          "last_seq": {
            "type": "integer",
            "description": "The newest part's Seq, including those the guest is not shown, so a wait asked after it waits for the next."
          },
          "parts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Part"
            }
          },
          "reply": {
            "$ref": "#/components/schemas/ReplyExpectation"
          },
          "state": {
            "$ref": "#/components/schemas/ConversationState",
            "description": "Open or closed; snoozed is shown as open."
          },
          "ticket_id": {
            "type": "string",
            "description": "The ticket this conversation is about, when there is one."
          },
          "unread": {
            "type": "integer",
            "description": "How many staff parts the guest has not read."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "created_at",
          "id",
          "last_seq",
          "parts",
          "reply",
          "state",
          "unread",
          "updated_at"
        ]
      },
      "ConversationPage": {
        "type": "object",
        "description": "A page of the guest's conversations, the most recently changed first. NextBefore is there when older ones are.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConversationSummary"
            }
          },
          "next_before": {
            "type": "string"
          }
        },
        "required": [
          "items"
        ]
      },
      "ConversationState": {
        "type": "string",
        "description": "Where a conversation has got to. A guest is only ever shown open or closed: a conversation staff snoozed is still open to the guest who is waiting on it.",
        "enum": [
          "open",
          "snoozed",
          "closed"
        ]
      },
      "ConversationSummary": {
        "type": "object",
        "description": "A conversation in a list.",
        "properties": {
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "id": {
            "type": "string"
          },
          "preview": {
            "type": "string",
            "description": "The newest part the guest sees, as a line of text."
          },
          "preview_author": {
            "$ref": "#/components/schemas/Author"
          },
          "state": {
            "$ref": "#/components/schemas/ConversationState"
          },
          "unread": {
            "type": "integer"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "created_at",
          "id",
          "preview",
          "preview_author",
          "state",
          "unread",
          "updated_at"
        ]
      },
      "ConversationUpdate": {
        "type": "object",
        "description": "Answers a wait: the parts after the one asked after that the guest sees, and the conversation as it is now. Parts is empty when the wait ran out with nothing new.",
        "properties": {
          "awaiting": {
            "type": "string",
            "description": "As Conversation.Awaiting, now."
          },
          "last_seq": {
            "type": "integer",
            "description": "What the next wait is after: the newest part this answer read, which is the conversation's newest unless the parts since were too many for one answer."
          },
          "parts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Part"
            }
          },
          "state": {
            "$ref": "#/components/schemas/ConversationState"
          },
          "unread": {
            "type": "integer"
          }
        },
        "required": [
          "last_seq",
          "parts",
          "state",
          "unread"
        ]
      },
      "Corner": {
        "type": "string",
        "description": "How round a carousel's corners are: its sheet, its pictures, its tiles (carousel_style.json's radii).",
        "enum": [
          "soft",
          "round",
          "square"
        ]
      },
      "DeclareOutcome": {
        "type": "string",
        "description": "What a declaration did with one definition.",
        "enum": [
          "created",
          "props_added",
          "unchanged",
          "refused"
        ]
      },
      "DeclaredEvent": {
        "type": "object",
        "description": "What was done with one definition.",
        "properties": {
          "added": {
            "type": "array",
            "description": "The props a DeclarePropsAdded gave the event.",
            "items": {
              "type": "string"
            }
          },
          "index": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "outcome": {
            "$ref": "#/components/schemas/DeclareOutcome"
          },
          "problem": {
            "type": "string",
            "description": "Why a DeclareRefused was not added."
          }
        },
        "required": [
          "index",
          "name",
          "outcome"
        ]
      },
      "DeclaredEvents": {
        "type": "object",
        "description": "Answers EventDefinitions: one DeclaredEvent for each definition, in the order they were sent, and how many of each outcome.",
        "properties": {
          "created": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeclaredEvent"
            }
          },
          "props_added": {
            "type": "integer"
          },
          "refused": {
            "type": "integer"
          },
          "unchanged": {
            "type": "integer"
          }
        },
        "required": [
          "created",
          "items",
          "props_added",
          "refused",
          "unchanged"
        ]
      },
      "DeclaredLink": {
        "type": "object",
        "description": "One destination the app declares (the SDK's CrmLink): a name for staff, its path template (crm.AppLink's grammar), the page on the web for a phone without the app, and the screen it shows.",
        "properties": {
          "name": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "screen": {
            "type": "string"
          },
          "web_url": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "path"
        ]
      },
      "Definition": {
        "type": "object",
        "description": "One event an app has.",
        "properties": {
          "category": {
            "$ref": "#/components/schemas/Category"
          },
          "description": {
            "type": "string",
            "description": "What the CRM site says the event means."
          },
          "name": {
            "$ref": "#/components/schemas/Name"
          },
          "passive": {
            "type": "boolean",
            "description": "An event that happens to a person rather than one they do: it is recorded, and does not move when they were last seen."
          },
          "props": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Prop"
            }
          },
          "source": {
            "$ref": "#/components/schemas/Source"
          }
        },
        "required": [
          "category",
          "description",
          "name",
          "source"
        ]
      },
      "DeviceBody": {
        "type": "object",
        "description": "PUT devices: the registration token the host's Firebase gave this device, which the package registers on every start and again whenever it changes.",
        "properties": {
          "app_version": {
            "type": "string",
            "description": "AppVersion and Locale are what the app is and speaks, for staff to tell devices apart; both may be left out."
          },
          "locale": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/PushPlatform"
          },
          "token": {
            "type": "string"
          }
        },
        "required": [
          "platform",
          "token"
        ]
      },
      "DevicePlatform": {
        "type": "string",
        "description": "What the app runs on, as every batch of events and every check-in says: a phone, or a browser (a website or web app calling the guest API, or a Flutter app built for the web).",
        "enum": [
          "ios",
          "android",
          "web"
        ]
      },
      "EmailEvent": {
        "type": "object",
        "description": "A mail provider telling the CRM what became of an email it sent, signed as InboundEmail is:",
        "properties": {
          "address": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "hard": {
            "type": "boolean",
            "description": "A bounce that is permanent; a soft one is counted and suppresses nothing."
          },
          "id": {
            "type": "string",
            "description": "The provider's id for the event: the same event posted twice is counted once."
          },
          "kind": {
            "$ref": "#/components/schemas/EmailEventKind"
          },
          "message_id": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "address",
          "id",
          "kind",
          "message_id",
          "occurred_at"
        ]
      },
      "EmailEventKind": {
        "type": "string",
        "description": "What became of an email.",
        "enum": [
          "bounce",
          "complaint",
          "delivered"
        ]
      },
      "EventBody": {
        "type": "object",
        "description": "One thing the guest did.",
        "properties": {
          "id": {
            "type": "string",
            "description": "The app's own id for the event, a UUID drawn when it happened. A batch sent twice (the answer was lost) is kept once."
          },
          "name": {
            "$ref": "#/components/schemas/AppEvent"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "props": {
            "type": "object",
            "description": "What the event's comment in AppEvents names, as strings: crm reads a number from its string by the event's definition. Left out when there are none.",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "required": [
          "id",
          "name",
          "occurred_at"
        ]
      },
      "EventDefinitions": {
        "type": "object",
        "description": "PUT /server/v1/event-definitions: the app's own events, as its servers declare them.",
        "properties": {
          "definitions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Definition"
            }
          }
        },
        "required": [
          "definitions"
        ]
      },
      "EventRejected": {
        "type": "object",
        "description": "One event of a batch refused, by its place in the batch.",
        "properties": {
          "id": {
            "type": "string"
          },
          "index": {
            "type": "integer"
          },
          "reason": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "index",
          "reason"
        ]
      },
      "EventsAccepted": {
        "type": "object",
        "description": "Answers a batch.",
        "properties": {
          "accepted": {
            "type": "integer",
            "description": "How many of the batch's events were kept, which is fewer than were sent when some had been sent before, were refused, or the guest has turned product events off. The first batch of a guest the CRM has not met yet is accepted whole, and kept a moment later, once the CRM knows whether the guest turned product events off."
          },
          "rejected": {
            "type": "array",
            "description": "The events refused, each alone: a name the app does not define, one its servers send, or one older than MaxEventAge. Left out when there are none.",
            "items": {
              "$ref": "#/components/schemas/EventRejected"
            }
          }
        },
        "required": [
          "accepted"
        ]
      },
      "EventsBody": {
        "type": "object",
        "description": "A batch of events, oldest first, with the facts about the phone that every event in it shares.",
        "properties": {
          "app_version": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EventBody"
            }
          },
          "platform": {
            "$ref": "#/components/schemas/DevicePlatform"
          },
          "sent_at": {
            "type": "string",
            "format": "date-time",
            "description": "The phone's clock as it sent the batch, drawn again for each attempt: crm measures the phone's clock by it (ClockSkew) and moves every event's OccurredAt by as much, so that a phone whose clock is wrong neither makes everything late nor puts it in the future. Left out by apps older than it, whose events are taken as their clocks say."
          },
          "time_zone": {
            "type": "string",
            "description": "The phone's IANA zone name (\"Europe/London\"), which crm uses for a guest's quiet hours. Left out when the phone does not say."
          }
        },
        "required": [
          "app_version",
          "events",
          "platform"
        ]
      },
      "ExportArticleRating": {
        "type": "object",
        "description": "The guest saying whether an article helped.",
        "properties": {
          "article": {
            "type": "string"
          },
          "helpful": {
            "type": "boolean"
          },
          "rated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "article",
          "helpful",
          "rated_at"
        ]
      },
      "ExportAttribute": {
        "type": "object",
        "description": "One attribute and its value.",
        "properties": {
          "name": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "value"
        ]
      },
      "ExportCollected": {
        "type": "object",
        "description": "One thing the help bot kept from a conversation: what it is called (order_ref, email) and what the guest gave.",
        "properties": {
          "conversation_id": {
            "type": "string"
          },
          "key": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        },
        "required": [
          "conversation_id",
          "key",
          "value"
        ]
      },
      "ExportConsentChange": {
        "type": "object",
        "description": "One change of the guest's choices, for their export: a topic's (by its name), or a channel's own switch (no topic).",
        "properties": {
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "channel": {
            "$ref": "#/components/schemas/PreferenceChannel"
          },
          "source": {
            "type": "string"
          },
          "subscribed": {
            "type": "boolean"
          },
          "topic": {
            "type": "string"
          },
          "wording": {
            "type": "string"
          }
        },
        "required": [
          "at",
          "channel",
          "source",
          "subscribed"
        ]
      },
      "ExportContact": {
        "type": "object",
        "description": "What the crm service keeps about a guest, their contact, for the guest's export (Export.CRM): the attributes the app's staff see on their profile, the tags and notes staff gave them, and the events recorded about them, newest first.",
        "properties": {
          "article_ratings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportArticleRating"
            }
          },
          "attributes": {
            "type": "array",
            "description": "The standard attributes crm derives from what the guest did (first seen, app version, orders, …) and the custom ones staff keep, by name, each value as a string. Only those that have a value.",
            "items": {
              "$ref": "#/components/schemas/ExportAttribute"
            }
          },
          "collected": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportCollected"
            }
          },
          "consent_history": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportConsentChange"
            }
          },
          "conversations": {
            "type": "array",
            "description": "Conversations, Tickets and ArticleRatings are the messenger's and the help center's; left out by a crm older than them, or when there are none.",
            "items": {
              "$ref": "#/components/schemas/ExportConversation"
            }
          },
          "devices": {
            "type": "array",
            "description": "The devices the guest registered for the CRM's pushes, and Notices their inbox; left out by a crm older than them, or when there are none.",
            "items": {
              "$ref": "#/components/schemas/ExportDevice"
            }
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportEvent"
            }
          },
          "events_truncated": {
            "type": "boolean",
            "description": "The export holding only the newest MaxExportEvents events of more."
          },
          "notes": {
            "type": "array",
            "description": "What staff wrote about the guest. Who wrote each is left out: that is the member of staff's, not the guest's.",
            "items": {
              "$ref": "#/components/schemas/ExportNote"
            }
          },
          "notices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportNotice"
            }
          },
          "receipts": {
            "type": "array",
            "description": "Receipts and SurveyResponses are outbound's: the messages the guest was sent or shown, and their answers to surveys; left out by a crm older than them, or when there are none.",
            "items": {
              "$ref": "#/components/schemas/ExportReceipt"
            }
          },
          "subscriptions": {
            "type": "array",
            "description": "The guest's own choices about the app's topics, and ConsentHistory every change of them and of their channels' switches; left out by a crm older than them, or when there are none.",
            "items": {
              "$ref": "#/components/schemas/ExportSubscription"
            }
          },
          "survey_responses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportSurveyResponse"
            }
          },
          "tags": {
            "type": "array",
            "description": "The names of the tags staff put on the guest.",
            "items": {
              "type": "string"
            }
          },
          "test_phone": {
            "$ref": "#/components/schemas/ExportTestPhone",
            "description": "The guest's account being a test phone, which staff send messages to so as to try them, and TestSends the tests sent to it (kept a week); left out by a crm older than them, or when there are none."
          },
          "test_sends": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportTestSend"
            }
          },
          "tickets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportTicket"
            }
          },
          "workflows": {
            "type": "array",
            "description": "The runs of crm's workflows the guest was in, and Collected what the help bot kept from their conversations; left out by a crm older than them, or when there are none.",
            "items": {
              "$ref": "#/components/schemas/ExportWorkflowRun"
            }
          }
        },
        "required": [
          "attributes",
          "events",
          "notes",
          "tags"
        ]
      },
      "ExportConversation": {
        "type": "object",
        "description": "One of the guest's conversations, for their export: every part they could see, and their rating. Staff's notes are left out, and so is who each member of staff is beyond the name they were shown.",
        "properties": {
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "id": {
            "type": "string"
          },
          "parts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportPart"
            }
          },
          "rating": {
            "type": "integer"
          },
          "remark": {
            "type": "string"
          },
          "state": {
            "$ref": "#/components/schemas/ConversationState"
          }
        },
        "required": [
          "created_at",
          "id",
          "parts",
          "state"
        ]
      },
      "ExportDevice": {
        "type": "object",
        "description": "One device the guest registered for pushes, for their export. Its token is a credential for the device and is left out.",
        "properties": {
          "app_version": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_seen_at": {
            "type": "string",
            "format": "date-time"
          },
          "locale": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/PushPlatform"
          }
        },
        "required": [
          "created_at",
          "last_seen_at",
          "platform"
        ]
      },
      "ExportEvent": {
        "type": "object",
        "description": "One event recorded about the guest: one of AppEvents, or a server event.",
        "properties": {
          "name": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "props": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "required": [
          "name",
          "occurred_at"
        ]
      },
      "ExportNote": {
        "type": "object",
        "description": "One note staff wrote about the guest.",
        "properties": {
          "body": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "body",
          "created_at"
        ]
      },
      "ExportNotice": {
        "type": "object",
        "description": "One notice in the guest's inbox, for their export.",
        "properties": {
          "body": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "link": {
            "type": "string"
          },
          "read_at": {
            "type": "string",
            "format": "date-time"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/PushType"
          }
        },
        "required": [
          "body",
          "created_at",
          "title",
          "type"
        ]
      },
      "ExportPart": {
        "type": "object",
        "description": "One part of an exported conversation. A picture is named by its content type and size: the file is sent on request, not in the export.",
        "properties": {
          "attachments": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "author": {
            "$ref": "#/components/schemas/Author"
          },
          "blocks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "kind": {
            "$ref": "#/components/schemas/PartKind"
          },
          "text": {
            "type": "string"
          }
        },
        "required": [
          "author",
          "created_at",
          "kind"
        ]
      },
      "ExportReceipt": {
        "type": "object",
        "description": "One message the guest was sent or shown, for their export: what it was and what they did with it.",
        "properties": {
          "clicked_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time"
          },
          "dismissed_at": {
            "type": "string",
            "format": "date-time"
          },
          "kind": {
            "$ref": "#/components/schemas/MessageKind"
          },
          "message": {
            "type": "string",
            "description": "The message's name, as staff called it."
          },
          "seen_at": {
            "type": "string",
            "format": "date-time"
          },
          "sent_at": {
            "type": "string",
            "format": "date-time"
          },
          "variant": {
            "type": "string"
          }
        },
        "required": [
          "kind",
          "message"
        ]
      },
      "ExportSubscription": {
        "type": "object",
        "description": "One of the guest's choices about a topic by a channel, for their export: when and how it was made, and under which wording.",
        "properties": {
          "changed_at": {
            "type": "string",
            "format": "date-time"
          },
          "channel": {
            "$ref": "#/components/schemas/PreferenceChannel"
          },
          "source": {
            "type": "string"
          },
          "subscribed": {
            "type": "boolean"
          },
          "topic": {
            "type": "string"
          },
          "wording": {
            "type": "string"
          }
        },
        "required": [
          "changed_at",
          "channel",
          "source",
          "subscribed",
          "topic"
        ]
      },
      "ExportSurveyAnswer": {
        "type": "object",
        "description": "One answer, as the words of the question and of the answer.",
        "properties": {
          "answer": {
            "type": "string"
          },
          "question": {
            "type": "string"
          }
        },
        "required": [
          "answer",
          "question"
        ]
      },
      "ExportSurveyResponse": {
        "type": "object",
        "description": "The guest's answers to one survey.",
        "properties": {
          "answers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportSurveyAnswer"
            }
          },
          "completed_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "survey": {
            "type": "string"
          }
        },
        "required": [
          "answers",
          "started_at",
          "survey"
        ]
      },
      "ExportTestPhone": {
        "type": "object",
        "description": "The guest's account being a test phone: staff's name for it, and since when. Who in staff chose it is theirs, not the guest's, and is left out.",
        "properties": {
          "added_at": {
            "type": "string",
            "format": "date-time"
          },
          "label": {
            "type": "string"
          }
        },
        "required": [
          "added_at"
        ]
      },
      "ExportTestSend": {
        "type": "object",
        "description": "One test staff sent to the guest's test phone: what it was, and what became of it. Who sent it is left out.",
        "properties": {
          "answers": {
            "type": "array",
            "description": "A survey test's, in the words of the questions.",
            "items": {
              "$ref": "#/components/schemas/ExportSurveyAnswer"
            }
          },
          "clicked_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time"
          },
          "dismissed_at": {
            "type": "string",
            "format": "date-time"
          },
          "kind": {
            "$ref": "#/components/schemas/MessageKind"
          },
          "name": {
            "type": "string",
            "description": "The message's, as staff called it when they sent it, or the built-in sample's, when Sample."
          },
          "outcome": {
            "type": "string",
            "description": "A push's or a chat's: sent, notifications_off, not_sent or started."
          },
          "picked_up_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the app took an in-app test at a check-in; what the guest did with it follows."
          },
          "sample": {
            "type": "boolean"
          },
          "seen_at": {
            "type": "string",
            "format": "date-time"
          },
          "sent_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "kind",
          "name",
          "sent_at"
        ]
      },
      "ExportTicket": {
        "type": "object",
        "description": "One of the guest's tickets, for their export.",
        "properties": {
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TicketField"
            }
          },
          "ref": {
            "type": "string"
          },
          "resolved_at": {
            "type": "string",
            "format": "date-time"
          },
          "state": {
            "$ref": "#/components/schemas/TicketState"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "created_at",
          "fields",
          "ref",
          "state",
          "title",
          "type"
        ]
      },
      "ExportWorkflowRun": {
        "type": "object",
        "description": "One run of a workflow the guest was in: the workflow's name, where the run is and, once it has ended, how.",
        "properties": {
          "ended_at": {
            "type": "string",
            "format": "date-time"
          },
          "outcome": {
            "type": "string"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "state": {
            "type": "string"
          },
          "workflow": {
            "type": "string"
          }
        },
        "required": [
          "started_at",
          "state",
          "workflow"
        ]
      },
      "Feature": {
        "type": "string",
        "description": "One of the CRM's parts an app has switched on for its guests. The app shows nothing of a part it has not been given, and ignores a word it does not know, so a part can be switched on without a release and an older app is not broken by a newer word.",
        "enum": [
          "messenger",
          "help_center",
          "in_app_messages"
        ]
      },
      "FieldType": {
        "type": "string",
        "description": "What a ticket's field holds.",
        "enum": [
          "text",
          "list",
          "number",
          "bool",
          "date"
        ]
      },
      "ForgetDeviceBody": {
        "type": "object",
        "description": "DELETE devices: the token the package registered, on sign-out, so that the next guest on the device is not pushed the last one's.",
        "properties": {
          "token": {
            "type": "string"
          }
        },
        "required": [
          "token"
        ]
      },
      "HomeCard": {
        "type": "object",
        "description": "One card of the messenger's home. Only what its Kind uses is set: a link card's Title, Text and Link; a status card's Text, and its Title and Link when it has them; an articles card's Articles. The send, search, conversations and tickets cards are drawn from the rest of the home (its open conversations, its tickets) and the app's own words.",
        "properties": {
          "articles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArticleSummary"
            }
          },
          "id": {
            "type": "string",
            "description": "The card's, as staff made it: the same on every read."
          },
          "kind": {
            "$ref": "#/components/schemas/HomeCardKind"
          },
          "link": {
            "type": "string",
            "description": "A path in the app or an https link, personalised."
          },
          "text": {
            "type": "string"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "kind"
        ]
      },
      "HomeCardKind": {
        "type": "string",
        "description": "What a card of the messenger's home is.",
        "enum": [
          "send",
          "search_help",
          "articles",
          "conversations",
          "tickets",
          "link",
          "status"
        ]
      },
      "Identify": {
        "type": "object",
        "description": "PUT /server/v1/contacts/{external_id}: who the person is. Only what is sent changes.",
        "properties": {
          "attributes": {
            "type": "object",
            "description": "The app's custom attributes, by name, each held to its definition: a string, a number, true or false, a date as 2026-09-27 or an RFC 3339 time, or a list of strings. Null removes the value. One the CRM computes is refused; one the app does not define is made on first sight, its type worked out, for review, unless the app's strict data refuses it.",
            "additionalProperties": {}
          },
          "consent": {
            "$ref": "#/components/schemas/crm.Consent"
          },
          "display_name": {
            "type": "string",
            "description": "What they are called, at most 100 characters."
          },
          "email": {
            "type": "string",
            "description": "Their address, which the CRM sends email to."
          },
          "language": {
            "type": "string",
            "description": "Their language as a BCP 47 tag (\"en\", \"en-GB\")."
          },
          "provider": {
            "type": "string",
            "description": "How they sign in, in the app's own word: apple, google, password, or any other of at most 40 characters."
          },
          "signed_up_at": {
            "type": "string",
            "format": "date-time",
            "description": "When they made their account."
          },
          "time_zone": {
            "type": "string",
            "description": "Their IANA zone (\"Europe/London\")."
          }
        }
      },
      "IdentifyAnswer": {
        "type": "object",
        "description": "Answers an Identify: the person, and whether this made them.",
        "properties": {
          "created": {
            "type": "boolean"
          },
          "external_id": {
            "type": "string"
          }
        },
        "required": [
          "created",
          "external_id"
        ]
      },
      "ImageAspect": {
        "type": "string",
        "description": "A picture's shape, width to height; auto is the picture's own.",
        "enum": [
          "auto",
          "1:1",
          "4:3",
          "16:9",
          "3:4"
        ]
      },
      "ImageFit": {
        "type": "string",
        "description": "How a picture fills its box: cover crops it to fill, contain shows it whole.",
        "enum": [
          "cover",
          "contain"
        ]
      },
      "ImageShape": {
        "type": "string",
        "description": "A picture's corners.",
        "enum": [
          "rounded",
          "circle",
          "square"
        ]
      },
      "ImageSize": {
        "type": "string",
        "description": "How much of the screen a picture takes: a hero's height, or an image block's width.",
        "enum": [
          "small",
          "medium",
          "large",
          "full"
        ]
      },
      "InAppMessage": {
        "type": "object",
        "description": "One message to show: its kind's content, and the variant the guest was given, which every receipt about it says again.",
        "properties": {
          "banner": {
            "$ref": "#/components/schemas/Banner",
            "description": "Exactly one of these is set, the one Kind names."
          },
          "carousel": {
            "$ref": "#/components/schemas/Carousel"
          },
          "id": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/MessageKind"
          },
          "post": {
            "$ref": "#/components/schemas/Post"
          },
          "screens": {
            "type": "array",
            "description": "The screens it is shown on, when it is meant for some: the app holds it until one of them is on (the screen its host names), and shows it then. Left out for a message shown wherever the guest is.",
            "items": {
              "type": "string"
            }
          },
          "survey": {
            "$ref": "#/components/schemas/Survey"
          },
          "test": {
            "type": "boolean",
            "description": "A copy staff sent to a test phone: ID is the test's, not the message's. It is shown at once, whatever the caps, the audience and the one large message a session, and what the guest does with it (receipts, answers) is kept with the test, never in the message's report or results."
          },
          "variant": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "kind",
          "variant"
        ]
      },
      "InboundEmail": {
        "type": "object",
        "description": "A reply by email to one of crm's support emails, as the mail provider's inbound hook posts it to crm, signed:",
        "properties": {
          "from": {
            "type": "string"
          },
          "id": {
            "type": "string",
            "description": "The provider's id for the message: the same message posted twice is threaded once."
          },
          "received_at": {
            "type": "string",
            "format": "date-time"
          },
          "subject": {
            "type": "string"
          },
          "text": {
            "type": "string",
            "description": "The plain-text body, the quoted email under the reply still in it: crm keeps the reply alone."
          },
          "to": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "from",
          "id",
          "received_at",
          "text",
          "to"
        ]
      },
      "ListItem": {
        "type": "object",
        "description": "One item of a list.",
        "properties": {
          "spans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Span"
            }
          }
        },
        "required": [
          "spans"
        ]
      },
      "MessageBody": {
        "type": "object",
        "description": "The guest writing: in a new conversation, in one of theirs, or on a ticket. Text, pictures, or both.",
        "properties": {
          "attachment_ids": {
            "type": "array",
            "description": "Pictures uploaded through an AttachmentUpload and not yet sent.",
            "items": {
              "type": "string"
            }
          },
          "text": {
            "type": "string"
          }
        },
        "required": [
          "text"
        ]
      },
      "MessageKind": {
        "type": "string",
        "description": "What an outbound message is. Only the in-app kinds reach the app at a check-in (InAppKinds); a chat arrives in the messenger as a conversation staff started, a push as a Push of type crm_message, and an email by email.",
        "enum": [
          "chat",
          "post",
          "banner",
          "carousel",
          "survey",
          "push",
          "email"
        ]
      },
      "MessengerHome": {
        "type": "object",
        "description": "The messenger's first screen.",
        "properties": {
          "articles": {
            "type": "array",
            "description": "The help center's featured articles, when the help center is switched on; empty otherwise.",
            "items": {
              "$ref": "#/components/schemas/ArticleSummary"
            }
          },
          "layout": {
            "$ref": "#/components/schemas/MessengerLayout",
            "description": "The home as the app's staff set it: its greeting, its team, its cards in order and its options, worked out for this guest now. Null until they publish one, and the app then draws its own home as before."
          },
          "open": {
            "type": "array",
            "description": "The guest's open conversations, the most recently changed first, at most three.",
            "items": {
              "$ref": "#/components/schemas/ConversationSummary"
            }
          },
          "opening": {
            "$ref": "#/components/schemas/MessengerOpening",
            "description": "What the help bot says to a new conversation before the guest writes, worked out for them now; null when no help bot greets them, or when the one that would does something first that only a conversation can have done (a tag, a webhook, a hand-off), and so speaks once they have written. Null too while the app's staff allow no new conversation now (MessengerLayout.NewConversation)."
          },
          "reply": {
            "$ref": "#/components/schemas/ReplyExpectation"
          },
          "tickets": {
            "type": "array",
            "description": "The guest's tickets not yet resolved, and those resolved in the last week.",
            "items": {
              "$ref": "#/components/schemas/SupportTicketSummary"
            }
          },
          "unread": {
            "type": "integer",
            "description": "Every staff part the guest has not read, in every conversation: the badge on the app's top bar."
          }
        },
        "required": [
          "articles",
          "open",
          "reply",
          "tickets",
          "unread"
        ]
      },
      "MessengerLayout": {
        "type": "object",
        "description": "The messenger's home as the app's staff published it, for one guest at one moment: the greeting with their name in it, the faces, how soon a reply comes, the cards they are to see now (in order, those for a segment they are not in, a status out of its dates and the help center's while it is off already left out) and what they may do.",
        "properties": {
          "attachments": {
            "type": "boolean",
            "description": "The guest being able to send pictures: crm refuses a picture (attachment_invalid) while it is false."
          },
          "away_text": {
            "type": "string",
            "description": "Said in place of either while the team is away (Reply.Open false); empty for \"We're back …\"."
          },
          "cards": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HomeCard"
            }
          },
          "faces": {
            "$ref": "#/components/schemas/TeamFaces"
          },
          "greeting": {
            "type": "string",
            "description": "The heading, personalised; empty for the app's own words (\"Talk to the … team\")."
          },
          "help_center": {
            "type": "boolean",
            "description": "The home pointing to the help center (a search card, articles, a link to browse it)."
          },
          "intro": {
            "type": "string",
            "description": "A line under it, personalised; empty for none."
          },
          "names": {
            "type": "boolean",
            "description": "Names writes the chosen teammates' names under their faces."
          },
          "new_conversation": {
            "$ref": "#/components/schemas/NewConversations",
            "description": "When the guest may start one; the send card is left out of Cards whenever they may not now."
          },
          "reply_text": {
            "type": "string",
            "description": "Says how soon a reply comes in staff's own words, in place of the line worked out from Reply; empty for that line."
          },
          "team": {
            "type": "array",
            "description": "The teammates to show when Faces is chosen, by first name, as Author of kind teammate; empty otherwise.",
            "items": {
              "$ref": "#/components/schemas/Author"
            }
          }
        },
        "required": [
          "attachments",
          "cards",
          "faces",
          "greeting",
          "help_center",
          "intro",
          "new_conversation",
          "team"
        ]
      },
      "MessengerOpening": {
        "type": "object",
        "description": "A help bot's first words to a new conversation, as the guest would be shown them now: the bot's parts up to its first question (an ask, or a collection), in the shape of a conversation's, numbered from 1. Nothing is kept: a conversation is made when the guest answers (StartBody), and its ids are not these.",
        "properties": {
          "awaiting": {
            "type": "string",
            "description": "The id of the part whose question the bot waits on, as Conversation.Awaiting: its buttons are live, or the composer asks for what it collects. Empty when the bot says something and asks nothing."
          },
          "parts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Part"
            }
          },
          "version": {
            "type": "integer"
          },
          "workflow": {
            "type": "string",
            "description": "Workflow and Version are the help bot's, for StartBody.Opening."
          }
        },
        "required": [
          "parts",
          "version",
          "workflow"
        ]
      },
      "Name": {
        "type": "string",
        "description": "The name of an event: one of the standard events below, which every app has, or one of an app's own. An app's own events, and a standard event the app sends (guest.AppEvents), arrive from the app; the rest from the app's servers or the CRM itself.",
        "enum": [
          "signed_up",
          "signed_in",
          "profile_updated",
          "marketing_consent_changed",
          "account_deletion_requested",
          "notice_read",
          "push_sent",
          "email_sent",
          "email_delivered",
          "email_opened",
          "email_clicked",
          "email_bounced",
          "email_complained",
          "email_unsubscribed",
          "conversation_started",
          "message_sent",
          "conversation_rated",
          "bot_resolved",
          "handed_off",
          "ticket_created",
          "ticket_resolved",
          "article_viewed",
          "article_rated",
          "sla_breached",
          "conversation_state_changed",
          "message_seen",
          "message_clicked",
          "message_dismissed",
          "message_answered",
          "message_completed",
          "survey_shown",
          "survey_answered",
          "survey_completed",
          "survey_dismissed",
          "goal_reached",
          "segment_entered",
          "segment_left"
        ]
      },
      "NewConversations": {
        "type": "string",
        "description": "When a guest may start a conversation.",
        "enum": [
          "always",
          "when_none_open",
          "never"
        ]
      },
      "Notice": {
        "type": "object",
        "description": "One notice in the guest's inbox the CRM keeps: what it pushed them, kept to read again.",
        "properties": {
          "body": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "id": {
            "type": "string"
          },
          "link": {
            "type": "string",
            "description": "A path in the app, which the app's allow-list decides whether to open."
          },
          "read_at": {
            "type": "string",
            "format": "date-time"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/PushType"
          }
        },
        "required": [
          "body",
          "created_at",
          "id",
          "title",
          "type"
        ]
      },
      "NoticePage": {
        "type": "object",
        "description": "A page of the guest's inbox, newest first.",
        "properties": {
          "next_before": {
            "type": "string",
            "description": "The notice to ask for older ones before, and empty on the last page."
          },
          "notices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Notice"
            }
          },
          "unread": {
            "type": "integer",
            "description": "Unread counts the whole inbox, not only this page: the number on the bell."
          }
        },
        "required": [
          "notices",
          "unread"
        ]
      },
      "NoticesReadBody": {
        "type": "object",
        "description": "POST notices/read: the notices shown, or all of them. A notice that is not the guest's is left alone.",
        "properties": {
          "all": {
            "type": "boolean"
          },
          "ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "OpenMessageBody": {
        "type": "object",
        "description": "POST /guest/v1/messages/{message_id}/open: the session the app opened the message in.",
        "properties": {
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ]
      },
      "OpeningRef": {
        "type": "object",
        "description": "Names an opening by its workflow and the version it was worked out from.",
        "properties": {
          "version": {
            "type": "integer"
          },
          "workflow": {
            "type": "string"
          }
        },
        "required": [
          "version",
          "workflow"
        ]
      },
      "OrderRef": {
        "type": "object",
        "description": "An app's order reference: what its guests call an order's reference, what one looks like, and the event prop that names a guest's latest order. The help bot collects one only in an app that has it, and a customer ticket's order_ref field is prefilled from Prop.",
        "properties": {
          "example": {
            "type": "string",
            "description": "One that matches, which the bot shows (\"ORD-7K2QD\")."
          },
          "label": {
            "type": "string",
            "description": "What the app calls it, \"Order number\": at most 40 characters."
          },
          "pattern": {
            "type": "string",
            "description": "What a reference looks like, a regular expression (RE2) the whole answer must match, at most MaxOrderRefPattern bytes."
          },
          "prop": {
            "type": "string",
            "description": "The event prop naming the order, which prefills a ticket's order_ref field from the guest's newest event that carries it; empty prefills nothing."
          },
          "upper": {
            "type": "boolean",
            "description": "An answer matched and kept in upper case, so that a guest may type it in either."
          }
        },
        "required": [
          "example",
          "label",
          "pattern"
        ]
      },
      "Part": {
        "type": "object",
        "description": "One part of a conversation: one message, reply or change. Only the fields its Kind names are set.",
        "properties": {
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Attachment"
            }
          },
          "author": {
            "$ref": "#/components/schemas/Author"
          },
          "blocks": {
            "type": "array",
            "description": "A reply's or the bot's words (the content schema above).",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          },
          "bot": {
            "$ref": "#/components/schemas/BotPart",
            "description": "What a help-bot part asks of the guest."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "id": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/PartKind"
          },
          "rating": {
            "type": "integer",
            "description": "The guest's answer to a rating request, 1 to MaxRating, once they give it."
          },
          "seq": {
            "type": "integer",
            "description": "Seq orders a conversation's parts, from 1, and is what a wait is asked after."
          },
          "state": {
            "$ref": "#/components/schemas/ConversationState",
            "description": "Where a state part moved the conversation to."
          },
          "text": {
            "type": "string",
            "description": "What the guest wrote, as they wrote it: never read as markup."
          }
        },
        "required": [
          "author",
          "created_at",
          "id",
          "kind",
          "seq"
        ]
      },
      "PartKind": {
        "type": "string",
        "description": "What one part of a conversation is.",
        "enum": [
          "message",
          "reply",
          "note",
          "bot",
          "assignment",
          "state",
          "rating_request"
        ]
      },
      "Post": {
        "type": "object",
        "description": "A full screen of blocks, from a member of staff.",
        "properties": {
          "blocks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          },
          "sender": {
            "$ref": "#/components/schemas/Author",
            "description": "Who it is from, by first name; left out for the app itself (its brand's name)."
          }
        },
        "required": [
          "blocks"
        ]
      },
      "PreferenceChannel": {
        "type": "string",
        "description": "A way a topic reaches the guest.",
        "enum": [
          "email",
          "push",
          "in_app",
          "chat"
        ]
      },
      "PreferenceChoice": {
        "type": "object",
        "description": "One choice.",
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/PreferenceChannel"
          },
          "subscribed": {
            "type": "boolean"
          },
          "topic_id": {
            "type": "string"
          }
        },
        "required": [
          "channel",
          "subscribed",
          "topic_id"
        ]
      },
      "PreferenceState": {
        "type": "object",
        "description": "One channel of a topic: whether the guest hears it.",
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/PreferenceChannel"
          },
          "subscribed": {
            "type": "boolean"
          }
        },
        "required": [
          "channel",
          "subscribed"
        ]
      },
      "PreferenceTopic": {
        "type": "object",
        "description": "One of the app's topics, and the guest's state on each channel it uses.",
        "properties": {
          "channels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PreferenceState"
            }
          },
          "description": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "channels",
          "id",
          "name"
        ]
      },
      "Preferences": {
        "type": "object",
        "description": "The guest's choices: every topic of the app's that is not archived, by name, and whether every channel is off (AllOff: they get no marketing at all).",
        "properties": {
          "all_off": {
            "type": "boolean"
          },
          "topics": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PreferenceTopic"
            }
          }
        },
        "required": [
          "all_off",
          "topics"
        ]
      },
      "PreferencesBody": {
        "type": "object",
        "description": "Changes the guest's choices: each choice where it differs from what they had, or AllOff, \"Unsubscribe from all marketing\", which turns every channel and every topic off. Wording names the version of the words they chose under (the SDK's preferences screen sends \"crm_sdk/preferences-1\"), kept with the choice as it is sent.",
        "properties": {
          "all_off": {
            "type": "boolean"
          },
          "choices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PreferenceChoice"
            }
          },
          "wording": {
            "type": "string"
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "A refusal (RFC 9457). Switch on code, never on detail, which is a sentence for people and may be reworded.",
        "properties": {
          "code": {
            "type": "string",
            "description": "What went wrong, as a word to switch on: bad_request, unauthorized, token_expired, not_found, rate_limited, app_unavailable and the others each route lists."
          },
          "detail": {
            "type": "string",
            "description": "What went wrong, in a sentence that can be shown to a person."
          },
          "errors": {
            "type": "array",
            "description": "The fields that were wrong, for a 400 that has them.",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string",
                  "description": "Where the field is, such as attributes.plan or events.2.name."
                },
                "message": {
                  "type": "string",
                  "description": "What is wrong with it."
                }
              },
              "required": [
                "field",
                "message"
              ]
            }
          },
          "request_id": {
            "type": "string",
            "description": "The request's id, to quote when asking for help."
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status."
          },
          "title": {
            "type": "string",
            "description": "The HTTP status in words."
          },
          "type": {
            "type": "string",
            "description": "A URI for the kind of problem; about:blank for most."
          }
        },
        "required": [
          "code",
          "status",
          "title",
          "type"
        ]
      },
      "Prop": {
        "type": "object",
        "description": "One prop an event carries.",
        "properties": {
          "key": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/PropType"
          }
        },
        "required": [
          "key",
          "type"
        ]
      },
      "PropType": {
        "type": "string",
        "description": "How crm reads a prop's string: what it may be compared with in a rule, and how the dashboard breaks it down.",
        "enum": [
          "string",
          "number",
          "bool",
          "time",
          "money"
        ]
      },
      "PublicApp": {
        "type": "object",
        "description": "An app's public face: how it is named and coloured, what it has switched on, and the SDK it needs. Nothing in it is secret.",
        "properties": {
          "brand": {
            "$ref": "#/components/schemas/PublicBrand"
          },
          "currency": {
            "type": "string",
            "description": "The ISO 4217 code its money is in."
          },
          "features": {
            "type": "array",
            "description": "The parts of the guest experience it has on.",
            "items": {
              "$ref": "#/components/schemas/Feature"
            }
          },
          "handle": {
            "type": "string",
            "description": "The readable name a live app's public help center goes by now, <workspace slug>.<app slug> (/help/a/acme.rewards), whichever name or key it was asked by: the address the site sends an old name on to, and names as the canonical one. Left out for a test app, whose help center goes by its key alone."
          },
          "key": {
            "type": "string",
            "description": "The app's public key, the one asked for."
          },
          "languages": {
            "type": "array",
            "description": "The languages its content is written in: its default first, then the others. Left out by a CRM older than that.",
            "items": {
              "type": "string"
            }
          },
          "link_base": {
            "type": "string",
            "description": "Where the app's links are, with no slash at the end: https://<the CRM's host>/l/<name>, or https://<its own link host>; left out while it has no link domain. The SDK takes a link under it as the app's own (its handleLink)."
          },
          "min_sdk_version": {
            "type": "string",
            "description": "The oldest SDK the app's staff want its guests on (\"0.6.0\"); left out when they have set none."
          },
          "mode": {
            "$ref": "#/components/schemas/AppMode",
            "description": "AppModeLive, or AppModeTest for an app's test app: a staging build's, with test data of its own (the SDK's isTestMode)."
          },
          "order_ref": {
            "$ref": "#/components/schemas/OrderRef",
            "description": "Its order reference; left out for an app without one."
          }
        },
        "required": [
          "brand",
          "currency",
          "features",
          "key",
          "mode"
        ]
      },
      "PublicBrand": {
        "type": "object",
        "description": "An app's brand as its guests see it: its name (never empty), its accent (#rrggbb, or empty for the SDK's own), its logo (an https address, or empty) and where its guests may write.",
        "properties": {
          "accent": {
            "type": "string"
          },
          "logo_url": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "support_email": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ]
      },
      "PublicLinkApp": {
        "type": "object",
        "description": "GET /public/v1/links/apps/{name}: what the landing page of a link the phone did not open the app with shows. Nothing in it is secret.",
        "properties": {
          "app_store_url": {
            "type": "string"
          },
          "brand": {
            "$ref": "#/components/schemas/PublicBrand"
          },
          "key": {
            "type": "string",
            "description": "The app's public key."
          },
          "link_base": {
            "type": "string",
            "description": "Where the app's links are (PublicApp.LinkBase)."
          },
          "name": {
            "type": "string"
          },
          "play_store_url": {
            "type": "string"
          }
        },
        "required": [
          "brand",
          "key",
          "link_base",
          "name"
        ]
      },
      "PublicPreferences": {
        "type": "object",
        "description": "What the public preference centre shows: the app's brand, the address the link was sent to with most of it hidden, and the guest's preferences. Nothing else about them.",
        "properties": {
          "accent": {
            "type": "string",
            "description": "Accent (#rrggbb) and LogoURL are the app's, for the page to wear; either may be absent."
          },
          "all_off": {
            "type": "boolean"
          },
          "email": {
            "type": "string",
            "description": "The contact's address, all but its first letter and its domain hidden (\"j•••@example.com\"); empty for none."
          },
          "logo_url": {
            "type": "string"
          },
          "product": {
            "type": "string"
          },
          "topics": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PreferenceTopic"
            }
          }
        },
        "required": [
          "all_off",
          "product",
          "topics"
        ]
      },
      "PublicRatingBody": {
        "type": "object",
        "description": "POST /public/v1/help/articles/{slug}/rating: whether a public article helped a reader of the web's help center, who has no account. Visitor is the page's own id for its reader, a UUID it keeps in a cookie, so that a second answer replaces the first. Rate-limited per address; 204, or 404 for an article the web may not read.",
        "properties": {
          "helpful": {
            "type": "boolean"
          },
          "language": {
            "type": "string",
            "description": "The language the reader read it in (Article.Language), for the help report; left out, the app's default."
          },
          "visitor": {
            "type": "string"
          }
        },
        "required": [
          "helpful",
          "visitor"
        ]
      },
      "PushPlatform": {
        "type": "string",
        "description": "What a device registered for the CRM's pushes runs: a phone, or a browser, which FCM reaches through web push.",
        "enum": [
          "android",
          "ios",
          "web"
        ]
      },
      "PushType": {
        "type": "string",
        "description": "What a push the CRM sends, or a notice in the inbox, is about. An app's own pushes, sent by its own servers, are the app's.",
        "enum": [
          "support_reply",
          "crm_message"
        ]
      },
      "QuestionType": {
        "type": "string",
        "description": "How a question is answered.",
        "enum": [
          "nps",
          "numeric",
          "stars",
          "emoji",
          "dropdown",
          "short_text",
          "long_text",
          "single_choice",
          "multi_choice"
        ]
      },
      "RatingBody": {
        "type": "object",
        "description": "The guest's answer to a rating request.",
        "properties": {
          "rating": {
            "type": "integer",
            "description": "1 (bad) to MaxRating (great)."
          },
          "remark": {
            "type": "string",
            "description": "Anything they want to add."
          }
        },
        "required": [
          "rating"
        ]
      },
      "ReadBody": {
        "type": "object",
        "description": "Says the guest has seen a conversation up to a part.",
        "properties": {
          "seq": {
            "type": "integer"
          }
        },
        "required": [
          "seq"
        ]
      },
      "ReceiptAction": {
        "type": "string",
        "description": "What the guest did with a message the app showed.",
        "enum": [
          "seen",
          "clicked",
          "dismissed",
          "completed",
          "answered"
        ]
      },
      "ReceiptBody": {
        "type": "object",
        "description": "What the guest did with a message the app showed.",
        "properties": {
          "action": {
            "$ref": "#/components/schemas/ReceiptAction"
          },
          "answers": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "button": {
            "type": "string"
          },
          "link": {
            "type": "string",
            "description": "What was tapped, for clicked."
          },
          "message_id": {
            "type": "string"
          },
          "path": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "screen": {
            "type": "string",
            "description": "A carousel's receipts say where the guest was. Screen is the screen it is about: the one answered, the one whose button was tapped, the one the carousel ended or was closed on. Button is the button tapped, for clicked (with Link too, for a link button). Answers are the options picked, for answered: one for a single choice, up to MaxCarouselOptions for a multiple. Path is the screens' ids the guest has seen so far, in order (a screen seen twice is in it twice), at most MaxReceiptPath (the first ones kept), for completed and dismissed, and on any other carousel receipt that has it."
          },
          "session_id": {
            "type": "string",
            "description": "The check-in's session."
          },
          "test": {
            "type": "boolean",
            "description": "About a test copy (InAppMessage.Test): MessageID is the test's."
          },
          "variant": {
            "type": "string"
          }
        },
        "required": [
          "action",
          "message_id",
          "variant"
        ]
      },
      "ReplyExpectation": {
        "type": "object",
        "description": "How soon the guest can expect an answer.",
        "properties": {
          "open": {
            "type": "boolean",
            "description": "Staff being at work now, by the team's office hours."
          },
          "opens_at": {
            "type": "string",
            "format": "date-time",
            "description": "When they are next at work, when they are not now."
          },
          "within": {
            "$ref": "#/components/schemas/ReplyTime"
          }
        },
        "required": [
          "open",
          "within"
        ]
      },
      "ReplyTime": {
        "type": "string",
        "description": "How soon staff usually answer, which the team that answers says in the CRM's settings, for \"we usually reply in …\".",
        "enum": [
          "minutes",
          "hours",
          "day"
        ]
      },
      "Scrim": {
        "type": "string",
        "description": "A gradient laid over a picture so that words read on it (carousel_style.json's scrims).",
        "enum": [
          "none",
          "light",
          "dark"
        ]
      },
      "ServerAccepted": {
        "type": "object",
        "description": "Answers a batch: how many were new and kept, and the ones refused, by their place in the batch.",
        "properties": {
          "accepted": {
            "type": "integer"
          },
          "rejected": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ServerRejection"
            }
          }
        },
        "required": [
          "accepted",
          "rejected"
        ]
      },
      "ServerEvent": {
        "type": "object",
        "description": "One thing a person did, as a server saw it.",
        "properties": {
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "external_id": {
            "type": "string"
          },
          "id": {
            "type": "string",
            "description": "The server's idempotency key, 1 to 100 bytes: derive it from what the event is about, so that a retry carries the same one."
          },
          "name": {
            "type": "string"
          },
          "props": {
            "type": "object",
            "description": "The event's props, each as a string its definition reads by its type.",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "required": [
          "at",
          "external_id",
          "id",
          "name"
        ]
      },
      "ServerEvents": {
        "type": "object",
        "description": "POST /server/v1/events.",
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ServerEvent"
            }
          }
        },
        "required": [
          "events"
        ]
      },
      "ServerRejection": {
        "type": "object",
        "description": "One event refused, and why.",
        "properties": {
          "id": {
            "type": "string"
          },
          "index": {
            "type": "integer"
          },
          "reason": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "index",
          "reason"
        ]
      },
      "SignupAccepted": {
        "type": "object",
        "description": "A sign-up taken: a link is on its way to the address, or, when it has an account already, an email saying so. The answer is the same either way.",
        "properties": {
          "email": {
            "type": "string"
          }
        },
        "required": [
          "email"
        ]
      },
      "SignupBody": {
        "type": "object",
        "description": "POST /public/v1/signup: a new customer's address and password (the staff password rules), and the names of their workspace and its first app.",
        "properties": {
          "app_name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "password": {
            "type": "string"
          },
          "turnstile_token": {
            "type": "string"
          },
          "workspace_name": {
            "type": "string"
          }
        },
        "required": [
          "app_name",
          "email",
          "password",
          "workspace_name"
        ]
      },
      "Source": {
        "type": "string",
        "description": "Who sends an event: the app (its SDK), the app's servers (the server API), or the CRM itself. A definition says which sends it, and an event recorded keeps who sent it, as a fact. One of an app's own events may be sent by both the app and its servers (SourceBoth); an event is never recorded as sent by both.",
        "enum": [
          "app",
          "server",
          "crm",
          "both"
        ]
      },
      "Span": {
        "type": "object",
        "description": "A run of text with one style.",
        "properties": {
          "bold": {
            "type": "boolean"
          },
          "color": {
            "type": "string",
            "description": "A colour in content: a theme token (ColorToken), which the app draws in its light or dark mode's shade, or a fixed \"#rrggbb\". Where a colour may be empty, empty is the default of where it is."
          },
          "highlight": {
            "type": "boolean"
          },
          "italic": {
            "type": "boolean"
          },
          "link": {
            "type": "string",
            "description": "Link makes the run a link, as Block.Link."
          },
          "strike": {
            "type": "boolean"
          },
          "text": {
            "type": "string"
          },
          "underline": {
            "type": "boolean",
            "description": "Underline, Strike, Highlight and Color are formatting (BlockContext.Formatting). Highlight is a wash of the accent behind the run; Color is the run's own colour, the accent or one of the palette's, over the block's."
          }
        },
        "required": [
          "text"
        ]
      },
      "StartBody": {
        "type": "object",
        "description": "The guest starting a conversation: what they wrote (a MessageBody's text and pictures), or, with Opening, a tap on one of its question's buttons (Button) instead. Opening names the help bot's opening the guest was shown (MessengerHome.Opening), whose parts then come first in the conversation; without it, the guest's words do, as before there were openings.",
        "properties": {
          "attachment_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "button": {
            "type": "string",
            "description": "The id of the opening's button the guest tapped, with no words or pictures; empty when they wrote."
          },
          "opening": {
            "$ref": "#/components/schemas/OpeningRef",
            "description": "The opening the guest was shown when they wrote or tapped; nil for none."
          },
          "text": {
            "type": "string"
          }
        },
        "required": [
          "text"
        ]
      },
      "SupportTicket": {
        "type": "object",
        "description": "One of the guest's tickets: the fields staff let the guest see, and the conversation about it, oldest first.",
        "properties": {
          "conversation_id": {
            "type": "string",
            "description": "The conversation the ticket's parts are in."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "description": {
            "type": "array",
            "description": "What staff wrote the ticket is for, in blocks.",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          },
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TicketField"
            }
          },
          "id": {
            "type": "string"
          },
          "parts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Part"
            }
          },
          "ref": {
            "type": "string"
          },
          "resolved_at": {
            "type": "string",
            "format": "date-time"
          },
          "state": {
            "$ref": "#/components/schemas/TicketState"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "created_at",
          "fields",
          "id",
          "parts",
          "ref",
          "state",
          "title",
          "type",
          "updated_at"
        ]
      },
      "SupportTicketPage": {
        "type": "object",
        "description": "The guest's tickets, the most recently changed first.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SupportTicketSummary"
            }
          }
        },
        "required": [
          "items"
        ]
      },
      "SupportTicketSummary": {
        "type": "object",
        "description": "A ticket in a list.",
        "properties": {
          "id": {
            "type": "string"
          },
          "ref": {
            "type": "string",
            "description": "How the guest and staff name it: \"T-1042\"."
          },
          "state": {
            "$ref": "#/components/schemas/TicketState"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "ref",
          "state",
          "title",
          "type",
          "updated_at"
        ]
      },
      "Survey": {
        "type": "object",
        "description": "The questions of a survey message. Steps are shown in order, unless a step's branch on a closed answer jumps ahead; reaching a thank-you step, or the end, finishes it.",
        "properties": {
          "format": {
            "$ref": "#/components/schemas/SurveyFormat"
          },
          "intro": {
            "type": "array",
            "description": "The first screen of a large survey; left out for none.",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SurveyStep"
            }
          }
        },
        "required": [
          "format",
          "steps"
        ]
      },
      "SurveyAnswer": {
        "type": "object",
        "description": "The guest's answer to one question: Number for a scale, Choices (option ids) for a choice, with Other when \"other\" is among them, and Text for the guest's own words.",
        "properties": {
          "choices": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "number": {
            "type": "integer"
          },
          "other": {
            "type": "string"
          },
          "question_id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          }
        },
        "required": [
          "question_id"
        ]
      },
      "SurveyAnswerBody": {
        "type": "object",
        "description": "Answers given so far, sent as each step is answered: crm keeps them even if the guest never finishes. A newer answer to the same question replaces the older.",
        "properties": {
          "answers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SurveyAnswer"
            }
          },
          "test": {
            "type": "boolean",
            "description": "A test copy's answers (InAppMessage.Test), by the test's id."
          },
          "variant": {
            "type": "string"
          }
        },
        "required": [
          "answers",
          "variant"
        ]
      },
      "SurveyBranch": {
        "type": "object",
        "description": "SurveyBranch sends the guest to another step when a closed question's answer is one of Values (an option's id), or a number from Min to Max.",
        "properties": {
          "max": {
            "type": "integer"
          },
          "min": {
            "type": "integer"
          },
          "next": {
            "type": "string",
            "description": "The id of a later step."
          },
          "question_id": {
            "type": "string"
          },
          "values": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "next",
          "question_id"
        ]
      },
      "SurveyFormat": {
        "type": "string",
        "description": "How a survey is shown.",
        "enum": [
          "small",
          "large"
        ]
      },
      "SurveyOption": {
        "type": "object",
        "description": "One option of a choice question.",
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "label"
        ]
      },
      "SurveyQuestion": {
        "type": "object",
        "description": "One question.",
        "properties": {
          "description": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "max": {
            "type": "integer"
          },
          "max_label": {
            "type": "string"
          },
          "min": {
            "type": "integer",
            "description": "Min and Max are a numeric scale's ends, 0 or 1 to at most 10."
          },
          "min_label": {
            "type": "string",
            "description": "MinLabel and MaxLabel are the words under a scale's ends."
          },
          "options": {
            "type": "array",
            "description": "A choice question's, in order.",
            "items": {
              "$ref": "#/components/schemas/SurveyOption"
            }
          },
          "other": {
            "type": "boolean",
            "description": "Other adds \"Other\" to a single or multiple choice, with the guest's own words."
          },
          "placeholder": {
            "type": "string",
            "description": "The hint in a text answer's box."
          },
          "required": {
            "type": "boolean"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/QuestionType"
          }
        },
        "required": [
          "id",
          "title",
          "type"
        ]
      },
      "SurveyStep": {
        "type": "object",
        "description": "One screen of a survey.",
        "properties": {
          "blocks": {
            "type": "array",
            "description": "A thank-you step's words.",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          },
          "branches": {
            "type": "array",
            "description": "Tried in order when the step is answered; the first that matches says which step is next. None matching, or none at all, is the step after this one.",
            "items": {
              "$ref": "#/components/schemas/SurveyBranch"
            }
          },
          "id": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/SurveyStepKind"
          },
          "questions": {
            "type": "array",
            "description": "A questions step's.",
            "items": {
              "$ref": "#/components/schemas/SurveyQuestion"
            }
          }
        },
        "required": [
          "id",
          "kind"
        ]
      },
      "SurveyStepKind": {
        "type": "string",
        "description": "What a survey's step is.",
        "enum": [
          "questions",
          "thank_you"
        ]
      },
      "SurveySubmit": {
        "type": "object",
        "description": "SurveySubmit finishes a survey: every answer along the path the guest took. Only then are answers written to the guest's attributes.",
        "properties": {
          "answers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SurveyAnswer"
            }
          },
          "test": {
            "type": "boolean",
            "description": "A test copy finished (InAppMessage.Test): nothing is written to the guest's attributes."
          },
          "variant": {
            "type": "string"
          }
        },
        "required": [
          "answers",
          "variant"
        ]
      },
      "TeamFaces": {
        "type": "string",
        "description": "Whose faces the home shows.",
        "enum": [
          "auto",
          "chosen",
          "none"
        ]
      },
      "TextScale": {
        "type": "string",
        "description": "How large a carousel's words are, beside the phone's own text size, which scales them again.",
        "enum": [
          "compact",
          "regular",
          "large"
        ]
      },
      "TextWeight": {
        "type": "string",
        "description": "How heavy a text block's words are.",
        "enum": [
          "regular",
          "medium",
          "bold"
        ]
      },
      "TicketField": {
        "type": "object",
        "description": "One field of a ticket and its value: Text for a text or a list field (the option chosen), Number, Bool, or Date (YYYY-MM-DD). A field with no value has none of them.",
        "properties": {
          "bool": {
            "type": "boolean"
          },
          "date": {
            "type": "string"
          },
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "number": {
            "type": "number"
          },
          "text": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/FieldType"
          }
        },
        "required": [
          "key",
          "label",
          "type"
        ]
      },
      "TicketState": {
        "type": "string",
        "description": "Where a ticket has got to, in the guest's words.",
        "enum": [
          "submitted",
          "in_progress",
          "waiting_on_guest",
          "resolved"
        ]
      },
      "UnreadCount": {
        "type": "object",
        "description": "How many notices are still unread, for the bell.",
        "properties": {
          "unread": {
            "type": "integer"
          }
        },
        "required": [
          "unread"
        ]
      },
      "VAlign": {
        "type": "string",
        "description": "Where a screen's content sits in its height.",
        "enum": [
          "top",
          "center",
          "bottom"
        ]
      },
      "crm.Consent": {
        "type": "object",
        "description": "What the person agreed to.",
        "properties": {
          "changed_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the person made the offers choices this carries (OffersByEmail, OffersByPush), where the app keeps them. The CRM also learns these choices from the person directly (an email's unsubscribe, the preference centre, the SDK's screens, staff), and the newer choice wins:"
          },
          "offers_by_email": {
            "type": "boolean",
            "description": "Their agreeing to offers by email: a marketing email is sent only to a person who has."
          },
          "offers_by_push": {
            "type": "boolean",
            "description": "Their agreeing to offers by push: a marketing push is sent only to a person who has, when the CRM pushes for the app itself."
          },
          "product_events": {
            "type": "boolean",
            "description": "Their agreeing to what they do in the app being recorded (\"Help improve …\"): false drops every batch their app sends."
          }
        }
      }
    },
    "responses": {
      "Problem": {
        "description": "Refused: the body is problem JSON, and its code says why.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    }
  }
}
