Real-time for the apps your AI builds

Pages that change for everyone on them within a second of something happening, like a new chat line or another player's move.

Say you want a scoreboard for Sunday's pickup game. Ask ChatGPT or Claude for one, and when you type in a score from your phone, every phone and TV with the page open shows it within a second.

Built with it

Bootleg City

A multiplayer game where friends steal cars and outrun the cops in a 1920s city. It was built by describing it to Claude, over a few days of playtesting. The game is one HTML file, and there's no game server behind it.

Play Bootleg City

Squigly

A whiteboard that anyone with the link can draw on at the same time, with each person's cursor and name on the board. Claude built it in an afternoon. Drawings are encrypted in the browser, so Tessryx relays and saves every change without being able to read it.

Open Squigly

Try it

A live page, shared with everyone reading this one

Not connected

Reactions from everyone here float up in this space.

One reaction every 0.3 seconds, checked at the edge

Press Join to connect to a live endpoint on Tessryx, the way an app on another website would. You're anonymous: there's no name and nothing to type, nothing is saved, and nothing connects until you press it.

If you build with ChatGPT or Claude

Ask for a page that updates live

Your AI sets up the live page and whatever sends it updates, and it adds checks to anything people send.

  • A live scoreboard
    “make a scoreboard page for our pickup basketball games. i want to change the score from my phone and have it update right away on the tv at the gym”
  • A chat room with who's online
    “build a chat room for my book club with a list of who's online. only people i invite should be able to get in”
  • A two-player game
    “make a two-player tic-tac-toe game i can send a friend as a link. moves should show up for both of us instantly and nobody can move out of turn”

If you're a developer

A real-time backend for an app you host anywhere

Your app can live on any host. It connects to a live endpoint on Tessryx, and you bring your own users: your backend signs a short token, and a join workflow checks it with a key you stored as a secret. The workflow decides who gets in, the id they appear under, which channels they may send on and when their access ends.

Browsers don't apply CORS to WebSockets, and a live endpoint has no CORS setting.

Who gets in: a page on the Tessryx site itself can use the endpoint's own sign-in gate. A page on another website doesn't carry that sign-in, so there the join workflow and your tokens decide. A live endpoint with neither is open to anyone.

The join workflow: check your JWT, admit the player
{
  "steps": [
    {
      "name": "check",
      "blocks": ["verify", "variables"],
      "verify": {
        "mode": "jwt",
        "secret": "game-jwt-key",
        "token": { "language": "jsonata", "source": "$input.token" },
        "audience": { "language": "jsonata", "source": "'my-game'" }
      },
      "variables": [
        { "name": "v", "value": { "language": "jsonata", "source": "$verify" } }
      ]
    }
  ],
  "final_transform": {
    "language": "jsonata",
    "source": "$v.ok and $v.claims.room = $input.room ? { 'admit': true, 'viewer_id': $v.claims.sub, 'can_send': $v.claims.send, 'stream': { 'room': $input.room }, 'exp': $v.claims.exp * 1000, 'profile': { 'name': $v.claims.name } } : { 'admit': false, 'reason': 'Your link has expired' }"
  },
  "output_handling": { "disposition": "display_only", "content_type": "application/json" }
}
The live endpoint it guards
{
  "workflow": "game/shape@published",
  "input_mode": "transform",
  "query": ["room", "token"],
  "input_transform": "{ 'room': query.room, 'token': query.token }",
  "live": {
    "stream": "game",
    "join": { "workflow": "game/join@published" },
    "presence": true,
    "channels": {
      "moves": { "mode": "relay", "schema": "game/move@published", "keep": "latest" }
    }
  }
}
Your app, on any host
<script src="https://acme.tessryx.app/_tessryx/site-1.js"></script>
<script>
  const live = tessryx.live('game/live', {
    host: 'acme.tessryx.app',
    query: { room: 'r1', token: tokenFromYourBackend },
    onRelay: (channel, entries) => draw(entries),
    onPresence: (list, joined, left, count) => showPlayers(list, count),
    onStatus: (status, reason) => status === 'closed' && rejoinWithAFreshToken(),
  });
  live.send('moves', { x: 120, y: 48 });
</script>

How it works

  1. 01

    Sending an update

    A publish step in any workflow, like a webhook route or a schedule, sends an event to a stream. The live endpoint's workflow turns it into what viewers see, once for each different thing they're watching, like each match on a scores page, and every open page gets the result.

  2. 02

    Players and chat

    A relay channel passes each sender's messages straight to everyone else, in batches about ten times a second, with no run per message: positions in a game, strokes on a whiteboard. A screened channel sends each message through one of your routes first, which suits chat.

  3. 03

    Checks on every message

    At no charge, the edge holds each relay message to the channel's schema and its sender's rate. Rules can add a cooldown between messages, numbers that may only go up, like a score, and fields kept for each sender.

  4. 04

    Game rules

    For a top speed, a weapon's cooldown, ammo or whose turn it is, a handler workflow checks each batch before anyone sees it. It can drop a message, rewrite it, change the shared state or pass it on to another workflow.

  5. 05

    Who's here

    Pages get the list of people on them, who joined and who left, and a count. A reload or a dropped connection inside 30 seconds doesn't count as leaving.

  6. 06

    Shared state for a room

    Round settings or a game board can live in the room, which every page gets when it joins and whenever it changes. Workflows can fill it from your records when a stream starts, and write it back every few minutes.

  7. 07

    Leaving, and coming back

    A leave workflow can run once for each person who's gone for good, say to give their seat to the next player. A connection that drops reconnects by itself and catches up on what it missed, free within a few minutes.

  1. Browser

    Your page, on Tessryx or any other host

  2. Edge

    Connections, checks, relay and presence

  3. Your workflows

    Updates, joins, game rules and saves

What it costs

Real-time uses the same credits as everything else. Free comes with 10,000 a month and Pro with 100,000.

WhatCredits
Someone joining1 credit. Reconnecting within a few minutes is free.
An update1 credit, or 1 for each different thing viewers are watching, like separate matches.
Relaying1 credit for each person who sends in a minute, and at least 2 a minute while anyone sends.
A handler8 credits for each minute of relaying it checks.
Big audiences2 credits per relaying minute for each thousand watchers past the first thousand.
Who's hereFree.
Join, first view, leave and room workflows, and each chat line1 credit a run, like any run.
Four players for an hour, with a handler checking moves
4 players joining
4
4 senders, 60 minutes of relaying
240
The handler, 60 minutes
480
Total
724 credits

About 138 hours of play on Pro, before any saves or chat lines.

1,000 people watching a scoreboard for two hours
1,000 people joining
1,000
One update a minute, 120 minutes
120
Total
1,120 credits

Viewers who only watch cost their join and nothing more. More than 100 people needs Pro or above.

Limits

Viewers per live page
100 people at once on Free, and 5,000 on Pro, Agency and Scale. Once a page is full, the next person to arrive gets a message saying so, and everyone already on it stays connected.
Ordinary pages aren't streamed
Updates reach a page over its live connection. A normal page or route still answers in one piece, with no server-sent events or streamed responses.
A handler does calculation only
It can use variables, conditions, asserts, loops and a final answer, in JavaScript. It can't read or write records or call another service, so it forwards a message to a workflow for that.
The list of who is here stops at 100
Past 100 people, pages get the count without the list.

Questions

Do I need a server?

Not for a page on Tessryx: the connections and the checks on every message run at Tessryx's edge, and your logic runs as workflows. An app hosted somewhere else needs a way to sign tokens, and only if it brings its own users.

Can I use my own login?

Your backend signs a short token, a JWT or an HMAC signature, with a key you also store as a Tessryx secret. A join workflow checks it and admits the person under your own id for them, with the channels you allow and a time their access ends. A page on the Tessryx site itself can use sign-in with Tessryx instead.

What happens when credits run out?

New joins are refused and relaying pauses, with the open connections kept, and pages are told live updates are paused. A top-up within 10 minutes picks up where it left off. After that the pages close with the same reason and try again every 10 minutes.

How many people can be on one page?

From 100 on the free plan to 5,000 on Pro and above. Past the first thousand watchers, a page where people are sending costs 2 more credits a minute for each extra thousand.

Does it work on phones?

It's a page in the browser like any other. A phone that puts the tab away loses the connection, and the page picks it up again when the tab comes back, without another join charge if that's within a few minutes.

Does it cost anything when nobody is watching?

An update only runs when someone is connected to see it.

Under the hood

The technical details

live.stream
The stream the page listens to. Any workflow sends to it with a publish step: a webhook route, a schedule, a form post.
shaping
The live endpoint's own workflow turns each event into what viewers get ($event, $input), once per event for each different input among the people watching. A thousand viewers of one match is one run, and nobody watching means no run. Returning null sends nothing.
live.initial
Optional. A workflow that renders what someone sees the moment they join, so they don't wait for the next update.
tessryx.live()
The page connects with the site runtime: onEvent for updates, onStatus for reconnecting, paused, full or closed, each with a reason written for the visitor. A page on another website names the host it connects to.
reconnects
A dropped connection reconnects by itself and is sent what it missed, up to a minute's worth. Back within a few minutes, it's free.
viewer cap
People on one live endpoint at once: Free 100; Pro, Agency and Scale 5,000. Past it a new viewer is told the page is full, and nobody watching is dropped.
credits
An update is a credit for each different input, however many watch. A viewer joining is a credit, like a page view.
A live scoreboard, one stream per match
{
  "workflow": "scores/shape@published",
  "input_mode": "transform",
  "query": ["match"],
  "input_transform": "{ 'match': query.match }",
  "live": {
    "stream": "scores",
    "initial": { "workflow": "scores/shape@published" }
  }
}

Build it

More highlights

Give your AI somewhere to build.

Free to start, no credit card. Bring the AI you already use.

Start building for free