diff --git a/internal/ap/extract.go b/internal/ap/extract.go
index f5486a051..543ee8dca 100644
--- a/internal/ap/extract.go
+++ b/internal/ap/extract.go
@@ -1027,7 +1027,7 @@ func ExtractVisibility(addressable Addressable, actorFollowersURI string) (gtsmo
 	)
 
 	if len(to) == 0 && len(cc) == 0 {
-		return "", gtserror.Newf("message wasn't TO or CC anyone")
+		return 0, gtserror.Newf("message wasn't TO or CC anyone")
 	}
 
 	// Assume most restrictive visibility,
diff --git a/internal/api/client/accounts/accountverify_test.go b/internal/api/client/accounts/accountverify_test.go
index af8c2346c..3f67cdefb 100644
--- a/internal/api/client/accounts/accountverify_test.go
+++ b/internal/api/client/accounts/accountverify_test.go
@@ -28,7 +28,6 @@ import (
 	"github.com/stretchr/testify/suite"
 	"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"
 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
 	"github.com/superseriousbusiness/gotosocial/internal/oauth"
 )
 
@@ -99,7 +98,7 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() {
 	suite.Equal(2, apimodelAccount.FollowersCount)
 	suite.Equal(2, apimodelAccount.FollowingCount)
 	suite.Equal(8, apimodelAccount.StatusesCount)
-	suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy)
+	suite.EqualValues(apimodel.VisibilityPublic, apimodelAccount.Source.Privacy)
 	suite.Equal(testAccount.Settings.Language, apimodelAccount.Source.Language)
 	suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note)
 }
diff --git a/internal/api/client/notifications/notificationsget.go b/internal/api/client/notifications/notificationsget.go
index f7bcf1994..cc3e5bdb7 100644
--- a/internal/api/client/notifications/notificationsget.go
+++ b/internal/api/client/notifications/notificationsget.go
@@ -164,6 +164,18 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) {
 		limit = int(i)
 	}
 
+	types, errWithCode := apiutil.ParseNotificationTypes(c.QueryArray(TypesKey))
+	if errWithCode != nil {
+		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+		return
+	}
+
+	exclTypes, errWithCode := apiutil.ParseNotificationTypes(c.QueryArray(ExcludeTypesKey))
+	if errWithCode != nil {
+		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+		return
+	}
+
 	resp, errWithCode := m.processor.Timeline().NotificationsGet(
 		c.Request.Context(),
 		authed,
@@ -171,8 +183,8 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) {
 		c.Query(SinceIDKey),
 		c.Query(MinIDKey),
 		limit,
-		c.QueryArray(TypesKey),
-		c.QueryArray(ExcludeTypesKey),
+		types,
+		exclTypes,
 	)
 	if errWithCode != nil {
 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
diff --git a/internal/api/util/parsequery.go b/internal/api/util/parsequery.go
index 9f4c02aed..9929524c5 100644
--- a/internal/api/util/parsequery.go
+++ b/internal/api/util/parsequery.go
@@ -18,11 +18,13 @@
 package util
 
 import (
+	"errors"
 	"fmt"
 	"strconv"
 	"strings"
 
 	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
+	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
 )
 
 const (
@@ -216,6 +218,51 @@ func ParseInteractionReblogs(value string, defaultValue bool) (bool, gtserror.Wi
 	return parseBool(value, defaultValue, InteractionReblogsKey)
 }
 
+func ParseNotificationType(value string) (gtsmodel.NotificationType, gtserror.WithCode) {
+	switch strings.ToLower(value) {
+	case "follow":
+		return gtsmodel.NotificationFollow, nil
+	case "follow_request":
+		return gtsmodel.NotificationFollowRequest, nil
+	case "mention":
+		return gtsmodel.NotificationMention, nil
+	case "reblog":
+		return gtsmodel.NotificationReblog, nil
+	case "favourite":
+		return gtsmodel.NotificationFave, nil
+	case "poll":
+		return gtsmodel.NotificationPoll, nil
+	case "status":
+		return gtsmodel.NotificationStatus, nil
+	case "admin.sign_up":
+		return gtsmodel.NotificationSignup, nil
+	case "pending.favourite":
+		return gtsmodel.NotificationPendingFave, nil
+	case "pending.reply":
+		return gtsmodel.NotificationPendingReply, nil
+	case "pending.reblog":
+		return gtsmodel.NotificationPendingReblog, nil
+	default:
+		text := fmt.Sprintf("unrecognized notification type %s", value)
+		return 0, gtserror.NewErrorBadRequest(errors.New(text), text)
+	}
+}
+
+func ParseNotificationTypes(values []string) ([]gtsmodel.NotificationType, gtserror.WithCode) {
+	if len(values) == 0 {
+		return nil, nil
+	}
+	ntypes := make([]gtsmodel.NotificationType, len(values))
+	for i, value := range values {
+		ntype, errWithCode := ParseNotificationType(value)
+		if errWithCode != nil {
+			return nil, errWithCode
+		}
+		ntypes[i] = ntype
+	}
+	return ntypes, nil
+}
+
 /*
 	Parse functions for *REQUIRED* parameters.
 */
diff --git a/internal/db/bundb/account_test.go b/internal/db/bundb/account_test.go
index 2caffdeb1..7dcc0f9e7 100644
--- a/internal/db/bundb/account_test.go
+++ b/internal/db/bundb/account_test.go
@@ -106,7 +106,7 @@ func (suite *AccountTestSuite) populateTestStatus(testAccountKey string, status
 	status.URI = fmt.Sprintf("http://localhost:8080/users/%s/statuses/%s", testAccount.Username, status.ID)
 	status.Local = util.Ptr(true)
 
-	if status.Visibility == "" {
+	if status.Visibility == 0 {
 		status.Visibility = gtsmodel.VisibilityDefault
 	}
 	if status.ActivityStreamsType == "" {
diff --git a/internal/db/bundb/instance.go b/internal/db/bundb/instance.go
index bbfd82ffb..613a2b13a 100644
--- a/internal/db/bundb/instance.go
+++ b/internal/db/bundb/instance.go
@@ -104,7 +104,7 @@ func (i *instanceDB) CountInstanceStatuses(ctx context.Context, domain string) (
 	q = q.Where("NOT ? = ?", bun.Ident("status.pending_approval"), true)
 
 	// Ignore statuses that are direct messages.
-	q = q.Where("NOT ? = ?", bun.Ident("status.visibility"), "direct")
+	q = q.Where("NOT ? = ?", bun.Ident("status.visibility"), gtsmodel.VisibilityDirect)
 
 	count, err := q.Count(ctx)
 	if err != nil {
diff --git a/internal/db/bundb/migrations/20231002153327_add_status_polls.go b/internal/db/bundb/migrations/20231002153327_add_status_polls.go
index 5e525cc27..019a369d4 100644
--- a/internal/db/bundb/migrations/20231002153327_add_status_polls.go
+++ b/internal/db/bundb/migrations/20231002153327_add_status_polls.go
@@ -21,7 +21,8 @@ import (
 	"context"
 	"strings"
 
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+	gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20231002153327_add_status_polls"
+
 	"github.com/uptrace/bun"
 )
 
diff --git a/internal/db/bundb/migrations/20231002153327_add_status_polls/polls.go b/internal/db/bundb/migrations/20231002153327_add_status_polls/polls.go
new file mode 100644
index 000000000..c3e03d267
--- /dev/null
+++ b/internal/db/bundb/migrations/20231002153327_add_status_polls/polls.go
@@ -0,0 +1,48 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package gtsmodel
+
+import (
+	"time"
+)
+
+// Poll represents an attached (to) Status poll, i.e. a questionaire. Can be remote / local.
+type Poll struct {
+	ID         string    `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // Unique identity string.
+	Multiple   *bool     `bun:",nullzero,notnull,default:false"`          // Is this a multiple choice poll? i.e. can you vote on multiple options.
+	HideCounts *bool     `bun:",nullzero,notnull,default:false"`          // Hides vote counts until poll ends.
+	Options    []string  `bun:",nullzero,notnull"`                        // The available options for this poll.
+	Votes      []int     `bun:",nullzero,notnull"`                        // Vote counts per choice.
+	Voters     *int      `bun:",nullzero,notnull"`                        // Total no. voters count.
+	StatusID   string    `bun:"type:CHAR(26),nullzero,notnull,unique"`    // Status ID of which this Poll is attached to.
+	ExpiresAt  time.Time `bun:"type:timestamptz,nullzero,notnull"`        // The expiry date of this Poll.
+	ClosedAt   time.Time `bun:"type:timestamptz,nullzero"`                // The closure date of this poll, will be zerotime until set.
+	Closing    bool      `bun:"-"`                                        // An ephemeral field only set on Polls in the middle of closing.
+	// no creation date, use attached Status.CreatedAt.
+}
+
+// PollVote represents a single instance of vote(s) in a Poll by an account.
+// If the Poll is single-choice, len(.Choices) = 1, if multiple-choice then
+// len(.Choices) >= 1. Can be remote or local.
+type PollVote struct {
+	ID        string    `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                    // Unique identity string.
+	Choices   []int     `bun:",nullzero,notnull"`                                           // The Poll's option indices of which these are votes for.
+	AccountID string    `bun:"type:CHAR(26),nullzero,notnull,unique:in_poll_by_account"`    // Account ID from which this vote originated.
+	PollID    string    `bun:"type:CHAR(26),nullzero,notnull,unique:in_poll_by_account"`    // Poll ID of which this is a vote in.
+	CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // The creation date of this PollVote.
+}
diff --git a/internal/db/bundb/migrations/20231002153327_add_status_polls/status.go b/internal/db/bundb/migrations/20231002153327_add_status_polls/status.go
new file mode 100644
index 000000000..8e3252e82
--- /dev/null
+++ b/internal/db/bundb/migrations/20231002153327_add_status_polls/status.go
@@ -0,0 +1,75 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package gtsmodel
+
+import "time"
+
+// Status represents a user-created 'post' or 'status' in the database, either remote or local
+type Status struct {
+	ID                       string     `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                    // id of this item in the database
+	CreatedAt                time.Time  `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
+	UpdatedAt                time.Time  `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	FetchedAt                time.Time  `bun:"type:timestamptz,nullzero"`                                   // when was item (remote) last fetched.
+	PinnedAt                 time.Time  `bun:"type:timestamptz,nullzero"`                                   // Status was pinned by owning account at this time.
+	URI                      string     `bun:",unique,nullzero,notnull"`                                    // activitypub URI of this status
+	URL                      string     `bun:",nullzero"`                                                   // web url for viewing this status
+	Content                  string     `bun:""`                                                            // content of this status; likely html-formatted but not guaranteed
+	AttachmentIDs            []string   `bun:"attachments,array"`                                           // Database IDs of any media attachments associated with this status
+	TagIDs                   []string   `bun:"tags,array"`                                                  // Database IDs of any tags used in this status
+	MentionIDs               []string   `bun:"mentions,array"`                                              // Database IDs of any mentions in this status
+	EmojiIDs                 []string   `bun:"emojis,array"`                                                // Database IDs of any emojis used in this status
+	Local                    *bool      `bun:",nullzero,notnull,default:false"`                             // is this status from a local account?
+	AccountID                string     `bun:"type:CHAR(26),nullzero,notnull"`                              // which account posted this status?
+	AccountURI               string     `bun:",nullzero,notnull"`                                           // activitypub uri of the owner of this status
+	InReplyToID              string     `bun:"type:CHAR(26),nullzero"`                                      // id of the status this status replies to
+	InReplyToURI             string     `bun:",nullzero"`                                                   // activitypub uri of the status this status is a reply to
+	InReplyToAccountID       string     `bun:"type:CHAR(26),nullzero"`                                      // id of the account that this status replies to
+	BoostOfID                string     `bun:"type:CHAR(26),nullzero"`                                      // id of the status this status is a boost of
+	BoostOfAccountID         string     `bun:"type:CHAR(26),nullzero"`                                      // id of the account that owns the boosted status
+	ThreadID                 string     `bun:"type:CHAR(26),nullzero"`                                      // id of the thread to which this status belongs; only set for remote statuses if a local account is involved at some point in the thread, otherwise null
+	PollID                   string     `bun:"type:CHAR(26),nullzero"`                                      //
+	ContentWarning           string     `bun:",nullzero"`                                                   // cw string for this status
+	Visibility               Visibility `bun:",nullzero,notnull"`                                           // visibility entry for this status
+	Sensitive                *bool      `bun:",nullzero,notnull,default:false"`                             // mark the status as sensitive?
+	Language                 string     `bun:",nullzero"`                                                   // what language is this status written in?
+	CreatedWithApplicationID string     `bun:"type:CHAR(26),nullzero"`                                      // Which application was used to create this status?
+	ActivityStreamsType      string     `bun:",nullzero,notnull"`                                           // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!.
+	Text                     string     `bun:""`                                                            // Original text of the status without formatting
+	Federated                *bool      `bun:",notnull"`                                                    // This status will be federated beyond the local timeline(s)
+	Boostable                *bool      `bun:",notnull"`                                                    // This status can be boosted/reblogged
+	Replyable                *bool      `bun:",notnull"`                                                    // This status can be replied to
+	Likeable                 *bool      `bun:",notnull"`                                                    // This status can be liked/faved
+}
+
+// Visibility represents the visibility granularity of a status.
+type Visibility string
+
+const (
+	// VisibilityPublic means this status will be visible to everyone on all timelines.
+	VisibilityPublic Visibility = "public"
+	// VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists.
+	VisibilityUnlocked Visibility = "unlocked"
+	// VisibilityFollowersOnly means this status is viewable to followers only.
+	VisibilityFollowersOnly Visibility = "followers_only"
+	// VisibilityMutualsOnly means this status is visible to mutual followers only.
+	VisibilityMutualsOnly Visibility = "mutuals_only"
+	// VisibilityDirect means this status is visible only to mentioned recipients.
+	VisibilityDirect Visibility = "direct"
+	// VisibilityDefault is used when no other setting can be found.
+	VisibilityDefault Visibility = VisibilityUnlocked
+)
diff --git a/internal/db/bundb/migrations/20240620074530_interaction_policy.go b/internal/db/bundb/migrations/20240620074530_interaction_policy.go
index bbc75d9ec..7678af7ed 100644
--- a/internal/db/bundb/migrations/20240620074530_interaction_policy.go
+++ b/internal/db/bundb/migrations/20240620074530_interaction_policy.go
@@ -161,11 +161,14 @@ func init() {
 				return err
 			}
 
+			// Get the mapping of old enum string values to new integer values.
+			visibilityMapping := visibilityEnumMapping[oldmodel.Visibility]()
+
 			// For each status found in this way, update
 			// to new version of interaction policy.
 			for _, oldStatus := range oldStatuses {
 				// Start with default policy for this visibility.
-				v := gtsmodel.Visibility(oldStatus.Visibility)
+				v := visibilityMapping[oldStatus.Visibility]
 				policy := gtsmodel.DefaultInteractionPolicyFor(v)
 
 				if !*oldStatus.Likeable {
diff --git a/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go b/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go
index ae96d047d..615c81b66 100644
--- a/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go
+++ b/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go
@@ -22,40 +22,61 @@ import (
 )
 
 type Status struct {
-	ID                       string    `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
-	CreatedAt                time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
-	UpdatedAt                time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
-	FetchedAt                time.Time `bun:"type:timestamptz,nullzero"`
-	PinnedAt                 time.Time `bun:"type:timestamptz,nullzero"`
-	URI                      string    `bun:",unique,nullzero,notnull"`
-	URL                      string    `bun:",nullzero"`
-	Content                  string    `bun:""`
-	AttachmentIDs            []string  `bun:"attachments,array"`
-	TagIDs                   []string  `bun:"tags,array"`
-	MentionIDs               []string  `bun:"mentions,array"`
-	EmojiIDs                 []string  `bun:"emojis,array"`
-	Local                    *bool     `bun:",nullzero,notnull,default:false"`
-	AccountID                string    `bun:"type:CHAR(26),nullzero,notnull"`
-	AccountURI               string    `bun:",nullzero,notnull"`
-	InReplyToID              string    `bun:"type:CHAR(26),nullzero"`
-	InReplyToURI             string    `bun:",nullzero"`
-	InReplyToAccountID       string    `bun:"type:CHAR(26),nullzero"`
-	InReplyTo                *Status   `bun:"-"`
-	BoostOfID                string    `bun:"type:CHAR(26),nullzero"`
-	BoostOfURI               string    `bun:"-"`
-	BoostOfAccountID         string    `bun:"type:CHAR(26),nullzero"`
-	BoostOf                  *Status   `bun:"-"`
-	ThreadID                 string    `bun:"type:CHAR(26),nullzero"`
-	PollID                   string    `bun:"type:CHAR(26),nullzero"`
-	ContentWarning           string    `bun:",nullzero"`
-	Visibility               string    `bun:",nullzero,notnull"`
-	Sensitive                *bool     `bun:",nullzero,notnull,default:false"`
-	Language                 string    `bun:",nullzero"`
-	CreatedWithApplicationID string    `bun:"type:CHAR(26),nullzero"`
-	ActivityStreamsType      string    `bun:",nullzero,notnull"`
-	Text                     string    `bun:""`
-	Federated                *bool     `bun:",notnull"`
-	Boostable                *bool     `bun:",notnull"`
-	Replyable                *bool     `bun:",notnull"`
-	Likeable                 *bool     `bun:",notnull"`
+	ID                       string     `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
+	CreatedAt                time.Time  `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
+	UpdatedAt                time.Time  `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
+	FetchedAt                time.Time  `bun:"type:timestamptz,nullzero"`
+	PinnedAt                 time.Time  `bun:"type:timestamptz,nullzero"`
+	URI                      string     `bun:",unique,nullzero,notnull"`
+	URL                      string     `bun:",nullzero"`
+	Content                  string     `bun:""`
+	AttachmentIDs            []string   `bun:"attachments,array"`
+	TagIDs                   []string   `bun:"tags,array"`
+	MentionIDs               []string   `bun:"mentions,array"`
+	EmojiIDs                 []string   `bun:"emojis,array"`
+	Local                    *bool      `bun:",nullzero,notnull,default:false"`
+	AccountID                string     `bun:"type:CHAR(26),nullzero,notnull"`
+	AccountURI               string     `bun:",nullzero,notnull"`
+	InReplyToID              string     `bun:"type:CHAR(26),nullzero"`
+	InReplyToURI             string     `bun:",nullzero"`
+	InReplyToAccountID       string     `bun:"type:CHAR(26),nullzero"`
+	InReplyTo                *Status    `bun:"-"`
+	BoostOfID                string     `bun:"type:CHAR(26),nullzero"`
+	BoostOfURI               string     `bun:"-"`
+	BoostOfAccountID         string     `bun:"type:CHAR(26),nullzero"`
+	BoostOf                  *Status    `bun:"-"`
+	ThreadID                 string     `bun:"type:CHAR(26),nullzero"`
+	PollID                   string     `bun:"type:CHAR(26),nullzero"`
+	ContentWarning           string     `bun:",nullzero"`
+	Visibility               Visibility `bun:",nullzero,notnull"`
+	Sensitive                *bool      `bun:",nullzero,notnull,default:false"`
+	Language                 string     `bun:",nullzero"`
+	CreatedWithApplicationID string     `bun:"type:CHAR(26),nullzero"`
+	ActivityStreamsType      string     `bun:",nullzero,notnull"`
+	Text                     string     `bun:""`
+	Federated                *bool      `bun:",notnull"`
+	Boostable                *bool      `bun:",notnull"`
+	Replyable                *bool      `bun:",notnull"`
+	Likeable                 *bool      `bun:",notnull"`
 }
+
+// Visibility represents the visibility granularity of a status.
+type Visibility string
+
+const (
+	// VisibilityNone means nobody can see this.
+	// It's only used for web status visibility.
+	VisibilityNone Visibility = "none"
+	// VisibilityPublic means this status will be visible to everyone on all timelines.
+	VisibilityPublic Visibility = "public"
+	// VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists.
+	VisibilityUnlocked Visibility = "unlocked"
+	// VisibilityFollowersOnly means this status is viewable to followers only.
+	VisibilityFollowersOnly Visibility = "followers_only"
+	// VisibilityMutualsOnly means this status is visible to mutual followers only.
+	VisibilityMutualsOnly Visibility = "mutuals_only"
+	// VisibilityDirect means this status is visible only to mentioned recipients.
+	VisibilityDirect Visibility = "direct"
+	// VisibilityDefault is used when no other setting can be found.
+	VisibilityDefault Visibility = VisibilityUnlocked
+)
diff --git a/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction.go b/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction.go
index d97d35372..d3c0f49a4 100644
--- a/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction.go
+++ b/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction.go
@@ -20,7 +20,7 @@ package migrations
 import (
 	"context"
 
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+	gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction"
 	"github.com/uptrace/bun"
 )
 
diff --git a/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction/sinbinstatus.go b/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction/sinbinstatus.go
new file mode 100644
index 000000000..e18affa9b
--- /dev/null
+++ b/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction/sinbinstatus.go
@@ -0,0 +1,66 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package gtsmodel
+
+import "time"
+
+// SinBinStatus represents a status that's been rejected and/or reported + quarantined.
+//
+// Automatically rejected statuses are not put in the sin bin, only statuses that were
+// stored on the instance and which someone (local or remote) has subsequently rejected.
+type SinBinStatus struct {
+	ID                  string     `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                    // ID of this item in the database.
+	CreatedAt           time.Time  `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Creation time of this item.
+	UpdatedAt           time.Time  `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Last-updated time of this item.
+	URI                 string     `bun:",unique,nullzero,notnull"`                                    // ActivityPub URI/ID of this status.
+	URL                 string     `bun:",nullzero"`                                                   // Web url for viewing this status.
+	Domain              string     `bun:",nullzero"`                                                   // Domain of the status, will be null if this is a local status, otherwise something like `example.org`.
+	AccountURI          string     `bun:",nullzero,notnull"`                                           // ActivityPub uri of the author of this status.
+	InReplyToURI        string     `bun:",nullzero"`                                                   // ActivityPub uri of the status this status is a reply to.
+	Content             string     `bun:",nullzero"`                                                   // Content of this status.
+	AttachmentLinks     []string   `bun:",nullzero,array"`                                             // Links to attachments of this status.
+	MentionTargetURIs   []string   `bun:",nullzero,array"`                                             // URIs of mentioned accounts.
+	EmojiLinks          []string   `bun:",nullzero,array"`                                             // Links to any emoji images used in this status.
+	PollOptions         []string   `bun:",nullzero,array"`                                             // String values of any poll options used in this status.
+	ContentWarning      string     `bun:",nullzero"`                                                   // CW / subject string for this status.
+	Visibility          Visibility `bun:",nullzero,notnull"`                                           // Visibility level of this status.
+	Sensitive           *bool      `bun:",nullzero,notnull,default:false"`                             // Mark the status as sensitive.
+	Language            string     `bun:",nullzero"`                                                   // Language code for this status.
+	ActivityStreamsType string     `bun:",nullzero,notnull"`                                           // ActivityStreams type of this status.
+}
+
+// Visibility represents the visibility granularity of a status.
+type Visibility string
+
+const (
+	// VisibilityNone means nobody can see this.
+	// It's only used for web status visibility.
+	VisibilityNone Visibility = "none"
+	// VisibilityPublic means this status will be visible to everyone on all timelines.
+	VisibilityPublic Visibility = "public"
+	// VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists.
+	VisibilityUnlocked Visibility = "unlocked"
+	// VisibilityFollowersOnly means this status is viewable to followers only.
+	VisibilityFollowersOnly Visibility = "followers_only"
+	// VisibilityMutualsOnly means this status is visible to mutual followers only.
+	VisibilityMutualsOnly Visibility = "mutuals_only"
+	// VisibilityDirect means this status is visible only to mentioned recipients.
+	VisibilityDirect Visibility = "direct"
+	// VisibilityDefault is used when no other setting can be found.
+	VisibilityDefault Visibility = VisibilityUnlocked
+)
diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go
new file mode 100644
index 000000000..10ae95c17
--- /dev/null
+++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints.go
@@ -0,0 +1,249 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package migrations
+
+import (
+	"context"
+	"errors"
+
+	old_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints"
+	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
+	new_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+	"github.com/superseriousbusiness/gotosocial/internal/log"
+	"github.com/superseriousbusiness/gotosocial/internal/util"
+
+	"github.com/uptrace/bun"
+)
+
+func init() {
+	up := func(ctx context.Context, db *bun.DB) error {
+		return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+
+			// Tables with visibility types.
+			var visTables = []struct {
+				Table   string
+				Column  string
+				Default *new_gtsmodel.Visibility
+			}{
+				{Table: "statuses", Column: "visibility"},
+				{Table: "sin_bin_statuses", Column: "visibility"},
+				{Table: "account_settings", Column: "privacy", Default: util.Ptr(new_gtsmodel.VisibilityDefault)},
+				{Table: "account_settings", Column: "web_visibility", Default: util.Ptr(new_gtsmodel.VisibilityDefault)},
+			}
+
+			// Visibility type indices.
+			var visIndices = []struct {
+				name  string
+				cols  []string
+				order string
+			}{
+				{
+					name:  "statuses_visibility_idx",
+					cols:  []string{"visibility"},
+					order: "",
+				},
+				{
+					name:  "statuses_profile_web_view_idx",
+					cols:  []string{"account_id", "visibility"},
+					order: "id DESC",
+				},
+				{
+					name:  "statuses_public_timeline_idx",
+					cols:  []string{"visibility"},
+					order: "id DESC",
+				},
+			}
+
+			// Before making changes to the visibility col
+			// we must drop all indices that rely on it.
+			for _, index := range visIndices {
+				if _, err := tx.NewDropIndex().
+					Index(index.name).
+					Exec(ctx); err != nil {
+					return err
+				}
+			}
+
+			// Get the mapping of old enum string values to new integer values.
+			visibilityMapping := visibilityEnumMapping[old_gtsmodel.Visibility]()
+
+			// Convert all visibility tables.
+			for _, table := range visTables {
+				if err := convertEnums(ctx, tx, table.Table, table.Column,
+					visibilityMapping, table.Default); err != nil {
+					return err
+				}
+			}
+
+			// Recreate the visibility indices.
+			for _, index := range visIndices {
+				q := tx.NewCreateIndex().
+					Table("statuses").
+					Index(index.name).
+					Column(index.cols...)
+				if index.order != "" {
+					q = q.ColumnExpr(index.order)
+				}
+				if _, err := q.Exec(ctx); err != nil {
+					return err
+				}
+			}
+
+			// Get the mapping of old enum string values to the new integer value types.
+			notificationMapping := notificationEnumMapping[old_gtsmodel.NotificationType]()
+
+			// Migrate over old notifications table column over to new column type.
+			if err := convertEnums(ctx, tx, "notifications", "notification_type", //nolint:revive
+				notificationMapping, nil); err != nil {
+				return err
+			}
+
+			return nil
+		})
+	}
+
+	down := func(ctx context.Context, db *bun.DB) error {
+		return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+			return nil
+		})
+	}
+
+	if err := Migrations.Register(up, down); err != nil {
+		panic(err)
+	}
+}
+
+// convertEnums performs a transaction that converts
+// a table's column of our old-style enums (strings) to
+// more performant and space-saving integer types.
+func convertEnums[OldType ~string, NewType ~int16](
+	ctx context.Context,
+	tx bun.Tx,
+	table string,
+	column string,
+	mapping map[OldType]NewType,
+	defaultValue *NewType,
+) error {
+	if len(mapping) == 0 {
+		return errors.New("empty mapping")
+	}
+
+	// Generate new column name.
+	newColumn := column + "_new"
+
+	log.Infof(ctx, "converting %s.%s enums; "+
+		"this may take a while, please don't interrupt!",
+		table, column,
+	)
+
+	// Ensure a default value.
+	if defaultValue == nil {
+		var zero NewType
+		defaultValue = &zero
+	}
+
+	// Add new column to database.
+	if _, err := tx.NewAddColumn().
+		Table(table).
+		ColumnExpr("? SMALLINT NOT NULL DEFAULT ?",
+			bun.Ident(newColumn),
+			*defaultValue).
+		Exec(ctx); err != nil {
+		return gtserror.Newf("error adding new column: %w", err)
+	}
+
+	// Get a count of all in table.
+	total, err := tx.NewSelect().
+		Table(table).
+		Count(ctx)
+	if err != nil {
+		return gtserror.Newf("error selecting total count: %w", err)
+	}
+
+	var updated int
+	for old, new := range mapping {
+
+		// Update old to new values.
+		res, err := tx.NewUpdate().
+			Table(table).
+			Where("? = ?", bun.Ident(column), old).
+			Set("? = ?", bun.Ident(newColumn), new).
+			Exec(ctx)
+		if err != nil {
+			return gtserror.Newf("error updating old column values: %w", err)
+		}
+
+		// Count number items updated.
+		n, _ := res.RowsAffected()
+		updated += int(n)
+	}
+
+	// Check total updated.
+	if total != updated {
+		log.Warnf(ctx, "total=%d does not match updated=%d", total, updated)
+	}
+
+	// Drop the old column from table.
+	if _, err := tx.NewDropColumn().
+		Table(table).
+		ColumnExpr("?", bun.Ident(column)).
+		Exec(ctx); err != nil {
+		return gtserror.Newf("error dropping old column: %w", err)
+	}
+
+	// Rename new to old name.
+	if _, err := tx.NewRaw(
+		"ALTER TABLE ? RENAME COLUMN ? TO ?",
+		bun.Ident(table),
+		bun.Ident(newColumn),
+		bun.Ident(column),
+	).Exec(ctx); err != nil {
+		return gtserror.Newf("error renaming new column: %w", err)
+	}
+
+	return nil
+}
+
+// visibilityEnumMapping maps old Visibility enum values to their newer integer type.
+func visibilityEnumMapping[T ~string]() map[T]new_gtsmodel.Visibility {
+	return map[T]new_gtsmodel.Visibility{
+		T(old_gtsmodel.VisibilityNone):          new_gtsmodel.VisibilityNone,
+		T(old_gtsmodel.VisibilityPublic):        new_gtsmodel.VisibilityPublic,
+		T(old_gtsmodel.VisibilityUnlocked):      new_gtsmodel.VisibilityUnlocked,
+		T(old_gtsmodel.VisibilityFollowersOnly): new_gtsmodel.VisibilityFollowersOnly,
+		T(old_gtsmodel.VisibilityMutualsOnly):   new_gtsmodel.VisibilityMutualsOnly,
+		T(old_gtsmodel.VisibilityDirect):        new_gtsmodel.VisibilityDirect,
+	}
+}
+
+// notificationEnumMapping maps old NotificationType enum values to their newer integer type.
+func notificationEnumMapping[T ~string]() map[T]new_gtsmodel.NotificationType {
+	return map[T]new_gtsmodel.NotificationType{
+		T(old_gtsmodel.NotificationFollow):        new_gtsmodel.NotificationFollow,
+		T(old_gtsmodel.NotificationFollowRequest): new_gtsmodel.NotificationFollowRequest,
+		T(old_gtsmodel.NotificationMention):       new_gtsmodel.NotificationMention,
+		T(old_gtsmodel.NotificationReblog):        new_gtsmodel.NotificationReblog,
+		T(old_gtsmodel.NotificationFave):          new_gtsmodel.NotificationFave,
+		T(old_gtsmodel.NotificationPoll):          new_gtsmodel.NotificationPoll,
+		T(old_gtsmodel.NotificationStatus):        new_gtsmodel.NotificationStatus,
+		T(old_gtsmodel.NotificationSignup):        new_gtsmodel.NotificationSignup,
+		T(old_gtsmodel.NotificationPendingFave):   new_gtsmodel.NotificationPendingFave,
+		T(old_gtsmodel.NotificationPendingReply):  new_gtsmodel.NotificationPendingReply,
+		T(old_gtsmodel.NotificationPendingReblog): new_gtsmodel.NotificationPendingReblog,
+	}
+}
diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/accountsettings.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/accountsettings.go
new file mode 100644
index 000000000..9a9cfd8e1
--- /dev/null
+++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/accountsettings.go
@@ -0,0 +1,45 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package gtsmodel
+
+import (
+	"time"
+
+	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+// AccountSettings models settings / preferences for a local, non-instance account.
+type AccountSettings struct {
+	AccountID                      string                      `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                    // AccountID that owns this settings.
+	CreatedAt                      time.Time                   `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created.
+	UpdatedAt                      time.Time                   `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated.
+	Privacy                        Visibility                  `bun:",nullzero,default:3"`                                         // Default post privacy for this account
+	Sensitive                      *bool                       `bun:",nullzero,notnull,default:false"`                             // Set posts from this account to sensitive by default?
+	Language                       string                      `bun:",nullzero,notnull,default:'en'"`                              // What language does this account post in?
+	StatusContentType              string                      `bun:",nullzero"`                                                   // What is the default format for statuses posted by this account (only for local accounts).
+	Theme                          string                      `bun:",nullzero"`                                                   // Preset CSS theme filename selected by this Account (empty string if nothing set).
+	CustomCSS                      string                      `bun:",nullzero"`                                                   // Custom CSS that should be displayed for this Account's profile and statuses.
+	EnableRSS                      *bool                       `bun:",nullzero,notnull,default:false"`                             // enable RSS feed subscription for this account's public posts at [URL]/feed
+	HideCollections                *bool                       `bun:",nullzero,notnull,default:false"`                             // Hide this account's followers/following collections.
+	WebVisibility                  Visibility                  `bun:",nullzero,notnull,default:3"`                                 // Visibility level of statuses that visitors can view via the web profile.
+	InteractionPolicyDirect        *gtsmodel.InteractionPolicy `bun:""`                                                            // Interaction policy to use for new direct visibility statuses by this account. If null, assume default policy.
+	InteractionPolicyMutualsOnly   *gtsmodel.InteractionPolicy `bun:""`                                                            // Interaction policy to use for new mutuals only visibility statuses. If null, assume default policy.
+	InteractionPolicyFollowersOnly *gtsmodel.InteractionPolicy `bun:""`                                                            // Interaction policy to use for new followers only visibility statuses. If null, assume default policy.
+	InteractionPolicyUnlocked      *gtsmodel.InteractionPolicy `bun:""`                                                            // Interaction policy to use for new unlocked visibility statuses. If null, assume default policy.
+	InteractionPolicyPublic        *gtsmodel.InteractionPolicy `bun:""`                                                            // Interaction policy to use for new public visibility statuses. If null, assume default policy.
+}
diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/notification.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/notification.go
new file mode 100644
index 000000000..77166a35d
--- /dev/null
+++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/notification.go
@@ -0,0 +1,57 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package gtsmodel
+
+import (
+	"time"
+
+	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+// Notification models an alert/notification sent to an account about something like a reblog, like, new follow request, etc.
+type Notification struct {
+	ID               string            `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                    // id of this item in the database
+	CreatedAt        time.Time         `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
+	UpdatedAt        time.Time         `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	NotificationType NotificationType  `bun:",nullzero,notnull"`                                           // Type of this notification
+	TargetAccountID  string            `bun:"type:CHAR(26),nullzero,notnull"`                              // ID of the account targeted by the notification (ie., who will receive the notification?)
+	TargetAccount    *gtsmodel.Account `bun:"-"`                                                           // Account corresponding to TargetAccountID. Can be nil, always check first + select using ID if necessary.
+	OriginAccountID  string            `bun:"type:CHAR(26),nullzero,notnull"`                              // ID of the account that performed the action that created the notification.
+	OriginAccount    *gtsmodel.Account `bun:"-"`                                                           // Account corresponding to OriginAccountID. Can be nil, always check first + select using ID if necessary.
+	StatusID         string            `bun:"type:CHAR(26),nullzero"`                                      // If the notification pertains to a status, what is the database ID of that status?
+	Status           *Status           `bun:"-"`                                                           // Status corresponding to StatusID. Can be nil, always check first + select using ID if necessary.
+	Read             *bool             `bun:",nullzero,notnull,default:false"`                             // Notification has been seen/read
+}
+
+// NotificationType describes the reason/type of this notification.
+type NotificationType string
+
+// Notification Types
+const (
+	NotificationFollow        NotificationType = "follow"            // NotificationFollow -- someone followed you
+	NotificationFollowRequest NotificationType = "follow_request"    // NotificationFollowRequest -- someone requested to follow you
+	NotificationMention       NotificationType = "mention"           // NotificationMention -- someone mentioned you in their status
+	NotificationReblog        NotificationType = "reblog"            // NotificationReblog -- someone boosted one of your statuses
+	NotificationFave          NotificationType = "favourite"         // NotificationFave -- someone faved/liked one of your statuses
+	NotificationPoll          NotificationType = "poll"              // NotificationPoll -- a poll you voted in or created has ended
+	NotificationStatus        NotificationType = "status"            // NotificationStatus -- someone you enabled notifications for has posted a status.
+	NotificationSignup        NotificationType = "admin.sign_up"     // NotificationSignup -- someone has submitted a new account sign-up to the instance.
+	NotificationPendingFave   NotificationType = "pending.favourite" // Someone has faved a status of yours, which requires approval by you.
+	NotificationPendingReply  NotificationType = "pending.reply"     // Someone has replied to a status of yours, which requires approval by you.
+	NotificationPendingReblog NotificationType = "pending.reblog"    // Someone has boosted a status of yours, which requires approval by you.
+)
diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/sinbinstatus.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/sinbinstatus.go
new file mode 100644
index 000000000..d1dfcddd1
--- /dev/null
+++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/sinbinstatus.go
@@ -0,0 +1,45 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package gtsmodel
+
+import "time"
+
+// SinBinStatus represents a status that's been rejected and/or reported + quarantined.
+//
+// Automatically rejected statuses are not put in the sin bin, only statuses that were
+// stored on the instance and which someone (local or remote) has subsequently rejected.
+type SinBinStatus struct {
+	ID                  string     `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                    // ID of this item in the database.
+	CreatedAt           time.Time  `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Creation time of this item.
+	UpdatedAt           time.Time  `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Last-updated time of this item.
+	URI                 string     `bun:",unique,nullzero,notnull"`                                    // ActivityPub URI/ID of this status.
+	URL                 string     `bun:",nullzero"`                                                   // Web url for viewing this status.
+	Domain              string     `bun:",nullzero"`                                                   // Domain of the status, will be null if this is a local status, otherwise something like `example.org`.
+	AccountURI          string     `bun:",nullzero,notnull"`                                           // ActivityPub uri of the author of this status.
+	InReplyToURI        string     `bun:",nullzero"`                                                   // ActivityPub uri of the status this status is a reply to.
+	Content             string     `bun:",nullzero"`                                                   // Content of this status.
+	AttachmentLinks     []string   `bun:",nullzero,array"`                                             // Links to attachments of this status.
+	MentionTargetURIs   []string   `bun:",nullzero,array"`                                             // URIs of mentioned accounts.
+	EmojiLinks          []string   `bun:",nullzero,array"`                                             // Links to any emoji images used in this status.
+	PollOptions         []string   `bun:",nullzero,array"`                                             // String values of any poll options used in this status.
+	ContentWarning      string     `bun:",nullzero"`                                                   // CW / subject string for this status.
+	Visibility          Visibility `bun:",nullzero,notnull"`                                           // Visibility level of this status.
+	Sensitive           *bool      `bun:",nullzero,notnull,default:false"`                             // Mark the status as sensitive.
+	Language            string     `bun:",nullzero"`                                                   // Language code for this status.
+	ActivityStreamsType string     `bun:",nullzero,notnull"`                                           // ActivityStreams type of this status.
+}
diff --git a/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/status.go b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/status.go
new file mode 100644
index 000000000..38583c7fc
--- /dev/null
+++ b/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints/status.go
@@ -0,0 +1,95 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package gtsmodel
+
+import (
+	"time"
+
+	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+// Status represents a user-created 'post' or 'status' in the database, either remote or local
+type Status struct {
+	ID                       string                      `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                    // id of this item in the database
+	CreatedAt                time.Time                   `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
+	UpdatedAt                time.Time                   `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	FetchedAt                time.Time                   `bun:"type:timestamptz,nullzero"`                                   // when was item (remote) last fetched.
+	PinnedAt                 time.Time                   `bun:"type:timestamptz,nullzero"`                                   // Status was pinned by owning account at this time.
+	URI                      string                      `bun:",unique,nullzero,notnull"`                                    // activitypub URI of this status
+	URL                      string                      `bun:",nullzero"`                                                   // web url for viewing this status
+	Content                  string                      `bun:""`                                                            // content of this status; likely html-formatted but not guaranteed
+	AttachmentIDs            []string                    `bun:"attachments,array"`                                           // Database IDs of any media attachments associated with this status
+	Attachments              []*gtsmodel.MediaAttachment `bun:"attached_media,rel:has-many"`                                 // Attachments corresponding to attachmentIDs
+	TagIDs                   []string                    `bun:"tags,array"`                                                  // Database IDs of any tags used in this status
+	Tags                     []*gtsmodel.Tag             `bun:"attached_tags,m2m:status_to_tags"`                            // Tags corresponding to tagIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
+	MentionIDs               []string                    `bun:"mentions,array"`                                              // Database IDs of any mentions in this status
+	Mentions                 []*gtsmodel.Mention         `bun:"attached_mentions,rel:has-many"`                              // Mentions corresponding to mentionIDs
+	EmojiIDs                 []string                    `bun:"emojis,array"`                                                // Database IDs of any emojis used in this status
+	Emojis                   []*gtsmodel.Emoji           `bun:"attached_emojis,m2m:status_to_emojis"`                        // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
+	Local                    *bool                       `bun:",nullzero,notnull,default:false"`                             // is this status from a local account?
+	AccountID                string                      `bun:"type:CHAR(26),nullzero,notnull"`                              // which account posted this status?
+	Account                  *gtsmodel.Account           `bun:"rel:belongs-to"`                                              // account corresponding to accountID
+	AccountURI               string                      `bun:",nullzero,notnull"`                                           // activitypub uri of the owner of this status
+	InReplyToID              string                      `bun:"type:CHAR(26),nullzero"`                                      // id of the status this status replies to
+	InReplyToURI             string                      `bun:",nullzero"`                                                   // activitypub uri of the status this status is a reply to
+	InReplyToAccountID       string                      `bun:"type:CHAR(26),nullzero"`                                      // id of the account that this status replies to
+	InReplyTo                *Status                     `bun:"-"`                                                           // status corresponding to inReplyToID
+	InReplyToAccount         *gtsmodel.Account           `bun:"rel:belongs-to"`                                              // account corresponding to inReplyToAccountID
+	BoostOfID                string                      `bun:"type:CHAR(26),nullzero"`                                      // id of the status this status is a boost of
+	BoostOfURI               string                      `bun:"-"`                                                           // URI of the status this status is a boost of; field not inserted in the db, just for dereferencing purposes.
+	BoostOfAccountID         string                      `bun:"type:CHAR(26),nullzero"`                                      // id of the account that owns the boosted status
+	BoostOf                  *Status                     `bun:"-"`                                                           // status that corresponds to boostOfID
+	BoostOfAccount           *gtsmodel.Account           `bun:"rel:belongs-to"`                                              // account that corresponds to boostOfAccountID
+	ThreadID                 string                      `bun:"type:CHAR(26),nullzero"`                                      // id of the thread to which this status belongs; only set for remote statuses if a local account is involved at some point in the thread, otherwise null
+	PollID                   string                      `bun:"type:CHAR(26),nullzero"`                                      //
+	Poll                     *gtsmodel.Poll              `bun:"-"`                                                           //
+	ContentWarning           string                      `bun:",nullzero"`                                                   // cw string for this status
+	Visibility               Visibility                  `bun:",nullzero,notnull"`                                           // visibility entry for this status
+	Sensitive                *bool                       `bun:",nullzero,notnull,default:false"`                             // mark the status as sensitive?
+	Language                 string                      `bun:",nullzero"`                                                   // what language is this status written in?
+	CreatedWithApplicationID string                      `bun:"type:CHAR(26),nullzero"`                                      // Which application was used to create this status?
+	CreatedWithApplication   *gtsmodel.Application       `bun:"rel:belongs-to"`                                              // application corresponding to createdWithApplicationID
+	ActivityStreamsType      string                      `bun:",nullzero,notnull"`                                           // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!.
+	Text                     string                      `bun:""`                                                            // Original text of the status without formatting
+	Federated                *bool                       `bun:",notnull"`                                                    // This status will be federated beyond the local timeline(s)
+	InteractionPolicy        *gtsmodel.InteractionPolicy `bun:""`                                                            // InteractionPolicy for this status. If null then the default InteractionPolicy should be assumed for this status's Visibility. Always null for boost wrappers.
+	PendingApproval          *bool                       `bun:",nullzero,notnull,default:false"`                             // If true then status is a reply or boost wrapper that must be Approved by the reply-ee or boost-ee before being fully distributed.
+	PreApproved              bool                        `bun:"-"`                                                           // If true, then status is a reply to or boost wrapper of a status on our instance, has permission to do the interaction, and an Accept should be sent out for it immediately. Field not stored in the DB.
+	ApprovedByURI            string                      `bun:",nullzero"`                                                   // URI of an Accept Activity that approves the Announce or Create Activity that this status was/will be attached to.
+}
+
+// Visibility represents the visibility granularity of a status.
+type Visibility string
+
+const (
+	// VisibilityNone means nobody can see this.
+	// It's only used for web status visibility.
+	VisibilityNone Visibility = "none"
+	// VisibilityPublic means this status will be visible to everyone on all timelines.
+	VisibilityPublic Visibility = "public"
+	// VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists.
+	VisibilityUnlocked Visibility = "unlocked"
+	// VisibilityFollowersOnly means this status is viewable to followers only.
+	VisibilityFollowersOnly Visibility = "followers_only"
+	// VisibilityMutualsOnly means this status is visible to mutual followers only.
+	VisibilityMutualsOnly Visibility = "mutuals_only"
+	// VisibilityDirect means this status is visible only to mentioned recipients.
+	VisibilityDirect Visibility = "direct"
+	// VisibilityDefault is used when no other setting can be found.
+	VisibilityDefault Visibility = VisibilityUnlocked
+)
diff --git a/internal/db/bundb/notification.go b/internal/db/bundb/notification.go
index ef2527637..a20ab7e3f 100644
--- a/internal/db/bundb/notification.go
+++ b/internal/db/bundb/notification.go
@@ -196,8 +196,8 @@ func (n *notificationDB) GetAccountNotifications(
 	sinceID string,
 	minID string,
 	limit int,
-	types []string,
-	excludeTypes []string,
+	types []gtsmodel.NotificationType,
+	excludeTypes []gtsmodel.NotificationType,
 ) ([]*gtsmodel.Notification, error) {
 	// Ensure reasonable
 	if limit < 0 {
@@ -303,7 +303,7 @@ func (n *notificationDB) DeleteNotificationByID(ctx context.Context, id string)
 	return nil
 }
 
-func (n *notificationDB) DeleteNotifications(ctx context.Context, types []string, targetAccountID string, originAccountID string) error {
+func (n *notificationDB) DeleteNotifications(ctx context.Context, types []gtsmodel.NotificationType, targetAccountID string, originAccountID string) error {
 	if targetAccountID == "" && originAccountID == "" {
 		return gtserror.New("one of targetAccountID or originAccountID must be set")
 	}
diff --git a/internal/db/bundb/relationship_follow_req.go b/internal/db/bundb/relationship_follow_req.go
index 030c99c58..f36d626ca 100644
--- a/internal/db/bundb/relationship_follow_req.go
+++ b/internal/db/bundb/relationship_follow_req.go
@@ -265,8 +265,8 @@ func (r *relationshipDB) AcceptFollowRequest(ctx context.Context, sourceAccountI
 	}
 
 	// Delete original follow request notification
-	if err := r.state.DB.DeleteNotifications(ctx, []string{
-		string(gtsmodel.NotificationFollowRequest),
+	if err := r.state.DB.DeleteNotifications(ctx, []gtsmodel.NotificationType{
+		gtsmodel.NotificationFollowRequest,
 	}, targetAccountID, sourceAccountID); err != nil {
 		return nil, err
 	}
@@ -281,8 +281,8 @@ func (r *relationshipDB) RejectFollowRequest(ctx context.Context, sourceAccountI
 	}
 
 	// Delete follow request notification
-	return r.state.DB.DeleteNotifications(ctx, []string{
-		string(gtsmodel.NotificationFollowRequest),
+	return r.state.DB.DeleteNotifications(ctx, []gtsmodel.NotificationType{
+		gtsmodel.NotificationFollowRequest,
 	}, targetAccountID, sourceAccountID)
 }
 
diff --git a/internal/db/notification.go b/internal/db/notification.go
index deb58835a..c962023be 100644
--- a/internal/db/notification.go
+++ b/internal/db/notification.go
@@ -29,7 +29,7 @@ type Notification interface {
 	//
 	// Returned notifications will be ordered ID descending (ie., highest/newest to lowest/oldest).
 	// If types is empty, *all* notification types will be included.
-	GetAccountNotifications(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, types []string, excludeTypes []string) ([]*gtsmodel.Notification, error)
+	GetAccountNotifications(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, types []gtsmodel.NotificationType, excludeTypes []gtsmodel.NotificationType) ([]*gtsmodel.Notification, error)
 
 	// GetNotificationByID returns one notification according to its id.
 	GetNotificationByID(ctx context.Context, id string) (*gtsmodel.Notification, error)
@@ -64,7 +64,7 @@ type Notification interface {
 	// originate from originAccountID will be deleted.
 	//
 	// At least one parameter must not be an empty string.
-	DeleteNotifications(ctx context.Context, types []string, targetAccountID string, originAccountID string) error
+	DeleteNotifications(ctx context.Context, types []gtsmodel.NotificationType, targetAccountID string, originAccountID string) error
 
 	// DeleteNotificationsForStatus deletes all notifications that relate to
 	// the given statusID. This function is useful when a status has been deleted,
diff --git a/internal/gtsmodel/accountsettings.go b/internal/gtsmodel/accountsettings.go
index 3151ba5b7..4624aa0b1 100644
--- a/internal/gtsmodel/accountsettings.go
+++ b/internal/gtsmodel/accountsettings.go
@@ -26,7 +26,7 @@ type AccountSettings struct {
 	AccountID                      string             `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                    // AccountID that owns this settings.
 	CreatedAt                      time.Time          `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created.
 	UpdatedAt                      time.Time          `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated.
-	Privacy                        Visibility         `bun:",nullzero"`                                                   // Default post privacy for this account
+	Privacy                        Visibility         `bun:",nullzero,default:3"`                                         // Default post privacy for this account
 	Sensitive                      *bool              `bun:",nullzero,notnull,default:false"`                             // Set posts from this account to sensitive by default?
 	Language                       string             `bun:",nullzero,notnull,default:'en'"`                              // What language does this account post in?
 	StatusContentType              string             `bun:",nullzero"`                                                   // What is the default format for statuses posted by this account (only for local accounts).
@@ -34,7 +34,7 @@ type AccountSettings struct {
 	CustomCSS                      string             `bun:",nullzero"`                                                   // Custom CSS that should be displayed for this Account's profile and statuses.
 	EnableRSS                      *bool              `bun:",nullzero,notnull,default:false"`                             // enable RSS feed subscription for this account's public posts at [URL]/feed
 	HideCollections                *bool              `bun:",nullzero,notnull,default:false"`                             // Hide this account's followers/following collections.
-	WebVisibility                  Visibility         `bun:",nullzero,notnull,default:public"`                            // Visibility level of statuses that visitors can view via the web profile.
+	WebVisibility                  Visibility         `bun:",nullzero,notnull,default:3"`                                 // Visibility level of statuses that visitors can view via the web profile.
 	InteractionPolicyDirect        *InteractionPolicy `bun:""`                                                            // Interaction policy to use for new direct visibility statuses by this account. If null, assume default policy.
 	InteractionPolicyMutualsOnly   *InteractionPolicy `bun:""`                                                            // Interaction policy to use for new mutuals only visibility statuses. If null, assume default policy.
 	InteractionPolicyFollowersOnly *InteractionPolicy `bun:""`                                                            // Interaction policy to use for new followers only visibility statuses. If null, assume default policy.
diff --git a/internal/gtsmodel/common.go b/internal/gtsmodel/common.go
new file mode 100644
index 000000000..e740bbb81
--- /dev/null
+++ b/internal/gtsmodel/common.go
@@ -0,0 +1,24 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package gtsmodel
+
+// enumType is the type we (at least, should) use
+// for database enum types. it is the largest size
+// supported by a PostgreSQL SMALLINT, since an
+// SQLite SMALLINT is actually variable in size.
+type enumType int16
diff --git a/internal/gtsmodel/interactionpolicy.go b/internal/gtsmodel/interactionpolicy.go
index d8d890e69..7fcafc80d 100644
--- a/internal/gtsmodel/interactionpolicy.go
+++ b/internal/gtsmodel/interactionpolicy.go
@@ -224,7 +224,7 @@ func DefaultInteractionPolicyFor(v Visibility) *InteractionPolicy {
 	case VisibilityDirect:
 		return DefaultInteractionPolicyDirect()
 	default:
-		panic("visibility " + v + " not recognized")
+		panic("invalid visibility")
 	}
 }
 
diff --git a/internal/gtsmodel/notification.go b/internal/gtsmodel/notification.go
index 5cf6b061a..49f1fe2bb 100644
--- a/internal/gtsmodel/notification.go
+++ b/internal/gtsmodel/notification.go
@@ -34,20 +34,51 @@ type Notification struct {
 	Read             *bool            `bun:",nullzero,notnull,default:false"`                             // Notification has been seen/read
 }
 
-// NotificationType describes the reason/type of this notification.
-type NotificationType string
+// NotificationType describes the
+// reason/type of this notification.
+type NotificationType enumType
 
-// Notification Types
 const (
-	NotificationFollow        NotificationType = "follow"            // NotificationFollow -- someone followed you
-	NotificationFollowRequest NotificationType = "follow_request"    // NotificationFollowRequest -- someone requested to follow you
-	NotificationMention       NotificationType = "mention"           // NotificationMention -- someone mentioned you in their status
-	NotificationReblog        NotificationType = "reblog"            // NotificationReblog -- someone boosted one of your statuses
-	NotificationFave          NotificationType = "favourite"         // NotificationFave -- someone faved/liked one of your statuses
-	NotificationPoll          NotificationType = "poll"              // NotificationPoll -- a poll you voted in or created has ended
-	NotificationStatus        NotificationType = "status"            // NotificationStatus -- someone you enabled notifications for has posted a status.
-	NotificationSignup        NotificationType = "admin.sign_up"     // NotificationSignup -- someone has submitted a new account sign-up to the instance.
-	NotificationPendingFave   NotificationType = "pending.favourite" // Someone has faved a status of yours, which requires approval by you.
-	NotificationPendingReply  NotificationType = "pending.reply"     // Someone has replied to a status of yours, which requires approval by you.
-	NotificationPendingReblog NotificationType = "pending.reblog"    // Someone has boosted a status of yours, which requires approval by you.
+	// Notification Types
+	NotificationFollow        NotificationType = 1  // NotificationFollow -- someone followed you
+	NotificationFollowRequest NotificationType = 2  // NotificationFollowRequest -- someone requested to follow you
+	NotificationMention       NotificationType = 3  // NotificationMention -- someone mentioned you in their status
+	NotificationReblog        NotificationType = 4  // NotificationReblog -- someone boosted one of your statuses
+	NotificationFave          NotificationType = 5  // NotificationFave -- someone faved/liked one of your statuses
+	NotificationPoll          NotificationType = 6  // NotificationPoll -- a poll you voted in or created has ended
+	NotificationStatus        NotificationType = 7  // NotificationStatus -- someone you enabled notifications for has posted a status.
+	NotificationSignup        NotificationType = 8  // NotificationSignup -- someone has submitted a new account sign-up to the instance.
+	NotificationPendingFave   NotificationType = 9  // Someone has faved a status of yours, which requires approval by you.
+	NotificationPendingReply  NotificationType = 10 // Someone has replied to a status of yours, which requires approval by you.
+	NotificationPendingReblog NotificationType = 11 // Someone has boosted a status of yours, which requires approval by you.
 )
+
+// String returns a stringified, frontend API compatible form of NotificationType.
+func (t NotificationType) String() string {
+	switch t {
+	case NotificationFollow:
+		return "follow"
+	case NotificationFollowRequest:
+		return "follow_request"
+	case NotificationMention:
+		return "mention"
+	case NotificationReblog:
+		return "reblog"
+	case NotificationFave:
+		return "favourite"
+	case NotificationPoll:
+		return "poll"
+	case NotificationStatus:
+		return "status"
+	case NotificationSignup:
+		return "admin.sign_up"
+	case NotificationPendingFave:
+		return "pending.favourite"
+	case NotificationPendingReply:
+		return "pending.reply"
+	case NotificationPendingReblog:
+		return "pending.reblog"
+	default:
+		panic("invalid notification type")
+	}
+}
diff --git a/internal/gtsmodel/status.go b/internal/gtsmodel/status.go
index 91c0ada61..f8bd068ab 100644
--- a/internal/gtsmodel/status.go
+++ b/internal/gtsmodel/status.go
@@ -263,27 +263,58 @@ type StatusToEmoji struct {
 	Emoji    *Emoji  `bun:"rel:belongs-to"`
 }
 
-// Visibility represents the visibility granularity of a status.
-type Visibility string
+// Visibility represents the
+// visibility granularity of a status.
+type Visibility enumType
 
 const (
 	// VisibilityNone means nobody can see this.
 	// It's only used for web status visibility.
-	VisibilityNone Visibility = "none"
-	// VisibilityPublic means this status will be visible to everyone on all timelines.
-	VisibilityPublic Visibility = "public"
-	// VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists.
-	VisibilityUnlocked Visibility = "unlocked"
+	VisibilityNone Visibility = 1
+
+	// VisibilityPublic means this status will
+	// be visible to everyone on all timelines.
+	VisibilityPublic Visibility = 2
+
+	// VisibilityUnlocked means this status will be visible to everyone,
+	// but will only show on home timeline to followers, and in lists.
+	VisibilityUnlocked Visibility = 3
+
 	// VisibilityFollowersOnly means this status is viewable to followers only.
-	VisibilityFollowersOnly Visibility = "followers_only"
-	// VisibilityMutualsOnly means this status is visible to mutual followers only.
-	VisibilityMutualsOnly Visibility = "mutuals_only"
-	// VisibilityDirect means this status is visible only to mentioned recipients.
-	VisibilityDirect Visibility = "direct"
+	VisibilityFollowersOnly Visibility = 4
+
+	// VisibilityMutualsOnly means this status
+	// is visible to mutual followers only.
+	VisibilityMutualsOnly Visibility = 5
+
+	// VisibilityDirect means this status is
+	// visible only to mentioned recipients.
+	VisibilityDirect Visibility = 6
+
 	// VisibilityDefault is used when no other setting can be found.
 	VisibilityDefault Visibility = VisibilityUnlocked
 )
 
+// String returns a stringified, frontend API compatible form of Visibility.
+func (v Visibility) String() string {
+	switch v {
+	case VisibilityNone:
+		return "none"
+	case VisibilityPublic:
+		return "public"
+	case VisibilityUnlocked:
+		return "unlocked"
+	case VisibilityFollowersOnly:
+		return "followers_only"
+	case VisibilityMutualsOnly:
+		return "mutuals_only"
+	case VisibilityDirect:
+		return "direct"
+	default:
+		panic("invalid visibility")
+	}
+}
+
 // Content models the simple string content
 // of a status along with its ContentMap,
 // which contains content entries keyed by
diff --git a/internal/processing/preferences.go b/internal/processing/preferences.go
index 0a5f566ae..fb445ec5b 100644
--- a/internal/processing/preferences.go
+++ b/internal/processing/preferences.go
@@ -46,7 +46,7 @@ func (p *Processor) PreferencesGet(ctx context.Context, accountID string) (*apim
 func mastoPrefVisibility(vis gtsmodel.Visibility) string {
 	switch vis {
 	case gtsmodel.VisibilityPublic, gtsmodel.VisibilityDirect:
-		return string(vis)
+		return vis.String()
 	case gtsmodel.VisibilityUnlocked:
 		return "unlisted"
 	default:
diff --git a/internal/processing/status/create.go b/internal/processing/status/create.go
index fbc2dadf7..ef8f8aa56 100644
--- a/internal/processing/status/create.go
+++ b/internal/processing/status/create.go
@@ -372,7 +372,7 @@ func (p *Processor) processVisibility(
 
 	// Fall back to account default, set
 	// this back on the form for later use.
-	case accountDefaultVis != "":
+	case accountDefaultVis != 0:
 		status.Visibility = accountDefaultVis
 		form.Visibility = p.converter.VisToAPIVis(ctx, accountDefaultVis)
 
diff --git a/internal/processing/timeline/notification.go b/internal/processing/timeline/notification.go
index 34e6d865d..92dbf851f 100644
--- a/internal/processing/timeline/notification.go
+++ b/internal/processing/timeline/notification.go
@@ -41,8 +41,8 @@ func (p *Processor) NotificationsGet(
 	sinceID string,
 	minID string,
 	limit int,
-	types []string,
-	excludeTypes []string,
+	types []gtsmodel.NotificationType,
+	excludeTypes []gtsmodel.NotificationType,
 ) (*apimodel.PageableResponse, gtserror.WithCode) {
 	notifs, err := p.state.DB.GetAccountNotifications(
 		ctx,
diff --git a/internal/processing/workers/surfacenotify.go b/internal/processing/workers/surfacenotify.go
index 872ccca65..1520d2ec0 100644
--- a/internal/processing/workers/surfacenotify.go
+++ b/internal/processing/workers/surfacenotify.go
@@ -542,7 +542,7 @@ func getNotifyLockURI(
 ) string {
 	builder := strings.Builder{}
 	builder.WriteString("notification:?")
-	builder.WriteString("type=" + string(notificationType))
+	builder.WriteString("type=" + notificationType.String())
 	builder.WriteString("&target=" + targetAccount.URI)
 	builder.WriteString("&origin=" + originAccount.URI)
 	if statusID != "" {
diff --git a/internal/typeutils/frontendtointernal.go b/internal/typeutils/frontendtointernal.go
index 1f7d1877e..82957ee05 100644
--- a/internal/typeutils/frontendtointernal.go
+++ b/internal/typeutils/frontendtointernal.go
@@ -41,7 +41,7 @@ func APIVisToVis(m apimodel.Visibility) gtsmodel.Visibility {
 	case apimodel.VisibilityNone:
 		return gtsmodel.VisibilityNone
 	}
-	return ""
+	return 0
 }
 
 func APIMarkerNameToMarkerName(m apimodel.MarkerName) gtsmodel.MarkerName {
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index 5f919f014..750d4eec4 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -1862,7 +1862,7 @@ func (c *Converter) NotificationToAPINotification(
 
 	return &apimodel.Notification{
 		ID:        n.ID,
-		Type:      string(n.NotificationType),
+		Type:      n.NotificationType.String(),
 		CreatedAt: util.FormatISO8601(n.CreatedAt),
 		Account:   apiAccount,
 		Status:    apiStatus,