diff --git a/AUTHORS b/AUTHORS
index 30dd8446ea96de4099e613e62712f6e854ca6fa8..df81c2d80358ee4b210d198349daa67a96e9ef23 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -11,3 +11,4 @@ Contributors
 ------------
 Allan Liu <aliu@digitalocean.com>
 Dave Cameron <dcameron@digitalocean.com>
+Quan D. Hoang <hdquan2014@gmail.com>
diff --git a/netbox/dcim.go b/netbox/dcim.go
index 0f955bd01e51c7197122fadd79123d353df3ecf6..28f88d28074682a0f78e80cb39f02fb6819aafb4 100644
--- a/netbox/dcim.go
+++ b/netbox/dcim.go
@@ -18,6 +18,7 @@ package netbox
 type DCIMService struct {
 	c              *Client
 	InventoryItems *InventoryItemsService
+	Devices        *DevicesService
 }
 
 // NewDCIMService returns a DCIMService initialized with all sub-services.
@@ -27,6 +28,9 @@ func NewDCIMService(client *Client) *DCIMService {
 		InventoryItems: &InventoryItemsService{
 			c: client,
 		},
+		Devices: &DevicesService{
+			c: client,
+		},
 	}
 }
 
diff --git a/netbox/dcim_devices.go b/netbox/dcim_devices.go
new file mode 100644
index 0000000000000000000000000000000000000000..61912044db771ea2ab454d8c0576949374a9a3d1
--- /dev/null
+++ b/netbox/dcim_devices.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"
+)
+
+// DevicesService is used in a Client to access NetBox's dcim/devices API methods.
+type DevicesService struct {
+	c *Client
+}
+
+// Get retrieves an Device object from NetBox by its ID.
+func (s *DevicesService) Get(id int) (*Device, error) {
+	req, err := s.c.NewRequest(
+		http.MethodGet,
+		fmt.Sprintf("api/dcim/devices/%d/", id),
+		nil,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	t := new(Device)
+	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 *DevicesService) List(options *ListDeviceOptions) *Page {
+	return NewPage(s.c, "api/dcim/devices/", options)
+}
+
+// Extract retrives a list of Device objects from page.
+func (s *DevicesService) Extract(page *Page) ([]*Device, error) {
+	if err := page.Err(); err != nil {
+		return nil, err
+	}
+
+	var groups []*Device
+	if err := json.Unmarshal(page.data.Results, &groups); err != nil {
+		return nil, err
+	}
+	return groups, nil
+}
+
+// Create creates a new Device object in NetBox and returns the ID of the new object.
+func (s *DevicesService) Create(data *Device) (int, error) {
+	req, err := s.c.NewJSONRequest(http.MethodPost, "api/dcim/devices/", nil, data)
+	if err != nil {
+		return 0, err
+	}
+
+	g := new(writableDevice)
+	err = s.c.Do(req, g)
+	if err != nil {
+		return 0, err
+	}
+	return g.ID, nil
+}
+
+// Update changes an existing Device object in NetBox, and returns the ID of the new object.
+func (s *DevicesService) Update(data *Device) (int, error) {
+	req, err := s.c.NewJSONRequest(
+		http.MethodPatch,
+		fmt.Sprintf("api/dcim/devices/%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 writableDevice correctly,
+	// everything went fine, and we do not need to update data.
+	g := new(writableDevice)
+	err = s.c.Do(req, g)
+	if err != nil {
+		return 0, err
+	}
+	return g.ID, nil
+}
+
+// Delete deletes an existing Device object from NetBox.
+func (s *DevicesService) Delete(data *Device) error {
+	req, err := s.c.NewRequest(
+		http.MethodDelete,
+		fmt.Sprintf("api/dcim/devices/%d/", data.ID),
+		nil,
+	)
+	if err != nil {
+		return err
+	}
+
+	return s.c.Do(req, nil)
+}
diff --git a/netbox/dcim_devices_basic_test.go b/netbox/dcim_devices_basic_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..fe7ef3d53ac980ecfd930ae27854bba384ad86c5
--- /dev/null
+++ b/netbox/dcim_devices_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_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 Device.MarshalJSON is what you want,
+// but not here as a return in testHandler
+type serverDataDevice Device
+
+func convertToServerDataDevice(data []*Device) []*serverDataDevice {
+	dataWant := make([]*serverDataDevice, len(data))
+	for i := range data {
+		tmp := serverDataDevice(*data[i])
+		dataWant[i] = &tmp
+	}
+	return dataWant
+}
+
+func TestBasicDeviceGet(t *testing.T) {
+	var tests = []struct {
+		desc string
+		want *Device
+	}{
+		{
+			desc: "Simple Device",
+			want: testDevice(1),
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) {
+			serverData := serverDataDevice(*tt.want)
+
+			c, done := testClient(t, testHandler(t, http.MethodGet, "/api/dcim/devices/1/", &serverData))
+			defer done()
+
+			res, err := c.DCIM.Devices.Get(1)
+			if err != nil {
+				t.Fatalf("unexpected error from Client.DCIM.Devices.Get: %v", err)
+			}
+
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected Device:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasicDeviceGet404(t *testing.T) {
+	c, done := testClient(t, testStatusHandler(t, http.MethodGet, "/api/dcim/devices/1/", &struct {
+		Detail string `json:"detail"`
+	}{
+		Detail: "Not found.",
+	},
+		http.StatusNotFound))
+	defer done()
+
+	res, err := c.DCIM.Devices.Get(1)
+	errstr := "404 - Not found."
+	if want, got := errors.New(errstr), err; !reflect.DeepEqual(want, got) {
+		t.Fatalf("unexpected error from Client.DCIM.Devices.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 TestBasicListExtractDevice(t *testing.T) {
+	want := []*Device{
+		testDevice(1),
+		testDevice(2),
+	}
+	serverWant := convertToServerDataDevice(want)
+	serverData, _ := json.Marshal(serverWant)
+	c, done := testClient(t, testHandler(t, http.MethodGet, "/api/dcim/devices/", &pageData{
+		Count:       2,
+		NextURL:     "",
+		PreviousURL: "",
+		Results:     serverData,
+	}))
+	defer done()
+
+	page := c.DCIM.Devices.List(nil)
+
+	if page == nil {
+		t.Fatalf("unexpexted result from c.DCIM.Devices.List.")
+	}
+
+	got := []*Device{}
+	counter := 0
+	for page.Next() {
+		var err error
+		got, err = c.DCIM.Devices.Extract(page)
+		if err != nil {
+			t.Fatalf("unexpected error from c.DCIM.Devices.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 TestBasicCreateDevice(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *Device
+		want       int
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Create with ID 0",
+			data:       testDeviceCreate(1),
+			want:       1,
+			status:     0,
+			errstr:     "",
+			serverData: testDevice(1),
+		},
+		{
+			desc:   "Create duplicate",
+			data:   testDeviceCreate(1),
+			want:   0,
+			status: http.StatusBadRequest,
+			errstr: "400 - {\"name\":[\"DevicesService with this name already exists.\"]}\n",
+			serverData: &struct {
+				Name []string `json:"name"`
+			}{
+				Name: []string{"DevicesService 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/dcim/devices/", 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.DCIM.Devices.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 Device:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasicUpdateDevice(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *Device
+		want       int
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Update with ID 1",
+			data:       testDevice(1),
+			want:       1,
+			serverData: testDevice(1),
+			status:     0,
+			errstr:     "",
+		},
+		{
+			desc: "Update not found",
+			data: testDevice(1),
+			want: 0,
+			serverData: &struct {
+				Detail string
+			}{
+				Detail: "Not found.",
+			},
+			status: http.StatusNotFound,
+			errstr: "404 - Not found.",
+		},
+		{
+			desc: "Update to duplicate",
+			data: testDevice(1),
+			want: 0,
+			serverData: &struct {
+				Name []string `json:"name"`
+			}{
+				Name: []string{"DevicesService with this name already exists."},
+			},
+			status: http.StatusBadRequest,
+			errstr: "400 - {\"name\":[\"DevicesService 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/dcim/devices/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.DCIM.Devices.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 Device:\n- want: %v\n-  got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestBasicDeleteDevice(t *testing.T) {
+	var tests = []struct {
+		desc       string
+		data       *Device
+		serverData interface{}
+		status     int
+		errstr     string
+	}{
+		{
+			desc:       "Delete ID 1",
+			data:       testDevice(1),
+			serverData: testDevice(1),
+			status:     0,
+			errstr:     "",
+		},
+		{
+			desc: "Delete not Found",
+			data: testDevice(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/dcim/devices/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.DCIM.Devices.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/dcim_devices_test.go b/netbox/dcim_devices_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..bbf809ea7ea2d083696bae793b5764659d98e3af
--- /dev/null
+++ b/netbox/dcim_devices_test.go
@@ -0,0 +1,298 @@
+// 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 TestDeviceGet(t *testing.T) {
+	var tests = []struct {
+		desc string
+		want *Device
+	}{
+		{
+			desc: "Device with Platform",
+			want: testDevice(1),
+		},
+	}
+
+	for idx, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", idx, tt.desc), func(t *testing.T) {
+			serverData := serverDataDevice(*tt.want)
+
+			c, done := testClient(t, testHandler(t, http.MethodGet, "/api/dcim/devices/1/", &serverData))
+			defer done()
+
+			res, err := c.DCIM.Devices.Get(1)
+			if err != nil {
+				t.Fatalf("unexpected error from c.DCIM.Devices.Get: %v", err)
+			}
+
+			if want, got := tt.want, res; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected Device\n- want: %v\n- got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestDeviceUnmarshalJSON(t *testing.T) {
+	var tests = []struct {
+		desc string
+		data []byte
+		want *Device
+	}{
+		{
+			desc: "Minimum device",
+			data: []byte(`{"id": 1, "name": "Device 1", "display_name": "Device 1", "device_type": { "id": 11, "url": "http://localhost/api/dcim/device-types/11/", "manufacturer": { "id": 21, "url": "http://localhost/api/dcim/manufacturers/21/", "name": "Manufacturer Name", "slug": "mfg-name"}, "model": "Device Type Model", "slug": "device-type-model"}, "device_role": { "id": 31, "url": "http://localhost/api/dcim/device-roles/31/", "name": "Device Role Name", "slug": "device-role-name"}, "site": { "id": 61, "url": "http://localhost/api/dcim/sites/61/", "name": "Site Name", "slug": "site-name" }, "status": { "value": 1, "label": "Active" } }`),
+			want: &Device{
+				ID:          1,
+				Name:        "Device 1",
+				DisplayName: "Device 1",
+				DeviceType: &NestedDeviceType{ID: 11,
+					URL: "http://localhost/api/dcim/device-types/11/",
+					Manufacturer: &NestedManufacturer{
+						ID: 21, URL: "http://localhost/api/dcim/manufacturers/21/",
+						Name: "Manufacturer Name",
+						Slug: "mfg-name",
+					},
+					Model: "Device Type Model",
+					Slug:  "device-type-model",
+				},
+				DeviceRole: &NestedDeviceRole{
+					ID:   31,
+					URL:  "http://localhost/api/dcim/device-roles/31/",
+					Name: "Device Role Name",
+					Slug: "device-role-name",
+				},
+				Site: &NestedSite{
+					ID:   61,
+					URL:  "http://localhost/api/dcim/sites/61/",
+					Name: "Site Name",
+					Slug: "site-name",
+				},
+				Status: &StatusType{
+					Value: 1,
+					Label: "Active",
+				},
+			},
+		},
+		{
+			desc: "Maximum device",
+			data: []byte(`{"id": 1, "name": "Device 1", "display_name": "Device 1", "device_type": { "id": 11, "url": "http://localhost/api/dcim/device-types/11/", "manufacturer": { "id": 21, "url": "http://localhost/api/dcim/manufacturers/21/", "name": "Manufacturer Name", "slug": "mfg-name"}, "model": "Device Type Model", "slug": "device-type-model"}, "device_role": { "id": 31, "url": "http://localhost/api/dcim/device-roles/31/", "name": "Device Role Name", "slug": "device-role-name"}, "tenant": { "id": 41, "url": "http://localhost/api/tenancy/tenants/41/", "name": "Tenant Name", "slug": "tenant-name" }, "platform": { "id": 51, "url": "http://localhost/api/dcim/platforms/51", "name": "Platform Name", "slug": "platform-name" }, "serial": "Serial", "asset_tag": "Tag", "site": { "id": 61, "url": "http://localhost/api/dcim/sites/61/", "name": "Site Name", "slug": "site-name" }, "rack": { "id": 71, "url": "http://localhost/api/dcim/racks/71/", "name": "Rack Name", "display_name": "Rack Name" }, "position": 81, "face": { "value": 0, "label": "Front" }, "status": { "value": 1, "label": "Active" } }`),
+			want: &Device{
+				ID:          1,
+				Name:        "Device 1",
+				DisplayName: "Device 1",
+				DeviceType: &NestedDeviceType{
+					ID:  11,
+					URL: "http://localhost/api/dcim/device-types/11/",
+					Manufacturer: &NestedManufacturer{
+						ID:   21,
+						URL:  "http://localhost/api/dcim/manufacturers/21/",
+						Name: "Manufacturer Name",
+						Slug: "mfg-name",
+					},
+					Model: "Device Type Model",
+					Slug:  "device-type-model",
+				},
+				DeviceRole: &NestedDeviceRole{
+					ID:   31,
+					URL:  "http://localhost/api/dcim/device-roles/31/",
+					Name: "Device Role Name",
+					Slug: "device-role-name",
+				},
+				Tenant: &NestedTenant{
+					ID:   41,
+					URL:  "http://localhost/api/tenancy/tenants/41/",
+					Name: "Tenant Name",
+					Slug: "tenant-name",
+				},
+				Platform: &NestedPlatform{
+					ID:   51,
+					URL:  "http://localhost/api/dcim/platforms/51",
+					Name: "Platform Name",
+					Slug: "platform-name",
+				},
+				Serial:   "Serial",
+				AssetTag: "Tag",
+				Site: &NestedSite{
+					ID:   61,
+					URL:  "http://localhost/api/dcim/sites/61/",
+					Name: "Site Name",
+					Slug: "site-name",
+				},
+				Rack: &NestedRack{
+					ID:          71,
+					URL:         "http://localhost/api/dcim/racks/71/",
+					Name:        "Rack Name",
+					DisplayName: "Rack Name",
+				},
+				Position: 81,
+				Face: &FaceType{
+					Value: 0,
+					Label: "Front",
+				},
+				Status: &StatusType{
+					Value: 1,
+					Label: "Active",
+				},
+			},
+		},
+	}
+
+	for idx, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", idx, tt.desc), func(t *testing.T) {
+			result := new(Device)
+			err := json.Unmarshal(tt.data, result)
+			if err != nil {
+				t.Fatalf("unexpected error from Device.UnmarshalJSON: %v", err)
+			}
+
+			if want, got := tt.want, result; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected Device:\n- want: %v\n- got: %v", want, got)
+			}
+		})
+	}
+}
+
+func TestDeviceMarshalJSON(t *testing.T) {
+	var tests = []struct {
+		desc string
+		data *Device
+		want []byte
+	}{
+		{
+			desc: "Sample Device",
+			data: testDevice(1),
+			want: []byte(`{"id":1,"name":"Device 1","display_name":"Device 1","device_type":2001,"device_role":1001,"tenant":3001,"platform":4001,"site":5001,"rack":6001}`),
+		},
+	}
+
+	for idx, tt := range tests {
+		t.Run(fmt.Sprintf("[%d] %s", idx, tt.desc), func(t *testing.T) {
+			result, err := json.Marshal(tt.data)
+			if err != nil {
+				t.Fatalf("unexpected error from writableDevice.MarshalJSON: %v", err)
+			}
+			if want, got := tt.want, result; bytes.Compare(want, got) != 0 {
+				t.Fatalf("unexpected JSON:\n- want: %v\n- got: %v", string(want), string(got))
+			}
+		})
+	}
+}
+
+func testNestedDeviceRole(id int) *NestedDeviceRole {
+	return &NestedDeviceRole{
+		ID:   id,
+		URL:  fmt.Sprintf("http://localhost/api/dcim/device-roles/%d/", id),
+		Name: fmt.Sprintf("Device Role %d", id),
+		Slug: fmt.Sprintf("device-role-%d", id),
+	}
+}
+
+func testNestedPlatform(id int) *NestedPlatform {
+	return &NestedPlatform{
+		ID:   id,
+		URL:  fmt.Sprintf("http://localhost/api/dcim/platforms/%d/", id),
+		Name: fmt.Sprintf("Platform %d", id),
+		Slug: fmt.Sprintf("platform-%d", id),
+	}
+}
+
+func testNestedTenant(id int) *NestedTenant {
+	return &NestedTenant{
+		ID:   id,
+		URL:  fmt.Sprintf("http://localhost/api/tenancy/tenants/%d/", id),
+		Name: fmt.Sprintf("Tenant %d", id),
+		Slug: fmt.Sprintf("tenant-%d", id),
+	}
+}
+
+func testNestedSite(id int) *NestedSite {
+	return &NestedSite{
+		ID:   id,
+		URL:  fmt.Sprintf("http://localhost/api/dcim/sites/%d/", id),
+		Name: fmt.Sprintf("Site %d", id),
+		Slug: fmt.Sprintf("site-%d", id),
+	}
+}
+
+func testNestedRack(id int) *NestedRack {
+	return &NestedRack{
+		ID:          id,
+		URL:         fmt.Sprintf("http://localhost/api/dcim/racks/%d/", id),
+		Name:        fmt.Sprintf("Rack %d", id),
+		DisplayName: fmt.Sprintf("Rack %d", id),
+	}
+}
+
+func testNestedDeviceType(id int) *NestedDeviceType {
+	manufacturerID := id + 1000
+	return &NestedDeviceType{
+		ID:           id,
+		URL:          fmt.Sprintf("http://localhost/api/dcim/device-types/%d/", id),
+		Manufacturer: testNestedManufacturer(manufacturerID),
+		Model:        fmt.Sprintf("Device Type Model of %d", id),
+		Slug:         fmt.Sprintf("test-device-type-model-%d", id),
+	}
+}
+
+func testDevice(id int) *Device {
+	roleID := id + 1000
+	typeID := id + 2000
+	tenantID := id + 3000
+	platformID := id + 4000
+	siteID := id + 5000
+	rackID := id + 6000
+
+	return testDeviceHelper(id, false, testNestedDeviceType(typeID), testNestedDeviceRole(roleID), testNestedTenant(tenantID), testNestedPlatform(platformID), testNestedSite(siteID), testNestedRack(rackID))
+}
+
+func testDeviceCreate(id int) *Device {
+	roleID := id + 1000
+	typeID := id + 2000
+	tenantID := id + 3000
+	platformID := id + 4000
+	siteID := id + 5000
+	rackID := id + 6000
+	return testDeviceHelper(id, true, testNestedDeviceType(typeID), testNestedDeviceRole(roleID), testNestedTenant(tenantID), testNestedPlatform(platformID), testNestedSite(siteID), testNestedRack(rackID))
+}
+
+func testDeviceHelper(id int, create bool, devicetype *NestedDeviceType, role *NestedDeviceRole, tenant *NestedTenant, platform *NestedPlatform, site *NestedSite, rack *NestedRack) *Device {
+	deviceID := id
+
+	if create {
+		deviceID = 0
+	}
+
+	return &Device{
+		ID:          deviceID,
+		Name:        fmt.Sprintf("Device %d", id),
+		DisplayName: fmt.Sprintf("Device %d", id),
+		DeviceType:  devicetype,
+		DeviceRole:  role,
+		Tenant:      tenant,
+		Platform:    platform,
+		Site:        site,
+		Rack:        rack,
+	}
+}
diff --git a/netbox/dcim_devices_types.go b/netbox/dcim_devices_types.go
new file mode 100644
index 0000000000000000000000000000000000000000..d130921c1ed8943f73776a47930dfddb10476190
--- /dev/null
+++ b/netbox/dcim_devices_types.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.
+
+package netbox
+
+import (
+	"encoding/json"
+	"net/url"
+	"strconv"
+)
+
+type nestedBase struct {
+	ID   int    `json:"id"`
+	URL  string `json:"url"`
+	Name string `json:"name"`
+	Slug string `json:"slug"`
+}
+
+type simpleValueLabel struct {
+	Value int    `json:"value"`
+	Label string `json:"label"`
+}
+
+// ShortDeviceBay represent the short form of a Device Bay
+// when a Device Bay appears in a parent device of
+// another object.
+type ShortDeviceBay struct {
+	ID   int    `json:"id"`
+	Name string `json:"name"`
+}
+
+// ShortDevice corresponds to the simple form of
+// a Device, when a Device appears as a
+// parent device of another object
+type ShortDevice struct {
+	DeviceBay *ShortDeviceBay `json:"device_bay"`
+	ID        int             `json:"id"`
+	Name      string          `json:"name"`
+}
+
+// FaceType represent the face of a Device in a Rack (Front/Rear)
+type FaceType simpleValueLabel
+
+// StatusType represent status of a Device in Netbox API.
+type StatusType simpleValueLabel
+
+// A NestedDeviceRole corresponds to the Netbox API's
+// nested serializer, when a DeviceRole appears as a
+// parent of another object
+type NestedDeviceRole nestedBase
+
+// A NestedPlatform corresponds to the Netbox API's
+// nested serializer, when a Platform appears as a
+// parent of another object
+type NestedPlatform nestedBase
+
+// A NestedTenant corresponds to the Netbox API's
+// nested serializer, when a Tenant appears as a
+// parent of another object
+type NestedTenant nestedBase
+
+// A NestedSite corresponds to the Netbox API's
+// nested serializer, when a Site appears as a
+// parent of another object
+type NestedSite nestedBase
+
+// A NestedDeviceType corresponds to the Netbox API's
+// nested serializer, when a DeviceType appears as a
+// parent of another object
+type NestedDeviceType struct {
+	ID           int                 `json:"id"`
+	URL          string              `json:"url"`
+	Manufacturer *NestedManufacturer `json:"manufacturer"`
+	Model        string              `json:"model"`
+	Slug         string              `json:"slug"`
+}
+
+// A NestedRack correspondes to the Netbox API's
+// nested serializer, when a Rack appears as a
+// parent of another object
+type NestedRack struct {
+	ID          int    `json:"id"`
+	URL         string `json:"url"`
+	Name        string `json:"name"`
+	DisplayName string `json:"display_name"`
+}
+
+// A NestedIP will be used as nested serializer for
+// IPv4, IPv6 ip address
+type NestedIP struct {
+	ID      int    `json:"id"`
+	URL     string `json:"url"`
+	Family  int    `json:"family"`
+	Address string `json:"address"`
+}
+
+// A Device corresponds to the Netbox API's
+// base serializer for Device
+type Device struct {
+	ID          int               `json:"id,omitempty"`
+	Name        string            `json:"name"`
+	DisplayName string            `json:"display_name"`
+	DeviceType  *NestedDeviceType `json:"device_type"`
+	DeviceRole  *NestedDeviceRole `json:"device_role"`
+	Tenant      *NestedTenant     `json:"tenant,omitempty"`
+	Platform    *NestedPlatform   `json:"platform,omitempty"`
+	Serial      string            `json:"serial,omitempty"`
+	AssetTag    string            `json:"asset_tag,omitempty"`
+	Site        *NestedSite       `json:"site"`
+	Rack        *NestedRack       `json:"rack,omitempty"`
+	Position    int               `json:"position,omitempty"`
+	Face        *FaceType         `json:"face,omitempty"`
+	Parent      *ShortDevice      `json:"parent_device,omitempty"`
+	Status      *StatusType       `json:"status"`
+	PrimaryIP   *NestedIP         `json:"primary_ip,omitempty"`
+	PrimaryIPv4 *NestedIP         `json:"primary_ip4,omitempty"`
+	PrimaryIPv6 *NestedIP         `json:"primary_ip6,omitempty"`
+	Comments    string            `json:"comments,omitempty"`
+}
+
+// A writableDevice corresponds to the Netbox API's
+// writable serializer for a Device. It is used transparently
+// when Devices are serialize into JSON.
+type writableDevice struct {
+	ID          int    `json:"id,omitempty"`
+	Name        string `json:"name"`
+	DisplayName string `json:"display_name"`
+	DeviceType  int    `json:"device_type"`
+	DeviceRole  int    `json:"device_role"`
+	Tenant      int    `json:"tenant,omitempty"`
+	Platform    int    `json:"platform,omitempty"`
+	Serial      string `json:"serial,omitempty"`
+	AssetTag    string `json:"asset_tag,omitempty"`
+	Site        int    `json:"site"`
+	Rack        int    `json:"rack,omitempty"`
+	Position    int    `json:"position,omitempty"`
+	Face        string `json:"face,omitempty"`
+	Parent      int    `json:"parent_device,omitempty"`
+	Status      string `json:"status,omitempty"`
+	PrimaryIP   int    `json:"primary_ip,omitempty"`
+	PrimaryIPv4 int    `json:"primary_ip4,omitempty"`
+	PrimaryIPv6 int    `json:"primary_ip6,omitempty"`
+	Comments    string `json:"comments,omitempty"`
+}
+
+// MarshalJSON marshals a Device into JSON bytes,
+// and is used by the standard json package.
+func (d *Device) MarshalJSON() ([]byte, error) {
+	var typeID, roleID, tenantID, platformID, siteID, rackID, parentID, primaryIPID, primaryIPv4ID, primaryIPv6ID int
+	var status, face string
+
+	if d.DeviceType != nil {
+		typeID = d.DeviceType.ID
+	}
+
+	if d.DeviceRole != nil {
+		roleID = d.DeviceRole.ID
+	}
+
+	if d.Tenant != nil {
+		tenantID = d.Tenant.ID
+	}
+
+	if d.Platform != nil {
+		platformID = d.Platform.ID
+	}
+
+	if d.Site != nil {
+		siteID = d.Site.ID
+	}
+
+	if d.Rack != nil {
+		rackID = d.Rack.ID
+	}
+
+	if d.PrimaryIP != nil {
+		primaryIPID = d.PrimaryIP.ID
+	}
+
+	if d.PrimaryIPv4 != nil {
+		primaryIPv4ID = d.PrimaryIPv4.ID
+	}
+
+	if d.PrimaryIPv6 != nil {
+		primaryIPv6ID = d.PrimaryIPv6.ID
+	}
+
+	if d.Face != nil {
+		face = d.Face.Label
+	}
+
+	if d.Parent != nil {
+		parentID = d.Parent.ID
+	}
+
+	if d.Status != nil {
+		status = d.Status.Label
+	}
+
+	return json.Marshal(writableDevice{
+		ID:          d.ID,
+		Name:        d.Name,
+		DisplayName: d.DisplayName,
+		DeviceType:  typeID,
+		DeviceRole:  roleID,
+		Tenant:      tenantID,
+		Platform:    platformID,
+		Serial:      d.Serial,
+		AssetTag:    d.AssetTag,
+		Site:        siteID,
+		Rack:        rackID,
+		Position:    d.Position,
+		Face:        face,
+		Parent:      parentID,
+		Status:      status,
+		PrimaryIP:   primaryIPID,
+		PrimaryIPv4: primaryIPv4ID,
+		PrimaryIPv6: primaryIPv6ID,
+		Comments:    d.Comments,
+	})
+
+}
+
+// ListDeviceOptions is used as an argument for Client.DCIM.Devices.List.
+// Integer fileds with an *ID suffix are preferred over their string
+// counterparts, and if both are set, only the *ID filed will be used.
+type ListDeviceOptions struct {
+	Name            string
+	Serial          string
+	AssetTag        string
+	MacAddress      string
+	IDIn            int
+	SiteID          int
+	Site            string
+	RackGroupID     int
+	RackID          int
+	RoleID          int
+	Role            string
+	TenantID        int
+	Tenant          string
+	DeviceTypeID    int
+	ManufacturerID  int
+	Manufacturer    string
+	DeviceModel     string
+	PlatformID      int
+	Platform        string
+	Status          string
+	IsConsoleServer *bool
+	IsPDU           *bool
+	IsNetworkDevice *bool
+	HasPrimaryIP    *bool
+
+	Query string
+}
+
+// Values generates a url.Values map from the data in ListDeviceOptions.
+func (o *ListDeviceOptions) Values() (url.Values, error) {
+	if o == nil {
+		return nil, nil
+	}
+
+	v := url.Values{}
+
+	status := map[string]int{
+		"Offline":   0,
+		"Active":    1,
+		"Planned":   2,
+		"Staged":    3,
+		"Failed":    4,
+		"Inventory": 5,
+	}
+
+	switch {
+	case o.SiteID != 0:
+		v.Set("site_id", strconv.Itoa(o.SiteID))
+	case o.Site != "":
+		v.Set("site", o.Site)
+	}
+
+	switch {
+	case o.TenantID != 0:
+		v.Set("tenant_id", strconv.Itoa(o.TenantID))
+	case o.Tenant != "":
+		v.Set("tenant", o.Tenant)
+	}
+
+	switch {
+	case o.ManufacturerID != 0:
+		v.Set("manufacturer_id", strconv.Itoa(o.ManufacturerID))
+	case o.Manufacturer != "":
+		v.Set("manufacturer", o.Manufacturer)
+	}
+
+	switch {
+	case o.PlatformID != 0:
+		v.Set("platform_id", strconv.Itoa(o.PlatformID))
+	case o.Platform != "":
+		v.Set("platform", o.Platform)
+	}
+
+	switch {
+	case o.RoleID != 0:
+		v.Set("role_id", strconv.Itoa(o.RoleID))
+	case o.Role != "":
+		v.Set("role", o.Role)
+	}
+
+	if o.Name != "" {
+		v.Set("name", o.Name)
+	}
+
+	if o.Serial != "" {
+		v.Set("serial", o.Serial)
+	}
+
+	if o.AssetTag != "" {
+		v.Set("asset_tag", o.AssetTag)
+	}
+
+	if o.MacAddress != "" {
+		v.Set("mac_address", o.MacAddress)
+	}
+
+	if o.IDIn != 0 {
+		v.Set("id__in", strconv.Itoa(o.IDIn))
+	}
+
+	if o.RackGroupID != 0 {
+		v.Set("rack_group_id", strconv.Itoa(o.RackGroupID))
+	}
+
+	if o.RackID != 0 {
+		v.Set("rack_id", strconv.Itoa(o.RackID))
+	}
+
+	if o.DeviceTypeID != 0 {
+		v.Set("device_type_id", strconv.Itoa(o.DeviceTypeID))
+	}
+
+	if o.DeviceModel != "" {
+		v.Set("model", o.DeviceModel)
+	}
+
+	if o.Status != "" {
+		v.Set("status", strconv.Itoa(status[o.Status]))
+	}
+
+	if o.IsConsoleServer != nil {
+		v.Set("is_console_server", strconv.FormatBool(*o.IsConsoleServer))
+	}
+
+	if o.IsPDU != nil {
+		v.Set("is_pdu", strconv.FormatBool(*o.IsPDU))
+	}
+
+	if o.IsNetworkDevice != nil {
+		v.Set("is_network_device", strconv.FormatBool(*o.IsNetworkDevice))
+	}
+
+	if o.HasPrimaryIP != nil {
+		v.Set("has_primary_ip", strconv.FormatBool(*o.HasPrimaryIP))
+	}
+
+	if o.Query != "" {
+		v.Set("q", o.Query)
+	}
+
+	return v, nil
+}
+
+//go:generate go run generate_functions.go -endpoint dcim -service devices -service-name DevicesService -type-name Device -update-type-name writableDevice
+//go:generate go run generate_basic_tests.go -client-endpoint DCIM -client-service Devices -endpoint dcim -service devices -service-name DevicesService -type-name Device