ReputationGrant records one deliberate movement of reputation. Where ordinary reputation is earned (posting, commenting, receiving reactions), a grant is given — by one user to another, or by your app to a user.
Grants are the primitive behind tipping, bounties, contest payouts, staff picks, “best answer” awards, and peer recognition. Sublay stays unopinionated about why a grant happens: the reason, the amounts, the eligibility rules, and the UI framing are all your app’s logic. Sublay moves the points correctly, keeps the per-space economies honest, records what happened, and notifies the recipient.
Grants belong to the reputation bundle — every project with reputation has them, with no separate opt-in. See Reputation and Bundles.
The two kinds of grant
sourceType is persisted rather than inferred from senderId, because senderId is also null on a transfer whose sender was later deleted.
Properties
Which bucket moves
A user’s reputation lives in buckets — one per space, plus a project-general bucket. A grant always concerns exactly one bucket on each side, and the sender chooses which of their buckets pays:- The reputation lands in the recipient’s bucket for the same space it came from. Senders never choose where it lands.
- The general bucket is its own lane. Reputation cannot move between the general bucket and a space bucket in either direction.
- Consequently, the total reputation held within a given space is unchanged by a transfer — it only changes hands.
spaceId is rejected with 404 reputation-grant/space-not-found, and naming a space on a project without the spaces bundle with 403 database/tables-not-available. Without that check a grant could fund a bucket for a space that does not exist: its score would still count toward its owner’s profile total while being invisible in every space-scoped view.
A transfer is rejected unless the source bucket holds at least the amount, evaluated at the moment of the transfer. A mint has no source, so it may drive a bucket below zero (buckets have no floor — downvotes can already take one negative).
The target is an annotation
A grant may point at one entity, comment, or chat message, so your app can render it in place — “this answer earned 120 points from 4 people”.- The target must exist when the grant is issued; a grant naming a missing record is rejected.
- The target does not have to be authored by the recipient.
- The target never determines which bucket is used. An app can reward a message living in one space using reputation from a different bucket.
- A target kind is only usable when the project has that capability. A
chat-messagetarget needs thechatbundle;entityandcommenttargets need their tables. Grants with no target are always available. - Deleting the target does not reverse the grant. The points stay with the recipient; the attachment simply stops rendering.
GrantSummary
Reading entities, comments, or chat messages with thegrants include attaches a per-item summary rather than a full list:
The summary covers positive grants only. When the token is requested on a project whose schema has no grants table, the server returns a zero-filled summary (
{ total: 0, count: 0, viewerTotal: 0 }) rather than omitting the key — so grants being undefined always means “nobody asked”, never “this project has no grants”.
The full per-item list of individual grants comes from Fetch Many Reputation Grants, not from the summary.
Grants on chat messages are private
A grant row carries itstargetType, targetId and the granter’s free-text note, and a list read can hydrate both parties. On a chat-message target those facts describe a private conversation — who is talking to whom, and what one of them said about it — so such a grant is exactly as private as the message it points at:
- The target shape is gated on membership. Listing grants by
targetType: "chat-message"+targetIdreturns rows only to a member of that message’s conversation. The test is the one Fetch Message itself applies — any membership row counts, so a member who has since left the conversation still sees the grants on a message chat still hands them. Everyone else — a user who was never a member, any user on a project without thechatbundle — gets a successful empty page with the zero summary, never a403, so the response cannot be used to prove the message exists. Service and master keys pass the gate unconditionally: they are the app’s own backend, which holds no membership. - The user-keyed shapes carry the same test into the query. Listing by
recipientIdorsenderIdreturns a chat-message-targeted grant to a caller who can see the underlying message and withholds it from one who cannot. So a user’s own “reputation I received” feed does include the grants made on messages in their own conversations, and reconciles with the reputation on their profile; a caller outside the conversation loses those rows from the page and fromtotalItemstogether, leaving nothing to infer arithmetically. Service and master keys are filtered on neither shape and read every row. - A removed message takes its grants with it. Once moderation removes a message it is invisible on every chat surface, so its grants are invisible on both of these shapes too — to a member of the conversation as much as to a stranger, and to the grant’s own sender and recipient. Only service and master keys still read them.
403 chat/not-a-member before they read a message.
Negative grants are private
A mint may be negative — a moderation clawback, a decay sweep, or a correction. Negative grants are deliberately invisible:- They never notify the recipient.
- They are never broadcast over the chat socket.
- They are excluded from the public list endpoint for every caller, including service and master keys.
- They are excluded from every
GrantSummary.
Immutability
Grant rows are append-only. There is no undo, revoke, cancel window, or editing of the note or metadata. To reverse a grant, issue a compensating negative mint — both records stand. Grant creation is not idempotent. A retried request creates a second grant and moves the points a second time, so clients are responsible for not double-submitting.Lifecycle
- Sender deleted — the grant record survives with
senderIdset tonull, so a target’s displayed total does not silently shrink. - Recipient deleted — their grants are removed along with their reputation buckets, consistent with how reputation already behaves on user deletion.
- Target deleted — the grant stands; only the attachment stops rendering.
reputationbundle uninstalled — grant history is destroyed along with the buckets.

