reputation module moves reputation deliberately: transfers between two users, mints that create reputation from nothing (or destroy it), and reads of the grant history. It maps directly to the Reputation Grants API.
Because the Node SDK authenticates with a service key (it acts as the whole project, not a single end user), createGrant takes an explicit actingUserId naming the sender. This is the mediated transfer path: it behaves exactly like a user-initiated grant — same bucket routing, same balance check — but it lets your backend enforce its own rules first (caps, cooldowns, daily limits, eligibility), which Sublay deliberately does not impose.
Two things a service key does not get you here:
- Mediated transfers still respect the sender’s balance. Only
mintGrantcan create reputation or drive a bucket below zero. - Negative grants are not readable through
listGrants. Theamount > 0filter applies to every caller, service keys included. Read them from the dashboard grant-history route.
reputation bundle. A chat-message target additionally needs the chat bundle, and a spaceId needs the spaces bundle. createGrant and mintGrant validate spaceId identically: an ID naming no space is rejected with 404 reputation-grant/space-not-found before anything moves, and naming a space on a project without spaces is rejected with 403 database/tables-not-available.
createGrant
Transfers reputation from one user to another — the amount leaves the sender’s bucket and lands in the recipient’s bucket for the same space. Nothing is created. Mediated transfers skip the project’s user-grants switch and ignore block relationships between the two parties.string
required
The sender — the user reputation is debited from. Spelled
actingUserId rather than userId because the body already names a target user (recipientId).string
required
The user credited. Must differ from
actingUserId.number
required
Whole number from
1 to 2147483647 (the signed 32-bit maximum). Never zero, negative, or fractional — use mintGrant to create or destroy reputation. It can never exceed what the sender holds in the source bucket.string | null
The bucket both legs move in. Omitted or
null = the project-general bucket. Reputation never crosses between the general bucket and a space bucket. Validated: an unknown space is rejected with 404 reputation-grant/space-not-found.string | null
Free-text note. Trimmed, up to 2000 characters.
object
Arbitrary key-value data. Up to 1 MB. Omit when unused.
"entity" | "comment" | "chat-message"
What the grant is for. Supplied together with
targetId. An annotation only — it never determines which bucket is used, and need not be authored by the recipient.string
The rewarded record’s ID. Supplied together with
targetType, and must exist.Promise<ReputationGrant> with sourceType: "user".
Throws 409 reputation-grant/insufficient-reputation when the sender’s bucket is short — nothing moves; that is also the answer when the named space is real but the sender holds no funded bucket in it. A mediated transfer is still checked against the sender’s suspension and, for a chat-message target, against the sender’s active membership of that conversation. A grant is not idempotent: a retried call moves the points a second time.
mintGrant
Creates reputation from nothing and credits a user. There is no sender and nothing is debited; the grant is written with anull senderId and sourceType: "app".
A negative amount destroys reputation instead — the moderation clawback, decay sweep, or correction path. It is the only operation permitted to drive a bucket below zero, and it is entirely silent: no notification, no socket broadcast, and invisible on every public read surface.
string
required
The user credited — or, with a negative amount, debited.
number
required
Any non-zero whole number in the signed 32-bit range. Negatives destroy reputation.
string | null
The bucket credited/debited. Omitted or
null = the project-general bucket. Validated: an unknown space is rejected with 404 reputation-grant/space-not-found.string | null
Free-text note. Trimmed, up to 2000 characters.
object
Arbitrary key-value data. Up to 1 MB. Omit when unused.
"entity" | "comment" | "chat-message"
What the grant is for. Supplied together with
targetId.string
The rewarded record’s ID. Supplied together with
targetType, and must exist.Promise<ReputationGrant> with sourceType: "app".
There is no counterpart in
@sublay/js — minting is service/master-key only, and a user token can never reach the route.listGrants
Lists grants. Exactly one filter shape per request — by recipient, by sender, or by target. The shapes are mutually exclusive and are not AND-ed; supplying none or combining two is a400.
string
What this user received.
string
What this user sent.
"entity" | "comment" | "chat-message"
Who rewarded this item. Supplied together with
targetId.string
The rewarded record’s ID. Supplied together with
targetType.number
Page number (1-indexed). Defaults to
1.number
Page size. Defaults to
20; the maximum is 100. A larger value is rejected with 400 reputation-grant/invalid-query rather than clamped.string
Comma-separated associations. Only
"user" is supported — it hydrates both the sender and the recipient.object
Opt-in per-user space reputation on the hydrated users.
{ spaceId: "context" } scores each user against that grant’s own space. See Reputation.Promise<{ data: ReputationGrant[]; pagination; summary? }>. The summary block ({ total, count, viewerTotal }) is returned only on the target filter shape.
Grants on chat messages are private to their conversation — and a service key is exempt from that. For a user token, every shape asks whether that user can see the underlying message and withholds the grant if they cannot. A service key answers to neither gate: the target shape returns its rows unconditionally, and the
recipientId / senderId shapes apply no membership predicate to it at all, so a backend can enumerate every grant it minted, including grants on messages moderation has removed. It is the app’s own backend, already trusted to mint grants and to read every conversation. It holds no viewer identity, so summary.viewerTotal is always 0.Reading totals inline
For per-item totals, don’t page through this module — ask the read that already returns the item. Entities, comments and chat messages all accept a"grants" token in their include string (combine it with others, e.g. "files,grants"). Each item then carries grants: { total, count, viewerTotal }. See GrantSummary.

