@recursiv/sdk 0.7.72 → 0.7.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/resources/moderation.d.ts +41 -0
- package/dist/resources/moderation.js +22 -0
- package/dist/resources/protocols.d.ts +15 -1
- package/dist/resources/protocols.js +12 -3
- package/llm.md +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -117,6 +117,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
117
117
|
| `r.github` | GitHub integration |
|
|
118
118
|
| `r.integrations` | External service connections |
|
|
119
119
|
| `r.admin` | User management, stats, content reports (`listReports`, `resolveReport`, `dismissReport`) |
|
|
120
|
+
| `r.moderation` | Moderation actions: `publicLog`, `mine`, `appeal`; staff `apply`, `resolveAppeal`, `list({ target_type?, target_id? })` (one target's recorded actions, newest first, at most 100; none named = the network's latest; 0.7.74) and `reverse(actionId)` (sent once: a repeat answers 404 `action_not_found`; 0.7.74); agent grants `listAgentGrants`, `grantAgent`, `revokeAgent` |
|
|
120
121
|
| `r.dispatcher` | Task queue, claims, signals, outcomes, webhooks |
|
|
121
122
|
| `r.goals` | Org goals with task rollups (group dispatcher tasks) |
|
|
122
123
|
| `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices; filter with `control`, each item may carry a `display` label), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. Native follows: `nativeFollowStates` (every connected account's state for one source, one read), `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`; radar stories: `getPublicRadarStory` (one story by id with its posts; one that left the radar within its minimum lifetime, or within the last half hour, still answers live with `liveUntil` and the story that continues it; one that has left for longer comes with its archived copy and the live story it continues as; the id may be a World Today edition story's, and the answer then names it in `editionStory` under the story's radar id), keyless `getSharedRadarStory(projectId, { cluster })` and `getSharedRadarPosts(projectId, { cluster })` (keyless reads carry images and videos only from outlets and members marked `visitorMedia`; signed-in reads keep all media, so a share card uses only those); the Viral read (fastest-moving posts across Bluesky, the Fediverse, Nostr and Minds, each against its own network's pace; 0.7.59): `getPublicViral({ network?, category?, media?: 'video', limit?: 5 | 10 | 20 | 40, cursor?, compass? })` under the member's key and keyless `getSharedViral(projectId, params)` (visitors: `compass` `all` or `hide` only; a visitor's `media: 'video'` read can carry items whose video the server withheld, so filter with the exported `viralItemHasVideo` before a video player; `item.post` narrows on `item.protocol`). `status: 'off'` resolves with no items until the app enables the read, and `getSharedViral` also resolves an older API's 401 `missing_api_key` as off; `getPublicViral` throws every 401 and 403, so a session failure is never read as off; 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` |
|
|
@@ -468,6 +469,17 @@ opaque; topic catalogs have no pagination and the Mastodon post window stops at
|
|
|
468
469
|
current human project-bound app key. Nostr does not advertise trends. Provider
|
|
469
470
|
failures propagate as SDK errors, and neither method ingests or publishes posts.
|
|
470
471
|
|
|
472
|
+
A clip player scoped to one network asks for its video window instead (0.7.73):
|
|
473
|
+
`getPublicTrends({ protocol: 'bluesky', limit: 25, media: 'video', cursor })`, or keyless
|
|
474
|
+
`getSharedTrends(projectId, { protocol: 'bluesky', limit: 25, media: 'video', cursor })`.
|
|
475
|
+
The answer is a `kind: 'posts'` page holding only posts that carry a video (Bluesky: What's
|
|
476
|
+
Hot's videos, one provider request a page), paged by its opaque cursor until it is `null`;
|
|
477
|
+
a page may be empty while its cursor goes on. It takes no `category` and judges no layout;
|
|
478
|
+
`compass: 'hide'` (0.7.72) leaves the political clips out of it as it does from trends.
|
|
479
|
+
A signed-out read holds only clips whose video the visitor may see, and counts the rest in
|
|
480
|
+
`mediaFilter.withheld`. A network without a video window answers 404 `unsupported_protocol`;
|
|
481
|
+
an API that predates `media` answers 400 `invalid_input`, so fall back to its trends.
|
|
482
|
+
|
|
471
483
|
### Native Nostr signing
|
|
472
484
|
|
|
473
485
|
Use `protocols.nativeNostrAccount()` to check the app's explicit native-write
|
package/dist/index.d.ts
CHANGED
|
@@ -208,7 +208,7 @@ export type { ReportTargetType, ReportStatus, CreateReportInput, ReportRecord, R
|
|
|
208
208
|
export { JudgmentsResource, isJudgmentSharedWordsError } from './resources/judgments.js';
|
|
209
209
|
export type { JudgmentSubjectKind, JudgmentSource, JudgmentOverrideMethod, PostLabelsStatus, PostLabelsQuery, PostLabelsSubject, PostLabelsCategory, PostLabelsPolitics, PostJudgmentRecord, PostJudgment, PostSafetyAttribution, PostLabelsCategoryView, PostLabelsSummaryView, PostLabelsFullView, PostLabels, DisputeJudgmentInput, JudgmentDispute, ReviewJudgmentInput, JudgmentReviewUpheld, JudgmentReviewAwaitingReviewer, JudgmentReviewOverridden, JudgmentReview, JudgmentSharedWordsDetails, JudgmentSharedWordsError, } from './resources/judgments.js';
|
|
210
210
|
export { ModerationResource } from './resources/moderation.js';
|
|
211
|
-
export type { ModerationTargetType, ModerationActionType, ModerationAppealStatus, ModerationPrinciple, PublicModerationAction, PublicModerationLog, OwnModerationAppeal, OwnModerationAction, OwnModerationLog, ApplyModerationActionInput, AgentModerationMode, AgentModerationGrant, } from './resources/moderation.js';
|
|
211
|
+
export type { ModerationTargetType, ModerationActionType, ModerationAppealStatus, ModerationPrinciple, PublicModerationAction, PublicModerationLog, OwnModerationAppeal, OwnModerationAction, OwnModerationLog, ApplyModerationActionInput, ModerationAction, ListModerationActionsParams, RecordedModerationTargetType, AgentModerationMode, AgentModerationGrant, } from './resources/moderation.js';
|
|
212
212
|
export { LinkPreviewResource } from './resources/link-preview.js';
|
|
213
213
|
export { SEARCH_SECTIONS, SearchResource } from './resources/search.js';
|
|
214
214
|
export type { SearchAnswer, SearchGroup, SearchOmitReason, SearchParams, SearchPerson, SearchRelated, SearchResponse, SearchSection, SearchStory, SearchTopic } from './resources/search.js';
|
|
@@ -63,6 +63,30 @@ export interface ApplyModerationActionInput {
|
|
|
63
63
|
actor_type?: 'human' | 'ai' | 'system';
|
|
64
64
|
source_report_id?: string;
|
|
65
65
|
}
|
|
66
|
+
/** Where an action can be recorded: `source_item` is one open-network post withheld from promotion surfaces. */
|
|
67
|
+
export type RecordedModerationTargetType = ModerationTargetType | 'source_item';
|
|
68
|
+
/** One recorded action as staff read it (GET /moderation/actions): who did what to which target, and why. */
|
|
69
|
+
export interface ModerationAction {
|
|
70
|
+
id: string;
|
|
71
|
+
target_type: RecordedModerationTargetType;
|
|
72
|
+
target_id: string;
|
|
73
|
+
action: ModerationActionType;
|
|
74
|
+
label: string | null;
|
|
75
|
+
reason: string | null;
|
|
76
|
+
/** The moderator or agent who acted; null for a system or classifier action. */
|
|
77
|
+
actor_id: string | null;
|
|
78
|
+
actor_type: 'human' | 'ai' | 'system';
|
|
79
|
+
source_report_id: string | null;
|
|
80
|
+
expires_at: string | null;
|
|
81
|
+
/** Null while the action is in force. */
|
|
82
|
+
reversed_at: string | null;
|
|
83
|
+
created_at: string;
|
|
84
|
+
}
|
|
85
|
+
/** Both fields, for one target's history; neither, for the network's latest actions. */
|
|
86
|
+
export interface ListModerationActionsParams {
|
|
87
|
+
target_type?: RecordedModerationTargetType;
|
|
88
|
+
target_id?: string;
|
|
89
|
+
}
|
|
66
90
|
/** What a granted agent may do on the report queue: shadow reads it; act also resolves and dismisses. */
|
|
67
91
|
export type AgentModerationMode = 'shadow' | 'act';
|
|
68
92
|
/** A network admin's grant letting one AI agent work the report queue. */
|
|
@@ -88,6 +112,23 @@ export declare class ModerationResource {
|
|
|
88
112
|
success: true;
|
|
89
113
|
id: string;
|
|
90
114
|
}>>;
|
|
115
|
+
/**
|
|
116
|
+
* Admin (0.7.74): recorded actions, newest first (at most 100), inside the caller's
|
|
117
|
+
* network and a bound key's app. Name a target (both fields) for its history,
|
|
118
|
+
* e.g. to reverse a ban still in force; name none for the latest actions.
|
|
119
|
+
*/
|
|
120
|
+
list(params?: ListModerationActionsParams): Promise<SingleResponse<ModerationAction[]>>;
|
|
121
|
+
/**
|
|
122
|
+
* Admin (0.7.74): reverse an action; the target's state is derived again from what is
|
|
123
|
+
* still in force (an account is unbanned when its last ban goes) and the
|
|
124
|
+
* public log shows the reversal. Sent once, never retried: a repeat of a
|
|
125
|
+
* reversal that landed answers 404 `action_not_found` (`NotFoundError`), as
|
|
126
|
+
* does an action already reversed. A reviewed label override answers 409
|
|
127
|
+
* `use_judgment_review`.
|
|
128
|
+
*/
|
|
129
|
+
reverse(actionId: string): Promise<SingleResponse<{
|
|
130
|
+
success: true;
|
|
131
|
+
}>>;
|
|
91
132
|
/**
|
|
92
133
|
* List decisions affecting the caller or content they own. This remains
|
|
93
134
|
* reachable through a valid key while the account is suspended or banned.
|
|
@@ -15,6 +15,28 @@ export class ModerationResource {
|
|
|
15
15
|
apply(input) {
|
|
16
16
|
return this.client.post('/moderation/actions', input);
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Admin (0.7.74): recorded actions, newest first (at most 100), inside the caller's
|
|
20
|
+
* network and a bound key's app. Name a target (both fields) for its history,
|
|
21
|
+
* e.g. to reverse a ban still in force; name none for the latest actions.
|
|
22
|
+
*/
|
|
23
|
+
list(params = {}) {
|
|
24
|
+
return this.client.get('/moderation/actions', {
|
|
25
|
+
target_type: params.target_type,
|
|
26
|
+
target_id: params.target_id,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Admin (0.7.74): reverse an action; the target's state is derived again from what is
|
|
31
|
+
* still in force (an account is unbanned when its last ban goes) and the
|
|
32
|
+
* public log shows the reversal. Sent once, never retried: a repeat of a
|
|
33
|
+
* reversal that landed answers 404 `action_not_found` (`NotFoundError`), as
|
|
34
|
+
* does an action already reversed. A reviewed label override answers 409
|
|
35
|
+
* `use_judgment_review`.
|
|
36
|
+
*/
|
|
37
|
+
reverse(actionId) {
|
|
38
|
+
return this.client.post(`/moderation/actions/${encodeURIComponent(actionId)}/reverse`, undefined, { retry: false });
|
|
39
|
+
}
|
|
18
40
|
/**
|
|
19
41
|
* List decisions affecting the caller or content they own. This remains
|
|
20
42
|
* reachable through a valid key while the account is suspended or banned.
|
|
@@ -1289,6 +1289,11 @@ export type SocialTrends = SocialTrendProvenance & ({
|
|
|
1289
1289
|
safetyFilter?: {
|
|
1290
1290
|
withheld: number;
|
|
1291
1291
|
};
|
|
1292
|
+
/** A `media: 'video'` page read signed out: the clips left off because the visitor may not see their video yet (counts only). */
|
|
1293
|
+
mediaFilter?: {
|
|
1294
|
+
media: 'video';
|
|
1295
|
+
withheld: number;
|
|
1296
|
+
};
|
|
1292
1297
|
}) & {
|
|
1293
1298
|
/** Present when `getPublicTrends` named a filtering `compass`: what Hide politics did to this page (counts only). */
|
|
1294
1299
|
meta?: {
|
|
@@ -1773,6 +1778,11 @@ export declare class ProtocolsResource {
|
|
|
1773
1778
|
* out the topics and posts whose cached browse category is politics, for every visitor, and the page says what it did
|
|
1774
1779
|
* in `meta.compass_filter` (`basis: 'flags'` until the operator serves visitors the full filter). An API older than
|
|
1775
1780
|
* 0.7.72 refuses `compass` here with a 400.
|
|
1781
|
+
* `media: 'video'` (0.7.73; Bluesky) reads the network's own window of video posts instead of its trends: a `posts`
|
|
1782
|
+
* page paged by `cursor`, with no category and no layout, and `compass: 'hide'` applies to it as to trends. A visitor
|
|
1783
|
+
* is served only the clips whose video they may see; `mediaFilter.withheld` counts the rest. A network without a video
|
|
1784
|
+
* window answers 404 `unsupported_protocol`, and an API that predates the parameter 400 `invalid_input`: read its
|
|
1785
|
+
* trends instead.
|
|
1776
1786
|
*/
|
|
1777
1787
|
getSharedTrends(projectId: string, params: {
|
|
1778
1788
|
protocol: string;
|
|
@@ -1782,6 +1792,7 @@ export declare class ProtocolsResource {
|
|
|
1782
1792
|
category?: string;
|
|
1783
1793
|
discovery?: boolean;
|
|
1784
1794
|
compass?: string;
|
|
1795
|
+
media?: 'video';
|
|
1785
1796
|
}): Promise<SingleResponse<SocialTrends>>;
|
|
1786
1797
|
/** Open a provider-listed topic through the same public-only app boundary, with the same safety floor. */
|
|
1787
1798
|
getSharedTrendFeed(projectId: string, params: {
|
|
@@ -1850,7 +1861,9 @@ export declare class ProtocolsResource {
|
|
|
1850
1861
|
* choice, spelled as `posts.list` takes it): Hide politics (`hide`) leaves out the topics and posts whose cached
|
|
1851
1862
|
* browse category is politics, for every member (from 0.7.72; before, only where the filter was served to them), with
|
|
1852
1863
|
* `meta.compass_filter.basis` 'flags' where the full filter is not served; a side or Mix is answered unfiltered. The
|
|
1853
|
-
* page then says what it did in `meta.compass_filter`.
|
|
1864
|
+
* page then says what it did in `meta.compass_filter`. `media: 'video'` (0.7.73; Bluesky) reads the network's own window
|
|
1865
|
+
* of video posts instead of its trends, as `getSharedTrends` does, with every video kept for a member and Hide politics
|
|
1866
|
+
* applied where asked.
|
|
1854
1867
|
*/
|
|
1855
1868
|
getPublicTrends(params: {
|
|
1856
1869
|
protocol: string;
|
|
@@ -1860,6 +1873,7 @@ export declare class ProtocolsResource {
|
|
|
1860
1873
|
category?: string;
|
|
1861
1874
|
discovery?: boolean;
|
|
1862
1875
|
compass?: string;
|
|
1876
|
+
media?: 'video';
|
|
1863
1877
|
}, options?: SocialReadOptions): Promise<SingleResponse<SocialTrends>>;
|
|
1864
1878
|
/** Open a topic currently listed by the provider. Use its nativeId as topic and preserve the opaque cursor. Screened like `getPublicTrends`. */
|
|
1865
1879
|
getPublicTrendFeed(params: {
|
|
@@ -128,10 +128,16 @@ export class ProtocolsResource {
|
|
|
128
128
|
* out the topics and posts whose cached browse category is politics, for every visitor, and the page says what it did
|
|
129
129
|
* in `meta.compass_filter` (`basis: 'flags'` until the operator serves visitors the full filter). An API older than
|
|
130
130
|
* 0.7.72 refuses `compass` here with a 400.
|
|
131
|
+
* `media: 'video'` (0.7.73; Bluesky) reads the network's own window of video posts instead of its trends: a `posts`
|
|
132
|
+
* page paged by `cursor`, with no category and no layout, and `compass: 'hide'` applies to it as to trends. A visitor
|
|
133
|
+
* is served only the clips whose video they may see; `mediaFilter.withheld` counts the rest. A network without a video
|
|
134
|
+
* window answers 404 `unsupported_protocol`, and an API that predates the parameter 400 `invalid_input`: read its
|
|
135
|
+
* trends instead.
|
|
131
136
|
*/
|
|
132
137
|
getSharedTrends(projectId, params) {
|
|
133
138
|
return this.client.get(`/protocols/public/${encodeURIComponent(projectId)}/trends`, { protocol: params.protocol, limit: params.limit, cursor: params.cursor,
|
|
134
|
-
includePreviews: params.includePreviews, category: params.category, discovery: params.discovery, compass: params.compass
|
|
139
|
+
includePreviews: params.includePreviews, category: params.category, discovery: params.discovery, compass: params.compass,
|
|
140
|
+
...(params.media !== undefined ? { media: params.media } : {}) });
|
|
135
141
|
}
|
|
136
142
|
/** Open a provider-listed topic through the same public-only app boundary, with the same safety floor. */
|
|
137
143
|
getSharedTrendFeed(projectId, params) {
|
|
@@ -213,14 +219,17 @@ export class ProtocolsResource {
|
|
|
213
219
|
* choice, spelled as `posts.list` takes it): Hide politics (`hide`) leaves out the topics and posts whose cached
|
|
214
220
|
* browse category is politics, for every member (from 0.7.72; before, only where the filter was served to them), with
|
|
215
221
|
* `meta.compass_filter.basis` 'flags' where the full filter is not served; a side or Mix is answered unfiltered. The
|
|
216
|
-
* page then says what it did in `meta.compass_filter`.
|
|
222
|
+
* page then says what it did in `meta.compass_filter`. `media: 'video'` (0.7.73; Bluesky) reads the network's own window
|
|
223
|
+
* of video posts instead of its trends, as `getSharedTrends` does, with every video kept for a member and Hide politics
|
|
224
|
+
* applied where asked.
|
|
217
225
|
*/
|
|
218
226
|
getPublicTrends(params, options) {
|
|
219
227
|
return this.client.get('/protocols/social/trends', { protocol: params.protocol, limit: params.limit, cursor: params.cursor,
|
|
220
228
|
...(params.includePreviews !== undefined ? { includePreviews: params.includePreviews } : {}),
|
|
221
229
|
...(params.category !== undefined ? { category: params.category } : {}),
|
|
222
230
|
...(params.discovery !== undefined ? { discovery: params.discovery } : {}),
|
|
223
|
-
...(params.compass !== undefined ? { compass: params.compass } : {})
|
|
231
|
+
...(params.compass !== undefined ? { compass: params.compass } : {}),
|
|
232
|
+
...(params.media !== undefined ? { media: params.media } : {}) }, ...readOptions(options));
|
|
224
233
|
}
|
|
225
234
|
/** Open a topic currently listed by the provider. Use its nativeId as topic and preserve the opaque cursor. Screened like `getPublicTrends`. */
|
|
226
235
|
getPublicTrendFeed(params, options) {
|
package/llm.md
CHANGED
|
@@ -820,6 +820,7 @@ const { data: quote } = await r.appSubscriptions.previewTierChange({ tier: 'pro'
|
|
|
820
820
|
await r.appSubscriptions.changeTier({ tier: 'pro', proration_date: quote.prorationDate })
|
|
821
821
|
```
|
|
822
822
|
- `r.admin` — Admin operations (user management, stats, network settings); content reports: `listReports({ status? })`, `resolveReport(id)`, `dismissReport(id)` (project-bound keys see only their app's reports)
|
|
823
|
+
- `r.moderation` — Moderation actions. Staff (admin key), 0.7.74: `const { data: actions } = await r.moderation.list({ target_type: 'user', target_id: userId })` reads one target's recorded actions (`ModerationAction`: `id`, `target_type` incl. `source_item`, `action`, `label`, `reason`, `actor_id`, `actor_type`, `source_report_id`, `expires_at`, `reversed_at`, `created_at`), newest first, at most 100, inside the caller's network and a project-bound key's app; `list()` with no target reads the network's latest. Unban = reverse each ban still in force: `for (const a of actions.filter(a => a.action === 'ban' && !a.reversed_at)) await r.moderation.reverse(a.id)`; the target's state is derived again from what is still in force. `reverse` is sent once, never retried: 404 `action_not_found` (`NotFoundError`) means already reversed or not in the caller's scope; 409 `use_judgment_review` means a reviewed label override (change it with `r.judgments.review`). An older SDK has neither method. Also `apply`, `publicLog`, `mine`, `appeal`, `resolveAppeal`, and agent grants `listAgentGrants`, `grantAgent(agentId, 'shadow' | 'act')`, `revokeAgent`.
|
|
823
824
|
- `r.judgments` (0.7.71) — Jev's labels of one post. `get({ kind: 'post' | 'network_post', id })`: switch on `data.view` — `'full'` for the post's author and staff (`judgments` with answers, `safety` verdict, `viral.withheld`; staff also `reviewer_ids`, `review_note`), `'summary'` for other members (`category`, `politics` band in words with its `judgment_id`; never a probability or cell). `data.status === 'unavailable'` (returned, not thrown): hide label actions. `getPublic(projectId, { kind, id })` on an anonymous client: `view: 'category'` only. `dispute(judgmentId, { question_key: 'placement', proposed: 'cm' | … | 'not_political', note? })` (safety: `question_key: 'safety'`, author only): the author's dispute is an appeal; a repeat answers `existing: true`. Staff: `review(judgmentId, { outcome: 'uphold' } | { outcome: 'override', proposed, note?, apply_to_shared? })`, sent once (never retried): a compass override answers 202 `{ status: 'awaiting_reviewer', agreeing, needed }` until a second reviewer (or an agreeing Jev re-judge) proposes the same; a safety override to `'none'` that would free other posts throws 409 `shared_words` (`isJudgmentSharedWordsError(e)`, `e.details.shared_posts`) until resent with `apply_to_shared: true`. Budgets (60 reads/hour, 25 of one author's posts, 20 disputes/day) throw `RateLimitError` with `scope: 'action'` at once. Example: `const { data } = await r.judgments.get({ kind: 'post', id }); if (data.status === 'ok' && data.view === 'summary' && data.politics) await r.judgments.dispute(data.politics.judgment_id, { question_key: 'placement', proposed: 'not_political' })`
|
|
824
825
|
- `r.inviteCodes` / `r.inviteCodesAdmin` — Invite code management
|
|
825
826
|
- `r.organizationSettings` / `r.organizationSecurity` — Org configuration
|
|
@@ -832,7 +833,7 @@ await r.appSubscriptions.changeTier({ tier: 'pro', proration_date: quote.prorati
|
|
|
832
833
|
- Anonymous discovery: `const visitor = new Recursiv({ anonymous: true, inheritEnvironmentApiKey: false }); const { data } = await visitor.protocols.getSharedTrends(projectId, { protocol: 'nostr', limit: 20, discovery: true })`. Use `sharedNetworks(projectId)` capabilities; open a returned topic with `getSharedTrendFeed(projectId, { protocol: 'bluesky', topic: trend.nativeId, cursor })`. Configured apps only; no account authority or writes. App moderation and per-IP/app rate limits apply. Categories and Jev layouts reuse cached judgments without initiating paid inference; handle partial categories and finite provider windows. Category depth: `r.posts.list({ category: 'science', sort: 'hot', discovery: true })` pages the hot window by offset and then by keyset (`meta.next` is `{ offset }`, `{ cursor }` or `null`; pass a cursor back as `cursor`); when a stage ends, `meta.fallover` lists the next feeds in order (`category_older`, `category_network`, `category` with `via`, `for_you`, `all`; `feed` with `ref` is declared in 0.7.60 but not sent yet, so skip kinds you cannot open). One call for a category's modules: `const { data } = await r.discovery.category({ category: 'science' })` returns `stories`, `network`, `clips`, `people`, `groups`, each `{ items, ms, reason? }`. Watch's Minds videos: `const { data, meta } = await r.discovery.videos({ limit: 12 })` returns public video posts in hot order from a list shared per app (rebuilt about once a minute), each re-checked per call for the viewer; page on `meta.next_cursor`.
|
|
833
834
|
- Progressive Discover feed (0.7.71): `const feed = r.discovery.createProgressiveFeed({ networks, category, recommend, top: { ref: 'builtin:all' }, seen, seenClusters }); const round = await feed.next(part => draw(part))` opens one `createFeed` session per source (Minds with the radar and suggestions, one per network with `public_trends`) and hands each source's page on as a `DiscoveryPart` (`{ source, round, modules, composed?, mixed? }`) the moment it lands, instead of a page that waits for its slowest network (up to 15 s); a round waits `DISCOVERY_STRAGGLER_MS` past its first answer and rejects only when every source failed. The first load is held until Minds and one other network answered (or `FIRST_SCREEN_MS`, 2.5 s, after the first answer; `firstScreenMs: VISITOR_FIRST_SCREEN_MS` for a visitor) and arrives as one `composed: true` part from `composeFirstScreen`: the top's first post when it is a playable video, else a picture lead from the best 8 by engagement (never a story, rail, video strip, column, `isSensitivePost` post or `linksAdultSite` porn link), then every network's posts by engagement (`DiscoveryEngagementMerge`) with row 2 from another network than the lead's when one is left (`FIRST_SCREEN_NETWORK_ROWS`), a story as row 3 and a rail as row 5, nothing dropped. Merge later `mixed` parts with `mergeDiscoveryArrival(rows, arriving, reachedIds)`. `top`/`seen`/`seenClusters`/`bucket` reach every session; the top is read once per open. Also `retryFailed()`, `onStatus(listener)`, `setNetworks(catalog)`; settings `perSource`, `firstScreen`, `onLanded`. For you: `createProgressiveFeed({ createFeed: o => r.discovery.createPersonalFeed(o) }, options, { perSource: false })`. A session's `seen` now also leaves seen network posts out of trend pages, previews and topic pages; in a category a topic page with nothing new ends its topic and after `DISCOVERY_EMPTY_TOPIC_PAGES` (3) in a row the network's other topics are skipped.
|
|
834
835
|
- Viral read (0.7.59; off until the server enables it for the app): `const { data } = await r.protocols.getPublicViral({ media: 'video', category: 'science', limit: 20 })` (member key, users:read + posts:read, never cached) or keyless `visitor.protocols.getSharedViral(projectId, { media: 'video', compass: 'hide' })` (visitors send compass `all` or `hide` only). Params, all optional: `network` (all, minds, bluesky, activitypub, nostr), `category`, `media: 'video'`, `limit` 5/10/20/40, `cursor` (the last page's; a cursor whose 30-minute build expired ends the list), `compass`. Each item: `key` (stable; drop ones already shown), `protocol`, `post` (checking `protocol` narrows it: `ViralMindsCard` for minds, open at /post/:id; otherwise a `SocialPost`), `label` viral/rising/hot, `score`, `rank`, `category`, `velocity` (show counts with `countsReportedBy` as reported, never verified), `spread`. A visitor's read serves media only where the server cleared it, so even with `media: 'video'` some items arrive with the video withheld (empty `media`): `import { viralItemHasVideo } from '@recursiv/sdk'` and keep `data.items.filter(viralItemHasVideo)` before a vertical video player. `status` ok/building/screening/off: off resolves with no items, and `getSharedViral` also resolves an older API's 401 `missing_api_key` as off; `getPublicViral` throws every 401/403 (never off), so catch it and skip that step.
|
|
835
|
-
- Public trends: require `public_trends` from `publicNetworks()`, then `getPublicTrends({ protocol, limit?, cursor?, compass? })` (`compass: 'hide'` leaves out politics, 0.7.62); render `kind: topics` as source topics and `kind: posts` as source posts. With `trend_feed`, open only a returned current topic using `getPublicTrendFeed({ protocol, topic: trend.nativeId, limit?, cursor? })`. Show provider, observedAt and provider_ranked_window coverage; null rankingWindow is unknown, not a daily/global ranking. Preserve source order, canonical IDs and readRef, and opaque cursors. Topic catalogs do not paginate; Mastodon trends stop at 200 source positions. Both methods require users:read plus posts:read on the current human app key; Nostr trends are unsupported. Propagate errors; trend_unavailable means refresh the topic list. No ingestion or native writes.
|
|
836
|
+
- Public trends: require `public_trends` from `publicNetworks()`, then `getPublicTrends({ protocol, limit?, cursor?, compass?, media? })` (`compass: 'hide'` leaves out politics, 0.7.62; `media: 'video'`, 0.7.73, Bluesky: the network's own window of video posts, a `kind: posts` page paged by its cursor, no category or layout, 404 `unsupported_protocol` for a network without one, 400 on an API that predates it; keyless `getSharedTrends(projectId, { ..., media: 'video' })` holds only clips a visitor may see and counts the rest in `mediaFilter.withheld`); render `kind: topics` as source topics and `kind: posts` as source posts. With `trend_feed`, open only a returned current topic using `getPublicTrendFeed({ protocol, topic: trend.nativeId, limit?, cursor? })`. Show provider, observedAt and provider_ranked_window coverage; null rankingWindow is unknown, not a daily/global ranking. Preserve source order, canonical IDs and readRef, and opaque cursors. Topic catalogs do not paginate; Mastodon trends stop at 200 source positions. Both methods require users:read plus posts:read on the current human app key; Nostr trends are unsupported. Propagate errors; trend_unavailable means refresh the topic list. No ingestion or native writes.
|
|
836
837
|
- One outlet count per story (0.7.71): a radar story (list, story read, archive), a World Today story (`outlets`, with `outlet_count` its total and `lane_mix` its lanes), a category strip story, a `feeds.top`/`feeds.stories` story and a search story carry `outlets: { total, mainstream, independent, other }`, the distinct outlets of the articles the story lists (an allow-listed outlet once, an unlisted host in `other`; the buckets sum to `total`). Show `outlets.total`; `sources.outlet` counts articles. `DiscoveryCluster.outletCount` reads it. An older server omits it.
|
|
837
838
|
- Political stories (0.7.67): every radar story (`getPublicRadar`, `getSharedRadar`, a story read) and every World Today story (`r.curator.today()`) carries `political`: true when a quarter of its judged posts are politics, its theme or topic is politics, or its headline or summary names an election, a war or troop deployment, a court or justice, a legislature, or a party or politician. `compass: 'hide'` leaves those out whatever their theme; `r.curator.today({ compass: 'hide' })` does the same to the front page, the next story leads, and `meta.compass_filter` counts what was left out. A story from `r.feeds.stories()` or a `r.feeds.top()` lead carries `political` too, and both reads, and `r.curator.forYouWorld()`, leave political stories out under `compass: 'hide'` whatever their theme. An older server omits `political`: decide by `theme` as before.
|
|
838
839
|
- Hide politics as a floor (0.7.72): the radar, `r.curator.today()`, `getPublicTrends`, keyless `visitor.protocols.getSharedTrends(projectId, { protocol, compass: 'hide' })` (visitors send `all` or `hide` only; an older API answers 400 to `compass` there), the viral read, `r.discovery.videos` and a visitor's Discover posts apply `compass: 'hide'` for every caller. Where the operator does not serve the caller the full filter, the answer's `meta.compass_filter` is `{ status: 'applied', basis: 'flags', ... }`: left out by the server's own `political` flags and browse categories, no judgment read. Treat `applied` with or without `basis` as filtered. Choose sides and Mix sides still answer `unavailable` until the operator serves them, and the catalog's `compass_filter` capability still says when to offer Politics in Filters.
|