# Studio Network — what you can do

<!-- GENERATED by ops/tools/skill-sync — do not edit by hand. Run pnpm skills:sync. -->

<!-- GENERATED by ops/tools/skill-sync — do not edit by hand. Run `pnpm skills:sync`. -->

# Studio Network — what you can do

`/admin/d/studios` · pillar `protocols`

This Studio's address on the Leumas network — find other studios, see where they are, and message them.

## Fetch this, never remember it

These change per install and per release. Call for them - a list written here would be a future lie.

| What | Call | Why |
|---|---|---|
| whether this install is on the network at all, and what it publishes | `GET /api/studio-network/me` | ALWAYS call this first. `connected:false` with a `reason` is the normal state on a dev box or an unactivated install, and it is not an error — every other answer in this domain depends on it |
| who else is listed, right now | `GET /api/studio-network/directory?q=<term>` | the opt-in directory, read through this install's own licence token. `?handle=<address>` resolves one exact address and 404s when nobody holds it |
| this install's conversations and unread counts | `GET /api/studio-network/threads` | the local copy — mail is drained off the relay with an advancing cursor, so this, not the mailbox, is where a message lives after delivery |

## How to actually do things here

Written by hand, because the order and the traps are the part no file can derive.

### Get this Studio onto the network

1. `GET /api/studio-network/me`. If `connected` is false, stop and say so — the install needs an activated licence against a central Leumas server, and nothing below will work until it has one.
2. `PUT /api/studio-network/me` with `{ handle }` to claim the address. 3–30 characters, lowercase letters, digits and dashes.
3. Then `PUT` again with `{ displayName, about, city, listed: true }` to publish the rest.

**Trap:** The handle is claimed ONCE across the entire network and answers 409 when it is taken or reserved; a malformed one is a 400. The claim runs first and alone, so a refused address leaves the rest of the profile unwritten — retry with a different handle rather than resending the whole form. `listed` is what makes the Studio visible; an address alone does nothing.

### Find another studio and keep it

1. `GET /api/studio-network/directory?q=<term>` — matches a handle PREFIX or a display-name substring.
2. `POST /api/studio-network/contacts` with `{ handle }` or `{ instanceId }` to save it.

**Trap:** The search matches only those two fields. It cannot search a city, a hostname or a tenant, and that is deliberate: a directory that searches what it refuses to show is a disclosure channel. Saving is idempotent — the same studio twice refreshes one row. A studio the directory has never confirmed cannot be saved at all, so an unlisted or deactivated one answers 404.

### Send a message to another studio

1. Resolve the address first: `GET /api/studio-network/directory?handle=<address>`.
2. `POST /api/studio-network/threads/<instanceId>/messages` with `{ body }`.
3. `GET /api/studio-network/threads/<instanceId>` to read the conversation back.

**Trap:** [critical] Not end-to-end encrypted — Leumas relays and can read every payload, and staff see them in the fleet console. Say so before anybody sends anything sensitive. Delivery arrives on the recipient's presence beat (about a minute), not instantly, and it is at-least-once: the handler dedupes on the message id. Sending needs admin on this install, because it speaks as the whole Studio.

### Come off the map

1. `PUT /api/studio-network/me` with `{ listed: false }` to disappear from the directory entirely.
2. Or `{ city: "" }` to stay findable by address while leaving the globe.

**Trap:** Those are two different things and users conflate them. Un-listing hides the Studio from everyone including somebody who knows its address; clearing the city only removes the dot. Neither deletes messages already delivered — a copy lives on the other studio's machine and this one cannot reach it.

## Tabs

Every one is a real URL. A tab with its own section below carries a real job.

| Tab | Path | Group | What it is for |
|---|---|---|---|
| `map` | - | The other studios | Every listed Studio on a 3D globe, at the city it chose to publish. |
| `directory` | - | The other studios | Search every listed Studio by address or name, and keep the ones you talk to. |
| `inbox` | - | This studio | Messages between this Studio and another, one thread per studio. |
| `settings` | - | This studio | Claim this Studio's @address, name it, give it a city, and decide whether it is listed at all. |

## Tabs in focus

### `map` - Map

Every listed Studio on a 3D globe, at the city it chose to publish.

**Read with:** `GET /api/studio-network/directory`

**Write with:** `POST /api/studio-network/contacts`

1. Click a dot to see who it is, then save the studio or open a conversation with it.
2. Colour is liveness: green under five minutes since its last beat, amber within a day, grey beyond.

**Trap:** A studio with no published city is NOT on the globe — it is in the Directory instead. Nothing here is ever placed by guesswork, so a missing dot means "no city published", never "somewhere unknown". Dots in the same city are fanned apart to stay clickable; the fan is a rendering device, not a position.

### `directory` - Directory

Search every listed Studio by address or name, and keep the ones you talk to.

**Read with:** `GET /api/studio-network/directory?q=` | `GET /api/studio-network/contacts`

**Write with:** `POST /api/studio-network/contacts` | `DELETE /api/studio-network/contacts/:instanceId`

1. Type an @address or a name. An empty box is the whole directory.
2. Save a studio to keep its name beside its messages.

**Trap:** A contact is a BOOKMARK, not a friendship — it is one-sided and needs no acceptance, because everyone listed chose to be publicly addressable. Do not describe it as a friend request.

### `inbox` - Inbox

Messages between this Studio and another, one thread per studio.

**Read with:** `GET /api/studio-network/threads` | `GET /api/studio-network/threads/:instanceId`

**Write with:** `POST /api/studio-network/threads/:instanceId/messages` | `POST /api/studio-network/threads/:instanceId/read`

1. Pick a thread — each is its own URL — and write.
2. Opening a thread marks it read.

**Trap:** This is install-level mail: the sender is the machine, and anyone who administers the Studio can read it. Relayed by Leumas and not end-to-end encrypted.

### `settings` - Address

Claim this Studio's @address, name it, give it a city, and decide whether it is listed at all.

**Read with:** `GET /api/studio-network/me`

**Write with:** `PUT /api/studio-network/me`

1. Claim the address first — it is the only field that can be refused.
2. A city is optional and resolves to that city’s centre. Listing is off until it is switched on.

**Trap:** Never promise finer placement than a city: the form accepts no coordinates and the platform stores none beyond the city centroid. An unresolvable city is kept as a label with no coordinates, which lists the Studio without plotting it.

## What this domain owns

Its verb is **Open**.

Entity kinds: `studios.contact`



---
Source: .claude/skills/leumas-studio/reference/domains/studios.md
Canonical: https://docs.leumas.tech/p/skills/leumas-studio/domains/studios
