Skip to content

Local client API

The local client API lets a program in any language use Unseenet: it talks HTTP and a WebSocket to a client runtime on 127.0.0.1, and the runtime does the rest through Session. The graphical client and the web client are built on it.

  • The protocol - the requests, their JSON, the events and how a program authenticates - is UN-RFC 0046. Read that to write a client.
  • Running it for a program (a service with its own key, section 3.2): unseenet serve. For a person's vault, the graphical client starts it.

This page is the Python side: LocalAPI in unseenet.client.localapi, what answers each request before server.py puts it on HTTP. Its code follows the sections of UN-RFC 0046, one part each, so a section of the RFC and its code find each other.

State (section 4.1)

State (UN-RFC 0046 section 4.1): one answer with all a front end needs to draw itself.

Belongs here: state (identities, groups with counts, ripening, settings), pausing ripening, the about text and the emoji list.

Part of LocalAPI (see init.py).

State

state

state() -> dict

GET /api/state (section 4.1): everything a front end needs to draw itself - locked or not, setup, connection, identity, nodes, inbox, ripening.

set_ripening_paused async

set_ripening_paused(paused: bool) -> dict

Pause or resume ripening in the background (#337), for this run only. Answers the state's ripening; every listener gets ripening-paused.

set_ripening_to async

set_ripening_to(level: int) -> dict

Ripen this key to level (3 or 4) on this device (#740), from now on. Answers the state's ripening; every listener gets ripening-target.

markup

markup(data: dict) -> dict

POST /api/markup {"text", "kind", "markdown"} (section 4.4, #632): the tree a text will have once posted, for a Write / Preview toggle - the same tree messages carry, so a preview shows what readers will see. markdown false: as plain text; left out, as your posts go (your Markdown setting).

about

about() -> dict

GET /api/about (section 4.1): the "About Unseenet" text every client shows, with this version.

emoji staticmethod

emoji() -> dict

For a picker (section 4.4, #362): static, so a front end loads it once.

Unlocking and setting up (section 4.2)

Unlocking and setting up (UN-RFC 0046 section 4.2): a vault opened, created or imported.

Belongs here: whether the vault needs setting up, unlocking it with its passphrase, the first setup (an identity, a node), starting the runtime and closing it, and the Locked answer while there is no session.

Part of LocalAPI (see init.py).

Setup

needs_setup

needs_setup() -> bool

True when the vault is open but has no identity or no node yet: the front end shows the setup (section 4.2).

unlock async

unlock(
    passphrase: str, device: str = "", secret: str = ""
) -> dict

POST /api/unlock (section 4.2): open the vault with its passphrase (made if it does not exist yet) - or with a device's secret (device, secret in b64, UN-RFC 0050 section 5) - and start the runtime. A service has no vault to unlock.

setup async

setup(display: str, node: str) -> dict

POST /api/setup (section 4.2): the first setup of a new vault - your display name and a node (an address, or one from the known nodes). Starts the runtime.

start async

start() -> None

Run the client runtime: saved posts at once, then the network.

close async

close() -> None

Stop the runtime and every task, and close the session. The server calls it on shutdown; a test or another transport calls it when done.

DeviceUnlock

device_unlocks

device_unlocks() -> dict

GET /api/device-unlock (section 4.2, UN-RFC 0050 section 5): the devices that can open this vault without its passphrase - id (a passkey's credential ID), label, created; never what they hold. Also when locked, so the page knows which passkey to ask for. possible: false for a service.

add_device_unlock async

add_device_unlock(data: dict) -> dict

POST /api/device-unlock {"passphrase", "id", "secret", "label"}: let this device open the vault with secret (b64, 32 octets or more), which its secure hardware releases only after its PIN or unlock - a passkey's PRF output. Needs the vault's passphrase. The passphrase still opens the vault, everywhere.

remove_device_unlock

remove_device_unlock(data: dict) -> dict

POST /api/device-unlock/remove {"id"}, or {"all": true}: that device, or every one, no longer opens the vault without its passphrase.

Groups (section 4.3)

Groups (UN-RFC 0046 section 4.3): the groups you follow, one group's posts, finding more.

Belongs here: the group list as a tree with roles and name requests, one group's threads or chat with write permission per row (group), read marks and views, subscribing, browsing a node's groups, refreshing, known nodes, and opening a #group@fp reference.

Part of LocalAPI (see init.py).

Groups

groups

groups() -> dict

GET /api/groups (section 4.3): the groups you follow as a tree, the people you chat with, and what is for you - with unread counts, as every client lists them.

group

group(
    gid: str,
    show_all: bool = False,
    limit: int | None = None,
) -> dict

GET /api/groups/{gid} (section 4.3): one group as a client shows it - its head (title, view, roles, charter, write permission) and its rows, threaded or as a chat, each with whether you may reply. Also the pseudo-groups of a contact and "for you".

mark_read

mark_read(gid: str, ids: list, all_: bool = False) -> dict

POST /api/groups/{gid}/read (section 4.3): mark posts read, by Message-ID, at most MAX_READ at a time - or, with "all": true, the whole group (#625): answers how many were unread.

set_view

set_view(gid: str, view) -> dict

POST /api/groups/{gid}/view (section 4.3): show a group as threads, as a chat, as a feed (UN-RFC 0034) or, for a site, as its pages (UN-RFC 0035), or null for the group's own choice.

set_in_timeline

set_in_timeline(gid: str, on) -> dict

POST /api/groups/{gid}/timeline (section 4.3): put a group you follow into your timeline (true), take it out (false) or back to its default (null) - UN-RFC 0034 section 6.2.

subscribe

subscribe(gid: str, on: bool) -> dict

POST /api/subscribe (section 4.3): follow a group (and fetch it) or stop.

browse async

browse(pattern: str = '*') -> dict

GET /api/browse?pattern= (section 4.3): the groups your nodes carry, followed or not, for finding new ones.

refresh

refresh() -> dict

POST /api/refresh (section 4.3): fetch every group you follow now, in the background; what changes comes as events.

known_nodes

known_nodes() -> dict

GET /api/known-nodes (section 4.3): the nodes a client may start with, with names, descriptions and operators. Also while setting up, to choose a node.

group_ref async

group_ref(text: str) -> dict

What a #group reference can mean (unseenet #172), followed groups first.

Writing (sections 4.4, 4.5)

Writing (UN-RFC 0046 sections 4.4, 4.5): posts and private posts as jobs, and your own posts.

Belongs here: posting, private posts, editing, reacting (UN-RFC 0048) and withdrawing, each a job whose progress (postage, ripening) arrives as events; and mentions of you.

Part of LocalAPI (see init.py).

Writing

post

post(gid: str, data: dict) -> dict

POST /api/groups/{gid}/post (section 4.4): a post or a chat line, as a job whose progress (ripening, postage) comes as events. To a contact's pseudo-group it is a private post.

private

private(data: dict) -> dict

POST /api/private (section 4.4): a private post or chat line to a key's inbox (UN-RFC 0009), as a job.

edit

edit(gid: str, data: dict) -> dict

POST /api/groups/{gid}/edit (section 4.5): a new version of one of your posts (UN-RFC 0042), as a job; its progress comes as events.

react

react(gid: str, data: dict) -> dict

POST /api/groups/{gid}/react (section 4.5, UN-RFC 0048): react to a post, comment or chat line with emoji, as a job. The emoji you already gave takes it back, another changes it. who: true instead answers who gave emoji.

withdraw async

withdraw(gid: str, message_id: str) -> dict

POST /api/groups/{gid}/withdraw (section 4.5): withdraw a post - your own, or any in a group you own or moderate (UN-RFC 0004 section 7).

pin async

pin(gid: str, data: dict) -> dict

POST /api/groups/{gid}/pin (section 4.12): the owner or a moderator pins a post at the top of the group, kept beyond its window, with an optional note - or unpins it with "unpin": true (UN-RFC 0033). Answers the pins now.

comments async

comments(gid: str, data: dict) -> dict

POST /api/groups/{gid}/comments (section 4.24): close comments on an entry, with an optional note, or open them again (UN-RFC 0030 section 3.2) - its author, the owner or a moderator. Answers the entry's state now.

language

language(gid: str, data: dict) -> dict

POST /api/groups/{gid}/language {"text", "reply_to", "kind"} (UN-RFC 0052 5a, #686): what to suggest the author writes in, from the text as they type - {"codes", "from"}, from text, answered, conversation, group, you, profile or "". Local and cheap: ask at every pause in typing.

languages

languages() -> dict

GET /api/languages: the languages to choose from - code, own name, English name, and whether a text can be told to be in it (core.languages). No flags.

choose_language

choose_language(gid: str, data: dict) -> dict

POST /api/groups/{gid}/language/choose {"languages"}: what you write your chat lines and comments in here, winning over suggestions; [] takes it back.

mentioned

mentioned(gid: str, subject: str, body: str) -> dict

POST /api/groups/{gid}/mentioned (section 4.5): before posting, whom a text mentions by name and whether notices may be sent here (UN-RFC 0032) - for asking the writer.

comments_dict

comments_dict(state: dict) -> dict

A thread's comments state (Session.comments_state) as JSON (section 4.24).

People and safety (section 4.6)

People and safety (UN-RFC 0046 section 4.6): contacts, ignoring, reporting, blocking.

Part of LocalAPI (see init.py).

People

contact

contact(data: dict) -> dict

POST /api/contacts (section 4.6): add a key to your contacts with a name, or remove it (remove true).

close_chat

close_chat(data: dict) -> dict

POST /api/chats/close (section 4.6): take a chat with a person off the list (on true, the default), or put it back. It is in your state (UN-RFC 0044 section 4.2) and opens again by itself with the next chat line.

ignored

ignored() -> dict

GET /api/ignore (section 4.6): the keys you ignore, with their labels.

ignore

ignore(data: dict) -> dict

POST /api/ignore (section 4.6): ignore a key (on true, with a label) or stop ignoring it. Every open group is redrawn.

report_targets async

report_targets(gid: str, message_id: str) -> dict

GET /api/groups/{gid}/report?id= (section 4.6): who a post can be reported to - moderators, the owner, the hierarchy's maintainers, the node operator.

report async

report(gid: str, data: dict) -> dict

POST /api/groups/{gid}/report (section 4.6): report a post to the chosen targets (by their i from GET), with a reason, a note in Markdown and files (as on a post), optionally without your name. Answers at once with {"job", "targets"}; the job's events (section 5) say when it is sent.

block async

block(gid: str, data: dict) -> dict

POST /api/groups/{gid}/block (section 4.6): a hierarchy maintainer blocks a key in the hierarchy the group is in (UN-RFC 0018 section 8.2). Answers the hierarchy.

Groups you run (section 4.7)

Groups you run (UN-RFC 0046 section 4.7): the owner's and moderators' side.

Belongs here: creating a group, its settings (a new manifest), removing it, inviting and removing members, roles, bans, and the moderation queue of moderated groups.

Part of LocalAPI (see init.py).

Running

create_group async

create_group(data: dict) -> dict

POST /api/groups (section 4.7): create a group you own - name, type, title, description, kinds, view, charter and the channel settings. Answers its Group ID.

group_settings async

group_settings(gid: str, data: dict) -> dict

POST /api/groups/{gid}/settings (section 4.7): owner - change a group's settings (title, description, charter, view, kinds and the others in SETTINGS_FIELDS). A new manifest; type, owner and locality never change.

remove_group async

remove_group(gid: str, data: dict) -> dict

POST /api/groups/{gid}/remove (section 4.7): owner - remove a group from the whole network (UN-RFC 0013). Final, so it needs "confirm": "remove".

invite async

invite(gid: str, data: dict) -> dict

POST /api/groups/{gid}/invite (section 4.7): owner of a closed group - give a key the group's current key.

remove_member async

remove_member(gid: str, data: dict) -> dict

POST /api/groups/{gid}/members/remove (section 4.7): owner of a closed group - remove a member, which starts a new key epoch. Answers the epoch.

role async

role(gid: str, data: dict) -> dict

POST /api/groups/{gid}/role (section 4.7): owner - add or remove a moderator or a poster (role moderators or posters, on).

ban async

ban(gid: str, data: dict) -> dict

POST /api/groups/{gid}/ban (section 4.7): owner - ban a key (on true, with a reason) or lift a ban (on false). A new manifest.

moderation async

moderation() -> dict

GET /api/moderation (section 4.7): the submissions waiting for you as a moderator, fetched from your inbox, with who sent them and to which group.

moderate async

moderate(data: dict) -> dict

POST /api/moderation (section 4.7): moderator - approve a submission (approve true; it is posted) or reject it with a reason sent to its author.

Who you are (section 4.8)

Who you are (UN-RFC 0046 section 4.8): profiles, names, the passphrase, your settings.

Belongs here: reading and publishing profiles (profile, publish_profile, with label, mine, link_kinds and the public-profile warning), hiding someone's profile, changing the vault's passphrase, your names (UN-RFC 0019) and settings.

Part of LocalAPI (see init.py).

Identity

profile async

profile(
    key: str | None = None, context: str | None = None
) -> dict

A profile with what a front end shows around it (#320): its label, whether you hid it, and for yours whether one is published and the warning before the first (UN-RFC 0022 sections 9.1, 9.2). With context - where it is opened: a closed group's ID, or me (your private posts or a chat with them) - a profile they shared there only (section 6, UN-RFC 0046 4.16).

share_profile async

share_profile(data: dict) -> dict

POST /api/profile/share (section 4.16, UN-RFC 0022 section 6): share a profile with one person ("to": their key) or a closed group (its ID), where only they can read it - your public profile, or "profile" (an object, or {"edit": fields} as for POST /api/profile) made for them; with "withdraw": true take back the one you shared there. Nothing is published.

hide_profile

hide_profile(key: str, hidden: bool = True) -> dict

Hide or show someone's profile, for you alone (UN-RFC 0022 section 9.2).

change_passphrase async

change_passphrase(old: str, new: str) -> dict

A new passphrase for this vault file (#281). Slow, so off the event loop. Copies of the vault elsewhere keep the old one.

publish_profile async

publish_profile(profile) -> dict

POST /api/profile (section 4.8): publish your profile - an object, or {"edit": fields} from the profile form, or null to withdraw it. A program's profile says it is a bot. Answers the profile as GET shows it.

names async

names() -> dict

GET /api/names (section 4.8): your names (UN-RFC 0019) - the ones you hold, which is shown first, pending claims, and the DNS value to prove a domain.

name_action async

name_action(data: dict) -> dict

claim, renew, release, transfer, primary, ask, give, revoke, approve, refuse.

export async

export(data: dict) -> dict

POST /api/export (UN-RFC 0049 section 7, #577): write an export of your data - {"path", "private": false, "passphrase"}; path defaults to Downloads (else your home folder). With private the file is encrypted with the passphrase. Answers what Session.export does: path, counts and the note that it holds only what the nodes still carry.

notifications

notifications() -> dict

GET /api/notifications: what new has landed in For you since the last time any client asked - for the front end to tell the reader, as the reader chose (settings). Asking marks them as told.

settings

settings() -> dict

GET /api/settings (section 4.8): your settings, with a preview of your signature as it will be signed, and whether you issue spam reports (section 4.21).

change_settings async

change_settings(data: dict) -> dict

POST /api/settings: as set_settings, and state_local - keep your state on this device only, or in the network again (UN-RFC 0044 section 7.4).

client_settings

client_settings(client: str) -> dict

GET /api/settings/clients/<client> (section 4.8): the settings one kind of client keeps with your state, so they follow you from device to device.

set_client_settings

set_client_settings(client: str, data: dict) -> dict

POST /api/settings/clients/<client> {"key": value, ...}: change them; null removes one. Values are strings, numbers, true or false.

set_settings

set_settings(data: dict) -> dict

POST /api/settings (section 4.8): change your settings - signature, Markdown, notices, the ripening level private senders need, blocked posts, bots.

Articles from a program (section 4.9)

Articles from a program (UN-RFC 0046 section 4.9): a service posting what it built itself.

Belongs here: article (a signed article with the headers a program may add) and manifest (a group's manifest written by a program).

Part of LocalAPI (see init.py).

Articles

article

article(data: dict) -> dict

POST /api/articles (section 4.9): a program posts an article it built - group, kind, subject, body and the headers a program may add (X-*, Gateway-*) - signed with the session's key and paid for like any post. Answers its Message-ID.

manifest async

manifest(gid: str, data: dict) -> dict

A whole new manifest for a group this key owns (a gateway's, a bot's channel).

Events (section 5)

Events (UN-RFC 0046 section 5): what the front end is told without asking.

Belongs here: the WebSocket listeners, turning Session events into section 5's JSON events, jobs' progress (job), status lines (say), and spawn for work that runs on while a request has returned.

Part of LocalAPI (see init.py).

Events

listen

listen() -> asyncio.Queue

A queue of the events of section 5 for one WebSocket client; the server reads it and sends each event. Give it back with unlisten.

unlisten

unlisten(q: Queue) -> None

Stop sending events to a WebSocket client's queue (see listen).

emit

emit(event: str, **data) -> None

Send an event of a front end's own (UN-RFC 0046 section 6), such as an extension's progress, to everyone listening on /api/events. Name it x-<extension>-....

spawn

spawn(coro) -> asyncio.Task

Run a coroutine in the background, kept until done and cancelled by close.