npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@recursiv/sdk

v0.7.50

Published

TypeScript SDK for the Recursiv API

Readme

@recursiv/sdk

Infrastructure for AI agents and the apps that use them. Databases, deploy, file storage, AI agents with memory, auth, and social primitives. One SDK. Zero dependencies.

Install

npm install @recursiv/sdk

Node.js >= 18. ESM only (import works on 18+; require() needs Node >= 22, which can require ES modules). Zero runtime dependencies; realtime and the Next.js helpers each declare one optional peer, installed only if you use them.

60-second quickstart

import { Recursiv } from '@recursiv/sdk'

const r = new Recursiv() // reads RECURSIV_API_KEY from env

// Provision a database
const { data: db } = await r.databases.ensure({ name: 'my-app', project_id: 'proj_1' })
const { data: creds } = await r.databases.getCredentials({ project_id: 'proj_1', name: 'my-app' })
console.log(creds.connection_string) // postgresql://...

// Create an AI agent
const { data: agent } = await r.agents.create({
  name: 'Assistant',
  username: 'assistant_1',
  model: 'anthropic/claude-sonnet-4',
  system_prompt: 'You are a helpful assistant.',
  organization_id: 'org_xxx',
})

// Stream agent responses
for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' })) {
  process.stdout.write(chunk.delta ?? '')
}

What can you build?

| App type | Recursiv gives you | Example | |----------|-------------------|---------| | AI-native app | Database + AI agent + file storage + auth | Home maintenance tracker, AI CRM, smart inventory | | Autonomous agent | Agent with its own Postgres, memory, and code execution | Research agent, data pipeline agent, ops bot | | Social platform | Posts, communities, chat, profiles, follow graph | Team forum, community hub, social network | | Code playground | Sandboxes, deploy, AI coding assistant, live preview | Vibe coding tool, learning platform, code editor | | Any app that needs a backend | Auth + database + storage + billing | Skip Firebase/Supabase — one SDK covers it all |

Choose the right primitive

| Your app needs... | Use this | Not this | |---|---|---| | Structured data (users, records, tasks) | r.databases | r.posts with JSON blobs | | Social content, feeds, discussions | r.posts + r.communities | r.databases for feeds | | File uploads (photos, docs, media) | r.storage | r.uploads (legacy) | | User auth and API keys | r.auth | Raw fetch to auth endpoints | | AI agent with a model | r.agents | Direct LLM API calls | | Measure agents (evals, accuracy, cost) + audit trail | r.evidence | Ad-hoc logging | | Run code in a sandbox | r.projects.executeCode() | External sandbox services | | Deploy to production | r.projects.deploy() | Manual CI/CD |

API overview

Infrastructure

| Resource | Key methods | |----------|------------| | r.databases | ensure, create, list, getCredentials, query | | r.envVars | set, list, delete (values write-only; secrets masked on read) | | r.projects | create, deploy, executeCode, createSandbox, stopSandbox, deploymentLogs | | r.storage | ensureBucket, getUploadUrl, getDownloadUrl, listItems, deleteObject | | r.sandbox | execute (anonymous, no API key needed) |

AI Agents

| Resource | Key methods | |----------|------------| | r.agents | create, update, chat, chatStream, grantProjectAccess, conversations | | r.e2ee | registerDevice, devices, revokeDevice, publishKeyPackages, keyPackageStatus, claimKeyPackage, appendEvent, events, acknowledge (flag-gated E2EE v2 transport) | | r.brain | sendMessage | | r.commands | execute, gatePrompt | | r.projectBrain | tasks, decisions, milestones, completeTask, usage | | r.metrics | get, forOrg, forProject, children, definitions — the KPI ledger per tier (platform, network, org, project): numbers with definitions, status, and confidence |

Identity & Auth

| Resource | Key methods | |----------|------------| | r.auth | signUp, signIn, getSession, signOut, createApiKey (session reads/sign-out use explicit Bearer auth with a native/server Cookie fallback) | | @recursiv/sdk/next | createRecursivAuth — cookie-session server auth for Next.js App Router (see below) | | r.users | me, get | | r.organizations | create, members, addMember, invite | | r.profiles | me, get, search, follow, unfollow |

Social

| Resource | Key methods | |----------|------------| | r.posts | create, update, list, liked, search, react | | r.communities | create, join, members | | r.chat | send, dm, createGroup, messages | | r.tags | create, list | | r.uploads | uploadMedia — upload post/chat bytes end to end |

Platform

| Resource | Purpose | |----------|---------| | r.settings | User preferences (including app-scoped content choices), sessions, password management | | r.billing | Usage tracking, subscriptions, checkout | | r.appSubscriptions | Consumer app plans: checkout, status, createPortalSession | | r.notifications | Push notification tokens | | r.github | GitHub integration | | r.integrations | External service connections | | r.admin | User management, stats, content reports (listReports, resolveReport, dismissReport) | | r.dispatcher | Task queue, claims, signals, outcomes, webhooks | | r.goals | Org goals with task rollups (group dispatcher tasks) | | r.protocols | Private Minds safety: sourceControls (current enablement and saved choices), setSourceControl, onSourceControlChange, reportSource. Delegated accounts: nativeSocialAccounts, startNativeSocialConnection, finishNativeSocialConnection, disconnectNativeSocialAccount. Native follows: nativeFollowState, prepareNativeFollow, submitNativeFollow, nativeFollowStatus (native following or pending approval, distinct from a Minds feed subscription; preserve the complete signed Nostr list). Native reposts and undo: nativeRepostState, prepareNativeRepost, submitNativeRepost, nativeRepostStatus (public posts, explicit approval, connected account, no uncertain retries). Native likes and unlikes: nativeLikeState, prepareNativeLike, submitNativeLike, nativeLikeStatus (connected authority, app enablement and explicit approval; resume pending actions instead of duplicating them). Reviewed replies and optional linked posts: prepareNativeReply, submitNativeReply, nativeReplyStatus, nativeReplyHistory, prepareNativeReplyRemoval, removeNativeReply (require deployed API support and app enablement). Keyless shared post links: sharedNetworks(projectId), getSharedConversation(projectId, { protocol, post, readRef? }). Member public reading: searchPublicPosts (selected account required for Bluesky/ActivityPub; public Nostr search), getConnectedSocialConversation (preserve result readerAccountId/readRef), publicNetworks, searchPublicProfiles, getPublicProfile, getPublicAuthorFeed, getFollowingFeed, getPublicConversation, getPublicTrends, getPublicTrendFeed; private app subscriptions: listSubscriptions, subscribeToProfile, unsubscribeFromProfile, onSubscriptionChange. list returns bounded adapter maturity, limitations, and exhaustive supported/planned/unsupported implementation capabilities (not provider health or enablement); settings, candidate refresh/status, search, and read-only quarantined candidate review via listCandidates / getCandidate |

Full reference: see llm.md (ships with this package) or the type definitions in dist/.

Quarantined protocol candidates

r.protocols.listCandidates({ protocol?, limit?, cursor? }) and r.protocols.getCandidate(id) read unpublished candidates, not feed posts. Both require an admin-scoped, project-bound key, a live admin role, and existing project-admin access. App/network scope is not a caller parameter. These GET methods do not collect content, update settings, or publish anything.

Lists return { data, meta: { limit, has_more, next_cursor } } with no offset; the API defaults to 20 rows and permits at most 50. Details return { data }. Render candidate content as plain text and respect content_truncated. Safe source IDs are preserved byte-exact. A null external_id means unsafe or malformed provenance was withheld; an author's native_id may likewise be absent or withheld. Do not invent replacement IDs. API failures, including routes absent from an older deployment, throw the normal SDK errors; do not present them as an empty queue.

Configuration

const r = new Recursiv({
  apiKey: 'sk_live_...',                   // or set RECURSIV_API_KEY env var
  baseUrl: 'https://api.recursiv.io/api/v1',  // default
  timeout: 30000,                          // request timeout in ms
  maxRetries: 2,                           // auto-retry on 429/5xx
})

// Zero-arg works when RECURSIV_API_KEY is set
const r = new Recursiv()

// Anonymous sandbox (no API key needed)
const r = new Recursiv({ anonymous: true })

// Self-hosted
const r = new Recursiv({ baseUrl: 'https://my-instance.com/api/v1' })

Projects & deploy

// Deploy to production
const { data: deployment } = await r.projects.deploy('proj_1', {
  branch: 'main',
  type: 'production',
})

// Execute code in a sandbox
const { data: result } = await r.projects.executeCode('proj_1', {
  code: 'console.log("hello")',
  language: 'typescript',
})
console.log(result.output) // "hello\n"

// Get deployment logs
const { data: logs } = await r.projects.deploymentLogs('proj_1', deployment.id)

// Start/stop sandboxes
await r.projects.createSandbox('proj_1')
await r.projects.stopSandbox('proj_1')

AI agents

// Create an agent
const { data: agent } = await r.agents.create({
  name: 'Research Bot',
  username: 'researcher',
  model: 'anthropic/claude-sonnet-4',
  system_prompt: 'You help users find information.',
  tool_mode: 'autonomous',
  organization_id: 'org_xxx',
})

// Chat (async — agent responds asynchronously)
const { data: reply } = await r.agents.chat(agent.id, {
  message: 'What are the latest trends in AI?',
})
// reply: { message_id, conversation_id, content, created_at }

// Stream (token-by-token)
for await (const chunk of r.agents.chatStream(agent.id, {
  message: 'Tell me more about transformer architectures',
})) {
  process.stdout.write(chunk.delta ?? '')
}

// Give agent access to project infrastructure
await r.agents.grantProjectAccess(agent.id, {
  project_id: 'proj_1',
  permissions: ['execute_code', 'read_files', 'write_files'],
})

Social primitives

// Posts
const { data: post } = await r.posts.create({
  content: '# Shipped!\n\nNew deploy is live.',
  content_format: 'markdown',
  community_id: 'comm_abc',
})

// Post/chat media: the SDK obtains the signed URL, PUTs the bytes, checks
// every response, and returns the public URL.
const mediaUrl = await r.uploads.uploadMedia({
  blob: imageFile,
  contentType: imageFile.type,
})

// Chat
const { data: dm } = await r.chat.dm({ user_id: 'user_abc' })
await r.chat.send({ conversation_id: dm.id, content: 'Hello!' })

// Communities
const { data: community } = await r.communities.create({
  name: 'TypeScript Devs',
  slug: 'ts-devs',
  privacy: 'public',
})

Real-time Chat (React / React Native)

The SDK provides a ready-to-use WebSocket hook for building chat applications without REST polling.

import { Recursiv } from '@recursiv/sdk'
import { useChat } from '@recursiv/sdk/react'

const r = new Recursiv()

function ChatRoom({ conversationId }) {
  const { isConnected, error, sendTyping, sendMessage } = useChat(r, {
    conversationId,
    onMessage: (msg) => {
      console.log('New message received:', msg.text)
    },
    onTyping: (evt) => {
      console.log(`${evt.userName} is typing...`)
    },
    onAgentThinking: (evt) => {
      console.log(`Agent ${evt.agentName} status: ${evt.status}`)
    }
  })

  // Automatically connects, joins room, and listens to events!
  
  return (
    <div>
      {isConnected ? '🟢 Connected' : '🔴 Disconnected'}
      <button onClick={() => sendMessage('Hello world!')}>Send</button>
    </div>
  )
}

Customer-app password auth

On a shared Recursiv API hostname, pass the customer app's project when signing in and minting a per-user key. The SDK forwards that project on the password sign-in request so the server resolves same-email users in the app's network.

const anonymous = new Recursiv({ anonymous: true, baseUrl })
const { apiKey, user } = await anonymous.auth.signInAndCreateKey(
  { email, password },
  {
    name: 'my-app',
    scopes: ['posts:read'],
    projectId: 'prj_xxx',
    consumeSession: true,
  },
)

consumeSession: true atomically retires the temporary login session as the API key is minted. Use it when the customer app stores and authenticates only with the returned API key; omit it for flows that still need the cookie session.

Next.js server auth (@recursiv/sdk/next)

Cookie-session helpers for the App Router (Next 14 and 15). Each user signs in once, gets a project-scoped per-user API key in an httpOnly cookie, and every request acts as that user — no platform key on the server.

This subpath uses the optional server-only peer to keep its exports out of Client Components. Install it alongside the SDK in Next.js apps: npm install server-only.

// src/lib/recursiv.ts (server-only module)
import 'server-only';
import { createRecursivAuth } from '@recursiv/sdk/next';

export const auth = createRecursivAuth(); // RECURSIV_PROJECT_ID from env

// src/actions/auth.ts ('use server')
export async function signIn(input: { email: string; password: string }) {
  const { user } = await auth.signIn(input); // mints key + sets session cookie
  return user;
}

// any server component or route handler
const sdk = await auth.getSdk(); // Recursiv acting as the signed-in user
const { data: me } = await sdk.users.me();

Route handlers building their own response pass a cookie writer, and the cookie contract (name, lifetime, attributes) is fully configurable:

const auth = createRecursivAuth({ cookie: { name: 'myapp_session', maxAge: 60 * 60 * 24 * 90 } });

export async function POST(req: NextRequest) {
  const res = NextResponse.json({ ok: true });
  await auth.verifyOtp(await req.json(), { response: res.cookies });
  return res;
}

Server-side only — the server-only guard fails the build if it's ever imported into a client component. Keep it out of middleware.ts as well (edge bundle size); gate routes with a plain cookie-presence check on a shared constant.

Error handling

import {
  Recursiv,
  AuthenticationError,  // 401 — missing or invalid API key
  AuthorizationError,   // 403 — API key lacks required scope
  NotFoundError,        // 404
  ValidationError,      // 400 — check error.details
  RateLimitError,       // 429 — includes retryAfter, upgradeUrl
  ConflictError,        // 409
} from '@recursiv/sdk'

try {
  await r.projects.deploy('proj_1', { branch: 'main' })
} catch (err) {
  if (err instanceof RateLimitError) {
    console.log(`Retry after ${err.retryAfter}s`)
  }
}

Auto-retry is built in. 429 and 5xx responses retry with exponential backoff (configurable via maxRetries, default 2).

Pagination

const page1 = await r.posts.list({ limit: 20 })
if (page1.meta.has_more) {
  const page2 = await r.posts.list({ limit: 20, offset: 20 })
}

Response shapes

{ data: T[], meta: { limit, offset, has_more } }  // list
{ data: T }                                         // single
{ data: { deleted: true } }                         // delete
{ data: { success: true } }                         // success

Works everywhere

| Environment | Status | Notes | |-------------|--------|-------| | Node.js >= 18 | Full support | ESM only (require() needs Node >= 22). Uses native fetch. | | Next.js | Full support | Server components, server actions, and route handlers. Use @recursiv/sdk/next for cookie-session auth (Next 14 and 15). | | React (Vite, CRA) | Full support | Bundle-friendly, zero dependencies. Realtime needs the optional socket.io-client peer. | | React Native / Expo | Supported with caveats | chatStream() requires manual SSE handling. See React Native guide. | | Deno | Full support | ESM native, fetch native | | Bun | Full support | ESM native, fetch native | | Browser (direct) | Full support | Uses native fetch. CORS must be configured for your domain. |

AI assistant reference

This package includes llm.md — a complete reference designed for AI coding assistants (Claude, Cursor, Copilot). It covers all resources with decision rules, working examples, and known gotchas. Your AI assistant will read it automatically when it encounters this SDK.

Types

All request and response types are exported:

import type {
  Project, Deployment, DeployInput, ExecuteCodeInput,
  Agent, StreamChunk, AgentChatInput,
  Post, Community, Message, Conversation,
  PaginatedResponse, SingleResponse,
} from '@recursiv/sdk'

Guides

Links

License

FSL-1.1-ALv2 — source-available, converts to Apache 2.0 after 2 years.

Public source trends

Check publicNetworks() for public_trends before calling getPublicTrends({ protocol, limit: 10, cursor }). The response is a discriminated union: kind: 'topics' contains Bluesky topics; kind: 'posts' contains public ActivityPub posts ranked by mastodon.social. Preserve provider order and display provider, observedAt, and coverage: 'provider_ranked_window'. A null rankingWindow means the provider has not supplied an exact ranking interval.

For a network with trend_feed, open a returned topic with getPublicTrendFeed({ protocol, topic: topic.nativeId, limit: 10, cursor }). The service rechecks current topic availability before each page; a disappeared topic returns trend_unavailable rather than an empty feed. Keep canonical post IDs and provider readRef when opening getPublicConversation. Cursors are opaque; topic catalogs have no pagination and the Mastodon post window stops at 200 source positions. Both reads require users:read and posts:read on the current human project-bound app key. Nostr does not advertise trends. Provider failures propagate as SDK errors, and neither method ingests or publishes posts.

Native Nostr signing

Use protocols.nativeNostrAccount() to check the app's explicit native-write capability. For an enabled connected key, prepare a text post with prepareNativeNostr(identityId, { action: 'publish', content }), show its identity, relay and text, and ask the client signer to sign the returned event. Submit only the public event with submitNativeNostr(identityId, { intent, event, approve: true }). Preserve that exact event on uncertain retries. Check getNativeNostrPost(identityId, eventId) for relay visibility. An owned note can prepare { action: 'delete', postId }; deletion is a signed request, not a promise of erasure across all relays. Keys are never sent to this SDK service. Preparation and submission never retry automatically, even when maxRetries is configured. After an uncertain submission, check getNativeNostrPost(identityId, preview.postId ?? preview.eventId): deletion uses the original note's postId, while publication uses the new eventId. Any user-selected retry must keep the same intent and signed event.

When the deployed API advertises reply_text_note, the same method can prepare { action: 'reply', content: draftText, parentId: selectedPost.nativeId }. Review the returned parent, root, participants, relay and exact event before asking the signer to sign. The API constructs verified NIP-10 references; callers cannot inject tags, participant lists, relay URLs or signing keys. Reply prepare/submit also require posts:read. This capability remains off until the shared reply UI and live delivery are verified. Only public kind-1 notes are supported; additional participant selection is unfinished. textTruncated labels an abbreviated source preview. Preparation and submission never automatically retry; check the event ID after an uncertain result before choosing a retry of the identical signed event.

Delegated Bluesky and ActivityPub accounts

These methods require the same signed-in human app key throughout. The API must enable the app and deploy the authorization schema before use.

const { data: connection } = await r.protocols.startNativeSocialConnection({
  protocol: 'bluesky', identifier: 'alice.bsky.social',
})
// Navigate to connection.authorizationUrl. On the configured app callback:
const { data: account } = await r.protocols.finishNativeSocialConnection({
  protocol: 'bluesky', state, code, iss,
})
const { data: accounts } = await r.protocols.nativeSocialAccounts()
await r.protocols.disconnectNativeSocialAccount(account.id)

ActivityPub uses an operator-approved HTTPS server origin as identifier and requires Mastodon-compatible OAuth metadata with S256 PKCE. The server configures the exact callback path /social/connect/callback/:protocol; the app must remove its query from browser history immediately and never log codes. No password or native token is sent through these SDK methods. Start and callback are single-use requests and are never automatically retried. If a callback result is uncertain, refresh the account list before starting again.

Connection does not approve publication. The account's capabilities lists only implemented actions, currently empty for these adapters. Local disconnect takes effect even when providerRevocation is unconfirmed; the user can also revoke Minds from their network's authorized-app settings.

One reviewed reply across selected accounts

The shared reply API is off by default. Deploy its required schema/API and finish live authorization and delivery verification before enabling it. This example prepares a preview; it does not publish anything:

const { data: preview } = await r.protocols.prepareNativeReply({
  clientMutationId: crypto.randomUUID(), text: draftText,
  parent: { protocol: 'bluesky', nativeId: selectedPost.nativeId },
  accountIds: [blueskyAccount.id, activityPubAccount.id],
  linkedPostAccountIds: [activityPubAccount.id],
  // Optional, needs each account's `publish_media` capability:
  media: [{ url: uploadedImageUrl, alt: memberWrittenAltText }],
})
// Review each delivery's account, protocol, kind, parent, media and exact wireText.
// After the user approves selected delivery IDs:
await r.protocols.submitNativeReply(preview.id, { approve: true, deliveryIds: approvedDeliveryIds })

Use linkedPostAccountIds only after the user selects a standalone post with the source link on that different network. It is not a matching threaded reply, and a denied same-network reply never falls back automatically. Verified cross-network thread mappings are not implemented here. Nostr uses the connected client signer described above. Preserve the mutation ID while recovering an uncertain preparation; read nativeReplyStatus(preview.id) to show each destination's result. Repeated submit reconciles an attempted delivery without publishing it again. Removal uses a fresh prepareNativeReplyRemoval preview and explicit removeNativeReply approval for the selected own posts.

For a selected unsent or definitively rejected destination, the same prepare method accepts recoverFrom: { batchId: preview.id, deliveryIds: selectedIds } with a new mutation ID, the same source/accounts and freshly reviewed text. Preserve that new ID if preparation is uncertain. The server creates the fresh review and disables only those old destinations atomically; replacedBy points to the new batch. recoverable describes saved eligibility, while current account, source and safety checks still apply. Sending, unknown, accepted or published destinations cannot be resent this way. The fresh preview still needs explicit approval; completed destinations retain their outcomes.

nativeReplyHistory pages through one private history containing Bluesky/ActivityPub drafts and delivery results, plus Nostr replies and linked posts once submitted. It also includes unsent or expired preparations. The summary contains a bounded preview of the user's own text and source references, not old remote parent text or approval material. Opening a result with nativeReplyStatus(id) does not send anything. Reading remains available when new replies are disabled; it does not refresh credentials or contact a network. Nostr items have type: "nostr". nativeNostrReplyResult(id) checks the saved event on its recorded relay without signing or resending. It requires the same connected identity and preserves historical publication/deletion evidence separately from lastReadBack. A relay absence is not proof of global deletion. Signatures and intents are never retained in history.

const { data: history } = await r.protocols.nativeReplyHistory({ limit: 20 })
// Read a saved result without submitting it.
const firstResult = history.items[0]
if (firstResult) {
  if ('type' in firstResult && firstResult.type === 'nostr') {
    await r.protocols.nativeNostrReplyResult(firstResult.id)
  } else await r.protocols.nativeReplyStatus(firstResult.id)
}
if (history.cursor) {
  const { data: next } = await r.protocols.nativeReplyHistory({ limit: 20, cursor: history.cursor })
  console.log(next.items.length)
}

Keep the cursor opaque and discard it when the app or signed-in user changes. History order is fixed by creation time, so later delivery updates do not move older entries across page boundaries. Reopen each result for its current saved state and obtain a fresh review before a new write.

Public protocol post links

Visitors can read a public conversation in an explicitly configured app without signing in:

const visitor = new Recursiv({ anonymous: true });
const { data: networks } = await visitor.protocols.sharedNetworks(projectId);
const { data: conversation } = await visitor.protocols.getSharedConversation(projectId, {
  protocol: 'nostr', post: publicEventId,
});

These reads apply app moderation, provider public-visibility/deletion checks and bounded per-IP/app budgets. They return no subscriptions, personal safety preferences, account connections or signing authority. Missing configuration or unavailable safety checks fail closed. Signed-in members continue using getPublicConversation to apply personal blocks/mutes as well. All reply and account endpoints still require authentication.

Standalone public publication uses protocols.prepareNativePost({ clientMutationId, text, accountIds, media? }), then explicitly approved submitNativePost. Keep the same mutation ID after uncertain preparation; previously attempted deliveries are reconciled without resending. nativePostStatus and paginated nativePostHistory expose private app/user-owned outcomes. Review current own posts with prepareNativePostRemoval, then approve exact removal IDs through removeNativePost. Check connected-account publish_text capabilities; only explicitly enabled apps may publish. Text is sent unchanged, public only, with each provider’s actual limits. Optional media is a NativeMediaInput[] of at most four already-uploaded Minds images ({ url, alt?, width?, height?, mimeType? }, JPEG/PNG/WebP/GIF, Minds-hosted HTTPS URLs only, a Mastodon GIF must be the only attachment). It additionally requires the account's publish_media capability; accounts connected before image permissions existed must reconnect. Pass the member's own alt — an omitted one is sent as an empty description, never an invented one — and read receipt.media ({ count, alt_missing }) after sending. These methods still do not accept a reply parent or caller-supplied authority.

For Bluesky accounts advertising quote, prepare the same publication with quote: { protocol: 'bluesky', nativeId: sourceAtUri }. The server resolves the exact source CID using each connected account, checks quote permission and Minds safety, and rechecks before writing. Review each delivery’s quote alongside the account and comment before submitting. Changed or unavailable sources cannot publish; uncertain deliveries reconcile the original record key. ActivityPub/Nostr quotes are not provided by this method. Public post reads include at most one filtered quote with state: 'available' and post, or state: 'unavailable' without source content. Available quotes may include embeddedReferences: exact standalone source-reference lines in the unchanged outer text. Omit them only when the quoted original is actually visible; retain them for unavailable or locally hidden quotes. Nostr references are decoded and matched by the server, without following relay hints.

Topic browsing can request getPublicTrends({ protocol: "bluesky", includePreviews: true }). Optional SocialTrend.preview is a real image-bearing post from that topic, never a generated illustration. The server samples at most eight posts for each of ten topics, with three concurrent reads and a shared 2.5-second deadline; missing images leave usable text topics. App moderation and user source controls filter previews on every request. Bluesky CDN image attachments, quote-with-media images, and unlabeled link-preview thumbnails are supported; omitted content retains the source link.

Continuous public discovery

Create one sdk.discovery.createFeed({ networks, category, source, learning }) per signed-in viewer and filter. Pass the app-advertised network catalog, then call next() as the reader approaches the end. Each page contains safe json-render module specs and native or protocol post objects. Preserve their existing source authority and interactive controls. retryFailed() explicitly retries unavailable sources; create a new session to refresh exhausted source windows.

Optional DiscoveryLearning consumes real visible impressions and explicit engagement/hide outcomes. Persist its count-only snapshot per viewer; never share a session or learning state across accounts. It starts conservatively and does not imply measured retention improvement.