in inTouch Docs
Get your key
Embed

Put inTouch on your site

One tag, and your visitors can use inTouch on your page — QR linking and all. The design is entirely yours, down to individual radii. The feature set is not negotiable: an embedded chat can do everything the full one can.

Script

https://web.in-touch.cc/embed.js

The whole thing

<script src="https://web.in-touch.cc/embed.js" async></script>

<intouch-chat style="height:620px">
  <script type="application/json">
  {
    "colorScheme": "light",
    "colors": { "accent": "#b4541f", "bubbleOut": "#b4541f", "panel": "#fffdf9" },
    "radius": { "bubble": 14 },
    "typography": { "fontFamily": "Georgia, serif" }
  }
  </script>
</intouch-chat>

That is the complete integration. No account, no key, no registration — this endpoint is public and stays public. Build your theme in the theme studio and copy the snippet from there.

Whose chat is it

The visitor's. They scan the QR code with their own phone and talk to their own contacts. Your page provides the frame and the design; it never becomes a party to the conversation. There is no mode in which the site owner reads along.

Three ways to embed

FormUse when
Web component (above)Default. Readable theme, live updates, events, dark-mode sync.
Plain <iframe>Your CMS will not let you add a <script> — Webflow blocks, WordPress, Notion, Confluence.
Hosted theme idYour theme got long, or you want to reuse it across pages.
<!-- no script of your own -->
<iframe src="https://web.in-touch.cc/embed?theme=%7B%22colors%22%3A%7B%22accent%22%3A%22%23b4541f%22%7D%7D"
        style="width:100%;height:620px;border:0"
        allow="camera; microphone; clipboard-write"></iframe>

<!-- hosted theme -->
<intouch-chat theme="t_9fb2c1a4e803"></intouch-chat>

Hosting a theme

POST the theme and get a short id back. The id is derived from the content, so the same theme always yields the same id, an id can never be overwritten, and no account is involved.

curl -X POST https://web.in-touch.cc/embed/theme \
  -H 'Content-Type: application/json' \
  -d '{"colors":{"accent":"#b4541f"}}'

{ "id": "t_1465f38d3f1d", "reused": false, "bytes": 30,
  "url": "https://web.in-touch.cc/embed/theme/t_1465f38d3f1d",
  "snippet": "<intouch-chat theme=\"t_1465f38d3f1d\"></intouch-chat>" }

Limits: 32 KB per theme, 400 fields, 60 new themes per hour and IP. Changing a theme produces a new id — the old one keeps working, which is what you want once it is pasted into somebody else's page.

What you can change, and what you cannot

You can change every visual property: 159 documented fields, plus customCss for anything not covered. See the theme reference.

You cannot change behaviour. There is no field that removes voice messages, polls or groups, and there never will be. A visitor who links their account through your page gets the same messenger as everyone else — that is the deal that makes it safe to link an account in an unfamiliar frame.

Layout options move things, they do not delete them. Hiding the icon rail (layout.rail: "hidden") moves theme switching, language and sign-out into the sidebar menu automatically, and the unread counter into the list header. The validator enforces this; you do not have to think about it.

Showing a single chat

Set layout.mode to "conversation" and the frame shows one conversation instead of list and chat side by side — the shape you want in a sidebar or a support corner.

<intouch-chat style="height:560px">
  <script type="application/json">
  { "layout": { "mode": "conversation" } }
  </script>
</intouch-chat>

Which chat? By default the one the visitor had open last, otherwise their most recent. The chat list is not gone: a button in the header slides it over. That is not a nicety, it is the rule — an embedded chat never cuts the visitor off from their own conversations.

Pointing at one person

You can name the counterpart. peer takes a phone number in international form; the frame opens that conversation.

<intouch-chat peer="+4915112345678" style="height:560px">
  <script type="application/json">
  { "layout": { "mode": "conversation" } }
  </script>
</intouch-chat>

If the visitor already has that chat, it opens. If not, nothing is created: they see the header, an empty history and a line naming who they are about to write to. The chat comes into existence when they send — never because a page loaded. Otherwise any embedded page could quietly plant conversations in a visitor's account.

Resolving a number requires a linked session and is rate limited, because an open “is this number on inTouch?” lookup would be a directory oracle. Your page is not told the outcome: whether the person exists, whether a chat exists and what gets written all stay inside the frame. On failure you receive intouch:error with peer_not_found or peer_rate_limited, and the visitor gets their normal chat list.

Why it is a frame

The chat runs in an iframe on the inTouch origin, not as a script inside your page. That is deliberate: the visitor's session token and their private end-to-end key live in that frame's storage. If the messenger ran in your origin, your page — and any XSS on it — could read both and every message with them.

The consequence for you: your CSS cannot reach inside, which is why the theme schema is as detailed as it is. The consequence for your visitors: embedding inTouch does not make you a party to their conversations, and nobody has to trust you for that to hold.

Your page does learn three things, and only these: that somebody is linked, how many unread chats they have, and whether a chat is open. No names, no chat ids, no message content. The list is exhaustive and is documented on the embed API page.

The linking notice

While the QR code is shown, the frame displays a line naming the real domain the visitor is on. You can position and colour it; you cannot remove it. Without it, any site could present inTouch as its own and collect real account links behind a familiar-looking facade. The domain comes from the browser, not from a parameter you pass.

Storage in embedded frames — read this

Browsers partition storage per embedding site. Practical consequences:

BrowserBehaviourEffect
Chrome, EdgePartitioned, worksLinking applies per website
FirefoxPartitioned (dFPI), worksSame
SafariPartitioned, cleared after ~7 days idleOccasional re-linking

This cannot be configured away. If storage is unavailable the frame shows an explanation and a button that calls requestStorageAccess(); granting it gives the visitor one linking that works across every site. Your page is told through intouch:error with code: "storage_blocked".

If someone misuses this

The embed is public on purpose, and that cuts both ways: a phishing site can put the real inTouch linking screen inside a convincing facade. Two things stand against it.

First, the linking notice always names the domain the visitor is actually on, read from the browser. It cannot be removed, and CSS that hides it costs the site its entire custom styling — the frame checks afterwards whether the notice is really visible.

Second, a site can be blocked. Report one with the URL and, if you have it, a screenshot — the reporting address is set up before this goes public and will be named here. A blocked site no longer gets a QR code: the frame shows an explanation and a way to open inTouch Web directly. Blocking is keyed to the embedding origin and read from the browser, not from a header the site controls — a site cannot escape it by suppressing its referrer.

Blocking is a last resort and never a reaction to design we dislike. Themes are not moderated.

The studio

https://web.in-touch.cc/studio builds the theme against the real interface with sample data, shows validator warnings including contrast ratios, and hands you the finished snippet. Import and export are plain JSON files.

inTouch API · https://api.in-touch.cc · keep your key private.
# intouch api
base    https://api.in-touch.cc
auth    X-API-Key: itk_<64 hex>   one key per user, no expiry, no scopes
docs    https://docs.in-touch.cc
format  every response is JSON and carries "success"

# embed
Put inTouch on any website with one tag. Public, no account, no key.

Script: https://web.in-touch.cc/embed.js
Studio: https://web.in-touch.cc/studio
Theme reference: https://docs.in-touch.cc/embed-theme.txt
Event/command API: https://docs.in-touch.cc/embed-api.txt

# quickstart
<script src="https://web.in-touch.cc/embed.js" async></script>

<intouch-chat style="height:620px">
  <script type="application/json">
  { "colorScheme": "light",
    "colors": { "accent": "#b4541f", "bubbleOut": "#b4541f", "panel": "#fffdf9" },
    "radius": { "bubble": 14 },
    "typography": { "fontFamily": "Georgia, serif" } }
  </script>
</intouch-chat>

# who
The VISITOR links their own account by QR and talks to their own contacts.
The embedding site is frame and design only. There is no mode in which the
site owner reads along.

# ways
1. Web component (default): readable theme, live updates, events, dark-mode sync.
2. Plain iframe: for CMSs that forbid <script>.
   <iframe src="https://web.in-touch.cc/embed?theme=<url-encoded json>"
           style="width:100%;height:620px;border:0"
           allow="camera; microphone; clipboard-write"></iframe>
3. Hosted theme id: <intouch-chat theme="t_9fb2c1a4e803"></intouch-chat>

# hosting
POST https://web.in-touch.cc/embed/theme  body: the theme JSON
-> { id, reused, bytes, url, snippet }
GET  https://web.in-touch.cc/embed/theme/<id>  -> the theme JSON, immutable, cacheable forever

The id is the first 12 hex characters of SHA-256 over the canonical JSON.
Same theme = same id. An id is never overwritten. No account needed.
Limits: 32 KB, 400 fields, 60 new themes per hour and IP.

# scope
Changeable: every visual property (159 documented fields) plus customCss.
NOT changeable: behaviour. No field removes a feature. An embedded chat can do
everything the full one can.
Layout options move elements, never orphan them: layout.rail "hidden" moves
theme switch, language and sign-out into the sidebar menu automatically.

# single
layout.mode = "conversation" shows ONE conversation instead of list + chat.
Which one: the visitor's last opened chat, else their most recent.
The chat list stays reachable through a button in the header - an embedded
chat never cuts the visitor off from their own conversations.

# peer
<intouch-chat peer="+4915112345678"> names the counterpart (phone number,
international form). The frame opens that conversation.

If the chat exists, it opens. If not, NOTHING is created: the visitor sees the
header, an empty history and a line naming who they are about to write to. The
chat is created when they SEND - never because a page loaded.

Resolving requires a linked session and is rate limited (an open lookup would
be a directory oracle). The embedding page is not told the outcome. On failure:
intouch:error with code "peer_not_found" or "peer_rate_limited", and the
visitor gets their normal chat list.

# privacy
The chat runs in an iframe on the inTouch origin. The visitor's session token
and private end-to-end key live in THAT origin, not in the embedding page.
A script-in-your-origin widget would expose both to the page and to any XSS
on it. That is why styling goes through the theme schema and not through your
CSS.

The embedding page learns exactly three things: that somebody is linked, how
many unread chats there are, and whether a chat is open. No names, no chat ids,
no message content. The list is exhaustive.

# trust
While the QR code is shown, the frame names the real domain the visitor is on.
Position and colour are configurable; presence is not. The domain is read from
the browser (location.ancestorOrigins), not from a parameter.

# abuse
The embed is public on purpose; a phishing site can put the real linking screen
into a convincing facade. Two defences:

1. The linking notice always names the domain the visitor is actually on, read
   from the browser. It cannot be removed; CSS that hides it costs the site its
   entire custom styling (the frame verifies visibility after applying).
2. A site can be blocked. Report it with the URL and a screenshot; the
   reporting address is set up before this goes public and will be named here.
   A blocked site gets no QR code - the frame shows an explanation
   and a way to open inTouch Web directly. Blocking is keyed to the embedding
   origin read from the browser, not from a header the site controls.

Blocking is a last resort, never a reaction to design. Themes are not moderated.

# storage
Browsers partition storage per embedding site.
  Chrome/Edge: partitioned, works. Linking applies per website.
  Firefox:     partitioned (dFPI), works.
  Safari:      partitioned, cleared after ~7 days idle -> occasional re-linking.
Not configurable away. If storage is unavailable the frame shows a button that
calls requestStorageAccess(); the page is told via intouch:error
{ code: "storage_blocked" }.

# pages
/                  overview, capability list
/quickstart        get a key, first request
/authentication    key format, rotation, failure modes
/conventions       envelope, status codes, limits, dates
/recipes           recipes capability
/vocabulary        vocabulary capability
/roadmap           capability anatomy, what is planned
every page is also plain text: append .txt  (e.g. https://docs.in-touch.cc/vocabulary.txt)
all pages at once: /llms.txt (index) and /llms-full.txt (everything)

# for agents
- Use only the endpoints listed here; do not invent paths, fields or values.
- Check "success" in the body, not just the HTTP status.
- Error strings are German and developer-facing; branch on the status code.
- Batches are accepted partially: read "rejected" and "errors[].index".
- Dates are UTC. Calendar days are YYYY-MM-DD, timestamps are ISO 8601.
- The API key is a credential: never log it, never pass it to a third party.
- Ask the user before writing content into their app for the first time.