> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sublay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Reputation

> Give and list reputation grants from the browser

The `reputation` module gives reputation deliberately and reads grants back. It maps directly to the [Reputation Grants API](/api-reference/reputation-grants/create-reputation-grant).

Because the JS SDK authenticates as an **end user** (bearer token), you never pass an actor `userId` — the sender is always the user the token belongs to. Naming somebody else as the sender requires a service key, which this SDK never holds.

There is deliberately **no mint function**. Creating reputation from nothing — and destroying it — is service/master-key only and lives in [`@sublay/node`](/v7/node-sdk/reputation).

Requires the `reputation` bundle. A `chat-message` target additionally needs the `chat` bundle, and a `spaceId` needs the `spaces` bundle.

***

### createGrant

Transfers reputation from the logged-in user to another user. The amount leaves the sender's bucket and lands in the recipient's bucket **for the same space** — nothing is created, and reputation never crosses between the project-general bucket and a space bucket.

```typescript theme={null}
const grant = await sublay.reputation.createGrant({
  recipientId: "usr_def456",
  amount: 50,
  spaceId: "spc_789",
  note: "Best answer",
  targetType: "comment",
  targetId: "cmt_012",
});
```

<ParamField body="recipientId" type="string" required>The user receiving the reputation. Cannot be the logged-in user.</ParamField>

<ParamField body="amount" type="number" required>
  Whole number from `1` to `2147483647` (the signed 32-bit maximum). Never zero, negative, or fractional, and never more than the sender holds in the source bucket.
</ParamField>

<ParamField body="spaceId" type="string | null">
  The bucket both legs move in. Omitted or `null` = the project-general bucket. Validated before anything moves: an ID naming no space is rejected with `404 reputation-grant/space-not-found`, and naming a space at all on a project without the `spaces` bundle is rejected with `403 database/tables-not-available`.
</ParamField>

<ParamField body="note" type="string | null">Free-text note. Trimmed, up to 2000 characters.</ParamField>
<ParamField body="metadata" type="object">Arbitrary key-value data. Up to 1 MB. Omit when unused.</ParamField>

<ParamField body="targetType" type="&#x22;entity&#x22; | &#x22;comment&#x22; | &#x22;chat-message&#x22;">
  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.
</ParamField>

<ParamField body="targetId" type="string">The rewarded record's ID. Supplied together with `targetType`, and must exist.</ParamField>

**Returns** — `Promise<ReputationGrant>` with `sourceType: "user"`.

Common rejections: `409 reputation-grant/insufficient-reputation` (the chosen bucket is short — nothing moved), `400 reputation-grant/self-grant`, `403 reputation-grant/user-grants-disabled` (the project has `settings.reputationGrants.allowUserInitiated` set to `false` and routes grants through its own backend instead), `404 reputation-grant/space-not-found`, `404 reputation-grant/target-not-found` (which is also the answer when a `chat-message` target sits in a conversation the sender isn't an active member of), and `404 reputation-grant/user-not-found` (which is also the non-disclosing answer when one party has blocked the other).

<Warning>
  **Grant creation is not idempotent.** A retried call creates a second grant and moves the points a second time. Guard your submit path.
</Warning>

***

### 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 a `400`.

```typescript theme={null}
const { data, pagination, summary } = await sublay.reputation.listGrants({
  targetType: "entity",
  targetId: "ent_abc123",
  include: "user",
  limit: 25,
});

console.log(summary?.total, "points from", summary?.count, "people");
```

<ParamField body="recipientId" type="string">What this user received.</ParamField>
<ParamField body="senderId" type="string">What this user sent.</ParamField>
<ParamField body="targetType" type="&#x22;entity&#x22; | &#x22;comment&#x22; | &#x22;chat-message&#x22;">Who rewarded this item. Supplied together with `targetId`.</ParamField>
<ParamField body="targetId" type="string">The rewarded record's ID. Supplied together with `targetType`.</ParamField>
<ParamField body="page" type="number">Page number (1-indexed). Defaults to `1`.</ParamField>
<ParamField body="limit" type="number">Page size. Defaults to `20`; the maximum is `100`. A larger value is rejected with `400 reputation-grant/invalid-query` rather than clamped.</ParamField>

<ParamField body="include" type="string">
  Comma-separated associations. Only `"user"` is supported — it hydrates both the sender and the recipient.
</ParamField>

<ParamField body="spaceReputation" type="object">
  Opt-in per-user space reputation on the hydrated users. `{ spaceId: "context" }` scores each user against that grant's own space. See [Reputation](/data-models/reputation#reading-space-scoped-reputation).
</ParamField>

**Returns** — `Promise<{ data: ReputationGrant[]; pagination; summary? }>`. The `summary` block (`{ total, count, viewerTotal }`) is returned **only** on the target filter shape — it's exactly what a standalone "who rewarded this" view needs, so you don't have to fetch the parent item to print the totals.

<Note>
  **Grants on chat messages are private to their conversation.** With `targetType: "chat-message"` the list is populated only when the token's user is a member of that conversation — having left it still counts; everyone else gets an empty list and a `{ total: 0, count: 0, viewerTotal: 0 }` summary rather than an error. The `recipientId` / `senderId` shapes run the same test per row, so the token holder's own "reputation I received" feed *does* carry the grants made on messages in their own conversations, while a grant on a conversation they are not in drops out of the page and out of `pagination.totalItems` together. On either shape, grants on a message moderation has removed are hidden.
</Note>

<Note>
  **Only positive grants are returned, and lists are never block-filtered.** Negative grants (an app's moderation deductions) are invisible on every public surface. And unlike entity or comment feeds, grant lists apply no block exclusion — so the amounts you render always add up to the total you render beside them.
</Note>

***

## 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](/data-models/reputation-grant#grantsummary).

## See Also

* [ReputationGrant data model](/data-models/reputation-grant)
* [Create Reputation Grant API](/api-reference/reputation-grants/create-reputation-grant)
* [Fetch Many Reputation Grants API](/api-reference/reputation-grants/fetch-many-reputation-grants)
* [Node SDK reputation module](/v7/node-sdk/reputation)
