Skip to content

DropInServer

new DropInServer(opts, fetchFn?): DropInServer;
Parameter Type
opts DropInServerOptions
fetchFn? (input, init?) => Promise<Response>

DropInServer

readonly activities: object;

Activity-level edits. Use this to fix ONE activity’s own body — a typo, a corrected caption. For data shared across many activities, use objects instead: patching each activity is N writes where an object update is one.

body.refs (optional, top-level — not a custom. path) replaces the activity’s refs array wholesale, including refs: [] to clear it. This is the backfill path for an activity written before objects existed: it can adopt refs after the fact without the delete-and-repost that would otherwise burn its foreign_id and re-fan-out to every follower.

patch: <TCustom>(activityId, body, opts) => Promise<Activity<TCustom>>;
Type Parameter Default type
TCustom Record<string, unknown>
Parameter Type
activityId string
body PatchBody
opts RequestOptions

Promise<Activity<TCustom>>

await dropin.activities.patch('a1', { refs: ['session:1234'] })

readonly batch: object;

Cold-start import (server-token only, ≤100 items/call, quiet — no notifications, live pings, or webhooks fire for imported history; see the “Migrating existing data” guide). Per-item results: partial failure is still a 200, so check each results[i].ok. Rerunning a batch is safe when activities carry foreign_id + time.

activities: (activities, opts) => Promise<BatchResponse>;

QUIET: imported activities land in feeds but fire no notifications, live pings, or activity.added webhooks.

Parameter Type
activities object[]
opts RequestOptions

Promise<BatchResponse>

follows: (follows, opts) => Promise<BatchResponse>;

QUIET: the edges are created but the followed feeds get NO notification and no follow.added webhook. For a follow that just happened in your app, use DropInServer.userFollow / feed().follow() instead.

Parameter Type
follows object[]
opts RequestOptions

Promise<BatchResponse>

objects: (objects, opts) => Promise<BatchResponse>;

Bulk upsert objects. Idempotent — safe to re-run. Check results[i].ok.

Parameter Type
objects object[]
opts RequestOptions

Promise<BatchResponse>

userFollows: (pairs, opts) => Promise<BatchResponse>;

The 95% case, without feed-ref plumbing: “alice follows bob” expands to { source: 'timeline:alice', target: 'user:bob' } — alice’s home feed pulls from bob’s wall. Use follows directly for non-user feed graphs.

QUIET: bob is NOT notified that alice followed him, and no follow.added webhook fires. That is right for backfilling an existing social graph and wrong for mirroring a follow a user just made — use DropInServer.userFollow for that.

Parameter Type
pairs object[]
opts RequestOptions

Promise<BatchResponse>

users: (users, opts) => Promise<BatchResponse>;

QUIET: imported users are created without any side effect.

Parameter Type
users object[]
opts RequestOptions

Promise<BatchResponse>


readonly notifications: object;

Read and mark a user’s notifications from your backend — for sending push notifications or emails, or building an admin view. A server token has no identity, so every call names the owner it acts for; omitting it is FORBIDDEN, never a cross-user leak.

list: (q, opts) => Promise<NotificationPage>;
Parameter Type
q { cursor?: string; limit?: number; next?: string; owner: string; }
q.cursor? string
q.limit? number
q.next? string
q.owner string
opts RequestOptions

Promise<NotificationPage>

markRead: (q, opts) => Promise<void>;

Mark specific notifications read, or ALL of the owner’s when ids is omitted or empty.

Parameter Type
q { ids?: string[]; owner: string; }
q.ids? string[]
q.owner string
opts RequestOptions

Promise<void>

markSeen: (q, opts) => Promise<void>;

Mark specific notifications seen, or ALL of the owner’s when ids is omitted or empty.

Parameter Type
q { ids?: string[]; owner: string; }
q.ids? string[]
q.owner string
opts RequestOptions

Promise<void>


readonly objects: object;

Objects: data many activities share. Update one row and every timeline carrying a ref to it is fresh on the next read — no re-fan-out, regardless of how many activities point at it. Server-token only, because one object is shared by many activities.

get: <TCustom>(type, id, opts) => Promise<DropInObject<TCustom>>;
Type Parameter Default type
TCustom Record<string, unknown>
Parameter Type
type string
id string
opts RequestOptions

Promise<DropInObject<TCustom>>

getMany: <TCustom>(refs, opts) => Promise<Record<string, DropInObject<TCustom>>>;

Read up to 100 objects in ONE request, keyed type:id. Mirrors @dropinnodex/client’s objects.getMany — the same route, so a server-side revalidation (a cache warm, a webhook handler checking what moved) costs one request rather than one per object.

Refs with no stored object are absent from the map rather than an error, matching the feed-read objects sidecar. An empty list costs zero requests.

Type Parameter Default type
TCustom Record<string, unknown>
Parameter Type
refs string[]
opts RequestOptions

Promise<Record<string, DropInObject<TCustom>>>

const fresh = await dropin.objects.getMany(['session:1234', 'session:5678'])
fresh['session:1234']?.custom.spots_left
patch: <TCustom>(type, id, body, opts) => Promise<DropInObject<TCustom>>;

Merges into custom. The object must exist — use upsert to create.

Type Parameter Default type
TCustom Record<string, unknown>
Parameter Type
type string
id string
body PatchBody
opts RequestOptions

Promise<DropInObject<TCustom>>

remove: (type, id, opts) => Promise<void>;
Parameter Type
type string
id string
opts RequestOptions

Promise<void>

upsert: <TCustom>(type, id, custom, opts) => Promise<DropInObject<TCustom>>;

Replaces custom wholesale, creating the object if absent.

Type Parameter Default type
TCustom Record<string, unknown>
Parameter Type
type string
id string
custom TCustom
opts RequestOptions

Promise<DropInObject<TCustom>>

await dropin.objects.upsert('session', '1234', { spots_left: 2 })
// every activity with refs: ['session:1234'] now renders 2

readonly promoted: object;

Promoted activities: content that stays visible regardless of the follow graph and recency — an under-filled event, an announcement, a sponsor post, a member spotlight. Server-token only; creating one is a backend operation, usually automated.

Nothing is fanned out. One row serves every eligible reader, which is why retracting or expiring is instant and costs nothing.

Targeting is the follow graph. We store no user attributes, so an audience is expressed as feeds. Create a feed like city:belgrade, follow your users into it from your backend, then target it:

await dropin.batch.follows([{ source: 'timeline:alice', target: 'city:belgrade' }])
await dropin.promoted.create({
actor: 'system:fcurban',
verb: 'promote',
object: 'game:8842',
custom: { text: 'Thursday 20:00 — 6 spots left' },
audience: ['city:belgrade'],
expires_at: kickoffIso, // stops serving itself; no cleanup
})

Eligible rows arrive in the promoted array of a feed read’s FIRST page (never inside results, never affecting the cursor). That array is the eligible SET, not a slot assignment — the client caches it and decides placement.

This is promoted content, not an ad platform: no bidding, no demographic targeting, no viewability tracking. The label your users see (“Sponsored”, “Featured”) — and any disclosure obligation that comes with paid placement — is yours to render.

create: <TCustom>(input, opts) => Promise<PromotedActivityRecord<TCustom>>;

Promote something. Throws CONFLICT past 100 live rows (an abuse guard, not a plan limit).

Type Parameter Default type
TCustom Record<string, unknown>
Parameter Type
input PromotedActivityInput<TCustom>
opts RequestOptions

Promise<PromotedActivityRecord<TCustom>>

list: <TCustom>(q, opts) => Promise<Page<PromotedActivityRecord<TCustom>>>;

Your inventory, newest first — retracted rows included, each with its served_count.

Type Parameter Default type
TCustom Record<string, unknown>
Parameter Type
q { limit?: number; next?: string; }
q.limit? number
q.next? string
opts RequestOptions

Promise<Page<PromotedActivityRecord<TCustom>>>

remove: (id, opts) => Promise<void>;

Stop serving it. Takes effect on the next feed read; retracting twice is NOT_FOUND.

Parameter Type
id string
opts RequestOptions

Promise<void>


readonly reactions: object;

Admin reaction ops (server token deletes any user’s reaction). Adding a reaction is deliberately absent: a reaction needs an acting user, and a server token has no identity — mint a user token for that.

delete: (reactionId, opts) => Promise<void>;
Parameter Type
reactionId string
opts RequestOptions

Promise<void>

list: (activityId, q, opts) => Promise<Page<Reaction>>;

A page of reactions on an activity, newest first. kind filters server-side.

Parameter Type
activityId string
q { cursor?: string; kind?: string; limit?: number; next?: string; }
q.cursor? string
q.kind? string
q.limit? number
q.next? string
opts RequestOptions

Promise<Page<Reaction>>


readonly webhooks: object;

Outbound webhooks. Server-token only — this is where you register the endpoints feed events are delivered to. Storage, HMAC-SHA256 signing, and retries are handled for you; create returns the signing secret to verify deliveries with.

create: (d, opts) => Promise<WebhookDestination>;
Parameter Type
d { url: string; }
d.url string
opts RequestOptions

Promise<WebhookDestination>

list: (opts) => Promise<WebhookDestination[]>;
Parameter Type
opts RequestOptions

Promise<WebhookDestination[]>

remove: (id, opts) => Promise<void>;
Parameter Type
id string
opts RequestOptions

Promise<void>

createServerToken(opts?): Promise<string>;
Parameter Type
opts TokenOptions

Promise<string>


createUserToken(userId, opts?): Promise<string>;

Local HMAC. Zero network calls — tokens are minted entirely on your server.

Parameter Type
userId string
opts TokenOptions

Promise<string>


feed(group, id): object;
Parameter Type
group string
id string
addActivity: <TCustom>(a, opts) => Promise<Activity<TCustom>>;
Type Parameter Default type
TCustom Record<string, unknown>
Parameter Type Description
a { actor?: string; custom?: TCustom; foreign_id?: string | null; object: string; refs?: string[]; target?: string | null; time?: string; verb: string; } -
a.actor? string Server tokens set the actor explicitly (e.g. “user:alice”); user tokens have it overwritten by the gateway (spec §5).
a.custom? TCustom -
a.foreign_id? string | null -
a.object string -
a.refs? string[] Objects this activity points at, as type:id. Max 4. Resolved into the feed read’s objects sidecar — see dropin.objects.
a.target? string | null -
a.time? string -
a.verb string -
opts RequestOptions -

Promise<Activity<TCustom>>

follow: (targetGroup, targetId, opts) => Promise<void>;

Create a follow edge, LOUD: a user: target gets a follow notification and a follow.added webhook fires. This is the call for a follow that just happened in your app — batch.follows is the quiet import path and notifies nobody.

Idempotent: re-following an existing edge writes nothing and notifies nobody, so an at-least-once trigger can safely deliver twice.

Parameter Type
targetGroup string
targetId string
opts RequestOptions

Promise<void>

followers: (q, opts) => Promise<Page<Follow>>;

Who follows this feed, newest edge first.

Parameter Type
q { cursor?: string; limit?: number; next?: string; }
q.cursor? string
q.limit? number
q.next? string
opts RequestOptions

Promise<Page<Follow>>

following: (q, opts) => Promise<Page<Follow>>;

Who this feed follows, newest edge first.

Parameter Type
q { cursor?: string; limit?: number; next?: string; }
q.cursor? string
q.limit? number
q.next? string
opts RequestOptions

Promise<Page<Follow>>

followStats: (opts) => Promise<FollowStats>;
Parameter Type
opts RequestOptions

Promise<FollowStats>

get: <TCustom>(q, opts) => Promise<FeedPage<TCustom>>;
Type Parameter Default type
TCustom Record<string, unknown>
Parameter Type
q { cursor?: string; limit?: number; next?: string; }
q.cursor? string
q.limit? number
q.next? string
opts RequestOptions

Promise<FeedPage<TCustom>>

head: (opts) => Promise<{
changed: number | null;
latest: string | null;
}>;

Cheap change signal for a backend poller: redis-only, no tenant-DB read. The same shape as @dropinnodex/client’s feed().head() — two independent fields:

  • latest — opaque token for NEW activities. Compare with the last value you acted on; null means nothing new.
  • changed — tenant mutation counter, covering the changes latest cannot report: an activity edited, a reaction moved, an object written. Unchanged since your last revalidation means you can skip re-reading the page and its objects entirely. 0 means nothing has ever been mutated here; null means unknown (a corrupted counter) — revalidate rather than assume.

Use this from your server when a long-running watcher needs to know whether a feed moved without paying for a full page read on every poll — a bot pulling posts for a downstream system, a webhook-style revalidation sweep.

Parameter Type
opts RequestOptions

Promise<{ changed: number | null; latest: string | null; }>

removeActivity: (activityId, opts) => Promise<void>;

Soft-delete an activity. A server token may remove an activity from ANY feed — the origin-feed authority check applies to user tokens only — which is what makes this usable for moderation and for cleaning up content deleted in your own app.

Parameter Type
activityId string
opts RequestOptions

Promise<void>

suggestions: (q, opts) => Promise<{
results: Suggestion[];
}>;

Who this feed should follow — friends-of-friends by mutual overlap, topped up by popularity. A capped top-N, so there is no cursor.

Parameter Type
q { limit?: number; }
q.limit? number
opts RequestOptions

Promise<{ results: Suggestion[]; }>

unfollow: (targetGroup, targetId, opts) => Promise<void>;

Remove a follow edge. Same argument shape as follow.

Parameter Type
targetGroup string
targetId string
opts RequestOptions

Promise<void>


revokeUserTokens(userId, opts?): Promise<void>;

Invalidates all of a user’s existing tokens immediately. The revocation is recorded server-side and enforced from the next request onward, so tokens already handed to a browser stop working without waiting for them to expire.

Parameter Type
userId string
opts RequestOptions

Promise<void>


upsertUser(user, opts?): Promise<{
custom: Record<string, unknown>;
id: string;
}>;
Parameter Type
user { custom?: Record<string, unknown>; id: string; }
user.custom? Record<string, unknown>
user.id string
opts RequestOptions

Promise<{ custom: Record<string, unknown>; id: string; }>


userFollow(pair, opts?): Promise<void>;

“alice follows bob”, LOUD — the notifying counterpart of batch.userFollows, for a follow that just happened rather than one being imported. Expands to timeline:alice → user:bob; bob is notified and follow.added fires.

Parameter Type
pair { follower: string; following: string; }
pair.follower string
pair.following string
opts RequestOptions

Promise<void>


userUnfollow(pair, opts?): Promise<void>;

The inverse of DropInServer.userFollow.

Parameter Type
pair { follower: string; following: string; }
pair.follower string
pair.following string
opts RequestOptions

Promise<void>