gotextlog

gotextlog
git clone https://git.ryansepassi.com/git/gotextlog.git
Log | Files | Refs | README

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.

  1. POST /auth/request asks the instance to email a code.
  2. POST /auth/verify exchanges the email and six-digit code for a session.
  3. The client saves data.token and sends it as a bearer token.
  4. At startup, GET /me restores the signed-in account.
  5. DELETE /auth/session revokes 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:

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.