diff --git a/Sources/XXClient/Models/FactType.swift b/Sources/XXClient/Models/FactType.swift
new file mode 100644
index 0000000000000000000000000000000000000000..96caa0ff9ad7bb1ed326e3621305099eb276fc0a
--- /dev/null
+++ b/Sources/XXClient/Models/FactType.swift
@@ -0,0 +1,39 @@
+import Foundation
+
+public enum FactType: Equatable {
+  case username
+  case email
+  case phone
+  case other(Int)
+
+  public static let knownTypes: [FactType] = [.username, .email, .phone]
+
+  public init(rawValue: Int) {
+    if let known = FactType.knownTypes.first(where: { $0.rawValue == rawValue }) {
+      self = known
+    } else {
+      self = .other(rawValue)
+    }
+  }
+
+  public var rawValue: Int {
+    switch self {
+    case .username: return 0
+    case .email: return 1
+    case .phone: return 2
+    case .other(let rawValue): return rawValue
+    }
+  }
+}
+
+extension FactType: Codable {
+  public init(from decoder: Decoder) throws {
+    let container = try decoder.singleValueContainer()
+    self.init(rawValue: try container.decode(Int.self))
+  }
+
+  public func encode(to encoder: Encoder) throws {
+    var container = encoder.singleValueContainer()
+    try container.encode(rawValue)
+  }
+}
diff --git a/Tests/XXClientTests/Models/FactTypeTests.swift b/Tests/XXClientTests/Models/FactTypeTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f8c3347be43605cff8d2b240ae9d3bcbcd4631ed
--- /dev/null
+++ b/Tests/XXClientTests/Models/FactTypeTests.swift
@@ -0,0 +1,44 @@
+import CustomDump
+import Foundation
+import XCTest
+@testable import XXClient
+
+final class FactTypeTests: XCTestCase {
+  func testDecoding() throws {
+    let decoder = Foundation.JSONDecoder()
+
+    XCTAssertNoDifference(
+      [
+        try decoder.decode(FactType.self, from: "0".data(using: .utf8)!),
+        try decoder.decode(FactType.self, from: "1".data(using: .utf8)!),
+        try decoder.decode(FactType.self, from: "2".data(using: .utf8)!),
+        try decoder.decode(FactType.self, from: "3".data(using: .utf8)!),
+      ],
+      [
+        FactType.username,
+        FactType.email,
+        FactType.phone,
+        FactType.other(3),
+      ]
+    )
+  }
+
+  func testEncoding() throws {
+    let encoder = Foundation.JSONEncoder()
+
+    XCTAssertNoDifference(
+      [
+        try encoder.encode(FactType.username),
+        try encoder.encode(FactType.email),
+        try encoder.encode(FactType.phone),
+        try encoder.encode(FactType.other(3)),
+      ],
+      [
+        "0".data(using: .utf8)!,
+        "1".data(using: .utf8)!,
+        "2".data(using: .utf8)!,
+        "3".data(using: .utf8)!,
+      ]
+    )
+  }
+}