Record a preference
/v1/preferencesconst result = await bird.preferences.create({
channel: "sms",
handle: "+15550001234",
status: "revoked",
});
console.log(result.applied, result.preference?.id);result = client.preferences.create(
channel="email",
handle="jane@acme.com",
status="granted",
consented_at="2026-08-20T14:03:10Z",
source="signup-form-v2",
)
if result.preference:
print(result.applied, result.preference.id)result, err := client.Preferences.Create(context.Background(), bird.PreferencesCreateParams{
Channel: bird.PreferenceChannelEmail,
Handle: "recipient@example.com",
Status: bird.PreferenceStatusGranted,
Source: "signup-form-v2",
ConsentedAt: time.Now(),
})
if err != nil {
log.Fatal(err)
}
// A newer statement already on file answers Applied false instead of an
// error, with the surviving statement in Preference.
if result.Applied != nil && *result.Applied {
fmt.Println("grant recorded")
}$result = $bird->preferences->create(
channel: 'email',
handle: 'jane@acme.com',
status: 'granted',
source: 'signup-form-v2',
consentedAt: new DateTimeImmutable('2026-08-20T14:03:10Z'),
);
echo var_export($result->getApplied(), true);bird preferences create \
--channel sms \
--coverage non_transactional \
--handle +15550001234 \
--status revokedcurl -X POST "https://us1.platform.bird.com/v1/preferences" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel": "sms",
"handle": "+15550001234",
"status": "revoked",
"coverage": "non_transactional"
}'{
"transition_id": "prt_01krdgeqcxet5s7t44vh8rt9mg",
"preference": {
"id": "prf_01krdgeqcxet5s7t44vh8rt9mg",
"channel": "sms",
"handle": "+15550001234",
"sender_scope": "+15557654321",
"topic_id": null,
"status": "revoked",
"coverage": "non_transactional",
"origin": "api_key",
"source": "signup-form-v2",
"contact_id": "con_01krdgeqcxet5s7t44vh8rt9mg",
"created_at": "2026-05-20T09:14:52Z",
"updated_at": "2026-05-25T16:42:01Z"
}
}
Records one statement, a grant or an opt-out, for a handle on one channel. Writing is an upsert: the key is the channel, handle, and optional sender scope, and a new statement replaces the key's current one.
Statements are ordered by when they were made, not when they arrive. A statement older than the key's current one is refused and returned with applied: false alongside the statement that survived; refusals are recorded on the key's history. Granting over a stored opt-out needs consented_at later than the opt-out, and a person's own opt-out (an unsubscribe, a stop keyword) cannot be overridden by a grant asserted on their behalf.
A 201 means this key had no record and one was created; a 200 returns the key's surviving record, whether this statement replaced it, repeated it, or was refused.
Request Payload
handleWho the statement is about: an email address on the email channel, a phone number in E.164 format on SMS, WhatsApp, and Apple Messages for Business.
channelThe channel a preference statement applies to. A preference addresses one channel: the handle that identifies the person differs per channel, so opting out of one channel says nothing about the others. New channels can be added over time, so a value outside this list can be returned.
Possible values (may grow over time): email, sms, whatsapp, amb
statusWhat the statement says: granted records consent to receive messages, revoked records an opt-out. There is no third state: a person who never stated anything simply has no preference on record.
Possible values: granted, revoked
coverageHow much traffic the statement covers. Defaults to non_transactional, which keeps transactional messages such as receipts and verification codes flowing. Apple Messages for Business phone invitations have no transactional exemption, so either value covers them.
sender_scopeLimit the statement to one sender instead of the whole channel. On SMS this is the originator; on WhatsApp it identifies the business account; on Apple Messages for Business it is the Apple business ID used for invitations. Not supported on email, where preferences are always channel-wide.
sourceFree-form note on where the statement came from: a form name, an import batch, a campaign. Stored verbatim and returned on the preference.
consented_atWhen the person consented, on a granted statement. Required evidence when granting over a stored opt-out: the grant applies only if this is later than the opt-out it reverses. May not be in the future.
Response Payload
appliedWhether the request took effect. False only when it was refused as out of order; the surviving, newer statement is returned in preference.
transition_idIdentifies this write on the key's record, for applied and refused requests alike. Null when the write was a repeat of the current statement and recorded nothing new.
preferenceThe key's surviving statement. Null after an applied delete, when the key is back to having no record.
Related resources
Continue with the documentation, guides and examples for this topic.