Textlog API used by the terminal client
This document describes the HTTP API contract used by this version of
gotextlog. It is an integration guide for compatible Textlog instances,
not a description of the complete server API. The upstream instance publishes
its full, machine-readable contract at
/api/openapi.json.
Transport and instance selection
The client treats the configured URL as an instance origin and sends API requests below that origin:
<instance origin>/api/v1/<endpoint>
For example, textlog --url https://notes.example/path uses
https://notes.example/api/v1; any path, query, or fragment in the configured
URL is discarded. The configuration validator accepts HTTP and HTTPS URLs;
use HTTPS for instances reached over a network.
Ordinary requests use:
Accept: application/json
Authorization: Bearer <token> # when a token is configured
Content-Type: application/json # when the request has a JSON body
The client sends its bearer token on public as well as authenticated endpoints.
The live endpoint instead requests text/event-stream. Request cancellation is
propagated through the Go context.
Authentication
Textlog sign-in is an email-code exchange. Account creation is not available through the API, so a user must already have an account on the selected instance.
POST /auth/requestasks the instance to email a code.POST /auth/verifyexchanges the email and six-digit code for a session.- The client saves
data.tokenand sends it as a bearer token. - At startup,
GET /merestores the signed-in account. DELETE /auth/sessionrevokes the current session during sign-out.
Changing the configured instance also changes which server receives the saved bearer token. Credentials and accounts are local to an instance; this client does not implement federation or cross-instance identity.
Compatible instances may instead authenticate with an SSH Ed25519 key. The
login screen switches between email and key modes with Tab. Key authentication
uses POST /auth/key/challenge with an OpenSSH public key and optional
first-use handle, signs the returned base64-decoded message byte-for-byte, and
sends the SSH wire-encoded signature to POST /auth/key/verify. Verification
returns the same session envelope and bearer token as email verification. The
client signs with ssh-agent or a matching unencrypted private-key file; it
never sends private-key material.
In the endpoint tables, optional means the endpoint does not require a session but receives the bearer token when one is available. Required means the TUI invokes it for a signed-in operation. None identifies sign-in endpoints normally called before a token exists.
Endpoint inventory
All paths in this section are relative to /api/v1.
Feeds, search, and live data
| Method and path | Auth | Request | TUI use and response |
|---|---|---|---|
GET /feeds/latest |
Optional | limit, optional cursor |
Latest feed. Reads a paginated post collection. |
GET /feeds/hot |
Optional | limit, optional cursor |
Hot feed. Reads a paginated post collection. |
GET /activities/for-you |
Required | limit, optional cursor |
Personalized activity feed. Reads an activity collection. |
GET /activities/to-me |
Required | limit, optional cursor |
Replies, mentions, and follows directed to the user. Reads an activity collection. |
GET /search |
Optional | Required q; limit; optional cursor |
Searches public posts and reads a paginated post collection. |
GET /firehose |
Optional | No parameters | Live feed over server-sent events (SSE). |
Feed and search screens request 20 records at a time. Their Next and Previous
commands retain the opaque cursors returned by the server. The client decodes
has_unread from activity responses but does not currently display it or mark
feed/activity records as read.
The firehose returns server-sent events in this form:
event: post
data: {"id":123,"body":"hello",...}
The data value can be either a post object or {"data": <post>}. The parser
supports multiline SSE data, comments, CRLF, and a final event without a blank
line. It ignores event types other than post and skips malformed post events.
A post event must include id, body, created_at, and
author.handle. When an active stream ends or fails, the TUI reconnects after
a short delay; pausing or leaving the live screen cancels it. The protocol does
not replay posts missed between connections.
Posts and threads
| Method and path | Auth | Request | TUI use and response |
|---|---|---|---|
GET /posts/{id} |
Optional | Positive integer id |
Opens a post or obtains thread ancestry. Reads a post envelope. |
GET /posts/{id}/replies |
Optional | depth, limit, optional cursor |
Builds the recursive reply tree. Reads a paginated reply collection. |
POST /posts |
Required | {"body": string} or {"body": string, "parent_id": integer} |
Creates a top-level note or reply. Reads the created post envelope. |
PATCH /posts/{id} |
Required | {"body": string} |
Edits a post owned by the signed-in user. Reads the updated post envelope. |
DELETE /posts/{id} |
Required | No body | Deletes a post owned by the signed-in user. The client accepts data.deleted. |
POST /posts/{id}/report |
Required | {"reason":"harassment"|"spam"|"impersonation"|"other"} |
Reports another user's post. The client accepts data.reported. |
To render a thread, the TUI first gets the selected post. If it is a reply, it
uses top_id when available, otherwise follows parent_id, fetching
ancestors until it reaches the root. It then requests every reply page for the
root with depth=5 and limit=100. Repeated pagination cursors are treated
as an error. Replies deeper than five levels are not fetched by this client.
The compose editor currently limits submitted bodies to 280 runes and ten lines, even though the Textlog service permits notes up to 500 characters. The server remains responsible for authoritative validation and authorization.
Profiles and relationships
| Method and path | Auth | Request | TUI use and response |
|---|---|---|---|
GET /users/{handle} |
Optional | URL-escaped handle |
Loads public profile details in a data envelope. |
GET /users/{handle}/notes |
Optional | limit, optional cursor |
Loads the profile's top-level notes. |
GET /users/{handle}/replies |
Optional | limit, optional cursor |
Loads the profile's replies. |
GET /users/{handle}/following/users |
Optional | limit, optional cursor |
Lists accounts followed by the profile. |
GET /users/{handle}/following/tags |
Optional | limit, optional cursor |
Lists hashtags followed by the profile. |
GET /users/{handle}/followers |
Optional | limit, optional cursor |
Lists accounts following the profile. |
GET /users/{handle}/blocks |
Required | limit, optional cursor |
Lists blocks when viewing the signed-in user's profile. |
POST /users/{handle}/follow |
Required | No body | Follows an account; accepts data.following. |
DELETE /users/{handle}/follow |
Required | No body | Unfollows an account; accepts data.following. |
POST /users/{handle}/block |
Required | No body | Blocks an account; accepts data.blocked. |
DELETE /users/{handle}/block |
Required | No body | Unblocks an account; accepts data.blocked. |
Opening a profile is a composite operation: the TUI requests the profile,
notes, replies, followed users, followed tags, and followers sequentially. It
also requests blocks when the profile belongs to the signed-in user. Each list
is requested once with limit=100; profile tabs do not currently paginate
past that first page. A failure in a required profile request fails the whole
profile load. Failure to load the optional blocks tab is ignored.
The API client still contains support for the deprecated
GET /users/{handle}/posts alias, but the TUI uses /notes and never calls
the alias.
Hashtags
| Method and path | Auth | Request | TUI use and response |
|---|---|---|---|
GET /tags/{tag} |
Optional | URL-escaped tag, without # |
Loads hashtag details in a data envelope. |
GET /tags/{tag}/posts |
Optional | limit, optional cursor |
Loads posts carrying the hashtag. |
GET /tags/{tag}/followers |
Optional | limit, optional cursor |
Lists accounts following the hashtag. |
Opening a hashtag requests all three endpoints sequentially with limit=100
for the two collections. The TUI displays the first page and does not currently
provide hashtag follow/block actions.
Session and account
| Method and path | Auth | Request | TUI use and response |
|---|---|---|---|
POST /auth/request |
None | {"email": string} |
Emails a sign-in code. The client accepts data.sent. |
POST /auth/verify |
None | {"email": string, "code": string} |
Returns a session envelope containing a token, expiry, and current user. |
POST /auth/key/challenge |
None | {"public_key": string, "handle": optional string} |
Returns a key challenge, expiry, and registration state. |
POST /auth/key/verify |
None | {"challenge_id": string, "signature": base64 string} |
Verifies an SSH signature and returns the normal session envelope. |
DELETE /auth/session |
Required | No body | Revokes the active token; the client accepts data.revoked. |
GET /me |
Required | No parameters | Loads the current account in a data envelope. |
PATCH /me |
Required | {"bio": string} |
Updates the current account bio and returns the account envelope. |
The TUI validates that sign-in codes contain six digits before sending them. It stores the verified session token in its platform configuration directory and clears it after successful revocation. The bio editor limits input to 1,000 runes; the instance performs final validation.
Response shapes consumed by the client
Most single-resource and mutation responses use an envelope:
{"data": {}}
Collections use cursor pagination:
{
"data": [],
"pagination": {"next_cursor": null}
}
next_cursor is either an opaque string or null. The client sends a
nonempty value back unchanged as the next request's cursor.
Post and reply
The client consumes these post fields:
| Field | Type | Use |
|---|---|---|
id |
integer | Post identity, navigation, and mutation target. |
top_id |
integer or null | Root lookup for a reply thread. |
parent_id |
integer or null | Reply relationship and parent navigation. |
body |
string | Rendered note text. |
created_at |
RFC 3339 timestamp | Ordering/display and live-event validation. |
reply_count |
integer | Reply count metadata. |
tags |
array of strings | Hashtag rendering and navigation. |
mentions |
array of strings | Mention rendering and navigation. |
url |
URL string | Open-in-browser action. |
api_url |
URL string | Decoded for API compatibility; not used for routing. |
author |
user reference | Author rendering, profile navigation, follow/block actions. |
parent |
post or null | Quoted parent context in feeds. |
A reply contains the post fields plus integer depth, used to indent and fold
the thread tree.
User, profile, and hashtag
A user reference contains handle, url, and api_url. The full profile
adds bio, created_at, post_count, replies_count,
follower_count, following_user_count, following_tag_count,
following_count, and optional blocked_user_count and
blocked_tag_count.
A hashtag reference contains tag, post_count, follower_count, url,
and api_url.
The current-user response contains handle, email, bio,
email_verified, and can_post; key-only instances may return an empty email
and false email_verified. A verified session adds token and
expires_at around that user object.
Activity
An activity collection adds has_unread and returns objects with id,
type, created_at, unread, and a type-dependent payload. The TUI
understands these activity types:
post,reply, andmention: payload decodes as a post.user_follow,tag_follow, andsignup: payload supplies anactoruser reference and, when applicable, atarget.handleortarget.tag.
Unknown or malformed activity payloads are skipped.
Errors and compatibility requirements
Textlog errors are expected to have this form:
{"error":{"code":"rate_limited","message":"Slow down"}}
For a non-2xx response, the client records the HTTP status plus error.code
and error.message. If the body is absent or malformed, it uses
http_error and HTTP <status> (stream_error for the firehose). A
numeric Retry-After header is displayed to the user in seconds. Transport
failures use network_error; cancellation remains a context cancellation.
A compatible instance must implement the routes and JSON fields used by the screens it wants to support. JSON may include additional fields: Go's decoder ignores them. Successful JSON responses must have valid field types and RFC 3339 timestamps where timestamps are consumed.
The TUI does not currently call several upstream API families, including drafts, polls and voting, tag follow/block mutations, activity read markers, latest-feed read markers, explore, post unpublish, feed keys, archives, embeds, or RSS/Atom feeds. Consult the selected instance's OpenAPI document for those server capabilities.