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.
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.
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.