diff --git a/fact/fact.go b/fact/fact.go
new file mode 100644
index 0000000000000000000000000000000000000000..5771376cc168b14d9888d79f4b6eda36e907a89e
--- /dev/null
+++ b/fact/fact.go
@@ -0,0 +1,35 @@
+package fact
+
+import "errors"
+
+type Fact struct {
+	Fact string
+	T    FactType
+}
+
+func NewFact(ft FactType, fact string) (Fact, error) {
+	//todo: filter the fact string
+	return Fact{
+		Fact: fact,
+		T:    ft,
+	}, nil
+}
+
+// marshal is for transmission for UDB, not a part of the fact interface
+func (f Fact) Stringify() string {
+	return f.T.Stringify() + f.Fact
+}
+
+func UnstringifyFact(s string) (Fact, error) {
+	if len(s) < 1 {
+		return Fact{}, errors.New("stringified facts must at least have a type at the start")
+	}
+	T := s[:1]
+	fact := s[1:]
+	ft, err := UnstringifyFactType(T)
+	if err != nil {
+		return Fact{}, err
+	}
+
+	return NewFact(ft, fact)
+}
diff --git a/fact/factList.go b/fact/factList.go
new file mode 100644
index 0000000000000000000000000000000000000000..21d0edbdde09a3d1cba1876ceac3a1ba2252b9bd
--- /dev/null
+++ b/fact/factList.go
@@ -0,0 +1,44 @@
+package fact
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"strings"
+)
+
+type FactList []Fact
+
+const factDelimiter = ","
+const factBreak = ";"
+
+func (fl FactList) Stringify() string {
+	stringList := make([]string, len(fl))
+	for index, f := range fl {
+		stringList[index] = f.Stringify()
+	}
+
+	return strings.Join(stringList, factDelimiter) + factBreak
+}
+
+// unstrignifys facts followed by a facts break and with arbatrary data
+// atttached at the end
+func UnstringifyFactList(s string) (FactList, string, error) {
+	parts := strings.SplitN(s, factBreak, 2)
+	if len(parts) != 2 {
+		return nil, "", errors.New("Invalid fact string passed")
+	}
+	factStrings := strings.Split(parts[0], factDelimiter)
+
+	var factList []Fact
+	for _, fString := range factStrings {
+		fact, err := UnstringifyFact(fString)
+		if err != nil {
+			jww.WARN.Printf("Fact failed to unstringify, dropped: %s",
+				err)
+		} else {
+			factList = append(factList, fact)
+		}
+
+	}
+	return factList, parts[1], nil
+}
diff --git a/fact/factList_test.go b/fact/factList_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c9ca485aee26cba37bae3614bd6a16aa3c44654a
--- /dev/null
+++ b/fact/factList_test.go
@@ -0,0 +1,30 @@
+package fact
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestFactList_StringifyUnstringify(t *testing.T) {
+	expected := FactList{}
+	expected = append(expected, Fact{
+		Fact: "vivian@elixxir.io",
+		T:    Email,
+	})
+	expected = append(expected, Fact{
+		Fact: "(270) 301-5797",
+		T:    Phone,
+	})
+
+	FlString := expected.Stringify()
+	// Manually check and verify that the string version is as expected
+	t.Log(FlString)
+
+	actual, _, err := UnstringifyFactList(FlString)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !reflect.DeepEqual(actual, expected) {
+		t.Error("fact lists weren't equal")
+	}
+}
diff --git a/fact/fact_test.go b/fact/fact_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b89e12c042abf1bbff7a9b2272a0485f7b4ba313
--- /dev/null
+++ b/fact/fact_test.go
@@ -0,0 +1,65 @@
+package fact
+
+import (
+	"reflect"
+	"testing"
+)
+
+// Test NewFact() function returns a correctly formatted Fact
+func TestNewFact(t *testing.T) {
+	// Expected result
+	e := Fact{
+		Fact: "testing",
+		T:    1,
+	}
+
+	g, err := NewFact(Email, "testing")
+	if err != nil {
+		t.Error(err)
+	}
+
+	if !reflect.DeepEqual(e, g) {
+		t.Errorf("The returned Fact did not match the expected Fact")
+	}
+}
+
+// Test Stringify() creates a string of the Fact
+// The output is verified to work in the test below
+func TestFact_Stringify(t *testing.T) {
+	f := Fact{
+		Fact: "testing",
+		T:    1,
+	}
+
+	expected := "Etesting"
+	got := f.Stringify()
+	t.Log(got)
+
+	if got != expected {
+		t.Errorf("Marshalled object from Got did not match Expected.\n\tGot: %v\n\tExpected: %v", got, expected)
+	}
+}
+
+// Test the UnstringifyFact function creates a Fact from a string
+// NOTE: this test does not pass, with error "Unknown Fact FactType: Etesting"
+func TestFact_UnstringifyFact(t *testing.T) {
+	// Expected fact from above test
+	e := Fact{
+		Fact: "testing",
+		T:    Email,
+	}
+
+	// Stringify-ed Fact from above test
+	m := "Etesting"
+	f, err := UnstringifyFact(m)
+	if err != nil {
+		t.Error(err)
+	}
+
+	t.Log(f.Fact)
+	t.Log(f.T)
+
+	if !reflect.DeepEqual(e, f) {
+		t.Errorf("The returned Fact did not match the expected Fact")
+	}
+}
diff --git a/fact/type.go b/fact/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..32a48e3614da00cb720a62fdc2e30cb5da5976cd
--- /dev/null
+++ b/fact/type.go
@@ -0,0 +1,57 @@
+package fact
+
+import (
+	"fmt"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+)
+
+type FactType uint8
+
+const (
+	Username FactType = 0
+	Email    FactType = 1
+	Phone    FactType = 2
+)
+
+func (t FactType) String() string {
+	switch t {
+	case Username:
+		return "Username"
+	case Email:
+		return "Email"
+	case Phone:
+		return "Phone"
+	default:
+		return fmt.Sprintf("Unknown Fact FactType: %d", t)
+	}
+}
+
+func (t FactType) Stringify() string {
+	switch t {
+	case Username:
+		return "U"
+	case Email:
+		return "E"
+	case Phone:
+		return "P"
+	}
+	jww.FATAL.Panicf("Unknown Fact FactType: %d", t)
+	return "error"
+}
+
+func UnstringifyFactType(s string) (FactType, error) {
+	switch s {
+	case "U":
+		return Username, nil
+	case "E":
+		return Email, nil
+	case "P":
+		return Phone, nil
+	}
+	return 3, errors.Errorf("Unknown Fact FactType: %s", s)
+}
+
+func (t FactType) IsValid() bool {
+	return t == Username || t == Email || t == Phone
+}
diff --git a/fact/type_test.go b/fact/type_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3a1e7d136a8735d9fdeaa5dc088a695f786bf080
--- /dev/null
+++ b/fact/type_test.go
@@ -0,0 +1,60 @@
+package fact
+
+import (
+	"testing"
+)
+
+func TestFactType_String(t *testing.T) {
+	// FactTypes and expected strings for them
+	FTs := []FactType{Username, Email, Phone, FactType(200)}
+	Strs := []string{"Username", "Email", "Phone", "Unknown Fact FactType: 200"}
+	for i, ft := range FTs {
+		if FactType.String(ft) != Strs[i] {
+			t.Errorf("Got unexpected string for FactType.\n\tGot: %s\n\tExpected: %s", FactType.String(ft), Strs[i])
+		}
+	}
+}
+
+func TestFactType_Stringify(t *testing.T) {
+	// FactTypes and expected strings for them
+	FTs := []FactType{Username, Email, Phone}
+	Strs := []string{"U", "E", "P"}
+	for i, ft := range FTs {
+		if FactType.Stringify(ft) != Strs[i] {
+			t.Errorf("Got unexpected string for FactType.\n\tGot: %s\n\tExpected: %s", FactType.Stringify(ft), Strs[i])
+		}
+	}
+}
+
+func TestFactType_Unstringify(t *testing.T) {
+	// FactTypes and expected strings for them
+	FTs := []FactType{Username, Email, Phone}
+	Strs := []string{"U", "E", "P"}
+	for i, ft := range FTs {
+		gotft, err := UnstringifyFactType(Strs[i])
+		if err != nil {
+			t.Error(err)
+		}
+		if gotft != ft {
+			t.Errorf("Got unexpected string for FactType.\n\tGot: %s\n\tExpected: %s", FactType.Stringify(ft), Strs[i])
+		}
+	}
+
+	_, err := UnstringifyFactType("x")
+	if err == nil {
+		t.Errorf("UnstringifyFactType did not return an error on an invalid type")
+	}
+}
+
+func TestFactType_IsValid(t *testing.T) {
+	if !FactType.IsValid(Username) ||
+		!FactType.IsValid(Email) ||
+		!FactType.IsValid(Phone) {
+
+		t.Errorf("FactType.IsValid did not report a FactType as valid")
+	}
+
+	if FactType.IsValid(FactType(200)) {
+		t.Errorf("FactType.IsValid reported a non-valid FactType value as valid")
+	}
+}