UBNet browser SDK reference
API version: 6.4.1 · SDK version: 6.5.3 · Docs version: 6.5.3
This is the normative reference for the public UBNet browser SDK implemented by frontend/public/ubnet/ owned by the dedicated sdk component and installed as immutable releases by native clients.
For copy-ready examples, see the SDK guide.
Messaging inactivity configuration
createClient({ messagingIdleUnlock: true, messagingRecoveryAuthorizesSession: true }) selects server-backed inactivity unlocks. messagingUnlockDurationMs defaults to 1800000 (30 minutes), with a supported range of 60000–1800000. This mode requires API 6.4.1 and its chat-access-session migration; JA876 and UBNet enable it explicitly. The SDK's default remains fixed-duration for existing/headless consumers. See messaging sessions and activity rules and the HTTP session contract.
Renewal is handled inside the SDK. client.ubnet.chats.accessSession(authorization, action, activityAt?) is a low-level compatibility transport: use client.chats.unlock, lock and, for explicitly authorized headless activity, touchUnlock instead. authorization is the exact signed version-6 credential plus wallet/profile, local expiry and server clock offset; action is create, activity or revoke. Activity is a local epoch-millisecond interaction timestamp. It returns the same credential with updated local expires_at and server_offset_ms, never persists it by itself, and never signs an event. Ordinary message/history/inbox requests do not renew a session. The server rejects expired, revoked, wrong-profile or superseded-key credentials.
Client creation
type ClientOptions = {
apiBase?: string;
apiBaseUrl?: string;
baseUrl?: string;
walletProvider?: { request(args: object): Promise<unknown> };
fetch?: typeof globalThis.fetch;
spasmSubmitPath?: string;
submitPath?: string;
profileName?: string;
profile_name?: string;
messagingKeyStore?: object;
fileSales?: { wallet: string; audience: string; signMessage(message: string): Promise<string> };
fileSalesBuyer?: object; // See the buyer guide for exact signer/payment configuration.
// Optional override; canonical Spasm ID calculation is included by default.
eventIdForSignedEvent?: (signedEvent: SignedEventResponse) => string | Promise<string>;
};
walletProvider is optional for public reads and required by SDK operations that need wallet signing. Signed operations use EIP-1193 eth_accounts and personal_sign where required.
Global and client surface
Global API:
UBNet.createClient(options)UBNet.configure(options)UBNet.UbnetErrorUBNet.versionUBNet.versions
Every current client exposes fileSales, fileSalesBuyer, categories, productions, apiBase, api, cache, posts, lists, profiles, uploads, ipfs, chats, ubnet, nfts, reservedSubdomains, domainRegistration, support, events, configure(), getProfileName(), setProfileName(), read-only profileName, version, versions, and sdkSurface: "current".
There is no client.wallet namespace.
The Default profile is represented by an empty profile name. Named identities use wallet@profile.
Parameter conventions
type PostFeedQuery = {
profile?: string;
profile_name?: string;
name?: string;
limit?: number;
cursor?: string | null;
author?: string;
post_type?: string;
keyword?: string;
media_type?: string;
has_cid?: boolean;
schema_name?: string;
schema_version?: string;
source_instance_host?: string;
};
type ListFeedQuery = {
keyword?: string;
limit?: number;
cursor?: string | null;
};
type CategoryRankingQuery = {
category: string;
sort?: "likes" | "upvotes" | "newest";
latest?: boolean;
latest_per_author?: boolean;
limit?: number;
};
Visibility is not a post-feed filter; visibility belongs to wallet-owned/public wallet reads. Profile, visibility, and list_type filtering belong to wallet list reads, not the generic list feed.
Generic reads and UBNet Feed-curated reads
There is one public SDK. Generic reads never inherit UBNet Feed allowlists. Curated UBNet frontend reads live under client.ubnet.feed.
Generic posts
client.posts.list(filters?, requestOptions?)— genericGET /api/postsclient.ubnet.posts.list(filters?, requestOptions?)— genericGET /api/postsclient.ubnet.posts.mine(wallet, opts?)client.ubnet.posts.forWallet(wallet, opts?)client.ubnet.posts.topByCategory(query)— genericGET /api/posts/top-by-categoryclient.ubnet.posts.blockPowers(query?)— global/public Block Power ranking
Neither client.ubnet.posts.list() nor client.ubnet.posts.topByCategory() applies Post Feed Allow.
Generic lists
client.lists.list(filters?, requestOptions?)— genericGET /api/collectionsclient.ubnet.lists.list(filters?, requestOptions?)— genericGET /api/collectionsclient.ubnet.lists.mine(wallet, opts?)client.ubnet.lists.publicForWallet(wallet, opts?)client.ubnet.lists.topByCategory(query)— genericGET /api/collections/top-by-category
These methods do not apply List Feed Allow.
UBNet Feed posts
client.ubnet.feed.posts.list(filters?, requestOptions?)—GET /api/ubnet/posts/feedclient.ubnet.feed.posts.mine(wallet, opts?)client.ubnet.feed.posts.forWallet(wallet, opts?)client.ubnet.feed.posts.topByCategory(query)—GET /api/ubnet/posts/top-by-category
Post Feed Allow applies to client.ubnet.feed.posts.*.
UBNet Feed lists
client.ubnet.feed.lists.list(filters?, requestOptions?)—GET /api/ubnet/listsclient.ubnet.feed.lists.mine(wallet, opts?)client.ubnet.feed.lists.publicForWallet(wallet, opts?)client.ubnet.feed.lists.topByCategory(query)—GET /api/ubnet/lists/top-by-category
List Feed Allow applies to client.ubnet.feed.lists.* only when its server toggle is enabled.
There is no combined post-and-list ranking API. client.ubnet.content.topByCategory() and client.events.topByCategory() do not exist. Block Powers must use generic post ranking, never the Feed-scoped ranking API.
UBNet chat reads
client.ubnet.chats provides UBNet's uncategorized-message read policy:
client.ubnet.chats.list(wallet, opts?)client.ubnet.chats.messages(chatId, wallet, opts?)client.ubnet.chats.state(chatId, wallet, opts?)client.ubnet.chats.activity(wallet, opts?)
These use /api/ubnet/messages/.... Categorized messages remain available through generic high-level client.chats reads.
Use high-level client.chats for messaging.
Posts
client.posts.search({ keyword, limit, cursor }, { signal }) uses GET /api/posts/search, without SDK result caching. Check GET /api/settings: settings.post_search_enabled must be true before showing a post-search control. The switch defaults off; a disabled switch or an empty search-category allowlist returns an empty result page. Only public, undeleted, non-reply posts in an approved search category qualify, including imported posts. Search approval is independent of Feed placement. Query parameters cannot enable search or bypass category approval. Other post reads are unchanged.
Read methods:
client.posts.list(filters?, requestOptions?)client.posts.search(filters?, requestOptions?)— public, approved-category search; see the setting below.client.ubnet.posts.search(filters?, requestOptions?)— the same search endpoint, filters, cancellation and no-cache behavior.client.posts.get(postId, query?, requestOptions?)client.posts.history(postId, requestOptions?)— returns{ root_id, has_edits, edit_count, versions }. The server excludes visibility/publication-only updates from content edit history. Usehas_editsto show View History; the original alone hashas_edits: false. Changed title, text and media values count as edits, including clearing a populated field. Repeating unchanged values does not count. Revisions retain their event IDs and original edit depths;is_latestmarks the newest returned content revision. Raw signed visibility events remain stored.requestOptions.signalcancels the uncached read. Requires API 6.4.1 for these response fields.client.posts.replies(postId, opts?)client.posts.mine(wallet, opts?)
Write methods:
client.posts.create(wallet, input)client.posts.edit(wallet, postId, input)client.posts.update(wallet, postId, input)client.posts.reply(wallet, postId, content, opts?)client.posts.react(wallet, targetId, type, opts?)client.posts.publication(wallet, targetId, state)client.posts.withdraw(wallet, targetId)client.posts.remove(wallet, targetId)client.posts.deleteThread(wallet, threadRootId)
At least one explicit category is required at post creation. Categories are immutable after creation. Post writes use the client's active profile; use profileName or client.setProfileName() rather than placing profile_name in PostInput.
Post writes accept text, post, link, image, audio, video, and reply. Omitting post_type defaults to text, and client.posts.reply() emits reply. Unsupported post types fail with invalid_post_type before signing.
HTTP(S) mediaValue becomes link media. CID/IPFS media uses discovered media metadata. An explicit thumbnailUrl overrides a discovered thumbnail. Edits use patch semantics.
Lists
Read methods:
client.lists.list(filters?, requestOptions?)client.lists.get(listId, opts?)client.lists.items(listId, opts?)client.lists.item(listId, itemId, opts?)client.lists.mine(wallet, opts?)client.lists.publicForWallet(wallet, opts?)
client.lists.items(listId, { limit, offset }) uses offset pagination, not cursor pagination. The response includes items, has_more, and next_offset.
Write/access methods include client.lists.create, update, addItem, updateItem, removeItem, react, reply, remove, unlockChallenge, unlock, unlockItemMediaChallenge, and unlockItemMedia.
Current authored list types are exactly:
audio-listvideo-listimage-listpost-listlink-listmixed-list
There is no authored any list type. mixed-list accepts audio, video, image, post, and link items.
list_type is the only authored list classifier. Newly signed list events do not emit post_type, and list-item events use target_type plus media_type instead. The type is fixed by the root event; client.lists.update() discards all list/post type aliases rather than signing an ineffective type change.
For CID items, the persistent identity is the root cid; media_metadata.media_path identifies a file inside a folder CID. A blank gateway_url does not imply a hard-coded public gateway. Public playback uses the enabled gateway registry.
IPFS discovery and playback
Discovery methods:
client.ipfs.discover(value, options?)client.ipfs.discoverCid(value, options?)
Playback/gateway methods:
client.ipfs.gateways(options?)client.ipfs.playbackUrls(value, options?)client.ipfs.playbackUrl(value, options?)client.ipfs.playback(value, options?)client.ipfs.markPlaybackSucceeded(url, options?)client.ipfs.markPlaybackFailed(url, options?)
discover() accepts options.expectedType / expected_type values audio, video, image, or media. Discovery uses the backend gateway registry.
Playback accepts a CID/IPFS reference, an ordinary direct URL, or a post/list-item-shaped object containing cid plus canonical media_metadata. A file CID resolves from the root CID. A folder CID requires media_metadata.media_path; the SDK deliberately returns no public playback candidate for a known folder root without a media path. For artwork inside a folder use playbackUrls(item, { asset: "thumbnail", operation: "image" }), which uses media_metadata.thumbnail_path unless an explicit thumbnail URL is present.
playbackUrls() returns ordered candidates from gateways whose registry entry enables client_playback. It preserves gateway path prefixes, remembers a successful gateway per operation, forgets a preferred gateway after failure, caches the last-good registry, tries at most eight configured gateways, and never invents an ipfs.io fallback. HTTP(S) URLs containing query parameters or fragments, including signed asset URLs, and private relative unlock URLs are opaque and are returned unchanged rather than reconstructed.
playbackUrl() returns the first candidate or an empty string. playback() returns a stateful helper with urls, url, index, hasNext, fail() / failed(), next(), succeed() / succeeded(), and reset(). Call fail(currentUrl) after a media load error and use its returned next URL; call succeed(currentUrl) once playback or image loading succeeds so later resolutions can prefer that gateway.
Profiles and follows
Profile saves are partial updates: omitted properties leave existing values unchanged. An explicitly submitted empty string clears the corresponding display name, bio, avatar, or banner. This applies to top-level values and their supported fields aliases. Saving a preference alone does not clear profile details.
Named profiles accept letters, numbers, dashes (-), and underscores (_). Invalid names fail with invalid_profile_name; they do not fall back to Default.
Follow relationships are public, signed, profile-to-profile events. The relationship key is (follower_wallet, follower_profile_name, following_wallet, following_profile_name). follow activates that projection and unfollow makes it inactive; neither operation erases signed event history. Newer signed timestamps take precedence over older projected state. See the detailed follow protocol for event envelopes, conflict behavior, pagination, and exports.
Common methods include:
client.profiles.getclient.profiles.namesclient.profiles.ensBindingsclient.profiles.verifyEnsControlclient.profiles.searchclient.profiles.followersclient.profiles.followingclient.profiles.followExportclient.profiles.uploadAvatarclient.profiles.uploadBannerclient.profiles.saveclient.profiles.updateclient.profiles.createEnsProfileclient.profiles.enableMessagingclient.profiles.destroyMessagingKeyclient.profiles.saveSocialLinkclient.profiles.deleteSocialLinkclient.profiles.followclient.profiles.unfollow
Avatar storage is selected by the installation's uncached upload challenge. Disk is the default avatar mode; signed-event storage is optional. Read the actual challenge and preserve current URLs. The SDK follows the selected mode and applies its current approval requirements. Signed-event images always require an author's signature; an avatar approval preference does not disable that requirement. Banner uploads use their own supported contract.
PNG, JPEG and WebP are supported subject to the challenge's MIME/size limits. Signed image events include image bytes, MIME type and SHA-256 digest, and their returned URL can reference /api/uploads/profile-images/<spasm_id>. Disk uploads instead return the applicable stored URL. Uploading alone does not update the selected profile; use a separately authorized profile save to associate the URL. A successful submission may still need indexing before it becomes readable. Do not remove or replace the previous image merely because a provider request failed.
Encrypted chats
client.chats is the current high-level messaging API. It owns identity, local key storage/recovery, unlock state, encryption/decryption, signing/submission, previews, reconciliation, polling, watchers, and unread state.
Metadata:
client.chats.versionclient.chats.protocol—ubnet-e2ee-v2client.chats.encodings
Private reads are authorized by the high-level client.chats.getStatus(identity) / client.chats.unlock(identity) lifecycle. Do not call low-level authorize() directly.
Identity and lifecycle methods include getIdentity, directId, directChatIncludesIdentity, otherDirectIdentity, hasLocalKey, getLocalPublicKey, listLocalKeys, hasPendingRecoveryMaterial, isUnlocked, unlock, lock, touchUnlock, generateKey, removeLocalKey, buildRecoveryMessage, createWalletRecoveryBackup, restoreWalletRecoveryBackup, exportPasswordBackup, importPasswordBackup, getStatus, enable, restore, repair, sync, and disable.
A successful wallet-recovery restore establishes a signed, short-lived messaging session. A valid wallet/profile unlock is reused; otherwise recovery requests a separate UBNet messaging unlock signature before returning ready. The recovery signature alone does not authorize private API reads. After client.chats.restore() or client.chats.restoreWalletRecoveryBackup() succeeds, no second immediate unlock() is required. Server-recovery restores reached through repair() or sync() behave the same way. Cancelling the unlock keeps the restored key but leaves messaging locked; retry unlock() without replacing the key. Password-only imports, expiry, and explicit lock() still use the normal unlock flow.
Use status.can_read for readable-history gating.
Read/send methods include client.chats.list, client.chats.get, client.chats.state, client.chats.activity, client.chats.messagingProfile, client.chats.messagingKeyBackup, client.chats.messages, client.chats.preview, client.chats.createGroup, client.chats.remove, client.chats.leaveGroup, and client.chats.sendText. messages() decrypts by default. Plaintext stays local to the browser SDK; only ciphertext is submitted.
Unified messages, attachments and retention
client.chats.sendMessage(identity, chatId, input, options?) sends text, an encrypted attachment, or both. input is a string or an object with message, file, expiresAt, viewSeconds and optional categories. The established client.chats.sendText(identity, chatId, text, options) signature accepts these same optional fields in options. Both return the submission state and matching spasm_id, event_id and message_event_id, calculated canonically and locally before submission. signEncryptedMessage(identity, payload) returns spasm_id and event_id with the exact signed envelope before a request is made. submitSignedMessage recomputes that ID and returns it even if the server omits an ID or returns only a success string. Submission errors retain the calculated ID on those same three error fields. The submitting send-state callback exposes all three ID aliases before the network request. Store the signed encrypted event in protected storage, confirm indexing by ID, and retry only the same signed bytes when authorized. See the agent messaging guide for the complete restart-safe flow. Local encryption and wallet approval happen before submission; do not automatically repeat a signed send after a timeout.
Normal direct and group messages use conversation delivery: participants, including the sender, can read history until an optional absolute expiry. expiresAt is an epoch-millisecond value or Date-compatible value in the next 30 days. Group expiry deletes the shared message for all members.
A non-null viewSeconds selects recipient-only delivery, supported only for direct messages. It is an integer from 30 to 3600. A fresh ephemeral sender key encrypts the envelope to the recipient; no recoverable sent copy is retained. An omitted unopened expiry defaults to one day for these timed sends. The timer starts only at the recipient's explicit Open action and never resets on reopening. Group viewing timers are rejected before signing and by the server. Do not implement background opening to produce previews or search results.
client.chats.limits() returns inline_attachments_enabled and inline_attachment_max_bytes. The default maximum attachment is 3 MiB; the administrator can configure it. The small encrypted message manifest contains the text, filename, MIME type, size and a random file key. The file is separately encrypted in the same signed event; category names, participant identities, attachment presence and retention metadata are public event metadata.
client.chats.sendAttachment(identity, chatId, file, { message?, ...options }) is a convenience for an encrypted attachment, with an optional caption. attachment is also accepted as an alias for file in send/prepare options. client.chats.attachmentLimits() returns the most recently loaded limit as { max_bytes, max_files: 1 }, defaulting to 3 MiB; call limits() for authoritative current settings before choosing a file. Both encodings.attachment and encodings.group_attachment name the corresponding envelope encodings.
Conversation history and previews fetch only the small message payload and expose decrypted attachment metadata. They never fetch file ciphertext. Downloads fetch and decrypt the file on demand, checking its hash and size against the encrypted manifest. A send still requires exactly one event signature. The former combined attachment envelope is unsupported. downloadAttachment(identity, chatId, message, options?) is also supported alongside the two-argument opened/history-message form. History decryption runs at most four jobs concurrently.
Read compatibility: the current SDK also reads the historical version 2 length-prefixed envelope, including its inline attachment (up to the historical 8 MiB maximum). History and preview results contain only text and sanitized attachment metadata; inline file bytes are discarded after those reads. An explicit downloadAttachment re-decrypts the authenticated historical message locally, with the same identity, unlock and expiry checks and no additional signing. Historical Whispers still require explicit opening. Version 3 remains the only format written by sendMessage and sendWhisper, with separately encrypted attachment payloads. No server migration or key replacement is required.
The chat UI exposes Whispers under New chat → Whisper and as sealed inbox rows. Opening a row shows a sealed viewer; only its explicit Open Whisper action requests ciphertext. A Whisper needs both an unopened expiry and a viewing window. SDK callers can supply message_type: "whisper" in sendMessage options, with delivery_mode: "recipient", expiresAt and viewSeconds. The ESM client also provides createUbnetV2Client(options).v2.messages.sendWhisper({ from, to, message, file?, expiresAt, viewSeconds }), which uses the same messaging pipeline.
After unlocking the ESM inbox, call v2.messages.inbox({ category, limit: 50, cursor }) for metadata pages. Pass the previous response's opaque next_cursor until has_more is false; omit cursor for the first page. Pagination never opens or decrypts a Whisper. This has the same cursor contract as client.chats.inbox(identity, { limit, cursor }).
Encrypted message signatures cover the submitted ciphertext and routing metadata. Newly signed payloads include signature_intent: "authorize_encrypted_message" and plaintext_attestation: "not_asserted": they authorize transmission, not authorship or truth of the decrypted text. The server still accepts existing signed messages without these optional fields.
client.chats.inbox(identity, { category?, limit?, cursor? }) returns recipient-only message metadata without ciphertext or plaintext. Limit is 1–100, default 50; pass next_cursor as cursor while has_more is true. Pages contain metadata only and must never open or decrypt a Whisper. unread_count covers all live unopened Whispers, including later pages. client.chats.openMessage(identity, messageId) requires an unlocked exact wallet/profile, starts any viewing timer and returns locally decrypted plaintext, an optional attachment, and lifecycle timestamps. It does not mark a normal chat as read. client.chats.downloadAttachment(identity, openedOrHistoryMessage) returns a Blob while the matching profile is unlocked and the message has not expired. Conversation reads return attachment metadata alongside plaintext; file bytes are decrypted on explicit download, or when include_attachment_bytes: true is requested. Search must remain in-browser and must not open recipient-only messages.
Discard displayed plaintext, attachment references and object URLs on close, expiry, lock and identity change. Physical server deletion runs in a separate lifecycle worker; reads deny expired content immediately, even before that worker runs. No message backup or permanent deletion receipt is created. Screenshots and already downloaded copies cannot be recalled.
await client.chats.sendMessage(identity, chatId, {
message: "The document is attached",
file: selectedFile,
expiresAt: Date.now() + 86400000
});
await client.chats.sendMessage(identity, directChatId, {
message: "View this for one minute",
viewSeconds: 60
});
const inbox = await client.chats.inbox(identity);
// Only in an explicit Open button handler:
const opened = await client.chats.openMessage(identity, selectedMessageId);
prepareMessage() and sendText() accept optional categories. Categories are public event metadata and are not encrypted with message plaintext. For explicit unified messages they are routing labels, not public-feed admission rules.
Reconciliation, crypto, watcher, and diagnostics methods include mergeMessages, reconcilePendingMessages, encryptDirect, decryptDirect, encryptGroup, decryptGroup, decryptMessage, prepareMessage, signEncryptedMessage, submitSignedMessage, sendEncrypted, waitForMessageIndexed, getUnread, markRead, markAllRead, pollUnread, watchConversation, watchConversations, watchUnread, getSettings, updateSettings, diagnostics, normalizeError, subscribe, and dispose.
type DirectEncryptedPayload = {
messaging_protocol: "ubnet-e2ee-v2";
chat_id: string;
sender_public_key: string;
recipient_public_key: string;
categories?: Array<{ name: string }>;
ciphertext: string;
iv: string;
encoding: "aes-gcm-v2" | "aes-gcm-envelope-v2";
delivery_mode?: "conversation" | "recipient";
has_attachment?: boolean;
lifecycle?: { expires_at: number | null; view_seconds: number | null };
encrypted: true;
};
type GroupEncryptedPayload = {
messaging_protocol: "ubnet-e2ee-v2";
chat_id: string;
sender_public_key: string;
categories?: Array<{ name: string }>;
ciphertext: string;
iv: string;
encoding: "group-aes-gcm-v2" | "group-aes-gcm-envelope-v2";
delivery_mode?: "conversation";
has_attachment?: boolean;
lifecycle?: { expires_at: number | null; view_seconds: null };
encrypted_keys: Array<{
wallet: string;
profile_name?: string;
ciphertext: string;
iv: string;
encoding: "aes-key-wrap-ecdh-v2";
}>;
encrypted: true;
};
Never add plaintext message or content fields to an encrypted payload.
Read status and background refresh
Read status stays local unless the user enables sync_chat_read_status for their wallet/profile. await client.chats.getReadSyncSetting(identity, { refresh: true }) returns { enabled }. await client.chats.setReadSync(identity, true) signs a profile preference and waits for indexing; it does not replace messaging keys. await client.chats.syncReadStatus(identity) refreshes private server read markers and retries pending local reads. Turning synchronization off removes that profile's server markers, while retaining this browser's local read state. Decrypted message contents and private keys are never included in read-status requests.
watchConversation, watchConversations, and watchUnread share a refresh controller for the same SDK client and wallet/profile. Duplicate reads are combined; unchanged data is not repeatedly emitted. Watchers pause while locked or hidden unless poll_when_hidden: true is explicitly supplied, and refresh immediately on return. Stop unused watchers and dispose unused clients.
Polling discovers activity; it never marks a conversation read, including when active_chat_id is supplied. Call markRead only when the latest decrypted message has actually been viewed. Message search remains browser-only over decrypted messages already loaded into memory.
Uploads
Public Pinata uploads use a fresh wallet signature and a short-lived upload URL. No persistent IPFS upload token is part of the public upload API.
client.uploads.avatar(wallet, file)client.uploads.pinataChallenge(data)client.uploads.pinataPresignedUrl(data)client.uploads.pinataSignedUrl(uploadUrl, file)client.uploads.pinata(data)
client.uploads.pinata(data) performs the complete challenge, wallet signature, presign, and direct Pinata upload flow. public_ipfs_uploads_disabled means the administrator disabled that path.
NFT galleries, names, domain registration, and support
client.nfts.publicGallery(wallet, cursor?, options?)client.nfts.editor(wallet, options?)client.nfts.inventory(wallet, networkIds?)client.nfts.save(wallet, input)client.reservedSubdomains.check(name)client.domainRegistration.buildMessage(input)client.domainRegistration.submit(input)client.domainRegistration.registerAuthorized(response)client.support.contact(input)client.support.sendMessage(input)
Public event queries
client.events.list(queryOrLimit?)client.events.get(eventId)client.events.summary()client.events.nftSales(query?)
Event discovery supports limit, cursor, action, author/wallet, category, category_mode, schema_name, schema_version, post_type, list_type, source_instance_host, nft_sale, and latest where implemented. post_type filters posts and list_type filters lists.
Use the returned list_type when rendering or grouping lists.
Category ranking is not an event-level API. Select posts or lists explicitly with client.ubnet.posts.topByCategory() or client.ubnet.lists.topByCategory(); use the corresponding client.ubnet.feed.* method only for Feed-curated surfaces.
Advanced transport and cache helpers
Most integrations should use the semantic namespaces above. For cases that need direct access, client.api exposes get(path, options?), post(path, body, options?), and request(path, fetchOptions?, requestOptions?).
client.cache exposes get, peek, set, remember, resolve, prefetch, invalidate, invalidatePrefix, invalidateTag, clear, and stats. Cache policies are implementation-level controls; do not use them as a persistence layer.
Return and error contracts
The SDK returns server JSON directly unless a high-level helper explicitly normalizes it. Structured SDK errors expose error.status, error.code, and error.payload.
List-item pages return next_offset; private file-sales pages return {items, nextCursor}. Other cursor APIs commonly use next_cursor.
Private file sales for autonomous agents
The optional client.fileSales service is disabled by default. Configure the client with fileSales: { wallet, audience, signMessage }. The callback signs messages using the agent's wallet; no interactive browser prompt or API key is required. Seller calls check access-token ownership individually when Require access token is on. Explicit open access skips that check; signatures and allowances remain required. The token is held, not spent.
Use fileSales.upload(file, {requestId, completionRequestId, onPrepared}), then fileSales.create({fileId, price}, {requestId}). The price is a positive decimal USDC string with at most six decimal places. The result includes purchaseUrl; Pinata x402 handles payment and file delivery. Payment goes directly to the signing wallet. Any file type is supported within effective IPFS allowances. Retention and expiry terms are outside this API.
For recoverable workflows use prepareUpload({name,bytes}), uploadToUrl(prepared,file) and completeUpload(uploadId) separately. The transfer helper sends bytes once, validates the private Pinata destination and upload expiry, and never treats transfer success as registration. Preserve returned IDs and reuse a request ID with identical parameters after a timeout. Changed parameters under that ID are rejected. Files remain private before sale creation. Existing files must belong to the authenticated wallet; a CID alone grants no permission.
disable(saleId), enable(saleId) and deleteFile(fileId) control the lifecycle. Price and recipient are fixed. Explicit disable and permanent deletion obtain fresh action-specific signatures and remain available if the token is transferred away or the service is disabled. Permanent deletion stops future downloads and frees storage only after confirmed removal.
Inspection defaults off globally and per seller. Sellers control setInspectionPreference(enabled); both switches must permit listFiles, getFile, listSales, getSale and getUsage. List methods accept {limit,cursor} and return {items,nextCursor}. There is no public sale catalogue, free file link or purchase-once entitlement.
The service uses configured IPFS allowances, with Off/Limited/Unlimited settings and administrator wallet overrides. It never exposes shared Pinata credentials. Existing public upload methods retain their public behavior; use fileSales for private sales.
The async onPrepared(prepared, {preparationRequestId, completionRequestId}) hook must finish before any file transfer. Persist its upload ID and request IDs to recover if the process stops. Upload errors preserve stage, uploadId and both request IDs when known; API errors expose status, code, payload, requestId and a retryable hint. Neither signed API calls nor direct transfers retry automatically. Never replay the entire upload helper after an uncertain transfer; resume completion using the saved ID.
See the complete file-sales agent guide for all 14 methods, return fields, runnable workflows, authorization rules and the error-recovery table. The current bundle also works in Node with Web Crypto, fetch, File/Blob and FormData; no browser wallet is required for this namespace.
Feed-enabled category discovery
client.categories.list({feedOnly: true, signal}) requests and returns only active categories marked allow_in_feed: true. Without feedOnly, the default remains all active categories, including non-feed categories. Use the returned category string with client.ubnet.feed.posts.list({category, limit, cursor}). Category selection narrows server feed policy, including federation imports; it cannot add unapproved categories. Reset the cursor when selection changes.
Paid-file buyers (SDK 6.4)
Use client.fileSalesBuyer.links(recordOrText), quote(url, {signal}), pay(offer, {wallet, approve, onPayment, signal}), retry(payment, {signal}), exportPayment(payment), restorePayment(saved) and complete(payment). Buying is disabled unless the client is configured with fileSalesBuyer: {enabled:true}. Buyers use their own wallet and explicit payment approval, independently of the separately authorized seller API. See the complete buyer and browser checkout guide for supported payment terms, signer adapters, federation, safe proof journaling, download recovery, CORS and default-off UI settings. Never automatically sign another payment after an uncertain result.
Agent input and result supplement
The generated callable catalog gives the exact positional parameter names. This section defines the remaining common object contracts; the specialized messaging, seller and buyer guides contain their full schemas and examples. Optional fields must be omitted when unchanged. Raw server JSON can contain additional fields, and an empty page is a valid result.
| Surface | Input and result contract |
|---|---|
client.categories.list(options?) | options.feedOnly selects active categories with allow_in_feed: true; signal cancels discovery. Returns {categories} including the canonical category, description and permission flags. Default discovery includes all active categories. |
| Public post/list/profile/event reads | Supply the positional wallet/ID where listed, with method-supported limit, cursor, filters and profile in the correct query argument. Returns the route's JSON record/page. Post/list histories can return root and edit-event identifiers; preserve both. List-item pages use offset, not cursor. |
client.posts.create | Requires input.categories; title, content, post type, media source/metadata and visibility/publication fields are normalized by the SDK. edit/update must omit categories and unchanged media. reply takes the approved reply content; react takes the supported reaction type. Mutation responses contain submission/event identifiers and still need indexing/read verification. |
client.lists.create/update | Creation requires an explicit supported list_type; supported title, description, categories and access/gating fields are defined above. An item requires a supported type and source_value/equivalent source ID. Media folder CIDs require a media path; distinguish list root, item and referenced source IDs. Unlock methods use the issued challenge contract, not a guessed signature. |
client.profiles.save/update | input.fields or supported top-level fields can include display name, bio, avatar/banner/header URL and ENS preferences. SDK profile saves preserve omitted fields; an explicit empty string clears a supported text/URL field. The chosen profile_name must match the intended identity. createEnsProfile requires a valid unused named profile and .eth ENS name, then checks the supported ownership/binding flow. |
client.profiles.uploadAvatar/uploadBanner | Accept a wallet, image Blob/File and optional provider override. Read the uncached challenge; avatar mode can be disk or signed event. Signed image events always need a signature. PNG/JPEG/WebP and the challenge's size constraints apply. Returns upload URL/storage metadata or signed event identifiers; associate the returned URL through a separately authorized profile save. |
client.productions | list(wallet, options?) supports profile/cursor and signal; get(id, options?) supports signal. create(wallet) has no content input. bind(wallet, id, cid) requires the production root and folder CID. remove(wallet,id) signs removal. Read methods return the production/page JSON; mutations return signed-event submission metadata. |
client.nfts | editor(wallet, options?) returns {wallet, config, networks, inventory}. Gallery configuration uses enabled, supported networks, mode (all or curated) and selected_nfts accepted by the current editor/server contract. save(wallet,input) signs this input. Preserve saved selections and metadata on provider failure; inventory/current public projections may be placeholders and are not an ownership oracle. |
client.reservedSubdomains.check(name) | Requires a nonempty name up to 255 characters and returns the instance's availability/reservation JSON. It does not sign, reserve or register the name. |
client.domainRegistration.buildMessage(input) | Supply label, walletAddress/registrant, optional parentEnsName/parent, timestamp and nonce. Returns normalized label/wallet/parent, timestamp, nonce and the exact message to sign. The request message itself authorizes no blockchain transaction. |
client.domainRegistration.submit(input) | Submit that exact message and signature with its label/wallet/parent/timestamp/nonce, optional registrationSolution and sponsored. Returns registration service JSON. A user-paid response can include status: "authorized", registrar address, label, registration solution, signature, transaction calldata and authorization containing registrant/template/fee/nonce/issuedAt/deadline/sponsored. Do not manufacture or edit those fields. |
client.domainRegistration.registerAuthorized(response) | Validates a user-paid authorization, the fixed supported registrar, template, calldata, fee and deadline; checks Ethereum Mainnet and the matching connected wallet; then requests a real eth_sendTransaction. Returns {success, status: "submitted", transactionHash, label, registrarAddress, registrantWalletAddress}. Approval of exact spending and gas is required. Wait for a receipt before claiming confirmation. |
client.support.contact/sendMessage(input) | Required message (up to 4,000 characters); optional sender_wallet/senderWallet (120), sender_name/senderName (120), sender_contact/senderContact (240), subject (160). Sends an unsigned real support request and returns service JSON. Require authorization to contact support with that content. |
client.events | list(queryOrLimit) accepts a number or filter object. get requires an event ID; summary takes no arguments; nftSales(query?) accepts cursor/limit and applies NFT-sale filters. These return public projections, not private messages. client.content.query delegates to the public event list and is deprecated. |
client.api | get(path, settings?), post(path, body, settings?), request(path, fetchInit?, settings?). settings.query carries query fields, settings.cache controls supported caching, and fetch init carries method/headers/body/signal. Use higher-level methods whenever possible; never bypass route authentication or leak a private header to a provider URL. |
For uploads, IPFS playback, social links/follows, cache settings and advanced key/crypto helpers, read the namespace sections and exact signature catalog. For private files and messages, use the dedicated raw guides linked above; their journal IDs, proof/key material, lifecycle and replay rules are part of the contract. No SDK method authorizes enabling a service, changing installation allowances or invoking the separate private administration API.