diff --git a/netbox/client.go b/netbox/client.go
index 2542c82758490c117ff0832715bc46856965e9fc..194079a2cbebf2eac8414ee5895dcac28b69d4be 100644
--- a/netbox/client.go
+++ b/netbox/client.go
@@ -35,6 +35,9 @@ type Client struct {
 	// IPAM provides access to methods in NetBox's IPAM API.
 	IPAM *IPAMService
 
+	// Tenancy provides access to methods in NetBox's Tenancy API.
+	Tenancy *TenancyService
+
 	u      *url.URL
 	client *http.Client
 }
@@ -68,6 +71,7 @@ func NewClient(addr string, client *http.Client) (*Client, error) {
 
 	c.DCIM = &DCIMService{c: c}
 	c.IPAM = &IPAMService{c: c}
+	c.Tenancy = NewTenancyService(c)
 
 	return c, nil
 }
diff --git a/netbox/client_test.go b/netbox/client_test.go
index 02d65f2ff7f1f4982a66248a0297e8e3fb5f884f..4170a338e1a19b4b25ca7353360e2c6cc3ea42d0 100644
--- a/netbox/client_test.go
+++ b/netbox/client_test.go
@@ -223,6 +223,10 @@ func testClient(t *testing.T, fn func(w http.ResponseWriter, r *http.Request)) (
 }
 
 func testHandler(t *testing.T, method string, path string, v interface{}) http.HandlerFunc {
+	return testStatusHandler(t, method, path, v, 0)
+}
+
+func testStatusHandler(t *testing.T, method string, path string, v interface{}, statusCode int) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		if want, got := method, r.Method; want != got {
 			t.Fatalf("unexpected HTTP method:\n- want: %v\n-  got: %v", want, got)
@@ -232,8 +236,56 @@ func testHandler(t *testing.T, method string, path string, v interface{}) http.H
 			t.Fatalf("unexpected URL path:\n- want: %v\n-  got: %v", want, got)
 		}
 
+		if statusCode > 0 {
+			w.WriteHeader(statusCode)
+		}
+
 		if err := json.NewEncoder(w).Encode(v); err != nil {
 			t.Fatalf("error while encoding JSON: %v", err)
 		}
 	}
 }
+
+func testTenantGroupCreate(n int) *TenantGroup {
+	return &TenantGroup{
+		Name: fmt.Sprintf("Tenant Group %d", n),
+		Slug: fmt.Sprintf("tenant-group-%d", n),
+	}
+}
+
+func testTenantGroup(n int) *TenantGroup {
+	return &TenantGroup{
+		ID:   n,
+		Name: fmt.Sprintf("Tenant Group %d", n),
+		Slug: fmt.Sprintf("tenant-group-%d", n),
+	}
+}
+
+func testTenant(n int) *Tenant {
+	return testTenantWithGroup(n, testTenantGroup(n))
+}
+
+func testTenantCreate(n int) *Tenant {
+	return testTenantWithGroupCreate(n, testTenantGroup(n))
+}
+
+func testTenantWithGroupCreate(n int, t *TenantGroup) *Tenant {
+	return &Tenant{
+		Name:        fmt.Sprintf("Tenant %d", n),
+		Slug:        fmt.Sprintf("tenant-%d", n),
+		Description: fmt.Sprintf("Tenant %d Description", n),
+		Comments:    fmt.Sprintf("Tenant %d Comments", n),
+		Group:       t,
+	}
+}
+
+func testTenantWithGroup(n int, t *TenantGroup) *Tenant {
+	return &Tenant{
+		ID:          n,
+		Name:        fmt.Sprintf("Tenant %d", n),
+		Slug:        fmt.Sprintf("tenant-%d", n),
+		Description: fmt.Sprintf("Tenant %d Description", n),
+		Comments:    fmt.Sprintf("Tenant %d Comments", n),
+		Group:       t,
+	}
+}
diff --git a/netbox/generate_basic_tests.go b/netbox/generate_basic_tests.go
new file mode 100644
index 0000000000000000000000000000000000000000..80d879b73416b7dbfdba24b4e68b82c80ef04028
--- /dev/null
+++ b/netbox/generate_basic_tests.go
@@ -0,0 +1,383 @@
+// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//+build ignore
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"go/format"
+	"log"
+	"os"
+	"text/template"
+	"time"
+)
+
+func main() {
+	typeName := flag.String("type-name", "Example", "Name of the type to use (e.g. TenantGroup).")
+	serviceName := flag.String("service-name", "ExampleService", "Name of the service to create (e.g. TenantGroupsService).")
+	endpoint := flag.String("endpoint", "tenancy", "Name of the endpoint (e.g. dcim, ipam, tenancy).")
+	service := flag.String("service", "example", "Name of the service below endpoint (e.g. tenant-groups).")
+	clientEndpoint := flag.String("client-endpoint", "Tenancy", "Name of the client endpoint (e.g. DCIM, IPAM, Tenancy).")
+	clientService := flag.String("client-service", "TenantGroups", "Name of the client service (e.g. TenantGroups, Tenants).")
+	withoutListOpts := flag.Bool("without-list-opts", false, "Disable list options for this endpoint.")
+
+	flag.Parse()
+
+	b := &bytes.Buffer{}
+	functionsTemplate.Execute(b, struct {
+		Timestamp      time.Time
+		TypeName       string
+		ServiceName    string
+		Endpoint       string
+		Service        string
+		ClientEndpoint string
+		ClientService  string
+		ListOpts       bool
+		JSONTag        func(string) string
+	}{
+		Timestamp:      time.Now(),
+		TypeName:       *typeName,
+		ServiceName:    *serviceName,
+		Endpoint:       *endpoint,
+		Service:        *service,
+		ClientEndpoint: *clientEndpoint,
+		ClientService:  *clientService,
+		ListOpts:       !*withoutListOpts,
+		JSONTag:        func(name string) string { return "`json:\"" + name + "\"`" },
+	})
+
+	// go fmt
+	res, err := format.Source(b.Bytes())
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	f, err := os.Create(fmt.Sprintf("%s_%s_basic_test.go", *endpoint, *service))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer f.Close()
+	_, err = f.Write(res)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+var functionsTemplate = template.Must(template.New("").Parse(`// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by generate_basic_tests.go. DO NOT EDIT.
+
+package netbox
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+// Using this to override MarshalJSON
+// In all cases when posting data to netbox-API, the {{ .TypeName }}.MarshalJSON is what you want,
+// but not here as a return in testHandler
+type serverData{{ .TypeName }} {{ .TypeName }}
+
+func convertToServerData{{ .TypeName }}(data []*{{ .TypeName }}) []*serverData{{ .TypeName }} {
+	dataWant := make([]*serverData{{ .TypeName }}, len(data))
+	for i := range data {
+		tmp := serverData{{ .TypeName }}(*data[i])
+		dataWant[i] = &tmp
+	}
+	return dataWant
+}
+
+func TestBasic{{ .TypeName }}Get(t *testing.T) {
+	var tests = []struct {
+		desc      string
+		want      *{{ .TypeName }}
+	}{
+		{
+			desc:      "Simple {{ .TypeName }}",
+			want:      test{{ .TypeName }}(1),
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			serverData := serverData{{ .TypeName }}(*tt.want)
+
+			c, done := testClient(t, testHandler(t, http.MethodGet, "/api/{{ .Endpoint }}/{{ .Service }}/1/", &serverData))
+			defer done()
+
+			res, err := c.{{ .ClientEndpoint }}.{{ .ClientService }}.Get(1)
+			if err != nil {
+				t.Fatalf("unexpected error from Client.{{ .ClientEndpoint }}.{{ .ClientService }}.Get: %v", err)
+			}
+
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected {{ .TypeName }}:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasic{{ .TypeName }}Get404(t *testing.T) {
+	c, done := testClient(t, testStatusHandler(t, http.MethodGet, "/api/{{ .Endpoint }}/{{ .Service }}/1/", &struct {
+		Detail string {{ call .JSONTag "detail" }}
+	}{
+		Detail: "Not found.",
+	},
+		http.StatusNotFound))
+	defer done()
+
+	res, err := c.{{ .ClientEndpoint }}.{{ .ClientService }}.Get(1)
+	errstr := "404 - Not found."
+	if want, got := errors.New(errstr), err; !reflect.DeepEqual(want, got) {
+		t.Fatalf("unexpected error from Client.{{ .ClientEndpoint }}.{{ .ClientService }}.Get:\n- want: %v\n-  got: %v", want, got)
+	}
+
+	if res != nil {
+		t.Fatalf("unexpected result:\n- want: %v\n-  got: %v", nil, res)
+	}
+}
+
+func TestBasicListExtract{{ .TypeName }}(t *testing.T) {
+	want := []*{{ .TypeName }}{
+		test{{ .TypeName }}(1),
+		test{{ .TypeName }}(2),
+	}
+	serverWant := convertToServerData{{ .TypeName }}(want)
+	serverData, _ := json.Marshal(serverWant)
+	c, done := testClient(t, testHandler(t, http.MethodGet, "/api/{{ .Endpoint }}/{{ .Service }}/", &pageData{
+		Count:       2,
+		NextURL:     "",
+		PreviousURL: "",
+		Results:     serverData,
+	}))
+	defer done()
+
+	{{ if .ListOpts -}}
+	page := c.{{ .ClientEndpoint }}.{{ .ClientService }}.List(nil)
+	{{ else }}
+	page := c.{{ .ClientEndpoint }}.{{ .ClientService }}.List()
+	{{ end }}
+	if page == nil {
+		t.Fatalf("unexpexted result from c.{{ .ClientEndpoint }}.{{ .ClientService }}.List.")
+	}
+
+	got := []*{{ .TypeName }}{}
+	counter := 0
+	for page.Next() {
+		var err error
+		got, err = c.{{ .ClientEndpoint }}.{{ .ClientService }}.Extract(page)
+		if err != nil {
+			t.Fatalf("unexpected error from c.{{ .ClientEndpoint }}.{{ .ClientService }}.Extract: %v", err)
+		}
+		counter = counter + 1
+		if counter > 2 { // Safe guard
+			break
+		}
+	}
+	if counter != 1 {
+		t.Fatalf("unexpected page count:\n- want: 1\n-  got: %d", counter)
+	}
+
+	if !reflect.DeepEqual(want, got) {
+		t.Fatalf("unexpected result:\n- want: %v\n-  got: %v", want, got)
+	}
+
+	if page.Err() != nil {
+		t.Fatalf("unexpected error from page:\n- want: %v\n-  got: %v", want, got)
+	}
+}
+
+func TestBasicCreate{{ .TypeName }}(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *{{ .TypeName }}
+		want       int
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Create with ID 0",
+			data:       test{{ .TypeName }}Create(1),
+			want:       1,
+			status:     0,
+			errstr:     "",
+			serverData: test{{ .TypeName }}(1),
+		},
+		{
+			desc:   "Create duplicate",
+			data:   test{{ .TypeName }}Create(1),
+			want:   0,
+			status: http.StatusBadRequest,
+			errstr: "400 - {\"name\":[\"{{ .ServiceName }} with this name already exists.\"]}\n",
+			serverData: &struct {
+				Name []string {{ call .JSONTag "name" }}
+			}{
+				Name: []string{"{{ .ServiceName }} with this name already exists."},
+			},
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			c, done := testClient(t, testStatusHandler(t, http.MethodPost, "/api/{{ .Endpoint }}/{{ .Service }}/", tt.serverData, tt.status))
+			defer done()
+
+			var terr error
+			if tt.errstr != "" {
+				terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
+			}
+
+			res, err := c.{{ .ClientEndpoint }}.{{ .ClientService }}.Create(tt.data)
+			if want, got := terr, err; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected error:\n- want: %v\n-  got: %v", want, got)
+			}
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected {{ .TypeName }}:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasicUpdate{{ .TypeName }}(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *{{ .TypeName }}
+		want       int
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Update with ID 1",
+			data:       test{{ .TypeName }}(1),
+			want:       1,
+			serverData: test{{ .TypeName }}(1),
+			status:     0,
+			errstr:     "",
+		},
+		{
+			desc: "Update not found",
+			data: test{{ .TypeName }}(1),
+			want: 0,
+			serverData: &struct {
+				Detail string
+			}{
+				Detail: "Not found.",
+			},
+			status: http.StatusNotFound,
+			errstr: "404 - Not found.",
+		},
+		{
+			desc: "Update to duplicate",
+			data: test{{ .TypeName }}(1),
+			want: 0,
+			serverData: &struct {
+				Name []string {{ call .JSONTag "name" }}
+			}{
+				Name: []string{"{{ .ServiceName }} with this name already exists."},
+			},
+			status: http.StatusBadRequest,
+			errstr: "400 - {\"name\":[\"{{ .ServiceName }} with this name already exists.\"]}\n",
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			c, done := testClient(t, testStatusHandler(t, http.MethodPatch, "/api/{{ .Endpoint }}/{{ .Service }}/1/", tt.serverData, tt.status))
+			defer done()
+
+			var terr error
+			if tt.errstr != "" {
+				terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
+			}
+
+			res, err := c.{{ .ClientEndpoint }}.{{ .ClientService }}.Update(tt.data)
+			if want, got := terr, err; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected error:\n- want: %v\n-  got: %v", want, got)
+			}
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected {{ .TypeName }}:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasicDelete{{ .TypeName }}(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *{{ .TypeName }}
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Delete ID 1",
+			data:       test{{ .TypeName }}(1),
+			serverData: test{{ .TypeName }}(1),
+			status:     0,
+			errstr:     "",
+		},
+		{
+			desc: "Delete not Found",
+			data: test{{ .TypeName }}(1),
+			serverData: &struct {
+				Detail string {{ call .JSONTag "detail" }}
+			}{
+				Detail: "Not found.",
+			},
+			status: http.StatusNotFound,
+			errstr: "404 - Not found.",
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			c, done := testClient(t, testStatusHandler(t, http.MethodDelete, "/api/{{ .Endpoint }}/{{ .Service }}/1/", tt.serverData, tt.status))
+			defer done()
+
+			var terr error
+			if tt.errstr != "" {
+				terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
+			}
+
+			err := c.{{ .ClientEndpoint }}.{{ .ClientService }}.Delete(tt.data)
+			if want, got := terr, err; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected error:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+`))
diff --git a/netbox/generate_functions.go b/netbox/generate_functions.go
new file mode 100644
index 0000000000000000000000000000000000000000..9411f8c3b8c218d65d1319d8d8bed90f6d907839
--- /dev/null
+++ b/netbox/generate_functions.go
@@ -0,0 +1,204 @@
+// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//+build ignore
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"go/format"
+	"log"
+	"os"
+	"text/template"
+	"time"
+)
+
+func main() {
+	typeName := flag.String("type-name", "Example", "Name of the type to use (e.g. TenantGroup).")
+	serviceName := flag.String("service-name", "ExampleService", "Name of the service to create (e.g. TenantGroupsService).")
+	endpoint := flag.String("endpoint", "tenancy", "Name of the endpoint (e.g. dcim, ipam, tenancy).")
+	service := flag.String("service", "example", "Name of the service below endpoint (e.g. tenant-groups).")
+	updateTypeName := flag.String("update-type-name", "", "Name of the type to use for creates and updates, to change the marshal behavior. Default typeName.")
+	withoutListOpts := flag.Bool("without-list-opts", false, "Disable list options for this endpoint.")
+
+	flag.Parse()
+
+	if *updateTypeName == "" {
+		*updateTypeName = *typeName
+	}
+
+	b := &bytes.Buffer{}
+	functionsTemplate.Execute(b, struct {
+		Timestamp      time.Time
+		TypeName       string
+		UpdateTypeName string
+		ServiceName    string
+		Endpoint       string
+		Service        string
+		ListOpts       bool
+	}{
+		Timestamp:      time.Now(),
+		TypeName:       *typeName,
+		UpdateTypeName: *updateTypeName,
+		ServiceName:    *serviceName,
+		Endpoint:       *endpoint,
+		Service:        *service,
+		ListOpts:       !*withoutListOpts,
+	})
+
+	// go fmt
+	res, err := format.Source(b.Bytes())
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	f, err := os.Create(fmt.Sprintf("%s_%s.go", *endpoint, *service))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer f.Close()
+	_, err = f.Write(res)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+var functionsTemplate = template.Must(template.New("").Parse(`// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by generate_functions.go. DO NOT EDIT.
+
+package netbox
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+)
+
+// {{ .ServiceName }} is used in a Client to access NetBox's {{ .Endpoint }}/{{ .Service }} API methods.
+type {{ .ServiceName }} struct {
+	c *Client
+}
+
+// Get retrieves an {{ .TypeName }} object from NetBox by its ID.
+func (s *{{ .ServiceName }}) Get(id int) (*{{ .TypeName }}, error) {
+	req, err := s.c.NewRequest(
+		http.MethodGet,
+		fmt.Sprintf("api/{{ .Endpoint }}/{{ .Service }}/%d/", id),
+		nil,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	t := new({{ .TypeName }})
+	err = s.c.Do(req, t)
+	if err != nil {
+		return nil, err
+	}
+	return t, nil
+}
+
+// List returns a Page associated with an NetBox API Endpoint.
+{{ if .ListOpts -}}
+func (s *{{ .ServiceName }}) List(options *List{{ .TypeName }}Options) *Page {
+	return NewPage(s.c, "api/{{ .Endpoint }}/{{ .Service }}/", options)
+}
+{{ else -}}
+func (s *{{ .ServiceName }}) List() *Page {
+	return NewPage(s.c, "api/{{ .Endpoint }}/{{ .Service }}/", nil)
+}
+{{ end }}
+
+// Extract retrives a list of {{ .TypeName }} objects from page.
+func (s *{{ .ServiceName }}) Extract(page *Page) ([]*{{ .TypeName }}, error) {
+	if err := page.Err(); err != nil {
+		return nil, err
+	}
+
+	var groups []*{{ .TypeName }}
+	if err := json.Unmarshal(page.data.Results, &groups); err != nil {
+		return nil, err
+	}
+	return groups, nil
+}
+
+// Create creates a new {{ .TypeName }} object in NetBox and returns the ID of the new object.
+func (s *{{ .ServiceName }}) Create(data *{{ .TypeName }}) (int, error) {
+	req, err := s.c.NewJSONRequest(http.MethodPost, "api/{{ .Endpoint }}/{{ .Service }}/", nil, data)
+	if err != nil {
+		return 0, err
+	}
+
+	g := new({{ .UpdateTypeName }})
+	err = s.c.Do(req, g)
+	if err != nil {
+		return 0, err
+	}
+	return g.ID, nil
+}
+
+// Update changes an existing {{ .TypeName }} object in NetBox, and returns the ID of the new object.
+func (s *{{ .ServiceName }}) Update(data *{{ .TypeName }}) (int, error) {
+	req, err := s.c.NewJSONRequest(
+		http.MethodPatch,
+		fmt.Sprintf("api/{{ .Endpoint }}/{{ .Service }}/%d/", data.ID),
+		nil,
+		data,
+	)
+	if err != nil {
+		return 0, err
+	}
+
+	// g is just used to verify correct api result.
+	// data is not changed, because the g is not the full representation that one would
+	// get with Get. But if the response was unmarshaled into {{ .UpdateTypeName }} correctly,
+	// everything went fine, and we do not need to update data.
+	g := new({{ .UpdateTypeName }})
+	err = s.c.Do(req, g)
+	if err != nil {
+		return 0, err
+	}
+	return g.ID, nil
+}
+
+// Delete deletes an existing {{ .TypeName }} object from NetBox.
+func (s *{{ .ServiceName }}) Delete(data *{{ .TypeName }}) error {
+	req, err := s.c.NewRequest(
+		http.MethodDelete,
+		fmt.Sprintf("api/{{ .Endpoint }}/{{ .Service }}/%d/", data.ID),
+		nil,
+	)
+	if err != nil {
+		return err
+	}
+
+	return s.c.Do(req, nil)
+}
+`))
diff --git a/netbox/tenancy.go b/netbox/tenancy.go
new file mode 100644
index 0000000000000000000000000000000000000000..b44124f2a8a1211831becba9475d548d1cb2e81f
--- /dev/null
+++ b/netbox/tenancy.go
@@ -0,0 +1,35 @@
+// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package netbox
+
+// A TenancyService is udes in a Client to access NetBox's Tenancy API methods.
+type TenancyService struct {
+	c            *Client
+	TenantGroups *TenantGroupsService
+	Tenants      *TenantsService
+}
+
+// NewTenancyService returns a TenancyService initialized with all sub-services.
+func NewTenancyService(client *Client) *TenancyService {
+	return &TenancyService{
+		c: client,
+		TenantGroups: &TenantGroupsService{
+			c: client,
+		},
+		Tenants: &TenantsService{
+			c: client,
+		},
+	}
+}
diff --git a/netbox/tenancy_tenant-groups.go b/netbox/tenancy_tenant-groups.go
new file mode 100644
index 0000000000000000000000000000000000000000..8e1a62ad2a14fbf0aeb041c8f16596f2318da7b4
--- /dev/null
+++ b/netbox/tenancy_tenant-groups.go
@@ -0,0 +1,118 @@
+// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by generate_functions.go. DO NOT EDIT.
+
+package netbox
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+)
+
+// TenantGroupsService is used in a Client to access NetBox's tenancy/tenant-groups API methods.
+type TenantGroupsService struct {
+	c *Client
+}
+
+// Get retrieves an TenantGroup object from NetBox by its ID.
+func (s *TenantGroupsService) Get(id int) (*TenantGroup, error) {
+	req, err := s.c.NewRequest(
+		http.MethodGet,
+		fmt.Sprintf("api/tenancy/tenant-groups/%d/", id),
+		nil,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	t := new(TenantGroup)
+	err = s.c.Do(req, t)
+	if err != nil {
+		return nil, err
+	}
+	return t, nil
+}
+
+// List returns a Page associated with an NetBox API Endpoint.
+func (s *TenantGroupsService) List() *Page {
+	return NewPage(s.c, "api/tenancy/tenant-groups/", nil)
+}
+
+// Extract retrives a list of TenantGroup objects from page.
+func (s *TenantGroupsService) Extract(page *Page) ([]*TenantGroup, error) {
+	if err := page.Err(); err != nil {
+		return nil, err
+	}
+
+	var groups []*TenantGroup
+	if err := json.Unmarshal(page.data.Results, &groups); err != nil {
+		return nil, err
+	}
+	return groups, nil
+}
+
+// Create creates a new TenantGroup object in NetBox and returns the ID of the new object.
+func (s *TenantGroupsService) Create(data *TenantGroup) (int, error) {
+	req, err := s.c.NewJSONRequest(http.MethodPost, "api/tenancy/tenant-groups/", nil, data)
+	if err != nil {
+		return 0, err
+	}
+
+	g := new(TenantGroup)
+	err = s.c.Do(req, g)
+	if err != nil {
+		return 0, err
+	}
+	return g.ID, nil
+}
+
+// Update changes an existing TenantGroup object in NetBox, and returns the ID of the new object.
+func (s *TenantGroupsService) Update(data *TenantGroup) (int, error) {
+	req, err := s.c.NewJSONRequest(
+		http.MethodPatch,
+		fmt.Sprintf("api/tenancy/tenant-groups/%d/", data.ID),
+		nil,
+		data,
+	)
+	if err != nil {
+		return 0, err
+	}
+
+	// g is just used to verify correct api result.
+	// data is not changed, because the g is not the full representation that one would
+	// get with Get. But if the response was unmarshaled into TenantGroup correctly,
+	// everything went fine, and we do not need to update data.
+	g := new(TenantGroup)
+	err = s.c.Do(req, g)
+	if err != nil {
+		return 0, err
+	}
+	return g.ID, nil
+}
+
+// Delete deletes an existing TenantGroup object from NetBox.
+func (s *TenantGroupsService) Delete(data *TenantGroup) error {
+	req, err := s.c.NewRequest(
+		http.MethodDelete,
+		fmt.Sprintf("api/tenancy/tenant-groups/%d/", data.ID),
+		nil,
+	)
+	if err != nil {
+		return err
+	}
+
+	return s.c.Do(req, nil)
+}
diff --git a/netbox/tenancy_tenant-groups_basic_test.go b/netbox/tenancy_tenant-groups_basic_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2e3322e38d8a969f7887bec47ef8ac5ee61ed8c7
--- /dev/null
+++ b/netbox/tenancy_tenant-groups_basic_test.go
@@ -0,0 +1,299 @@
+// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by generate_functions.go. DO NOT EDIT.
+
+package netbox
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+// Using this to override MarshalJSON
+// In all cases when posting data to netbox-API, the TenantGroup.MarshalJSON is what you want,
+// but not here as a return in testHandler
+type serverDataTenantGroup TenantGroup
+
+func convertToServerDataTenantGroup(data []*TenantGroup) []*serverDataTenantGroup {
+	dataWant := make([]*serverDataTenantGroup, len(data))
+	for i := range data {
+		tmp := serverDataTenantGroup(*data[i])
+		dataWant[i] = &tmp
+	}
+	return dataWant
+}
+
+func TestBasicTenantGroupGet(t *testing.T) {
+	var tests = []struct {
+		desc string
+		want *TenantGroup
+	}{
+		{
+			desc: "Simple TenantGroup",
+			want: testTenantGroup(1),
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			serverData := serverDataTenantGroup(*tt.want)
+
+			c, done := testClient(t, testHandler(t, http.MethodGet, "/api/tenancy/tenant-groups/1/", &serverData))
+			defer done()
+
+			res, err := c.Tenancy.TenantGroups.Get(1)
+			if err != nil {
+				t.Fatalf("unexpected error from Client.Tenancy.TenantGroups.Get: %v", err)
+			}
+
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected TenantGroup:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasicTenantGroupGet404(t *testing.T) {
+	c, done := testClient(t, testStatusHandler(t, http.MethodGet, "/api/tenancy/tenant-groups/1/", &struct {
+		Detail string `json:"detail"`
+	}{
+		Detail: "Not found.",
+	},
+		http.StatusNotFound))
+	defer done()
+
+	res, err := c.Tenancy.TenantGroups.Get(1)
+	errstr := "404 - Not found."
+	if want, got := errors.New(errstr), err; !reflect.DeepEqual(want, got) {
+		t.Fatalf("unexpected error from Client.Tenancy.TenantGroups.Get:\n- want: %v\n-  got: %v", want, got)
+	}
+
+	if res != nil {
+		t.Fatalf("unexpected result:\n- want: %v\n-  got: %v", nil, res)
+	}
+}
+
+func TestBasicListExtractTenantGroup(t *testing.T) {
+	want := []*TenantGroup{
+		testTenantGroup(1),
+		testTenantGroup(2),
+	}
+	serverWant := convertToServerDataTenantGroup(want)
+	serverData, _ := json.Marshal(serverWant)
+	c, done := testClient(t, testHandler(t, http.MethodGet, "/api/tenancy/tenant-groups/", &pageData{
+		Count:       2,
+		NextURL:     "",
+		PreviousURL: "",
+		Results:     serverData,
+	}))
+	defer done()
+
+	page := c.Tenancy.TenantGroups.List()
+
+	if page == nil {
+		t.Fatalf("unexpexted result from c.Tenancy.TenantGroups.List.")
+	}
+
+	got := []*TenantGroup{}
+	counter := 0
+	for page.Next() {
+		var err error
+		got, err = c.Tenancy.TenantGroups.Extract(page)
+		if err != nil {
+			t.Fatalf("unexpected error from c.Tenancy.TenantGroups.Extract: %v", err)
+		}
+		counter = counter + 1
+		if counter > 2 { // Safe guard
+			break
+		}
+	}
+	if counter != 1 {
+		t.Fatalf("unexpected page count:\n- want: 1\n-  got: %d", counter)
+	}
+
+	if !reflect.DeepEqual(want, got) {
+		t.Fatalf("unexpected result:\n- want: %v\n-  got: %v", want, got)
+	}
+
+	if page.Err() != nil {
+		t.Fatalf("unexpected error from page:\n- want: %v\n-  got: %v", want, got)
+	}
+}
+
+func TestBasicCreateTenantGroup(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *TenantGroup
+		want       int
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Create with ID 0",
+			data:       testTenantGroupCreate(1),
+			want:       1,
+			status:     0,
+			errstr:     "",
+			serverData: testTenantGroup(1),
+		},
+		{
+			desc:   "Create duplicate",
+			data:   testTenantGroupCreate(1),
+			want:   0,
+			status: http.StatusBadRequest,
+			errstr: "400 - {\"name\":[\"TenantGroupsService with this name already exists.\"]}\n",
+			serverData: &struct {
+				Name []string `json:"name"`
+			}{
+				Name: []string{"TenantGroupsService with this name already exists."},
+			},
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			c, done := testClient(t, testStatusHandler(t, http.MethodPost, "/api/tenancy/tenant-groups/", tt.serverData, tt.status))
+			defer done()
+
+			var terr error
+			if tt.errstr != "" {
+				terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
+			}
+
+			res, err := c.Tenancy.TenantGroups.Create(tt.data)
+			if want, got := terr, err; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected error:\n- want: %v\n-  got: %v", want, got)
+			}
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected TenantGroup:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasicUpdateTenantGroup(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *TenantGroup
+		want       int
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Update with ID 1",
+			data:       testTenantGroup(1),
+			want:       1,
+			serverData: testTenantGroup(1),
+			status:     0,
+			errstr:     "",
+		},
+		{
+			desc: "Update not found",
+			data: testTenantGroup(1),
+			want: 0,
+			serverData: &struct {
+				Detail string
+			}{
+				Detail: "Not found.",
+			},
+			status: http.StatusNotFound,
+			errstr: "404 - Not found.",
+		},
+		{
+			desc: "Update to duplicate",
+			data: testTenantGroup(1),
+			want: 0,
+			serverData: &struct {
+				Name []string `json:"name"`
+			}{
+				Name: []string{"TenantGroupsService with this name already exists."},
+			},
+			status: http.StatusBadRequest,
+			errstr: "400 - {\"name\":[\"TenantGroupsService with this name already exists.\"]}\n",
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			c, done := testClient(t, testStatusHandler(t, http.MethodPatch, "/api/tenancy/tenant-groups/1/", tt.serverData, tt.status))
+			defer done()
+
+			var terr error
+			if tt.errstr != "" {
+				terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
+			}
+
+			res, err := c.Tenancy.TenantGroups.Update(tt.data)
+			if want, got := terr, err; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected error:\n- want: %v\n-  got: %v", want, got)
+			}
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected TenantGroup:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasicDeleteTenantGroup(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *TenantGroup
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Delete ID 1",
+			data:       testTenantGroup(1),
+			serverData: testTenantGroup(1),
+			status:     0,
+			errstr:     "",
+		},
+		{
+			desc: "Delete not Found",
+			data: testTenantGroup(1),
+			serverData: &struct {
+				Detail string `json:"detail"`
+			}{
+				Detail: "Not found.",
+			},
+			status: http.StatusNotFound,
+			errstr: "404 - Not found.",
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			c, done := testClient(t, testStatusHandler(t, http.MethodDelete, "/api/tenancy/tenant-groups/1/", tt.serverData, tt.status))
+			defer done()
+
+			var terr error
+			if tt.errstr != "" {
+				terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
+			}
+
+			err := c.Tenancy.TenantGroups.Delete(tt.data)
+			if want, got := terr, err; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected error:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
diff --git a/netbox/tenancy_tenant-groups_test.go b/netbox/tenancy_tenant-groups_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..351110344a90abdfce71e44f165528b4c22c95a8
--- /dev/null
+++ b/netbox/tenancy_tenant-groups_test.go
@@ -0,0 +1,83 @@
+// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package netbox
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"testing"
+)
+
+func TestTenantGroupUnmarshalJSON(t *testing.T) {
+	var tests = []struct {
+		desc string
+		data []byte
+		want *TenantGroup
+	}{
+		{
+			desc: "full",
+			data: []byte(`{ "id": 1, "name": "Tenant Group 1", "slug": "tenant-group-1", "custom_fields": {} }`),
+			want: testTenantGroup(1),
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			result := new(TenantGroup)
+			err := json.Unmarshal(tt.data, result)
+			if err != nil {
+				t.Fatalf("unexpected error from TenantGroup.UnmarshalJSON: %v", err)
+			}
+
+			if want, got := tt.want, result; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected TenantGroup:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestTenantGroupMarshalJSON(t *testing.T) {
+	var tests = []struct {
+		desc string
+		data *TenantGroup
+		want []byte
+	}{
+		{
+			desc: "With TenantGroup.ID",
+			data: testTenantGroup(1),
+			want: []byte(`{"id":1,"name":"Tenant Group 1","slug":"tenant-group-1"}`),
+		},
+		{
+			desc: "No TenantGroup.ID",
+			data: testTenantGroup(0),
+			want: []byte(`{"name":"Tenant Group 0","slug":"tenant-group-0"}`),
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			result, err := json.Marshal(tt.data)
+			if err != nil {
+				t.Fatalf("unexpected error from TenantGroup.MarshalJSON: %v", err)
+			}
+
+			if want, got := tt.want, result; bytes.Compare(want, got) != 0 {
+				t.Fatalf("unexpected TenantGroup:\n- want: %s\n-  got: %s", want, got)
+			}
+		})
+	}
+}
diff --git a/netbox/tenancy_tenant-groups_types.go b/netbox/tenancy_tenant-groups_types.go
new file mode 100644
index 0000000000000000000000000000000000000000..813ef83d5829e3894af75bf591d3016cb6bcaa8f
--- /dev/null
+++ b/netbox/tenancy_tenant-groups_types.go
@@ -0,0 +1,25 @@
+// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package netbox
+
+// A TenantGroup is a representation of netbox tenant-groups
+type TenantGroup struct {
+	ID   int    `json:"id,omitempty"`
+	Name string `json:"name"`
+	Slug string `json:"slug"`
+}
+
+//go:generate go run generate_functions.go -type-name TenantGroup -service-name TenantGroupsService -endpoint tenancy -service tenant-groups -without-list-opts
+//go:generate go run generate_basic_tests.go -type-name TenantGroup -service-name TenantGroupsService -endpoint tenancy -service tenant-groups -client-endpoint Tenancy -client-service TenantGroups -without-list-opts
diff --git a/netbox/tenancy_tenants.go b/netbox/tenancy_tenants.go
new file mode 100644
index 0000000000000000000000000000000000000000..0c265acf8db817f25fa03143c90327f1bdfa3def
--- /dev/null
+++ b/netbox/tenancy_tenants.go
@@ -0,0 +1,118 @@
+// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by generate_functions.go. DO NOT EDIT.
+
+package netbox
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+)
+
+// TenantsService is used in a Client to access NetBox's tenancy/tenants API methods.
+type TenantsService struct {
+	c *Client
+}
+
+// Get retrieves an Tenant object from NetBox by its ID.
+func (s *TenantsService) Get(id int) (*Tenant, error) {
+	req, err := s.c.NewRequest(
+		http.MethodGet,
+		fmt.Sprintf("api/tenancy/tenants/%d/", id),
+		nil,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	t := new(Tenant)
+	err = s.c.Do(req, t)
+	if err != nil {
+		return nil, err
+	}
+	return t, nil
+}
+
+// List returns a Page associated with an NetBox API Endpoint.
+func (s *TenantsService) List(options *ListTenantOptions) *Page {
+	return NewPage(s.c, "api/tenancy/tenants/", options)
+}
+
+// Extract retrives a list of Tenant objects from page.
+func (s *TenantsService) Extract(page *Page) ([]*Tenant, error) {
+	if err := page.Err(); err != nil {
+		return nil, err
+	}
+
+	var groups []*Tenant
+	if err := json.Unmarshal(page.data.Results, &groups); err != nil {
+		return nil, err
+	}
+	return groups, nil
+}
+
+// Create creates a new Tenant object in NetBox and returns the ID of the new object.
+func (s *TenantsService) Create(data *Tenant) (int, error) {
+	req, err := s.c.NewJSONRequest(http.MethodPost, "api/tenancy/tenants/", nil, data)
+	if err != nil {
+		return 0, err
+	}
+
+	g := new(updateTenant)
+	err = s.c.Do(req, g)
+	if err != nil {
+		return 0, err
+	}
+	return g.ID, nil
+}
+
+// Update changes an existing Tenant object in NetBox, and returns the ID of the new object.
+func (s *TenantsService) Update(data *Tenant) (int, error) {
+	req, err := s.c.NewJSONRequest(
+		http.MethodPatch,
+		fmt.Sprintf("api/tenancy/tenants/%d/", data.ID),
+		nil,
+		data,
+	)
+	if err != nil {
+		return 0, err
+	}
+
+	// g is just used to verify correct api result.
+	// data is not changed, because the g is not the full representation that one would
+	// get with Get. But if the response was unmarshaled into updateTenant correctly,
+	// everything went fine, and we do not need to update data.
+	g := new(updateTenant)
+	err = s.c.Do(req, g)
+	if err != nil {
+		return 0, err
+	}
+	return g.ID, nil
+}
+
+// Delete deletes an existing Tenant object from NetBox.
+func (s *TenantsService) Delete(data *Tenant) error {
+	req, err := s.c.NewRequest(
+		http.MethodDelete,
+		fmt.Sprintf("api/tenancy/tenants/%d/", data.ID),
+		nil,
+	)
+	if err != nil {
+		return err
+	}
+
+	return s.c.Do(req, nil)
+}
diff --git a/netbox/tenancy_tenants_basic_test.go b/netbox/tenancy_tenants_basic_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e26756f54541e2f987bbaf66b60977033db5b968
--- /dev/null
+++ b/netbox/tenancy_tenants_basic_test.go
@@ -0,0 +1,299 @@
+// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by generate_functions.go. DO NOT EDIT.
+
+package netbox
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+// Using this to override MarshalJSON
+// In all cases when posting data to netbox-API, the Tenant.MarshalJSON is what you want,
+// but not here as a return in testHandler
+type serverDataTenant Tenant
+
+func convertToServerDataTenant(data []*Tenant) []*serverDataTenant {
+	dataWant := make([]*serverDataTenant, len(data))
+	for i := range data {
+		tmp := serverDataTenant(*data[i])
+		dataWant[i] = &tmp
+	}
+	return dataWant
+}
+
+func TestBasicTenantGet(t *testing.T) {
+	var tests = []struct {
+		desc string
+		want *Tenant
+	}{
+		{
+			desc: "Simple Tenant",
+			want: testTenant(1),
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			serverData := serverDataTenant(*tt.want)
+
+			c, done := testClient(t, testHandler(t, http.MethodGet, "/api/tenancy/tenants/1/", &serverData))
+			defer done()
+
+			res, err := c.Tenancy.Tenants.Get(1)
+			if err != nil {
+				t.Fatalf("unexpected error from Client.Tenancy.Tenants.Get: %v", err)
+			}
+
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected Tenant:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasicTenantGet404(t *testing.T) {
+	c, done := testClient(t, testStatusHandler(t, http.MethodGet, "/api/tenancy/tenants/1/", &struct {
+		Detail string `json:"detail"`
+	}{
+		Detail: "Not found.",
+	},
+		http.StatusNotFound))
+	defer done()
+
+	res, err := c.Tenancy.Tenants.Get(1)
+	errstr := "404 - Not found."
+	if want, got := errors.New(errstr), err; !reflect.DeepEqual(want, got) {
+		t.Fatalf("unexpected error from Client.Tenancy.Tenants.Get:\n- want: %v\n-  got: %v", want, got)
+	}
+
+	if res != nil {
+		t.Fatalf("unexpected result:\n- want: %v\n-  got: %v", nil, res)
+	}
+}
+
+func TestBasicListExtractTenant(t *testing.T) {
+	want := []*Tenant{
+		testTenant(1),
+		testTenant(2),
+	}
+	serverWant := convertToServerDataTenant(want)
+	serverData, _ := json.Marshal(serverWant)
+	c, done := testClient(t, testHandler(t, http.MethodGet, "/api/tenancy/tenants/", &pageData{
+		Count:       2,
+		NextURL:     "",
+		PreviousURL: "",
+		Results:     serverData,
+	}))
+	defer done()
+
+	page := c.Tenancy.Tenants.List(nil)
+
+	if page == nil {
+		t.Fatalf("unexpexted result from c.Tenancy.Tenants.List.")
+	}
+
+	got := []*Tenant{}
+	counter := 0
+	for page.Next() {
+		var err error
+		got, err = c.Tenancy.Tenants.Extract(page)
+		if err != nil {
+			t.Fatalf("unexpected error from c.Tenancy.Tenants.Extract: %v", err)
+		}
+		counter = counter + 1
+		if counter > 2 { // Safe guard
+			break
+		}
+	}
+	if counter != 1 {
+		t.Fatalf("unexpected page count:\n- want: 1\n-  got: %d", counter)
+	}
+
+	if !reflect.DeepEqual(want, got) {
+		t.Fatalf("unexpected result:\n- want: %v\n-  got: %v", want, got)
+	}
+
+	if page.Err() != nil {
+		t.Fatalf("unexpected error from page:\n- want: %v\n-  got: %v", want, got)
+	}
+}
+
+func TestBasicCreateTenant(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *Tenant
+		want       int
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Create with ID 0",
+			data:       testTenantCreate(1),
+			want:       1,
+			status:     0,
+			errstr:     "",
+			serverData: testTenant(1),
+		},
+		{
+			desc:   "Create duplicate",
+			data:   testTenantCreate(1),
+			want:   0,
+			status: http.StatusBadRequest,
+			errstr: "400 - {\"name\":[\"TenantsService with this name already exists.\"]}\n",
+			serverData: &struct {
+				Name []string `json:"name"`
+			}{
+				Name: []string{"TenantsService with this name already exists."},
+			},
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			c, done := testClient(t, testStatusHandler(t, http.MethodPost, "/api/tenancy/tenants/", tt.serverData, tt.status))
+			defer done()
+
+			var terr error
+			if tt.errstr != "" {
+				terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
+			}
+
+			res, err := c.Tenancy.Tenants.Create(tt.data)
+			if want, got := terr, err; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected error:\n- want: %v\n-  got: %v", want, got)
+			}
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected Tenant:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasicUpdateTenant(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *Tenant
+		want       int
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Update with ID 1",
+			data:       testTenant(1),
+			want:       1,
+			serverData: testTenant(1),
+			status:     0,
+			errstr:     "",
+		},
+		{
+			desc: "Update not found",
+			data: testTenant(1),
+			want: 0,
+			serverData: &struct {
+				Detail string
+			}{
+				Detail: "Not found.",
+			},
+			status: http.StatusNotFound,
+			errstr: "404 - Not found.",
+		},
+		{
+			desc: "Update to duplicate",
+			data: testTenant(1),
+			want: 0,
+			serverData: &struct {
+				Name []string `json:"name"`
+			}{
+				Name: []string{"TenantsService with this name already exists."},
+			},
+			status: http.StatusBadRequest,
+			errstr: "400 - {\"name\":[\"TenantsService with this name already exists.\"]}\n",
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			c, done := testClient(t, testStatusHandler(t, http.MethodPatch, "/api/tenancy/tenants/1/", tt.serverData, tt.status))
+			defer done()
+
+			var terr error
+			if tt.errstr != "" {
+				terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
+			}
+
+			res, err := c.Tenancy.Tenants.Update(tt.data)
+			if want, got := terr, err; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected error:\n- want: %v\n-  got: %v", want, got)
+			}
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected Tenant:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasicDeleteTenant(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *Tenant
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Delete ID 1",
+			data:       testTenant(1),
+			serverData: testTenant(1),
+			status:     0,
+			errstr:     "",
+		},
+		{
+			desc: "Delete not Found",
+			data: testTenant(1),
+			serverData: &struct {
+				Detail string `json:"detail"`
+			}{
+				Detail: "Not found.",
+			},
+			status: http.StatusNotFound,
+			errstr: "404 - Not found.",
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			c, done := testClient(t, testStatusHandler(t, http.MethodDelete, "/api/tenancy/tenants/1/", tt.serverData, tt.status))
+			defer done()
+
+			var terr error
+			if tt.errstr != "" {
+				terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint
+			}
+
+			err := c.Tenancy.Tenants.Delete(tt.data)
+			if want, got := terr, err; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected error:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
diff --git a/netbox/tenancy_tenants_test.go b/netbox/tenancy_tenants_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b46f9ead3bf34212a9b2ea5b7c1e0471825a3cde
--- /dev/null
+++ b/netbox/tenancy_tenants_test.go
@@ -0,0 +1,194 @@
+// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package netbox
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+	"reflect"
+	"testing"
+)
+
+func TestTenantGet(t *testing.T) {
+	var tests = []struct {
+		desc      string
+		want      *Tenant
+		wantGroup *TenantGroup
+	}{
+		{
+			desc:      "Without TenantGroup",
+			want:      testTenantWithGroup(1, nil),
+			wantGroup: nil,
+		},
+		{
+			desc:      "With TenantGroup",
+			want:      testTenantWithGroup(1, testTenantGroup(1)),
+			wantGroup: testTenantGroup(1),
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			serverData := serverDataTenant(*tt.want)
+
+			c, done := testClient(t, testHandler(t, http.MethodGet, "/api/tenancy/tenants/1/", &serverData))
+			defer done()
+
+			res, err := c.Tenancy.Tenants.Get(1)
+			if err != nil {
+				t.Fatalf("unexpected error from Client.Tenancy.Tenants.Get: %v", err)
+			}
+
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected Tenant:\n- want: %v\n-  got: %v", want, got)
+			}
+			if want, got := tt.wantGroup, res.Group; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected TenantGroup:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestTenantUnmarshalJSON(t *testing.T) {
+	var tests = []struct {
+		desc string
+		data []byte
+		want *Tenant
+	}{
+		{
+			desc: "Nil Group",
+			data: []byte(`{ "id": 1, "name": "Tenant 1", "slug": "tenant-1", "group": null, "description": "Tenant 1 Description", "comments": "Tenant 1 Comments", "custom_fields": {} }`),
+			want: testTenantWithGroup(1, nil),
+		},
+		{
+			desc: "With Group",
+			data: []byte(`{ "id": 1, "name": "Tenant 1", "slug": "tenant-1", "group": {"id": 1, "name": "Tenant Group 1", "slug": "tenant-group-1"}, "description": "Tenant 1 Description", "comments": "Tenant 1 Comments", "custom_fields": {} }`),
+			want: testTenant(1),
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			result := new(Tenant)
+			err := json.Unmarshal(tt.data, result)
+			if err != nil {
+				t.Fatalf("unexpected error from Tenant.UnmarshalJSON: %v", err)
+			}
+
+			if want, got := tt.want, result; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected Tenant:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestTenantMarshalJSON(t *testing.T) {
+	var tests = []struct {
+		desc string
+		data *Tenant
+		want []byte
+	}{
+		{
+			desc: "Nil Group",
+			data: testTenantWithGroup(1, nil),
+			want: []byte(`{"id":1,"name":"Tenant 1","slug":"tenant-1","description":"Tenant 1 Description","comments":"Tenant 1 Comments"}`),
+		},
+		{
+			desc: "With Group",
+			data: testTenant(1),
+			want: []byte(`{"id":1,"name":"Tenant 1","slug":"tenant-1","description":"Tenant 1 Description","comments":"Tenant 1 Comments","group":1}`),
+		},
+		{
+			desc: "No Tenant.ID",
+			data: testTenantWithGroup(0, nil),
+			want: []byte(`{"name":"Tenant 0","slug":"tenant-0","description":"Tenant 0 Description","comments":"Tenant 0 Comments"}`),
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			result, err := json.Marshal(tt.data)
+			if err != nil {
+				t.Fatalf("unexpected error from Tenant.MarshalJSON: %v", err)
+			}
+
+			if want, got := tt.want, result; bytes.Compare(want, got) != 0 {
+				t.Fatalf("unexpected Tenant:\n- want: %s\n-  got: %s", want, got)
+			}
+		})
+	}
+}
+
+func TestListTenantOptions(t *testing.T) {
+	var tests = []struct {
+		desc string
+		o    *ListTenantOptions
+		v    url.Values
+	}{
+		{
+			desc: "empty options",
+		},
+		{
+			desc: "full options",
+			o: &ListTenantOptions{
+				Name:    "Hello",
+				IDIn:    "1,2,3",
+				GroupID: 1,
+				Query:   "World",
+			},
+			v: url.Values{
+				"name":     []string{"Hello"},
+				"id__in":   []string{"1,2,3"},
+				"group_id": []string{"1"},
+				"q":        []string{"World"},
+			},
+		},
+		{
+			desc: "group vs group_id",
+			o: &ListTenantOptions{
+				GroupID: 1,
+				Group:   "Group1",
+			},
+			v: url.Values{
+				"group_id": []string{"1"},
+			},
+		},
+		{
+			desc: "group name",
+			o: &ListTenantOptions{
+				Group: "Group 1",
+			},
+			v: url.Values{
+				"group": []string{"Group 1"},
+			},
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			v, err := tt.o.Values()
+			if err != nil {
+				t.Fatalf("unexpected Values error: %v", err)
+			}
+
+			if want, got := tt.v, v; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected url.Values map:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
diff --git a/netbox/tenancy_tenants_types.go b/netbox/tenancy_tenants_types.go
new file mode 100644
index 0000000000000000000000000000000000000000..ea64d3de37e63a22a20e0a8c569eb76452e09eec
--- /dev/null
+++ b/netbox/tenancy_tenants_types.go
@@ -0,0 +1,103 @@
+// Copyright 2017 The go-netbox Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package netbox
+
+import (
+	"encoding/json"
+	"net/url"
+	"strconv"
+)
+
+// A Tenant is a representation of netbox tenants
+type Tenant struct {
+	ID          int          `json:"id,omitempty"`
+	Name        string       `json:"name"`
+	Slug        string       `json:"slug"`
+	Description string       `json:"description"`
+	Comments    string       `json:"comments"`
+	Group       *TenantGroup `json:"Group"`
+}
+
+// A updateTenant is the internal representation of tenants
+// needed to POST/PUT/PATCH to Netbox API
+type updateTenant struct {
+	ID          int    `json:"id,omitempty"`
+	Name        string `json:"name"`
+	Slug        string `json:"slug"`
+	Description string `json:"description"`
+	Comments    string `json:"comments"`
+	Group       int    `json:"group,omitempty"`
+}
+
+// MarshalJSON marshals an Tenant into JSON bytes.
+func (t *Tenant) MarshalJSON() ([]byte, error) {
+	group := 0
+	if t.Group != nil {
+		group = t.Group.ID
+	}
+	return json.Marshal(updateTenant{
+		ID:          t.ID,
+		Name:        t.Name,
+		Slug:        t.Slug,
+		Description: t.Description,
+		Comments:    t.Comments,
+		Group:       group,
+	})
+}
+
+// ListTenantOptions is used as an argument for Client.Tenancy.Tenant.List.
+// Integer fields with an *ID suffix are preferred over their string
+// counterparts, and if both are set, only the *ID field will be used.
+type ListTenantOptions struct {
+	Name    string
+	IDIn    string
+	GroupID int
+	Group   string
+
+	Query string
+}
+
+// Values generates a url.Values map from the data in ListTenantOptions.
+func (o *ListTenantOptions) Values() (url.Values, error) {
+	if o == nil {
+		return nil, nil
+	}
+
+	v := url.Values{}
+
+	switch {
+	case o.GroupID != 0:
+		v.Set("group_id", strconv.Itoa(o.GroupID))
+	case o.Group != "":
+		v.Set("group", o.Group)
+	}
+
+	if o.Name != "" {
+		v.Set("name", o.Name)
+	}
+
+	if o.IDIn != "" {
+		v.Set("id__in", o.IDIn)
+	}
+
+	if o.Query != "" {
+		v.Set("q", o.Query)
+	}
+
+	return v, nil
+}
+
+//go:generate go run generate_functions.go -type-name Tenant -update-type-name updateTenant -service-name TenantsService -endpoint tenancy -service tenants
+//go:generate go run generate_basic_tests.go -type-name Tenant -service-name TenantsService -endpoint tenancy -service tenants -client-endpoint Tenancy -client-service Tenants