curl -X PUT https://api.bodhveda.com/preferences \
-H "Authorization: Bearer bv_xxxxxxxxx" \
-H "Content-Type: application/json" \
-d '[
{ "channel": "digest", "topic": "none", "event": "sent", "medium": "email", "name": "Daily digest", "default_enabled": true }
]'import { Bodhveda } from "@bodhveda/js";
const bodhveda = new Bodhveda("bv_xxxxxxxxx");
const catalog = await bodhveda.preferences.upsertMany([
{
channel: "digest",
topic: "none",
event: "sent",
medium: "email",
name: "Daily digest",
default_enabled: true,
},
]);
// Pass { prune: true } to delete entries absent from the array.import (
"context"
bodhveda "github.com/MudgalLabs/bodhveda/sdk/go"
)
ctx := context.Background()
client := bodhveda.NewClient("bv_xxxxxxxxx", nil)
catalog, _ := client.Preferences.UpsertMany(ctx, []bodhveda.UpsertProjectPreferenceItem{
{
Channel: "digest",
Topic: "none",
Event: "sent",
Medium: bodhveda.MediumEmail,
Name: "Daily digest",
DefaultEnabled: true,
},
}, nil)
// Pass &bodhveda.UpsertProjectPreferencesOptions{Prune: true} to delete absent entries.{
"message": "Project preferences upserted",
"data": [
{
"id": 12,
"project_id": 1,
"target": {
"channel": "digest",
"topic": "none",
"event": "sent"
},
"medium": "email",
"default_enabled": true,
"label": "Daily digest",
"created_at": "2025-11-07T05:31:56Z",
"updated_at": "2025-11-07T05:31:56Z"
}
]
}Set up the catalog (bulk)
Declaratively merge a whole catalog in one call — the primitive for a one-off “set up my project’s preferences” script. Each item is upserted by its natural key (channel, topic, event, medium): new entries are inserted, existing ones have their label and default updated. Entries not in the array are left untouched, unless prune=true.
Returns the full resulting catalog.
curl -X PUT https://api.bodhveda.com/preferences \
-H "Authorization: Bearer bv_xxxxxxxxx" \
-H "Content-Type: application/json" \
-d '[
{ "channel": "digest", "topic": "none", "event": "sent", "medium": "email", "name": "Daily digest", "default_enabled": true }
]'import { Bodhveda } from "@bodhveda/js";
const bodhveda = new Bodhveda("bv_xxxxxxxxx");
const catalog = await bodhveda.preferences.upsertMany([
{
channel: "digest",
topic: "none",
event: "sent",
medium: "email",
name: "Daily digest",
default_enabled: true,
},
]);
// Pass { prune: true } to delete entries absent from the array.import (
"context"
bodhveda "github.com/MudgalLabs/bodhveda/sdk/go"
)
ctx := context.Background()
client := bodhveda.NewClient("bv_xxxxxxxxx", nil)
catalog, _ := client.Preferences.UpsertMany(ctx, []bodhveda.UpsertProjectPreferenceItem{
{
Channel: "digest",
Topic: "none",
Event: "sent",
Medium: bodhveda.MediumEmail,
Name: "Daily digest",
DefaultEnabled: true,
},
}, nil)
// Pass &bodhveda.UpsertProjectPreferencesOptions{Prune: true} to delete absent entries.{
"message": "Project preferences upserted",
"data": [
{
"id": 12,
"project_id": 1,
"target": {
"channel": "digest",
"topic": "none",
"event": "sent"
},
"medium": "email",
"default_enabled": true,
"label": "Daily digest",
"created_at": "2025-11-07T05:31:56Z",
"updated_at": "2025-11-07T05:31:56Z"
}
]
}channel, topic, event, medium): new entries are inserted, existing ones have their name, description and default updated. Entries not in the array are left untouched.
?prune=true to also delete entries absent from the array, making it your entire desired catalog. Pruning un-catalogs a (target, medium), which turns a non-in_app medium off for recipients relying on the default — so it is opt-in. Deliberate one-off removals go through delete.Authorizations
Bearer authentication header of the form Bearer bv_xxxxxxxxx, where bv_xxxxxxxxx is your API Key with Full access scope.
Query Parameters
When true, catalog entries absent from the array are deleted, making the array the entire desired catalog. Pruning un-catalogs a (target, medium), which turns a non-in_app medium off for recipients relying on the default — so it is opt-in.
Body
Broad category (e.g. "digest").
Subcategory. any matches all topics under the channel/event; none is the base channel/event.
Event that triggers the notification (e.g. "sent").
Human-readable name for the catalog entry (e.g. "Weekly digest").
The project-level default for recipients who have set no preference of their own.
The medium this catalog entry gates. Defaults to in_app.
in_app, email Optional longer blurb shown alongside the name on a settings screen. Omitted or blank stores none.
Whether recipients may opt out. A mandatory entry is cataloged (so sends pass the target gate) but its recipient toggle is refused with a 400, and any rule a recipient already had is ignored. For transactional notifications — password resets, security alerts, a one-shot welcome. default_enabled still applies, so setting it false stops the notification: mandatory removes the recipient's choice, not yours. Defaults to false.
Response
The full resulting catalog.