Groups let multiple participants share a wallet. A group is its own ledger entity with independent balances, separate from any individual participant. Useful for families, teams, or organizational pools.
Groups exist at the organization level and are not scoped to a single program. Balance operations are program-scoped (you specify program_id when adjusting), but the group itself can participate across programs.
Creating a Group
curl -X POST https://api.scrip.dev/v1/groups \
-H "Authorization: Bearer $SCRIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Smith Family",
"members": [
{"participant_id": "{alice_id}", "role": "ADMIN"},
{"participant_id": "{bob_id}", "role": "MEMBER"}
]
}'
| Field | Required | Description |
|---|
name | Yes | Display name (1-255 characters) |
members | Yes | Initial members. At least one must have the ADMIN role. |
Membership
Adding members
POST /v1/groups/{id}/members
{
"members": [
{"participant_id": "{participant_id_1}", "role": "MEMBER"},
{"participant_id": "{participant_id_2}", "role": "MEMBER"}
]
}
Removing members
DELETE /v1/groups/{id}/members/{participant_id}
Members are soft-deleted with a LEFT status. Use include_former=true when listing to see former members. Removing a member does not affect the group’s balance.
Roles
| Role | Description |
|---|
ADMIN | Can manage group membership |
MEMBER | Standard member (default if omitted) |
The last ADMIN in a group cannot be demoted or removed.
Group Balances
Adjust a group’s balance directly via the API:
POST /v1/groups/{id}/balances/adjust
{
"program_id": "{program_id}",
"asset_id": "{asset_id}",
"type": "CREDIT",
"amount": "500",
"description": "Monthly team allocation"
}
Group balances are returned as a flat map of {asset_id: amount} representing aggregate totals per asset. Unlike participant balances, which are bucketed into available and held, group balances do not distinguish between buckets.
Crediting Groups from Rules
Rules can credit a group’s wallet instead of the participant’s by adding a target to the action. This rule pools purchase points into a family group:
{
"name": "Family Pool Points",
"condition": "event.type == 'purchase'",
"actions": [
{
"type": "CREDIT",
"asset_id": "{asset_id}",
"amount": "event.amount * 2",
"target": {"type": "GROUP", "id": "{group_id}"}
}
]
}
The participant who triggers the event must be a member of the target group, or the action fails.
The same targeting works for state actions. For example, tracking how many purchases the group has made:
{
"name": "Track Team Purchases",
"condition": "event.type == 'purchase'",
"actions": [
{"type": "COUNTER", "key": "team_purchases", "value": "1", "target": {"type": "GROUP", "id": "{group_id}"}},
{"type": "CREDIT", "asset_id": "{asset_id}", "amount": "10", "target": {"type": "GROUP", "id": "{group_id}"}}
]
}
Groups in CEL Conditions
When a rule evaluates, the groups variable contains a list of groups the participant belongs to. Each entry has:
| Field | Type | Description |
|---|
id | string | Group UUID |
name | string | Group display name |
tags | list | Group tags |
counters | map | Group counters |
attributes | map | Group attributes |
tiers | map | Group tier memberships |
// Only fire if participant is in at least one group
groups.size() > 0
// Check a group-level counter
groups.size() > 0 && get(groups[0].counters, "team_purchases", 0.0) < 100.0
// Check a group-level tag
groups.size() > 0 && "premium_team" in groups[0].tags
groups is a list because a participant can belong to multiple groups. If your program uses a single group per participant, groups[0] is a convenient shorthand. For programs where participants may be in multiple groups, use groups.size() checks and index carefully.
Group State
Groups support the same state types as participants: tags, counters, and attributes.
# Tags
PUT /v1/groups/{id}/state/tags/{tag}
DELETE /v1/groups/{id}/state/tags/{tag}
# Attributes
PUT /v1/groups/{id}/state/attributes/{key}
PATCH /v1/groups/{id}/state/attributes
# Counters
PUT /v1/groups/{id}/state/counters/{key}
DELETE /v1/groups/{id}/state/counters/{key}
Group state is available in CEL via the groups variable and can be updated from rule actions using "target": {"type": "GROUP", "id": "{group_id}"}.
Archiving Groups
Archived groups are excluded from listings unless include_archived=true is set. Archived groups cannot be modified.