dsl struct tags; wip
diff --git a/openstack/blockstorage/v1/apiversions/requests.go b/openstack/blockstorage/v1/apiversions/requests.go
index 0c05558..4e9df04 100644
--- a/openstack/blockstorage/v1/apiversions/requests.go
+++ b/openstack/blockstorage/v1/apiversions/requests.go
@@ -15,7 +15,7 @@
 // Get will retrieve the volume type with the provided ID. To extract the volume
 // type from the result, call the Extract method on the GetResult.
 func Get(client *gophercloud.ServiceClient, v string) GetResult {
-	var res GetResult
-	_, res.Err = client.Get(getURL(client, v), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, v), &r.Body, nil)
+	return r
 }
diff --git a/openstack/blockstorage/v1/snapshots/requests.go b/openstack/blockstorage/v1/snapshots/requests.go
index 504ce6e..e8583cb 100644
--- a/openstack/blockstorage/v1/snapshots/requests.go
+++ b/openstack/blockstorage/v1/snapshots/requests.go
@@ -15,78 +15,48 @@
 // the snapshots.Create function. For more information about these parameters,
 // see the Snapshot object.
 type CreateOpts struct {
-	// OPTIONAL
-	Description string
-	// OPTIONAL
-	Force bool
-	// OPTIONAL
-	Metadata map[string]interface{}
-	// OPTIONAL
-	Name string
-	// REQUIRED
-	VolumeID string
+	VolumeID    string                 `json:"volume_id" required:"true"`
+	Description string                 `json:"display_description,omitempty"`
+	Force       bool                   `json:"force,omitempty"`
+	Metadata    map[string]interface{} `json:"metadata,omitempty"`
+	Name        string                 `json:"display_name,omitempty"`
 }
 
 // ToSnapshotCreateMap assembles a request body based on the contents of a
 // CreateOpts.
 func (opts CreateOpts) ToSnapshotCreateMap() (map[string]interface{}, error) {
-	s := make(map[string]interface{})
-
-	if opts.VolumeID == "" {
-		err := &gophercloud.ErrMissingInput{}
-		err.Argument = "CreateOpts.VolumeID"
-		err.Function = "snapshots.ToSnapshotCreateMap"
-		return nil, err
-	}
-	s["volume_id"] = opts.VolumeID
-
-	if opts.Description != "" {
-		s["display_description"] = opts.Description
-	}
-	if opts.Force == true {
-		s["force"] = opts.Force
-	}
-	if opts.Metadata != nil {
-		s["metadata"] = opts.Metadata
-	}
-	if opts.Name != "" {
-		s["display_name"] = opts.Name
-	}
-
-	return map[string]interface{}{"snapshot": s}, nil
+	return gophercloud.BuildRequestBody(opts, "snapshot")
 }
 
 // Create will create a new Snapshot based on the values in CreateOpts. To
 // extract the Snapshot object from the response, call the Extract method on the
 // CreateResult.
 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToSnapshotCreateMap()
+	var r CreateResult
+	b, err := opts.ToSnapshotCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200, 201},
 	})
-	return res
+	return r
 }
 
 // Delete will delete the existing Snapshot with the provided ID.
 func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
-	var res DeleteResult
-	_, res.Err = client.Delete(deleteURL(client, id), nil)
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(deleteURL(client, id), nil)
+	return r
 }
 
 // Get retrieves the Snapshot with the provided ID. To extract the Snapshot
 // object from the response, call the Extract method on the GetResult.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var res GetResult
-	_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+	return r
 }
 
 // ListOptsBuilder allows extensions to add additional parameters to the List
@@ -106,10 +76,7 @@
 // ToSnapshotListQuery formats a ListOpts into a query string.
 func (opts ListOpts) ToSnapshotListQuery() (string, error) {
 	q, err := gophercloud.BuildQueryString(opts)
-	if err != nil {
-		return "", err
-	}
-	return q.String(), nil
+	return q.String(), err
 }
 
 // List returns Snapshots optionally limited by the conditions provided in
@@ -123,11 +90,9 @@
 		}
 		url += query
 	}
-
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
 		return SnapshotPage{pagination.SinglePageBase(r)}
-	}
-	return pagination.NewPager(client, url, createPage)
+	})
 }
 
 // UpdateMetadataOptsBuilder allows extensions to add additional parameters to
@@ -140,50 +105,35 @@
 // object is passed to the snapshots.Update function. For more information
 // about the parameters, see the Snapshot object.
 type UpdateMetadataOpts struct {
-	Metadata map[string]interface{}
+	Metadata map[string]interface{} `json:"metadata,omitempty"`
 }
 
 // ToSnapshotUpdateMetadataMap assembles a request body based on the contents of
 // an UpdateMetadataOpts.
 func (opts UpdateMetadataOpts) ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) {
-	v := make(map[string]interface{})
-
-	if opts.Metadata != nil {
-		v["metadata"] = opts.Metadata
-	}
-
-	return v, nil
+	return gophercloud.BuildRequestBody(opts, "")
 }
 
 // UpdateMetadata will update the Snapshot with provided information. To
 // extract the updated Snapshot from the response, call the ExtractMetadata
 // method on the UpdateMetadataResult.
 func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) UpdateMetadataResult {
-	var res UpdateMetadataResult
-
-	reqBody, err := opts.ToSnapshotUpdateMetadataMap()
+	var r UpdateMetadataResult
+	b, err := opts.ToSnapshotUpdateMetadataMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Put(updateMetadataURL(client, id), reqBody, &res.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Put(updateMetadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-	return res
+	return r
 }
 
 // IDFromName is a convienience function that returns a snapshot's ID given its name.
 func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
 	count := 0
 	id := ""
-	if name == "" {
-		err := &gophercloud.ErrMissingInput{}
-		err.Function = "snapshots.IDFromName"
-		err.Argument = "name"
-		return "", err
-	}
-
 	pages, err := List(client, nil).AllPages()
 	if err != nil {
 		return "", err
@@ -203,19 +153,10 @@
 
 	switch count {
 	case 0:
-		err := &gophercloud.ErrResourceNotFound{}
-		err.Name = name
-		err.ResourceType = "snapshot"
-		err.Function = "snapshots.IDFromName"
-		return "", err
+		return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "snapshot"}
 	case 1:
 		return id, nil
 	default:
-		err := &gophercloud.ErrMultipleResourcesFound{}
-		err.Count = count
-		err.Name = name
-		err.ResourceType = "snapshot"
-		err.Function = "snapshots.IDFromName"
-		return "", err
+		return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "snapshot"}
 	}
 }
diff --git a/openstack/blockstorage/v1/volumes/requests.go b/openstack/blockstorage/v1/volumes/requests.go
index 1c6b635..7c063b2 100644
--- a/openstack/blockstorage/v1/volumes/requests.go
+++ b/openstack/blockstorage/v1/volumes/requests.go
@@ -15,94 +15,52 @@
 // the volumes.Create function. For more information about these parameters,
 // see the Volume object.
 type CreateOpts struct {
-	// OPTIONAL
-	Availability string
-	// OPTIONAL
-	Description string
-	// OPTIONAL
-	Metadata map[string]string
-	// OPTIONAL
-	Name string
-	// REQUIRED
-	Size int
-	// OPTIONAL
-	SnapshotID, SourceVolID, ImageID string
-	// OPTIONAL
-	VolumeType string
+	Size         int               `json:"size" required:"true"`
+	Availability string            `json:"availability,omitempty"`
+	Description  string            `json:"description,omitempty"`
+	Metadata     map[string]string `json:"metadata,omitempty"`
+	Name         string            `json:"name,omitempty"`
+	SnapshotID   string            `json:"snapshot_id,omitempty"`
+	SourceVolID  string            `json:"source_volid,omitempty"`
+	ImageID      string            `json:"imageRef,omitempty"`
+	VolumeType   string            `json:"volume_type,omitempty"`
 }
 
 // ToVolumeCreateMap assembles a request body based on the contents of a
 // CreateOpts.
 func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
-	v := make(map[string]interface{})
-
-	if opts.Size == 0 {
-		err := &gophercloud.ErrMissingInput{}
-		err.Argument = "CreateOpts.Size"
-		err.Function = "volumes.ToVolumeCreateMap"
-		return nil, err
-	}
-	v["size"] = opts.Size
-
-	if opts.Availability != "" {
-		v["availability_zone"] = opts.Availability
-	}
-	if opts.Description != "" {
-		v["display_description"] = opts.Description
-	}
-	if opts.ImageID != "" {
-		v["imageRef"] = opts.ImageID
-	}
-	if opts.Metadata != nil {
-		v["metadata"] = opts.Metadata
-	}
-	if opts.Name != "" {
-		v["display_name"] = opts.Name
-	}
-	if opts.SourceVolID != "" {
-		v["source_volid"] = opts.SourceVolID
-	}
-	if opts.SnapshotID != "" {
-		v["snapshot_id"] = opts.SnapshotID
-	}
-	if opts.VolumeType != "" {
-		v["volume_type"] = opts.VolumeType
-	}
-
-	return map[string]interface{}{"volume": v}, nil
+	return gophercloud.BuildRequestBody(opts, "volume")
 }
 
 // Create will create a new Volume based on the values in CreateOpts. To extract
 // the Volume object from the response, call the Extract method on the
 // CreateResult.
 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToVolumeCreateMap()
+	var r CreateResult
+	b, err := opts.ToVolumeCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200, 201},
 	})
-	return res
+	return r
 }
 
 // Delete will delete the existing Volume with the provided ID.
 func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
-	var res DeleteResult
-	_, res.Err = client.Delete(deleteURL(client, id), nil)
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(deleteURL(client, id), nil)
+	return r
 }
 
 // Get retrieves the Volume with the provided ID. To extract the Volume object
 // from the response, call the Extract method on the GetResult.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var res GetResult
-	_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+	return r
 }
 
 // ListOptsBuilder allows extensions to add additional parameters to the List
@@ -127,10 +85,7 @@
 // ToVolumeListQuery formats a ListOpts into a query string.
 func (opts ListOpts) ToVolumeListQuery() (string, error) {
 	q, err := gophercloud.BuildQueryString(opts)
-	if err != nil {
-		return "", err
-	}
-	return q.String(), nil
+	return q.String(), err
 }
 
 // List returns Volumes optionally limited by the conditions provided in ListOpts.
@@ -143,11 +98,9 @@
 		}
 		url += query
 	}
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
 		return VolumePage{pagination.SinglePageBase(r)}
-	}
-
-	return pagination.NewPager(client, url, createPage)
+	})
 }
 
 // UpdateOptsBuilder allows extensions to add additional parameters to the
@@ -161,59 +114,38 @@
 // the Volume object.
 type UpdateOpts struct {
 	// OPTIONAL
-	Name string
+	Name string `json:"name,omitempty"`
 	// OPTIONAL
-	Description string
+	Description string `json:"description,omitempty"`
 	// OPTIONAL
-	Metadata map[string]string
+	Metadata map[string]string `json:"metadata,omitempty"`
 }
 
 // ToVolumeUpdateMap assembles a request body based on the contents of an
 // UpdateOpts.
 func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
-	v := make(map[string]interface{})
-
-	if opts.Description != "" {
-		v["display_description"] = opts.Description
-	}
-	if opts.Metadata != nil {
-		v["metadata"] = opts.Metadata
-	}
-	if opts.Name != "" {
-		v["display_name"] = opts.Name
-	}
-
-	return map[string]interface{}{"volume": v}, nil
+	return gophercloud.BuildRequestBody(opts, "volume")
 }
 
 // Update will update the Volume with provided information. To extract the updated
 // Volume from the response, call the Extract method on the UpdateResult.
 func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
-	var res UpdateResult
-
-	reqBody, err := opts.ToVolumeUpdateMap()
+	var r UpdateResult
+	b, err := opts.ToVolumeUpdateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Put(updateURL(client, id), reqBody, &res.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-	return res
+	return r
 }
 
 // IDFromName is a convienience function that returns a server's ID given its name.
 func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
 	count := 0
 	id := ""
-	if name == "" {
-		err := &gophercloud.ErrMissingInput{}
-		err.Function = "volumes.IDFromName"
-		err.Argument = "name"
-		return "", err
-	}
-
 	pages, err := List(client, nil).AllPages()
 	if err != nil {
 		return "", err
@@ -233,19 +165,10 @@
 
 	switch count {
 	case 0:
-		err := &gophercloud.ErrResourceNotFound{}
-		err.Name = name
-		err.ResourceType = "volume"
-		err.Function = "volumes.IDFromName"
-		return "", err
+		return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"}
 	case 1:
 		return id, nil
 	default:
-		err := &gophercloud.ErrMultipleResourcesFound{}
-		err.Count = count
-		err.Name = name
-		err.ResourceType = "volume"
-		err.Function = "volumes.IDFromName"
-		return "", err
+		return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"}
 	}
 }
diff --git a/openstack/blockstorage/v1/volumetypes/requests.go b/openstack/blockstorage/v1/volumetypes/requests.go
index 24e50ad..b895482 100644
--- a/openstack/blockstorage/v1/volumetypes/requests.go
+++ b/openstack/blockstorage/v1/volumetypes/requests.go
@@ -13,64 +13,50 @@
 
 // CreateOpts are options for creating a volume type.
 type CreateOpts struct {
-	// OPTIONAL. See VolumeType.
-	ExtraSpecs map[string]interface{}
-	// OPTIONAL. See VolumeType.
-	Name string
+	// See VolumeType.
+	ExtraSpecs map[string]interface{} `json:"extra_specs,omitempty"`
+	// See VolumeType.
+	Name string `json:"name,omitempty"`
 }
 
 // ToVolumeTypeCreateMap casts a CreateOpts struct to a map.
 func (opts CreateOpts) ToVolumeTypeCreateMap() (map[string]interface{}, error) {
-	vt := make(map[string]interface{})
-
-	if opts.ExtraSpecs != nil {
-		vt["extra_specs"] = opts.ExtraSpecs
-	}
-	if opts.Name != "" {
-		vt["name"] = opts.Name
-	}
-
-	return map[string]interface{}{"volume_type": vt}, nil
+	return gophercloud.BuildRequestBody(opts, "volume_type")
 }
 
 // Create will create a new volume. To extract the created volume type object,
 // call the Extract method on the CreateResult.
 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToVolumeTypeCreateMap()
+	var r CreateResult
+	b, err := opts.ToVolumeTypeCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200, 201},
 	})
-	return res
+	return r
 }
 
 // Delete will delete the volume type with the provided ID.
 func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
-	var res DeleteResult
-	_, res.Err = client.Delete(deleteURL(client, id), nil)
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(deleteURL(client, id), nil)
+	return r
 }
 
 // Get will retrieve the volume type with the provided ID. To extract the volume
 // type from the result, call the Extract method on the GetResult.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var res GetResult
-	_, err := client.Get(getURL(client, id), &res.Body, nil)
-	res.Err = err
-	return res
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+	return r
 }
 
 // List returns all volume types.
 func List(client *gophercloud.ServiceClient) pagination.Pager {
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
 		return VolumeTypePage{pagination.SinglePageBase(r)}
-	}
-
-	return pagination.NewPager(client, listURL(client), createPage)
+	})
 }
diff --git a/openstack/cdn/v1/base/requests.go b/openstack/cdn/v1/base/requests.go
index ed3e7a0..15f8346 100644
--- a/openstack/cdn/v1/base/requests.go
+++ b/openstack/cdn/v1/base/requests.go
@@ -5,17 +5,17 @@
 // Get retrieves the home document, allowing the user to discover the
 // entire API.
 func Get(c *gophercloud.ServiceClient) GetResult {
-	var res GetResult
-	_, res.Err = c.Get(getURL(c), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = c.Get(getURL(c), &r.Body, nil)
+	return r
 }
 
 // Ping retrieves a ping to the server.
 func Ping(c *gophercloud.ServiceClient) PingResult {
-	var res PingResult
-	_, res.Err = c.Get(pingURL(c), nil, &gophercloud.RequestOpts{
+	var r PingResult
+	_, r.Err = c.Get(pingURL(c), nil, &gophercloud.RequestOpts{
 		OkCodes:     []int{204},
 		MoreHeaders: map[string]string{"Accept": ""},
 	})
-	return res
+	return r
 }
diff --git a/openstack/cdn/v1/flavors/requests.go b/openstack/cdn/v1/flavors/requests.go
index a7c0232..d42d1d7 100644
--- a/openstack/cdn/v1/flavors/requests.go
+++ b/openstack/cdn/v1/flavors/requests.go
@@ -7,16 +7,14 @@
 
 // List returns a single page of CDN flavors.
 func List(c *gophercloud.ServiceClient) pagination.Pager {
-	url := listURL(c)
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(c, listURL(c), func(r pagination.PageResult) pagination.Page {
 		return FlavorPage{pagination.SinglePageBase(r)}
-	}
-	return pagination.NewPager(c, url, createPage)
+	})
 }
 
 // Get retrieves a specific flavor based on its unique ID.
 func Get(c *gophercloud.ServiceClient, id string) GetResult {
-	var res GetResult
-	_, res.Err = c.Get(getURL(c, id), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = c.Get(getURL(c, id), &r.Body, nil)
+	return r
 }
diff --git a/openstack/cdn/v1/serviceassets/requests.go b/openstack/cdn/v1/serviceassets/requests.go
index 39afaff..3d0543e 100644
--- a/openstack/cdn/v1/serviceassets/requests.go
+++ b/openstack/cdn/v1/serviceassets/requests.go
@@ -24,10 +24,7 @@
 // ToCDNAssetDeleteParams formats a DeleteOpts into a query string.
 func (opts DeleteOpts) ToCDNAssetDeleteParams() (string, error) {
 	q, err := gophercloud.BuildQueryString(opts)
-	if err != nil {
-		return "", err
-	}
-	return q.String(), nil
+	return q.String(), err
 }
 
 // Delete accepts a unique service ID or URL and deletes the CDN service asset associated with
@@ -41,8 +38,15 @@
 	} else {
 		url = deleteURL(c, idOrURL)
 	}
-
-	var res DeleteResult
-	_, res.Err = c.Delete(url, nil)
-	return res
+	var r DeleteResult
+	if opts != nil {
+		q, err := opts.ToCDNAssetDeleteParams()
+		if err != nil {
+			r.Err = err
+			return r
+		}
+		url += q
+	}
+	_, r.Err = c.Delete(url, nil)
+	return r
 }
diff --git a/openstack/cdn/v1/services/requests.go b/openstack/cdn/v1/services/requests.go
index 89ee372..9a0f54b 100644
--- a/openstack/cdn/v1/services/requests.go
+++ b/openstack/cdn/v1/services/requests.go
@@ -24,10 +24,7 @@
 // ToCDNServiceListQuery formats a ListOpts into a query string.
 func (opts ListOpts) ToCDNServiceListQuery() (string, error) {
 	q, err := gophercloud.BuildQueryString(opts)
-	if err != nil {
-		return "", err
-	}
-	return q.String(), nil
+	return q.String(), err
 }
 
 // List returns a Pager which allows you to iterate over a collection of
@@ -42,15 +39,11 @@
 		}
 		url += query
 	}
-
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
 		p := ServicePage{pagination.MarkerPageBase{PageResult: r}}
 		p.MarkerPageBase.Owner = p
 		return p
-	}
-
-	pager := pagination.NewPager(c, url, createPage)
-	return pager
+	})
 }
 
 // CreateOptsBuilder is the interface options structs have to satisfy in order
@@ -64,123 +57,52 @@
 // CreateOpts is the common options struct used in this package's Create
 // operation.
 type CreateOpts struct {
-	// REQUIRED. Specifies the name of the service. The minimum length for name is
+	// Specifies the name of the service. The minimum length for name is
 	// 3. The maximum length is 256.
-	Name string
-	// REQUIRED. Specifies a list of domains used by users to access their website.
-	Domains []Domain
-	// REQUIRED. Specifies a list of origin domains or IP addresses where the
+	Name string `json:"name" required:"true"`
+	// Specifies a list of domains used by users to access their website.
+	Domains []Domain `json:"domains" required:"true"`
+	// Specifies a list of origin domains or IP addresses where the
 	// original assets are stored.
-	Origins []Origin
-	// REQUIRED. Specifies the CDN provider flavor ID to use. For a list of
+	Origins []Origin `json:"origins" required:"true"`
+	// Specifies the CDN provider flavor ID to use. For a list of
 	// flavors, see the operation to list the available flavors. The minimum
 	// length for flavor_id is 1. The maximum length is 256.
-	FlavorID string
-	// OPTIONAL. Specifies the TTL rules for the assets under this service. Supports wildcards for fine-grained control.
-	Caching []CacheRule
-	// OPTIONAL. Specifies the restrictions that define who can access assets (content from the CDN cache).
-	Restrictions []Restriction
+	FlavorID string `json:"flavor_id" required:"true"`
+	// Specifies the TTL rules for the assets under this service. Supports wildcards for fine-grained control.
+	Caching []CacheRule `json:"caching,omitempty"`
+	// Specifies the restrictions that define who can access assets (content from the CDN cache).
+	Restrictions []Restriction `json:"restrictions,omitempty"`
 }
 
 // ToCDNServiceCreateMap casts a CreateOpts struct to a map.
 func (opts CreateOpts) ToCDNServiceCreateMap() (map[string]interface{}, error) {
-	s := make(map[string]interface{})
 
-	if opts.Name == "" {
-		return nil, no("Name")
-	}
-	s["name"] = opts.Name
-
-	if opts.Domains == nil {
-		return nil, no("Domains")
-	}
-	for _, domain := range opts.Domains {
-		if domain.Domain == "" {
-			return nil, no("Domains[].Domain")
-		}
-	}
-	s["domains"] = opts.Domains
-
-	if opts.Origins == nil {
-		return nil, no("Origins")
-	}
-	for _, origin := range opts.Origins {
-		if origin.Origin == "" {
-			return nil, no("Origins[].Origin")
-		}
-		if origin.Rules == nil && len(opts.Origins) > 1 {
-			return nil, no("Origins[].Rules")
-		}
-		for _, rule := range origin.Rules {
-			if rule.Name == "" {
-				return nil, no("Origins[].Rules[].Name")
-			}
-			if rule.RequestURL == "" {
-				return nil, no("Origins[].Rules[].RequestURL")
+	/*
+		for _, origin := range opts.Origins {
+			if origin.Rules == nil && len(opts.Origins) > 1 {
+				return nil, no("Origins[].Rules")
 			}
 		}
-	}
-	s["origins"] = opts.Origins
+	*/
 
-	if opts.FlavorID == "" {
-		return nil, no("FlavorID")
-	}
-	s["flavor_id"] = opts.FlavorID
-
-	if opts.Caching != nil {
-		for _, cache := range opts.Caching {
-			if cache.Name == "" {
-				return nil, no("Caching[].Name")
-			}
-			if cache.Rules != nil {
-				for _, rule := range cache.Rules {
-					if rule.Name == "" {
-						return nil, no("Caching[].Rules[].Name")
-					}
-					if rule.RequestURL == "" {
-						return nil, no("Caching[].Rules[].RequestURL")
-					}
-				}
-			}
-		}
-		s["caching"] = opts.Caching
-	}
-
-	if opts.Restrictions != nil {
-		for _, restriction := range opts.Restrictions {
-			if restriction.Name == "" {
-				return nil, no("Restrictions[].Name")
-			}
-			if restriction.Rules != nil {
-				for _, rule := range restriction.Rules {
-					if rule.Name == "" {
-						return nil, no("Restrictions[].Rules[].Name")
-					}
-				}
-			}
-		}
-		s["restrictions"] = opts.Restrictions
-	}
-
-	return s, nil
+	return gophercloud.BuildRequestBody(opts, "")
 }
 
 // Create accepts a CreateOpts struct and creates a new CDN service using the
 // values provided.
 func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToCDNServiceCreateMap()
+	var r CreateResult
+	b, err := opts.ToCDNServiceCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
 	// Send request to API
-	resp, err := c.Post(createURL(c), &reqBody, nil, nil)
-	res.Header = resp.Header
-	res.Err = err
-	return res
+	resp, err := c.Post(createURL(c), &b, nil, nil)
+	r.Header = resp.Header
+	r.Err = err
+	return r
 }
 
 // Get retrieves a specific service based on its URL or its unique ID. For
@@ -195,9 +117,9 @@
 		url = getURL(c, idOrURL)
 	}
 
-	var res GetResult
-	_, res.Err = c.Get(url, &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = c.Get(url, &r.Body, nil)
+	return r
 }
 
 // Path is a JSON pointer location that indicates which service parameter is being added, replaced,
@@ -251,11 +173,11 @@
 
 // ToCDNServiceUpdateMap converts an Insertion into a request body fragment suitable for the
 // Update call.
-func (i Insertion) ToCDNServiceUpdateMap() map[string]interface{} {
+func (opts Insertion) ToCDNServiceUpdateMap() map[string]interface{} {
 	return map[string]interface{}{
 		"op":    "add",
-		"path":  i.Value.renderRootOr(func(p Path) string { return p.renderIndex(i.Index) }),
-		"value": i.Value.toPatchValue(),
+		"path":  opts.Value.renderRootOr(func(p Path) string { return p.renderIndex(opts.Index) }),
+		"value": opts.Value.toPatchValue(),
 	}
 }
 
@@ -320,16 +242,17 @@
 
 // ToCDNServiceUpdateMap converts a Removal into a request body fragment suitable for the
 // Update call.
-func (r Removal) ToCDNServiceUpdateMap() map[string]interface{} {
-	result := map[string]interface{}{"op": "remove"}
-	if r.All {
-		result["path"] = r.Path.renderRoot()
+func (opts Removal) ToCDNServiceUpdateMap() map[string]interface{} {
+	b := map[string]interface{}{"op": "remove"}
+	if opts.All {
+		b["path"] = opts.Path.renderRoot()
 	} else {
-		result["path"] = r.Path.renderIndex(r.Index)
+		b["path"] = opts.Path.renderIndex(opts.Index)
 	}
-	return result
+	return b
 }
 
+// UpdateOpts is a slice of Patches used to update a CDN service
 type UpdateOpts []Patch
 
 // Update accepts a slice of Patch operations (Insertion, Append, Replacement or Removal) and
@@ -345,19 +268,19 @@
 		url = updateURL(c, idOrURL)
 	}
 
-	reqBody := make([]map[string]interface{}, len(opts))
+	b := make([]map[string]interface{}, len(opts))
 	for i, patch := range opts {
-		reqBody[i] = patch.ToCDNServiceUpdateMap()
+		b[i] = patch.ToCDNServiceUpdateMap()
 	}
 
 	resp, err := c.Request("PATCH", url, &gophercloud.RequestOpts{
-		JSONBody: &reqBody,
+		JSONBody: &b,
 		OkCodes:  []int{202},
 	})
-	var result UpdateResult
-	result.Header = resp.Header
-	result.Err = err
-	return result
+	var r UpdateResult
+	r.Header = resp.Header
+	r.Err = err
+	return r
 }
 
 // Delete accepts a service's ID or its URL and deletes the CDN service
@@ -372,7 +295,7 @@
 		url = deleteURL(c, idOrURL)
 	}
 
-	var res DeleteResult
-	_, res.Err = c.Delete(url, nil)
-	return res
+	var r DeleteResult
+	_, r.Err = c.Delete(url, nil)
+	return r
 }
diff --git a/openstack/cdn/v1/services/results.go b/openstack/cdn/v1/services/results.go
index 6de3497..f9a1caa 100644
--- a/openstack/cdn/v1/services/results.go
+++ b/openstack/cdn/v1/services/results.go
@@ -9,7 +9,7 @@
 type Domain struct {
 	// Specifies the domain used to access the assets on their website, for which
 	// a CNAME is given to the CDN provider.
-	Domain string `json:"domain"`
+	Domain string `json:"domain" required:"true"`
 	// Specifies the protocol used to access the assets on this domain. Only "http"
 	// or "https" are currently allowed. The default is "http".
 	Protocol string `json:"protocol,omitempty"`
@@ -54,17 +54,17 @@
 // OriginRule represents a rule that defines when an origin should be accessed.
 type OriginRule struct {
 	// Specifies the name of this rule.
-	Name string `json:"name"`
+	Name string `json:"name" required:"true"`
 	// Specifies the request URL this rule should match for this origin to be used. Regex is supported.
-	RequestURL string `json:"request_url"`
+	RequestURL string `json:"request_url" required:"true"`
 }
 
 // Origin specifies a list of origin domains or IP addresses where the original assets are stored.
 type Origin struct {
 	// Specifies the URL or IP address to pull origin content from.
-	Origin string `json:"origin"`
+	Origin string `json:"origin" required:"true"`
 	// Specifies the port used to access the origin. The default is port 80.
-	Port int `json:"port"`
+	Port int `json:"port,omitempty"`
 	// Specifies whether or not to use HTTPS to access the origin. The default
 	// is false.
 	SSL bool `json:"ssl"`
@@ -119,17 +119,17 @@
 // TTLRule specifies a rule that determines if a TTL should be applied to an asset.
 type TTLRule struct {
 	// Specifies the name of this rule.
-	Name string `json:"name"`
+	Name string `json:"name" required:"true"`
 	// Specifies the request URL this rule should match for this TTL to be used. Regex is supported.
-	RequestURL string `json:"request_url"`
+	RequestURL string `json:"request_url" required:"true"`
 }
 
 // CacheRule specifies the TTL rules for the assets under this service.
 type CacheRule struct {
 	// Specifies the name of this caching rule. Note: 'default' is a reserved name used for the default TTL setting.
-	Name string `json:"name"`
+	Name string `json:"name" required:"true"`
 	// Specifies the TTL to apply.
-	TTL int `json:"ttl"`
+	TTL int `json:"ttl,omitempty"`
 	// Specifies a collection of rules that determine if this TTL should be applied to an asset.
 	Rules []TTLRule `json:"rules,omitempty"`
 }
@@ -177,17 +177,17 @@
 // RestrictionRule specifies a rule that determines if this restriction should be applied to an asset.
 type RestrictionRule struct {
 	// Specifies the name of this rule.
-	Name string `json:"name"`
+	Name string `json:"name" required:"true"`
 	// Specifies the http host that requests must come from.
-	Referrer string `json:"referrer"`
+	Referrer string `json:"referrer,omitempty"`
 }
 
 // Restriction specifies a restriction that defines who can access assets (content from the CDN cache).
 type Restriction struct {
 	// Specifies the name of this restriction.
-	Name string `json:"name"`
+	Name string `json:"name" required:"true"`
 	// Specifies a collection of rules that determine if this TTL should be applied to an asset.
-	Rules []RestrictionRule `json:"rules"`
+	Rules []RestrictionRule `json:"rules,omitempty"`
 }
 
 // Error specifies an error that occurred during the previous service action.
diff --git a/openstack/common/extensions/requests.go b/openstack/common/extensions/requests.go
index f49427c..05bf35d 100755
--- a/openstack/common/extensions/requests.go
+++ b/openstack/common/extensions/requests.go
@@ -7,9 +7,9 @@
 
 // Get retrieves information for a specific extension using its alias.
 func Get(c *gophercloud.ServiceClient, alias string) GetResult {
-	var res GetResult
-	_, res.Err = c.Get(ExtensionURL(c, alias), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = c.Get(ExtensionURL(c, alias), &r.Body, nil)
+	return r
 }
 
 // List returns a Pager which allows you to iterate over the full collection of extensions.
diff --git a/openstack/compute/v2/extensions/bootfromvolume/requests.go b/openstack/compute/v2/extensions/bootfromvolume/requests.go
index 7171afa..086cc0a 100644
--- a/openstack/compute/v2/extensions/bootfromvolume/requests.go
+++ b/openstack/compute/v2/extensions/bootfromvolume/requests.go
@@ -1,9 +1,6 @@
 package bootfromvolume
 
 import (
-	"fmt"
-	"strconv"
-
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
 )
@@ -12,37 +9,35 @@
 type SourceType string
 
 const (
-	Volume   SourceType = "volume"
+	// Volume SourceType
+	Volume SourceType = "volume"
+	// Snapshot SourceType
 	Snapshot SourceType = "snapshot"
-	Image    SourceType = "image"
-	Blank    SourceType = "blank"
+	// Image SourceType
+	Image SourceType = "image"
+	// Blank SourceType
+	Blank SourceType = "blank"
 )
 
 // BlockDevice is a structure with options for booting a server instance
 // from a volume. The volume may be created from an image, snapshot, or another
 // volume.
 type BlockDevice struct {
-	// BootIndex [optional] is the boot index. It defaults to 0.
+	// SourceType must be one of: "volume", "snapshot", "image".
+	SourceType SourceType `json:"source_type" required:"true"`
+	// UUID is the unique identifier for the volume, snapshot, or image (see above)
+	UUID string `json:"uuid,omitempty"`
+	// BootIndex is the boot index. It defaults to 0.
 	BootIndex int `json:"boot_index"`
-
-	// DeleteOnTermination [optional] specifies whether or not to delete the attached volume
+	// DeleteOnTermination specifies whether or not to delete the attached volume
 	// when the server is deleted. Defaults to `false`.
 	DeleteOnTermination bool `json:"delete_on_termination"`
-
-	// DestinationType [optional] is the type that gets created. Possible values are "volume"
+	// DestinationType is the type that gets created. Possible values are "volume"
 	// and "local".
-	DestinationType string `json:"destination_type"`
-
-	// GuestFormat [optional] specifies the format of the block device.
-	GuestFormat string `json:"guest_format"`
-
-	// SourceType [required] must be one of: "volume", "snapshot", "image".
-	SourceType SourceType `json:"source_type"`
-
-	// UUID [required] is the unique identifier for the volume, snapshot, or image (see above)
-	UUID string `json:"uuid"`
-
-	// VolumeSize [optional] is the size of the volume to create (in gigabytes).
+	DestinationType string `json:"destination_type,omitempty"`
+	// GuestFormat specifies the format of the block device.
+	GuestFormat string `json:"guest_format,omitempty"`
+	// VolumeSize is the size of the volume to create (in gigabytes).
 	VolumeSize int `json:"volume_size"`
 }
 
@@ -63,7 +58,6 @@
 
 	if len(opts.BlockDevice) == 0 {
 		err := gophercloud.ErrMissingInput{}
-		err.Function = "bootfromvolume.ToServerCreateMap"
 		err.Argument = "bootfromvolume.CreateOptsExt.BlockDevice"
 		return nil, err
 	}
@@ -73,29 +67,11 @@
 	blockDevice := make([]map[string]interface{}, len(opts.BlockDevice))
 
 	for i, bd := range opts.BlockDevice {
-		if string(bd.SourceType) == "" {
-			err := gophercloud.ErrMissingInput{}
-			err.Function = "bootfromvolume.ToServerCreateMap"
-			err.Argument = fmt.Sprintf("bootfromvolume.CreateOptsExt.BlockDevice[%d].SourceType", i)
+		b, err := gophercloud.BuildRequestBody(bd, "")
+		if err != nil {
 			return nil, err
 		}
-
-		blockDevice[i] = make(map[string]interface{})
-
-		blockDevice[i]["source_type"] = bd.SourceType
-		blockDevice[i]["boot_index"] = strconv.Itoa(bd.BootIndex)
-		blockDevice[i]["delete_on_termination"] = strconv.FormatBool(bd.DeleteOnTermination)
-		blockDevice[i]["volume_size"] = strconv.Itoa(bd.VolumeSize)
-		if bd.UUID != "" {
-			blockDevice[i]["uuid"] = bd.UUID
-		}
-		if bd.DestinationType != "" {
-			blockDevice[i]["destination_type"] = bd.DestinationType
-		}
-		if bd.GuestFormat != "" {
-			blockDevice[i]["guest_format"] = bd.GuestFormat
-		}
-
+		blockDevice[i] = b
 	}
 	serverMap["block_device_mapping_v2"] = blockDevice
 
@@ -104,21 +80,14 @@
 
 // Create requests the creation of a server from the given block device mapping.
 func Create(client *gophercloud.ServiceClient, opts servers.CreateOptsBuilder) servers.CreateResult {
-	var res servers.CreateResult
-
-	reqBody, err := opts.ToServerCreateMap()
+	var r servers.CreateResult
+	b, err := opts.ToServerCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	// Delete imageName and flavorName that come from ToServerCreateMap().
-	// As of Liberty, Boot From Volume is failing if they are passed.
-	delete(reqBody["server"].(map[string]interface{}), "imageName")
-	delete(reqBody["server"].(map[string]interface{}), "flavorName")
-
-	_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200, 202},
 	})
-	return res
+	return r
 }
diff --git a/openstack/compute/v2/extensions/bootfromvolume/requests_test.go b/openstack/compute/v2/extensions/bootfromvolume/requests_test.go
index 2359a77..d85070d 100644
--- a/openstack/compute/v2/extensions/bootfromvolume/requests_test.go
+++ b/openstack/compute/v2/extensions/bootfromvolume/requests_test.go
@@ -18,10 +18,11 @@
 		CreateOptsBuilder: base,
 		BlockDevice: []BlockDevice{
 			BlockDevice{
-				UUID:            "123456",
-				SourceType:      Image,
-				DestinationType: "volume",
-				VolumeSize:      10,
+				UUID:                "123456",
+				SourceType:          Image,
+				DestinationType:     "volume",
+				VolumeSize:          10,
+				DeleteOnTermination: false,
 			},
 		},
 	}
@@ -37,9 +38,9 @@
             "uuid":"123456",
             "source_type":"image",
             "destination_type":"volume",
-            "boot_index": "0",
-            "delete_on_termination": "false",
-            "volume_size": "10"
+            "boot_index": 0,
+            "delete_on_termination": false,
+            "volume_size": 10
           }
         ]
       }
@@ -94,28 +95,28 @@
         "flavorRef": "performance1-1",
         "block_device_mapping_v2":[
           {
-            "boot_index": "0",
-            "delete_on_termination": "true",
+            "boot_index": 0,
+            "delete_on_termination": true,
             "destination_type":"local",
             "source_type":"image",
             "uuid":"123456",
-            "volume_size": "0"
+            "volume_size": 0
           },
           {
-            "boot_index": "-1",
-            "delete_on_termination": "true",
+            "boot_index": -1,
+            "delete_on_termination": true,
             "destination_type":"local",
             "guest_format":"ext4",
             "source_type":"blank",
-            "volume_size": "1"
+            "volume_size": 1
           },
           {
-            "boot_index": "-1",
-            "delete_on_termination": "true",
+            "boot_index": -1,
+            "delete_on_termination": true,
             "destination_type":"local",
             "guest_format":"ext4",
             "source_type":"blank",
-            "volume_size": "1"
+            "volume_size": 1
           }
         ]
       }
diff --git a/openstack/compute/v2/extensions/defsecrules/requests.go b/openstack/compute/v2/extensions/defsecrules/requests.go
index 5e2d686..62c112e 100644
--- a/openstack/compute/v2/extensions/defsecrules/requests.go
+++ b/openstack/compute/v2/extensions/defsecrules/requests.go
@@ -7,24 +7,19 @@
 
 // List will return a collection of default rules.
 func List(client *gophercloud.ServiceClient) pagination.Pager {
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, rootURL(client), func(r pagination.PageResult) pagination.Page {
 		return DefaultRulePage{pagination.SinglePageBase(r)}
-	}
-
-	return pagination.NewPager(client, rootURL(client), createPage)
+	})
 }
 
 // CreateOpts represents the configuration for adding a new default rule.
 type CreateOpts struct {
-	// Required - the lower bound of the port range that will be opened.
-	FromPort int `json:"from_port"`
-
-	// Required - the upper bound of the port range that will be opened.
-	ToPort int `json:"to_port"`
-
-	// Required - the protocol type that will be allowed, e.g. TCP.
-	IPProtocol string `json:"ip_protocol"`
-
+	// The lower bound of the port range that will be opened.
+	FromPort int `json:"from_port" required:"true"`
+	// The upper bound of the port range that will be opened.
+	ToPort int `json:"to_port" required:"true"`
+	// The protocol type that will be allowed, e.g. TCP.
+	IPProtocol string `json:"ip_protocol" required:"true"`
 	// ONLY required if FromGroupID is blank. This represents the IP range that
 	// will be the source of network traffic to your security group. Use
 	// 0.0.0.0/0 to allow all IP addresses.
@@ -38,68 +33,33 @@
 
 // ToRuleCreateMap builds the create rule options into a serializable format.
 func (opts CreateOpts) ToRuleCreateMap() (map[string]interface{}, error) {
-	if opts.FromPort == 0 {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "defsecrules.ToRuleCreateMap"
-		err.Argument = "defsecrules.CreateOpts.FromPort"
-		return nil, err
-	}
-	if opts.ToPort == 0 {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "defsecrules.ToRuleCreateMap"
-		err.Argument = "defsecrules.CreateOpts.ToPort"
-		return nil, err
-	}
-	if opts.IPProtocol == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "defsecrules.ToRuleCreateMap"
-		err.Argument = "defsecrules.CreateOpts.IPProtocol"
-		return nil, err
-	}
-	if opts.CIDR == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "defsecrules.ToRuleCreateMap"
-		err.Argument = "defsecrules.CreateOpts.CIDR"
-		return nil, err
-	}
-
-	rule := make(map[string]interface{})
-
-	rule["from_port"] = opts.FromPort
-	rule["to_port"] = opts.ToPort
-	rule["ip_protocol"] = opts.IPProtocol
-	rule["cidr"] = opts.CIDR
-
-	return map[string]interface{}{"security_group_default_rule": rule}, nil
+	return gophercloud.BuildRequestBody(opts, "security_group_default_rule")
 }
 
 // Create is the operation responsible for creating a new default rule.
 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
-	var result CreateResult
-
-	reqBody, err := opts.ToRuleCreateMap()
+	var r CreateResult
+	b, err := opts.ToRuleCreateMap()
 	if err != nil {
-		result.Err = err
-		return result
+		r.Err = err
+		return r
 	}
-
-	_, result.Err = client.Post(rootURL(client), reqBody, &result.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(rootURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-
-	return result
+	return r
 }
 
 // Get will return details for a particular default rule.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var result GetResult
-	_, result.Err = client.Get(resourceURL(client, id), &result.Body, nil)
-	return result
+	var r GetResult
+	_, r.Err = client.Get(resourceURL(client, id), &r.Body, nil)
+	return r
 }
 
 // Delete will permanently delete a default rule from the project.
 func Delete(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
-	var result gophercloud.ErrResult
-	_, result.Err = client.Delete(resourceURL(client, id), nil)
-	return result
+	var r gophercloud.ErrResult
+	_, r.Err = client.Delete(resourceURL(client, id), nil)
+	return r
 }
diff --git a/openstack/compute/v2/extensions/diskconfig/requests.go b/openstack/compute/v2/extensions/diskconfig/requests.go
index 19798d7..41d04b9 100644
--- a/openstack/compute/v2/extensions/diskconfig/requests.go
+++ b/openstack/compute/v2/extensions/diskconfig/requests.go
@@ -50,16 +50,14 @@
 // RebuildOptsExt adds a DiskConfig option to the base RebuildOpts.
 type RebuildOptsExt struct {
 	servers.RebuildOptsBuilder
-
-	// DiskConfig [optional] controls how the rebuilt server's disk is partitioned.
-	DiskConfig DiskConfig
+	// DiskConfig controls how the rebuilt server's disk is partitioned.
+	DiskConfig DiskConfig `json:"OS-DCF:diskConfig,omitempty"`
 }
 
 // ToServerRebuildMap adds the diskconfig option to the base server rebuild options.
 func (opts RebuildOptsExt) ToServerRebuildMap() (map[string]interface{}, error) {
 	if opts.DiskConfig != Auto && opts.DiskConfig != Manual {
 		err := gophercloud.ErrInvalidInput{}
-		err.Function = "diskconfig.ToServerRebuildMap"
 		err.Argument = "diskconfig.RebuildOptsExt.DiskConfig"
 		err.Info = "Must be either diskconfig.Auto or diskconfig.Manual"
 		return nil, err
@@ -88,7 +86,6 @@
 func (opts ResizeOptsExt) ToServerResizeMap() (map[string]interface{}, error) {
 	if opts.DiskConfig != Auto && opts.DiskConfig != Manual {
 		err := gophercloud.ErrInvalidInput{}
-		err.Function = "diskconfig.ToServerResizeMap"
 		err.Argument = "diskconfig.ResizeOptsExt.DiskConfig"
 		err.Info = "Must be either diskconfig.Auto or diskconfig.Manual"
 		return nil, err
diff --git a/openstack/compute/v2/extensions/floatingips/requests.go b/openstack/compute/v2/extensions/floatingips/requests.go
index 00b2520..8137d76 100644
--- a/openstack/compute/v2/extensions/floatingips/requests.go
+++ b/openstack/compute/v2/extensions/floatingips/requests.go
@@ -21,158 +21,97 @@
 // CreateOpts specifies a Floating IP allocation request
 type CreateOpts struct {
 	// Pool is the pool of floating IPs to allocate one from
-	Pool string
-}
-
-// AssociateOpts specifies the required information to associate or disassociate a floating IP to an instance
-type AssociateOpts struct {
-	// ServerID is the UUID of the server
-	ServerID string
-
-	// FixedIP is an optional fixed IP address of the server
-	FixedIP string
-
-	// FloatingIP is the floating IP to associate with an instance
-	FloatingIP string
+	Pool string `json:"pool" required:"true"`
 }
 
 // ToFloatingIPCreateMap constructs a request body from CreateOpts.
 func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
-	if opts.Pool == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "floatingips.ToFloatingIPCreateMap"
-		err.Argument = "floatingips.CreateOpts.Pool"
-		return nil, err
-	}
-
-	return map[string]interface{}{"pool": opts.Pool}, nil
-}
-
-// ToAssociateMap constructs a request body from AssociateOpts.
-func (opts AssociateOpts) ToAssociateMap() (map[string]interface{}, error) {
-	if opts.ServerID == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "floatingips.ToAssociateMap"
-		err.Argument = "floatingips.AssociateOpts.ServerID"
-		return nil, err
-	}
-
-	if opts.FloatingIP == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "floatingips.ToAssociateMap"
-		err.Argument = "floatingips.AssociateOpts.FloatingIP"
-		return nil, err
-	}
-
-	associateInfo := map[string]interface{}{
-		"serverId":   opts.ServerID,
-		"floatingIp": opts.FloatingIP,
-		"fixedIp":    opts.FixedIP,
-	}
-
-	return associateInfo, nil
-
+	return gophercloud.BuildRequestBody(opts, "")
 }
 
 // Create requests the creation of a new floating IP
 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToFloatingIPCreateMap()
+	var r CreateResult
+	b, err := opts.ToFloatingIPCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-	return res
+	return r
 }
 
 // Get returns data about a previously created FloatingIP.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var res GetResult
-	_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+	return r
 }
 
 // Delete requests the deletion of a previous allocated FloatingIP.
 func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
-	var res DeleteResult
-	_, res.Err = client.Delete(deleteURL(client, id), nil)
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(deleteURL(client, id), nil)
+	return r
 }
 
-// association / disassociation
+// AssociateOptsBuilder is the interface types must satfisfy to be used as
+// Associate options
+type AssociateOptsBuilder interface {
+	ToFloatingIPAssociateMap() (map[string]interface{}, error)
+}
 
-// Associate pairs an allocated floating IP with an instance
-// Deprecated. Use AssociateInstance.
-func Associate(client *gophercloud.ServiceClient, serverId, fip string) AssociateResult {
-	var res AssociateResult
+// AssociateOpts specifies the required information to associate a floating IP with an instance
+type AssociateOpts struct {
+	// FloatingIP is the floating IP to associate with an instance
+	FloatingIP string `json:"address" required:"true"`
+	// FixedIP is an optional fixed IP address of the server
+	FixedIP string `json:"fixed_address,omitempty"`
+}
 
-	addFloatingIp := make(map[string]interface{})
-	addFloatingIp["address"] = fip
-	reqBody := map[string]interface{}{"addFloatingIp": addFloatingIp}
-
-	_, res.Err = client.Post(associateURL(client, serverId), reqBody, nil, nil)
-	return res
+// ToFloatingIPAssociateMap constructs a request body from AssociateOpts.
+func (opts AssociateOpts) ToFloatingIPAssociateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "addFloatingIp")
 }
 
 // AssociateInstance pairs an allocated floating IP with an instance.
-func AssociateInstance(client *gophercloud.ServiceClient, opts AssociateOpts) AssociateResult {
-	var res AssociateResult
-
-	associateInfo, err := opts.ToAssociateMap()
+func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts AssociateOptsBuilder) AssociateResult {
+	var r AssociateResult
+	b, err := opts.ToFloatingIPAssociateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	addFloatingIp := make(map[string]interface{})
-	addFloatingIp["address"] = associateInfo["floatingIp"].(string)
-
-	// fixedIp is not required
-	if associateInfo["fixedIp"] != "" {
-		addFloatingIp["fixed_address"] = associateInfo["fixedIp"].(string)
-	}
-
-	serverId := associateInfo["serverId"].(string)
-
-	reqBody := map[string]interface{}{"addFloatingIp": addFloatingIp}
-	_, res.Err = client.Post(associateURL(client, serverId), reqBody, nil, nil)
-	return res
+	_, r.Err = client.Post(associateURL(client, serverID), b, nil, nil)
+	return r
 }
 
-// Disassociate decouples an allocated floating IP from an instance
-// Deprecated. Use DisassociateInstance.
-func Disassociate(client *gophercloud.ServiceClient, serverId, fip string) DisassociateResult {
-	var res DisassociateResult
+// DisassociateOptsBuilder is the interface types must satfisfy to be used as
+// Disassociate options
+type DisassociateOptsBuilder interface {
+	ToFloatingIPDisassociateMap() (map[string]interface{}, error)
+}
 
-	removeFloatingIp := make(map[string]interface{})
-	removeFloatingIp["address"] = fip
-	reqBody := map[string]interface{}{"removeFloatingIp": removeFloatingIp}
+// DisassociateOpts specifies the required information to disassociate a floating IP with an instance
+type DisassociateOpts struct {
+	FloatingIP string `json:"address" required:"true"`
+}
 
-	_, res.Err = client.Post(disassociateURL(client, serverId), reqBody, nil, nil)
-	return res
+// ToFloatingIPDisassociateMap constructs a request body from AssociateOpts.
+func (opts DisassociateOpts) ToFloatingIPDisassociateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "removeFloatingIp")
 }
 
 // DisassociateInstance decouples an allocated floating IP from an instance
-func DisassociateInstance(client *gophercloud.ServiceClient, opts AssociateOpts) DisassociateResult {
-	var res DisassociateResult
-
-	associateInfo, err := opts.ToAssociateMap()
+func DisassociateInstance(client *gophercloud.ServiceClient, serverID string, opts DisassociateOptsBuilder) DisassociateResult {
+	var r DisassociateResult
+	b, err := opts.ToFloatingIPDisassociateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	removeFloatingIp := make(map[string]interface{})
-	removeFloatingIp["address"] = associateInfo["floatingIp"].(string)
-	reqBody := map[string]interface{}{"removeFloatingIp": removeFloatingIp}
-
-	serverId := associateInfo["serverId"].(string)
-
-	_, res.Err = client.Post(disassociateURL(client, serverId), reqBody, nil, nil)
-	return res
+	_, r.Err = client.Post(disassociateURL(client, serverID), b, nil, nil)
+	return r
 }
diff --git a/openstack/compute/v2/extensions/floatingips/requests_test.go b/openstack/compute/v2/extensions/floatingips/requests_test.go
index 705c92f..f0e9560 100644
--- a/openstack/compute/v2/extensions/floatingips/requests_test.go
+++ b/openstack/compute/v2/extensions/floatingips/requests_test.go
@@ -57,28 +57,16 @@
 	th.AssertNoErr(t, err)
 }
 
-func TestAssociateDeprecated(t *testing.T) {
-	th.SetupHTTP()
-	defer th.TeardownHTTP()
-	HandleAssociateSuccessfully(t)
-	serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0"
-	fip := "10.10.10.2"
-
-	err := Associate(client.ServiceClient(), serverId, fip).ExtractErr()
-	th.AssertNoErr(t, err)
-}
-
 func TestAssociate(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 	HandleAssociateSuccessfully(t)
 
 	associateOpts := AssociateOpts{
-		ServerID:   "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
 		FloatingIP: "10.10.10.2",
 	}
 
-	err := AssociateInstance(client.ServiceClient(), associateOpts).ExtractErr()
+	err := AssociateInstance(client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0", associateOpts).ExtractErr()
 	th.AssertNoErr(t, err)
 }
 
@@ -88,23 +76,11 @@
 	HandleAssociateFixedSuccessfully(t)
 
 	associateOpts := AssociateOpts{
-		ServerID:   "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
 		FloatingIP: "10.10.10.2",
 		FixedIP:    "166.78.185.201",
 	}
 
-	err := AssociateInstance(client.ServiceClient(), associateOpts).ExtractErr()
-	th.AssertNoErr(t, err)
-}
-
-func TestDisassociateDeprecated(t *testing.T) {
-	th.SetupHTTP()
-	defer th.TeardownHTTP()
-	HandleDisassociateSuccessfully(t)
-	serverId := "4d8c3732-a248-40ed-bebc-539a6ffd25c0"
-	fip := "10.10.10.2"
-
-	err := Disassociate(client.ServiceClient(), serverId, fip).ExtractErr()
+	err := AssociateInstance(client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0", associateOpts).ExtractErr()
 	th.AssertNoErr(t, err)
 }
 
@@ -113,11 +89,10 @@
 	defer th.TeardownHTTP()
 	HandleDisassociateSuccessfully(t)
 
-	associateOpts := AssociateOpts{
-		ServerID:   "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
+	disassociateOpts := DisassociateOpts{
 		FloatingIP: "10.10.10.2",
 	}
 
-	err := DisassociateInstance(client.ServiceClient(), associateOpts).ExtractErr()
+	err := DisassociateInstance(client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0", disassociateOpts).ExtractErr()
 	th.AssertNoErr(t, err)
 }
diff --git a/openstack/compute/v2/extensions/keypairs/requests.go b/openstack/compute/v2/extensions/keypairs/requests.go
index b9b32ee..2a01084 100644
--- a/openstack/compute/v2/extensions/keypairs/requests.go
+++ b/openstack/compute/v2/extensions/keypairs/requests.go
@@ -45,59 +45,43 @@
 
 // CreateOpts specifies keypair creation or import parameters.
 type CreateOpts struct {
-	// Name [required] is a friendly name to refer to this KeyPair in other services.
-	Name string
-
+	// Name is a friendly name to refer to this KeyPair in other services.
+	Name string `json:"name" required:"true"`
 	// PublicKey [optional] is a pregenerated OpenSSH-formatted public key. If provided, this key
 	// will be imported and no new key will be created.
-	PublicKey string
+	PublicKey string `json:"public_key,omitempty"`
 }
 
 // ToKeyPairCreateMap constructs a request body from CreateOpts.
 func (opts CreateOpts) ToKeyPairCreateMap() (map[string]interface{}, error) {
-	if opts.Name == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "keypairs.ToKeyPairCreateMap"
-		err.Argument = "keypairs.CreateOpts.Name"
-		return nil, err
-	}
-
-	keypair := make(map[string]interface{})
-	keypair["name"] = opts.Name
-	if opts.PublicKey != "" {
-		keypair["public_key"] = opts.PublicKey
-	}
-
-	return map[string]interface{}{"keypair": keypair}, nil
+	return gophercloud.BuildRequestBody(opts, "keypair")
 }
 
 // Create requests the creation of a new keypair on the server, or to import a pre-existing
 // keypair.
 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToKeyPairCreateMap()
+	var r CreateResult
+	b, err := opts.ToKeyPairCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-	return res
+	return r
 }
 
 // Get returns public data about a previously uploaded KeyPair.
 func Get(client *gophercloud.ServiceClient, name string) GetResult {
-	var res GetResult
-	_, res.Err = client.Get(getURL(client, name), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, name), &r.Body, nil)
+	return r
 }
 
 // Delete requests the deletion of a previous stored KeyPair from the server.
 func Delete(client *gophercloud.ServiceClient, name string) DeleteResult {
-	var res DeleteResult
-	_, res.Err = client.Delete(deleteURL(client, name), nil)
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(deleteURL(client, name), nil)
+	return r
 }
diff --git a/openstack/compute/v2/extensions/networks/requests.go b/openstack/compute/v2/extensions/networks/requests.go
index a505733..6f0f182 100644
--- a/openstack/compute/v2/extensions/networks/requests.go
+++ b/openstack/compute/v2/extensions/networks/requests.go
@@ -7,16 +7,14 @@
 
 // List returns a Pager that allows you to iterate over a collection of Network.
 func List(client *gophercloud.ServiceClient) pagination.Pager {
-	url := listURL(client)
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
 		return NetworkPage{pagination.SinglePageBase(r)}
-	}
-	return pagination.NewPager(client, url, createPage)
+	})
 }
 
 // Get returns data about a previously created Network.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var res GetResult
-	_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+	return r
 }
diff --git a/openstack/compute/v2/extensions/schedulerhints/requests.go b/openstack/compute/v2/extensions/schedulerhints/requests.go
index 5713e72..1e06b46 100644
--- a/openstack/compute/v2/extensions/schedulerhints/requests.go
+++ b/openstack/compute/v2/extensions/schedulerhints/requests.go
@@ -14,33 +14,28 @@
 type SchedulerHints struct {
 	// Group specifies a Server Group to place the instance in.
 	Group string
-
 	// DifferentHost will place the instance on a compute node that does not
 	// host the given instances.
 	DifferentHost []string
-
 	// SameHost will place the instance on a compute node that hosts the given
 	// instances.
 	SameHost []string
-
 	// Query is a conditional statement that results in compute nodes able to
 	// host the instance.
 	Query []interface{}
-
 	// TargetCell specifies a cell name where the instance will be placed.
-	TargetCell string
-
+	TargetCell string `json:"target_cell,omitempty"`
 	// BuildNearHostIP specifies a subnet of compute nodes to host the instance.
 	BuildNearHostIP string
 }
 
-// SchedulerHintsBuilder builds the scheduler hints into a serializable format.
-type SchedulerHintsBuilder interface {
-	ToServerSchedulerHintsMap() (map[string]interface{}, error)
+// CreateOptsBuilder builds the scheduler hints into a serializable format.
+type CreateOptsBuilder interface {
+	ToServerSchedulerHintsCreateMap() (map[string]interface{}, error)
 }
 
 // ToServerSchedulerHintsMap builds the scheduler hints into a serializable format.
-func (opts SchedulerHints) ToServerSchedulerHintsMap() (map[string]interface{}, error) {
+func (opts SchedulerHints) ToServerSchedulerHintsCreateMap() (map[string]interface{}, error) {
 	sh := make(map[string]interface{})
 
 	uuidRegex, _ := regexp.Compile("^[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}$")
@@ -48,7 +43,6 @@
 	if opts.Group != "" {
 		if !uuidRegex.MatchString(opts.Group) {
 			err := gophercloud.ErrInvalidInput{}
-			err.Function = "schedulerhints.ToServerSchedulerHintsMap"
 			err.Argument = "schedulerhints.SchedulerHints.Group"
 			err.Value = opts.Group
 			err.Info = "Group must be a UUID"
@@ -61,7 +55,6 @@
 		for _, diffHost := range opts.DifferentHost {
 			if !uuidRegex.MatchString(diffHost) {
 				err := gophercloud.ErrInvalidInput{}
-				err.Function = "schedulerhints.ToServerSchedulerHintsMap"
 				err.Argument = "schedulerhints.SchedulerHints.DifferentHost"
 				err.Value = opts.DifferentHost
 				err.Info = "The hosts must be in UUID format."
@@ -75,7 +68,6 @@
 		for _, sameHost := range opts.SameHost {
 			if !uuidRegex.MatchString(sameHost) {
 				err := gophercloud.ErrInvalidInput{}
-				err.Function = "schedulerhints.ToServerSchedulerHintsMap"
 				err.Argument = "schedulerhints.SchedulerHints.SameHost"
 				err.Value = opts.SameHost
 				err.Info = "The hosts must be in UUID format."
@@ -99,7 +91,6 @@
 	if len(opts.Query) > 0 {
 		if len(opts.Query) < 3 {
 			err := gophercloud.ErrInvalidInput{}
-			err.Function = "schedulerhints.ToServerSchedulerHintsMap"
 			err.Argument = "schedulerhints.SchedulerHints.Query"
 			err.Value = opts.Query
 			err.Info = "Must be a conditional statement in the format of [op,variable,value]"
@@ -115,7 +106,6 @@
 	if opts.BuildNearHostIP != "" {
 		if _, _, err := net.ParseCIDR(opts.BuildNearHostIP); err != nil {
 			err := gophercloud.ErrInvalidInput{}
-			err.Function = "schedulerhints.ToServerSchedulerHintsMap"
 			err.Argument = "schedulerhints.SchedulerHints.BuildNearHostIP"
 			err.Value = opts.BuildNearHostIP
 			err.Info = "Must be a valid subnet in the form 192.168.1.1/24"
@@ -132,9 +122,8 @@
 // CreateOptsExt adds a SchedulerHints option to the base CreateOpts.
 type CreateOptsExt struct {
 	servers.CreateOptsBuilder
-
 	// SchedulerHints provides a set of hints to the scheduler.
-	SchedulerHints SchedulerHintsBuilder
+	SchedulerHints CreateOptsBuilder
 }
 
 // ToServerCreateMap adds the SchedulerHints option to the base server creation options.
@@ -144,7 +133,7 @@
 		return nil, err
 	}
 
-	schedulerHints, err := opts.SchedulerHints.ToServerSchedulerHintsMap()
+	schedulerHints, err := opts.SchedulerHints.ToServerSchedulerHintsCreateMap()
 	if err != nil {
 		return nil, err
 	}
diff --git a/openstack/compute/v2/extensions/secgroups/requests.go b/openstack/compute/v2/extensions/secgroups/requests.go
index a0bb025..48ff698 100644
--- a/openstack/compute/v2/extensions/secgroups/requests.go
+++ b/openstack/compute/v2/extensions/secgroups/requests.go
@@ -6,11 +6,9 @@
 )
 
 func commonList(client *gophercloud.ServiceClient, url string) pagination.Pager {
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
 		return SecurityGroupPage{pagination.SinglePageBase(r)}
-	}
-
-	return pagination.NewPager(client, url, createPage)
+	})
 }
 
 // List will return a collection of all the security groups for a particular
@@ -29,11 +27,10 @@
 // security groups. It therefore represents the mutable attributes of a
 // security group.
 type GroupOpts struct {
-	// Required - the name of your security group.
-	Name string `json:"name"`
-
-	// Required - the description of your security group.
-	Description string `json:"description"`
+	// the name of your security group.
+	Name string `json:"name" required:"true"`
+	// the description of your security group.
+	Description string `json:"description" required:"true"`
 }
 
 // CreateOpts is the struct responsible for creating a security group.
@@ -46,42 +43,21 @@
 
 // ToSecGroupCreateMap builds the create options into a serializable format.
 func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) {
-	sg := make(map[string]interface{})
-
-	if opts.Name == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "secgroups.ToSecGroupCreateMap"
-		err.Argument = "secgroups.CreateOpts.Name"
-		return nil, err
-	}
-	if opts.Description == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "secgroups.ToSecGroupCreateMap"
-		err.Argument = "secgroups.CreateOpts.Description"
-		return nil, err
-	}
-
-	sg["name"] = opts.Name
-	sg["description"] = opts.Description
-
-	return map[string]interface{}{"security_group": sg}, nil
+	return gophercloud.BuildRequestBody(opts, "security_group")
 }
 
 // Create will create a new security group.
 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
-	var result CreateResult
-
-	reqBody, err := opts.ToSecGroupCreateMap()
+	var r CreateResult
+	b, err := opts.ToSecGroupCreateMap()
 	if err != nil {
-		result.Err = err
-		return result
+		r.Err = err
+		return r
 	}
-
-	_, result.Err = client.Post(rootURL(client), reqBody, &result.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(rootURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-
-	return result
+	return r
 }
 
 // UpdateOpts is the struct responsible for updating an existing security group.
@@ -94,79 +70,53 @@
 
 // ToSecGroupUpdateMap builds the update options into a serializable format.
 func (opts UpdateOpts) ToSecGroupUpdateMap() (map[string]interface{}, error) {
-	sg := make(map[string]interface{})
-
-	if opts.Name == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "secgroups.ToSecGroupUpdateMap"
-		err.Argument = "secgroups.UpdateOpts.Name"
-		return nil, err
-	}
-	if opts.Description == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "secgroups.ToSecGroupUpdateMap"
-		err.Argument = "secgroups.UpdateOpts.Description"
-		return nil, err
-	}
-
-	sg["name"] = opts.Name
-	sg["description"] = opts.Description
-
-	return map[string]interface{}{"security_group": sg}, nil
+	return gophercloud.BuildRequestBody(opts, "security_group")
 }
 
 // Update will modify the mutable properties of a security group, notably its
 // name and description.
 func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
-	var result UpdateResult
-
-	reqBody, err := opts.ToSecGroupUpdateMap()
+	var r UpdateResult
+	b, err := opts.ToSecGroupUpdateMap()
 	if err != nil {
-		result.Err = err
-		return result
+		r.Err = err
+		return r
 	}
-
-	_, result.Err = client.Put(resourceURL(client, id), reqBody, &result.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Put(resourceURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-
-	return result
+	return r
 }
 
 // Get will return details for a particular security group.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var result GetResult
-	_, result.Err = client.Get(resourceURL(client, id), &result.Body, nil)
-	return result
+	var r GetResult
+	_, r.Err = client.Get(resourceURL(client, id), &r.Body, nil)
+	return r
 }
 
 // Delete will permanently delete a security group from the project.
 func Delete(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
-	var result gophercloud.ErrResult
-	_, result.Err = client.Delete(resourceURL(client, id), nil)
-	return result
+	var r gophercloud.ErrResult
+	_, r.Err = client.Delete(resourceURL(client, id), nil)
+	return r
 }
 
 // CreateRuleOpts represents the configuration for adding a new rule to an
 // existing security group.
 type CreateRuleOpts struct {
 	// Required - the ID of the group that this rule will be added to.
-	ParentGroupID string `json:"parent_group_id"`
-
+	ParentGroupID string `json:"parent_group_id" required:"true"`
 	// Required - the lower bound of the port range that will be opened.
-	FromPort int `json:"from_port"`
-
+	FromPort int `json:"from_port" required:"true"`
 	// Required - the upper bound of the port range that will be opened.
-	ToPort int `json:"to_port"`
-
+	ToPort int `json:"to_port" required:"true"`
 	// Required - the protocol type that will be allowed, e.g. TCP.
-	IPProtocol string `json:"ip_protocol"`
-
+	IPProtocol string `json:"ip_protocol" required:"true"`
 	// ONLY required if FromGroupID is blank. This represents the IP range that
 	// will be the source of network traffic to your security group. Use
 	// 0.0.0.0/0 to allow all IP addresses.
 	CIDR string `json:"cidr,omitempty"`
-
 	// ONLY required if CIDR is blank. This value represents the ID of a group
 	// that forwards traffic to the parent group. So, instead of accepting
 	// network traffic from an entire IP range, you can instead refine the
@@ -181,78 +131,30 @@
 
 // ToRuleCreateMap builds the create rule options into a serializable format.
 func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) {
-	if opts.ParentGroupID == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "secgroups.ToRuleCreateMap"
-		err.Argument = "secgroups.CreateRuleOpts.ParentGroupID"
-		return nil, err
-	}
-	if opts.FromPort == 0 {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "secgroups.ToRuleCreateMap"
-		err.Argument = "secgroups.CreateRuleOpts.FromPort"
-		return nil, err
-	}
-	if opts.ToPort == 0 {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "secgroups.ToRuleCreateMap"
-		err.Argument = "secgroups.CreateRuleOpts.ToPort"
-		return nil, err
-	}
-	if opts.IPProtocol == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "secgroups.ToRuleCreateMap"
-		err.Argument = "secgroups.CreateRuleOpts.IPProtocol"
-		return nil, err
-	}
-	if opts.CIDR == "" && opts.FromGroupID == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "secgroups.ToRuleCreateMap"
-		err.Argument = "secgroups.CreateRuleOpts.CIDR/secgroups.CreateRuleOpts.FromGroupID"
-		return nil, err
-	}
-
-	rule := make(map[string]interface{})
-
-	rule["parent_group_id"] = opts.ParentGroupID
-	rule["from_port"] = opts.FromPort
-	rule["to_port"] = opts.ToPort
-	rule["ip_protocol"] = opts.IPProtocol
-
-	if opts.CIDR != "" {
-		rule["cidr"] = opts.CIDR
-	}
-	if opts.FromGroupID != "" {
-		rule["group_id"] = opts.FromGroupID
-	}
-
-	return map[string]interface{}{"security_group_rule": rule}, nil
+	return gophercloud.BuildRequestBody(opts, "security_group_rule")
 }
 
 // CreateRule will add a new rule to an existing security group (whose ID is
 // specified in CreateRuleOpts). You have the option of controlling inbound
 // traffic from either an IP range (CIDR) or from another security group.
 func CreateRule(client *gophercloud.ServiceClient, opts CreateRuleOptsBuilder) CreateRuleResult {
-	var result CreateRuleResult
-
-	reqBody, err := opts.ToRuleCreateMap()
+	var r CreateRuleResult
+	b, err := opts.ToRuleCreateMap()
 	if err != nil {
-		result.Err = err
-		return result
+		r.Err = err
+		return r
 	}
-
-	_, result.Err = client.Post(rootRuleURL(client), reqBody, &result.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(rootRuleURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-
-	return result
+	return r
 }
 
 // DeleteRule will permanently delete a rule from a security group.
 func DeleteRule(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
-	var result gophercloud.ErrResult
-	_, result.Err = client.Delete(resourceRuleURL(client, id), nil)
-	return result
+	var r gophercloud.ErrResult
+	_, r.Err = client.Delete(resourceRuleURL(client, id), nil)
+	return r
 }
 
 func actionMap(prefix, groupName string) map[string]map[string]string {
@@ -261,17 +163,17 @@
 	}
 }
 
-// AddServerToGroup will associate a server and a security group, enforcing the
+// AddServer will associate a server and a security group, enforcing the
 // rules of the group on the server.
-func AddServerToGroup(client *gophercloud.ServiceClient, serverID, groupName string) gophercloud.ErrResult {
-	var result gophercloud.ErrResult
-	_, result.Err = client.Post(serverActionURL(client, serverID), actionMap("add", groupName), &result.Body, nil)
-	return result
+func AddServer(client *gophercloud.ServiceClient, serverID, groupName string) gophercloud.ErrResult {
+	var r gophercloud.ErrResult
+	_, r.Err = client.Post(serverActionURL(client, serverID), actionMap("add", groupName), &r.Body, nil)
+	return r
 }
 
-// RemoveServerFromGroup will disassociate a server from a security group.
-func RemoveServerFromGroup(client *gophercloud.ServiceClient, serverID, groupName string) gophercloud.ErrResult {
-	var result gophercloud.ErrResult
-	_, result.Err = client.Post(serverActionURL(client, serverID), actionMap("remove", groupName), &result.Body, nil)
-	return result
+// RemoveServer will disassociate a server from a security group.
+func RemoveServer(client *gophercloud.ServiceClient, serverID, groupName string) gophercloud.ErrResult {
+	var r gophercloud.ErrResult
+	_, r.Err = client.Post(serverActionURL(client, serverID), actionMap("remove", groupName), &r.Body, nil)
+	return r
 }
diff --git a/openstack/compute/v2/extensions/secgroups/requests_test.go b/openstack/compute/v2/extensions/secgroups/requests_test.go
index 59a3898..9496d4a 100644
--- a/openstack/compute/v2/extensions/secgroups/requests_test.go
+++ b/openstack/compute/v2/extensions/secgroups/requests_test.go
@@ -233,7 +233,7 @@
 
 	mockAddServerToGroupResponse(t, serverID)
 
-	err := AddServerToGroup(client.ServiceClient(), serverID, "test").ExtractErr()
+	err := AddServer(client.ServiceClient(), serverID, "test").ExtractErr()
 	th.AssertNoErr(t, err)
 }
 
@@ -243,6 +243,6 @@
 
 	mockRemoveServerFromGroupResponse(t, serverID)
 
-	err := RemoveServerFromGroup(client.ServiceClient(), serverID, "test").ExtractErr()
+	err := RemoveServer(client.ServiceClient(), serverID, "test").ExtractErr()
 	th.AssertNoErr(t, err)
 }
diff --git a/openstack/compute/v2/extensions/servergroups/requests.go b/openstack/compute/v2/extensions/servergroups/requests.go
index 212edac..1348cfa 100644
--- a/openstack/compute/v2/extensions/servergroups/requests.go
+++ b/openstack/compute/v2/extensions/servergroups/requests.go
@@ -21,61 +21,40 @@
 // CreateOpts specifies a Server Group allocation request
 type CreateOpts struct {
 	// Name is the name of the server group
-	Name string
-
+	Name string `json:"name" required:"true"`
 	// Policies are the server group policies
-	Policies []string
+	Policies []string `json:"policies" required:"true"`
 }
 
 // ToServerGroupCreateMap constructs a request body from CreateOpts.
 func (opts CreateOpts) ToServerGroupCreateMap() (map[string]interface{}, error) {
-	if opts.Name == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "servergroups.ToServerGroupCreateMap"
-		err.Argument = "servergroups.CreateOpts.Name"
-		return nil, err
-	}
-
-	if len(opts.Policies) < 1 {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "servergroups.ToServerGroupCreateMap"
-		err.Argument = "servergroups.CreateOpts.Policies"
-		return nil, err
-	}
-
-	serverGroup := make(map[string]interface{})
-	serverGroup["name"] = opts.Name
-	serverGroup["policies"] = opts.Policies
-
-	return map[string]interface{}{"server_group": serverGroup}, nil
+	return gophercloud.BuildRequestBody(opts, "server_group")
 }
 
 // Create requests the creation of a new Server Group
 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToServerGroupCreateMap()
+	var r CreateResult
+	b, err := opts.ToServerGroupCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-	return res
+	return r
 }
 
 // Get returns data about a previously created ServerGroup.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var res GetResult
-	_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+	return r
 }
 
 // Delete requests the deletion of a previously allocated ServerGroup.
 func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
-	var res DeleteResult
-	_, res.Err = client.Delete(deleteURL(client, id), nil)
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(deleteURL(client, id), nil)
+	return r
 }
diff --git a/openstack/compute/v2/extensions/startstop/requests.go b/openstack/compute/v2/extensions/startstop/requests.go
index 8364487..460223a 100644
--- a/openstack/compute/v2/extensions/startstop/requests.go
+++ b/openstack/compute/v2/extensions/startstop/requests.go
@@ -8,16 +8,14 @@
 
 // Start is the operation responsible for starting a Compute server.
 func Start(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
-	var res gophercloud.ErrResult
-	reqBody := map[string]interface{}{"os-start": nil}
-	_, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
-	return res
+	var r gophercloud.ErrResult
+	_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-start": nil}, nil, nil)
+	return r
 }
 
 // Stop is the operation responsible for stopping a Compute server.
 func Stop(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
-	var res gophercloud.ErrResult
-	reqBody := map[string]interface{}{"os-stop": nil}
-	_, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
-	return res
+	var r gophercloud.ErrResult
+	_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-stop": nil}, nil, nil)
+	return r
 }
diff --git a/openstack/compute/v2/extensions/tenantnetworks/requests.go b/openstack/compute/v2/extensions/tenantnetworks/requests.go
index 68159f2..ca33c37 100644
--- a/openstack/compute/v2/extensions/tenantnetworks/requests.go
+++ b/openstack/compute/v2/extensions/tenantnetworks/requests.go
@@ -7,16 +7,14 @@
 
 // List returns a Pager that allows you to iterate over a collection of Network.
 func List(client *gophercloud.ServiceClient) pagination.Pager {
-	url := listURL(client)
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
 		return NetworkPage{pagination.SinglePageBase(r)}
-	}
-	return pagination.NewPager(client, url, createPage)
+	})
 }
 
 // Get returns data about a previously created Network.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var res GetResult
-	_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+	return r
 }
diff --git a/openstack/compute/v2/extensions/volumeattach/requests.go b/openstack/compute/v2/extensions/volumeattach/requests.go
index c2bc2ee..c8ef930 100644
--- a/openstack/compute/v2/extensions/volumeattach/requests.go
+++ b/openstack/compute/v2/extensions/volumeattach/requests.go
@@ -6,8 +6,8 @@
 )
 
 // List returns a Pager that allows you to iterate over a collection of VolumeAttachments.
-func List(client *gophercloud.ServiceClient, serverId string) pagination.Pager {
-	return pagination.NewPager(client, listURL(client, serverId), func(r pagination.PageResult) pagination.Page {
+func List(client *gophercloud.ServiceClient, serverID string) pagination.Pager {
+	return pagination.NewPager(client, listURL(client, serverID), func(r pagination.PageResult) pagination.Page {
 		return VolumeAttachmentPage{pagination.SinglePageBase(r)}
 	})
 }
@@ -21,56 +21,40 @@
 // CreateOpts specifies volume attachment creation or import parameters.
 type CreateOpts struct {
 	// Device is the device that the volume will attach to the instance as. Omit for "auto"
-	Device string
-
+	Device string `json:"device,omitempty"`
 	// VolumeID is the ID of the volume to attach to the instance
-	VolumeID string
+	VolumeID string `json:"volumeId" required:"true"`
 }
 
 // ToVolumeAttachmentCreateMap constructs a request body from CreateOpts.
 func (opts CreateOpts) ToVolumeAttachmentCreateMap() (map[string]interface{}, error) {
-	if opts.VolumeID == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "volumeattach.ToVolumeAttachmentCreateMap"
-		err.Argument = "volumeattach.CreateOpts.VolumeID"
-		return nil, err
-	}
-
-	volumeAttachment := make(map[string]interface{})
-	volumeAttachment["volumeId"] = opts.VolumeID
-	if opts.Device != "" {
-		volumeAttachment["device"] = opts.Device
-	}
-
-	return map[string]interface{}{"volumeAttachment": volumeAttachment}, nil
+	return gophercloud.BuildRequestBody(opts, "volumeAttachment")
 }
 
 // Create requests the creation of a new volume attachment on the server
-func Create(client *gophercloud.ServiceClient, serverId string, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToVolumeAttachmentCreateMap()
+func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsBuilder) CreateResult {
+	var r CreateResult
+	b, err := opts.ToVolumeAttachmentCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Post(createURL(client, serverId), reqBody, &res.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(createURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-	return res
+	return r
 }
 
 // Get returns public data about a previously created VolumeAttachment.
-func Get(client *gophercloud.ServiceClient, serverId, aId string) GetResult {
-	var res GetResult
-	_, res.Err = client.Get(getURL(client, serverId, aId), &res.Body, nil)
-	return res
+func Get(client *gophercloud.ServiceClient, serverID, attachmentID string) GetResult {
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, serverID, attachmentID), &r.Body, nil)
+	return r
 }
 
 // Delete requests the deletion of a previous stored VolumeAttachment from the server.
-func Delete(client *gophercloud.ServiceClient, serverId, aId string) DeleteResult {
-	var res DeleteResult
-	_, res.Err = client.Delete(deleteURL(client, serverId, aId), nil)
-	return res
+func Delete(client *gophercloud.ServiceClient, serverID, attachmentID string) DeleteResult {
+	var r DeleteResult
+	_, r.Err = client.Delete(deleteURL(client, serverID, attachmentID), nil)
+	return r
 }
diff --git a/openstack/compute/v2/flavors/requests.go b/openstack/compute/v2/flavors/requests.go
index 6e8f003..4f3fc46 100644
--- a/openstack/compute/v2/flavors/requests.go
+++ b/openstack/compute/v2/flavors/requests.go
@@ -34,10 +34,7 @@
 // ToFlavorListQuery formats a ListOpts into a query string.
 func (opts ListOpts) ToFlavorListQuery() (string, error) {
 	q, err := gophercloud.BuildQueryString(opts)
-	if err != nil {
-		return "", err
-	}
-	return q.String(), nil
+	return q.String(), err
 }
 
 // ListDetail instructs OpenStack to provide a list of flavors.
@@ -52,32 +49,23 @@
 		}
 		url += query
 	}
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
 		return FlavorPage{pagination.LinkedPageBase{PageResult: r}}
-	}
-
-	return pagination.NewPager(client, url, createPage)
+	})
 }
 
 // Get instructs OpenStack to provide details on a single flavor, identified by its ID.
 // Use ExtractFlavor to convert its result into a Flavor.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var res GetResult
-	_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
-	return res
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+	return r
 }
 
 // IDFromName is a convienience function that returns a flavor's ID given its name.
 func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
 	count := 0
 	id := ""
-	if name == "" {
-		err := &gophercloud.ErrMissingInput{}
-		err.Function = "flavors.IDFromName"
-		err.Argument = "name"
-		return "", err
-	}
-
 	allPages, err := ListDetail(client, nil).AllPages()
 	if err != nil {
 		return "", err
@@ -98,7 +86,6 @@
 	switch count {
 	case 0:
 		err := &gophercloud.ErrResourceNotFound{}
-		err.Function = "flavors.IDFromName"
 		err.ResourceType = "flavor"
 		err.Name = name
 		return "", err
@@ -106,7 +93,6 @@
 		return id, nil
 	default:
 		err := &gophercloud.ErrMultipleResourcesFound{}
-		err.Function = "flavors.IDFromName"
 		err.ResourceType = "flavor"
 		err.Name = name
 		err.Count = count
diff --git a/openstack/compute/v2/images/requests.go b/openstack/compute/v2/images/requests.go
index 88db657..00461f8 100644
--- a/openstack/compute/v2/images/requests.go
+++ b/openstack/compute/v2/images/requests.go
@@ -32,10 +32,7 @@
 // ToImageListQuery formats a ListOpts into a query string.
 func (opts ListOpts) ToImageListQuery() (string, error) {
 	q, err := gophercloud.BuildQueryString(opts)
-	if err != nil {
-		return "", err
-	}
-	return q.String(), nil
+	return q.String(), err
 }
 
 // ListDetail enumerates the available images.
@@ -48,40 +45,30 @@
 		}
 		url += query
 	}
-
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
 		return ImagePage{pagination.LinkedPageBase{PageResult: r}}
-	}
-
-	return pagination.NewPager(client, url, createPage)
+	})
 }
 
 // Get acquires additional detail about a specific image by ID.
 // Use ExtractImage() to interpret the result as an openstack Image.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var result GetResult
-	_, result.Err = client.Get(getURL(client, id), &result.Body, nil)
-	return result
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+	return r
 }
 
 // Delete deletes the specified image ID.
 func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
-	var result DeleteResult
-	_, result.Err = client.Delete(deleteURL(client, id), nil)
-	return result
+	var r DeleteResult
+	_, r.Err = client.Delete(deleteURL(client, id), nil)
+	return r
 }
 
 // IDFromName is a convienience function that returns an image's ID given its name.
 func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
 	count := 0
 	id := ""
-	if name == "" {
-		err := &gophercloud.ErrMissingInput{}
-		err.Function = "images.IDFromName"
-		err.Argument = "name"
-		return "", err
-	}
-
 	allPages, err := ListDetail(client, nil).AllPages()
 	if err != nil {
 		return "", err
@@ -102,7 +89,6 @@
 	switch count {
 	case 0:
 		err := &gophercloud.ErrResourceNotFound{}
-		err.Function = "images.IDFromName"
 		err.ResourceType = "image"
 		err.Name = name
 		return "", err
@@ -110,7 +96,6 @@
 		return id, nil
 	default:
 		err := &gophercloud.ErrMultipleResourcesFound{}
-		err.Function = "images.IDFromName"
 		err.ResourceType = "image"
 		err.Name = name
 		err.Count = count
diff --git a/openstack/compute/v2/servers/fixtures.go b/openstack/compute/v2/servers/fixtures.go
index 2c22b45..224b996 100644
--- a/openstack/compute/v2/servers/fixtures.go
+++ b/openstack/compute/v2/servers/fixtures.go
@@ -7,6 +7,7 @@
 	"net/http"
 	"testing"
 
+	"github.com/gophercloud/gophercloud"
 	th "github.com/gophercloud/gophercloud/testhelper"
 	"github.com/gophercloud/gophercloud/testhelper/client"
 )
@@ -349,6 +350,15 @@
 	}
 )
 
+type CreateOptsWithCustomField struct {
+	CreateOpts
+	Foo string `json:"foo,omitempty"`
+}
+
+func (opts CreateOptsWithCustomField) ToServerCreateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "server")
+}
+
 // HandleServerCreationSuccessfully sets up the test server to respond to a server creation request
 // with a given response.
 func HandleServerCreationSuccessfully(t *testing.T, response string) {
@@ -369,6 +379,27 @@
 	})
 }
 
+// HandleServerCreationWithCustomFieldSuccessfully sets up the test server to respond to a server creation request
+// with a given response.
+func HandleServerCreationWithCustomFieldSuccessfully(t *testing.T, response string) {
+	th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+		th.TestJSONRequest(t, r, `{
+			"server": {
+				"name": "derp",
+				"imageRef": "f90f6034-2570-4974-8351-6b49732ef2eb",
+				"flavorRef": "1",
+				"foo": "bar"
+			}
+		}`)
+
+		w.WriteHeader(http.StatusAccepted)
+		w.Header().Add("Content-Type", "application/json")
+		fmt.Fprintf(w, response)
+	})
+}
+
 // HandleServerListSuccessfully sets up the test server to respond to a server List request.
 func HandleServerListSuccessfully(t *testing.T) {
 	th.Mux.HandleFunc("/servers/detail", func(w http.ResponseWriter, r *http.Request) {
@@ -399,7 +430,7 @@
 	})
 }
 
-// HandleAdminPasswordChangeSuccessfully sets up the test server to respond to a server password
+// HandleServerForceDeletionSuccessfully sets up the test server to respond to a server password
 // change request.
 func HandleServerForceDeletionSuccessfully(t *testing.T) {
 	th.Mux.HandleFunc("/servers/asdfasdfasdf/action", func(w http.ResponseWriter, r *http.Request) {
@@ -673,4 +704,3 @@
 		w.WriteHeader(http.StatusAccepted)
 	})
 }
-
diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go
index 4129471..4257a4a 100644
--- a/openstack/compute/v2/servers/requests.go
+++ b/openstack/compute/v2/servers/requests.go
@@ -55,16 +55,12 @@
 // ToServerListQuery formats a ListOpts into a query string.
 func (opts ListOpts) ToServerListQuery() (string, error) {
 	q, err := gophercloud.BuildQueryString(opts)
-	if err != nil {
-		return "", err
-	}
-	return q.String(), nil
+	return q.String(), err
 }
 
 // List makes a request against the API to list servers accessible to you.
 func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
 	url := listDetailURL(client)
-
 	if opts != nil {
 		query, err := opts.ToServerListQuery()
 		if err != nil {
@@ -72,12 +68,9 @@
 		}
 		url += query
 	}
-
-	createPageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
 		return ServerPage{pagination.LinkedPageBase{PageResult: r}}
-	}
-
-	return pagination.NewPager(client, url, createPageFn)
+	})
 }
 
 // CreateOptsBuilder describes struct types that can be accepted by the Create call.
@@ -189,7 +182,7 @@
 
 // ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
 func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
-	b, err := gophercloud.BuildRequestBody(opts)
+	b, err := gophercloud.BuildRequestBody(opts, "")
 	if err != nil {
 		return nil, err
 	}
@@ -228,13 +221,11 @@
 	if opts.ImageRef == "" {
 		if opts.ImageName == "" {
 			err := ErrNeitherImageIDNorImageNameProvided{}
-			err.Function = "servers.CreateOpts.ToServerCreateMap"
 			err.Argument = "ImageRef/ImageName"
 			return nil, err
 		}
 		if opts.ServiceClient == nil {
 			err := ErrNoClientProvidedForIDByName{}
-			err.Function = "servers.CreateOpts.ToServerCreateMap"
 			err.Argument = "ServiceClient"
 			return nil, err
 		}
@@ -249,14 +240,12 @@
 	if opts.FlavorRef == "" {
 		if opts.FlavorName == "" {
 			err := ErrNeitherFlavorIDNorFlavorNameProvided{}
-			err.Function = "servers.CreateOpts.ToServerCreateMap"
 			err.Argument = "FlavorRef/FlavorName"
 			return nil, err
 		}
 		if opts.ServiceClient == nil {
 			err := ErrNoClientProvidedForIDByName{}
 			err.Argument = "ServiceClient"
-			err.Function = "servers.CreateOpts.ToServerCreateMap"
 			return nil, err
 		}
 		flavorID, err := flavors.IDFromName(opts.ServiceClient, opts.FlavorName)
@@ -272,13 +261,11 @@
 // Create requests a server to be provisioned to the user in the current tenant.
 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
 	var r CreateResult
-
 	reqBody, err := opts.ToServerCreateMap()
 	if err != nil {
 		r.Err = err
 		return r
 	}
-
 	_, r.Err = client.Post(listURL(client), reqBody, &r.Body, nil)
 	return r
 }
@@ -308,42 +295,36 @@
 
 // UpdateOptsBuilder allows extensions to add additional attributes to the Update request.
 type UpdateOptsBuilder interface {
-	ToServerUpdateMap() map[string]interface{}
+	ToServerUpdateMap() (map[string]interface{}, error)
 }
 
 // UpdateOpts specifies the base attributes that may be updated on an existing server.
 type UpdateOpts struct {
-	// Name [optional] changes the displayed name of the server.
+	// Name changes the displayed name of the server.
 	// The server host name will *not* change.
 	// Server names are not constrained to be unique, even within the same tenant.
-	Name string
+	Name string `json:"name,omitempty"`
 
-	// AccessIPv4 [optional] provides a new IPv4 address for the instance.
-	AccessIPv4 string
+	// AccessIPv4 provides a new IPv4 address for the instance.
+	AccessIPv4 string `json:"accessIPv4,omitempty"`
 
-	// AccessIPv6 [optional] provides a new IPv6 address for the instance.
-	AccessIPv6 string
+	// AccessIPv6 provides a new IPv6 address for the instance.
+	AccessIPv6 string `json:"accessIPv6,omitempty"`
 }
 
 // ToServerUpdateMap formats an UpdateOpts structure into a request body.
-func (opts UpdateOpts) ToServerUpdateMap() map[string]interface{} {
-	server := make(map[string]string)
-	if opts.Name != "" {
-		server["name"] = opts.Name
-	}
-	if opts.AccessIPv4 != "" {
-		server["accessIPv4"] = opts.AccessIPv4
-	}
-	if opts.AccessIPv6 != "" {
-		server["accessIPv6"] = opts.AccessIPv6
-	}
-	return map[string]interface{}{"server": server}
+func (opts UpdateOpts) ToServerUpdateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "server")
 }
 
 // Update requests that various attributes of the indicated server be changed.
 func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
 	var r UpdateResult
-	b := opts.ToServerUpdateMap()
+	b, err := opts.ToServerUpdateMap()
+	if err != nil {
+		r.Err = err
+		return r
+	}
 	_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
@@ -352,16 +333,13 @@
 
 // ChangeAdminPassword alters the administrator or root password for a specified server.
 func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) ActionResult {
-	var req struct {
-		ChangePassword struct {
-			AdminPass string `json:"adminPass"`
-		} `json:"changePassword"`
-	}
-
-	req.ChangePassword.AdminPass = newPassword
-
 	var r ActionResult
-	_, r.Err = client.Post(actionURL(client, id), req, nil, nil)
+	b := map[string]interface{}{
+		"changePassword": map[string]string{
+			"adminPass": newPassword,
+		},
+	}
+	_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
 	return r
 }
 
@@ -377,36 +355,27 @@
 	PowerCycle              = HardReboot
 )
 
+// RebootOptsBuilder is an interface that options must satisfy in order to be
+// used when rebooting a server instance
 type RebootOptsBuilder interface {
 	ToServerRebootMap() (map[string]interface{}, error)
 }
 
+// RebootOpts satisfies the RebootOptsBuilder interface
 type RebootOpts struct {
-	Type RebootMethod
+	Type RebootMethod `json:"type" required:"true"`
 }
 
+// ToServerRebootMap allows RebootOpts to satisfiy the RebootOptsBuilder
+// interface
 func (opts *RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
-	if (opts.Type != SoftReboot) && (opts.Type != HardReboot) {
-		err := &gophercloud.ErrInvalidInput{}
-		err.Argument = "servers.RebootOpts.Type"
-		err.Value = opts.Type
-		err.Function = "servers.Reboot"
-		return nil, err
-	}
-
-	reqBody := map[string]interface{}{
-		"reboot": map[string]interface{}{
-			"type": string(opts.Type),
-		},
-	}
-
-	return reqBody, nil
+	return gophercloud.BuildRequestBody(opts, "reboot")
 }
 
 // Reboot requests that a given server reboot.
 // Two methods exist for rebooting a server:
 //
-// HardReboot (aka PowerCycle) rtarts the server instance by physically cutting power to the machine, or if a VM,
+// HardReboot (aka PowerCycle) starts the server instance by physically cutting power to the machine, or if a VM,
 // terminating it at the hypervisor level.
 // It's done. Caput. Full stop.
 // Then, after a brief while, power is rtored or the VM instance rtarted.
@@ -415,14 +384,12 @@
 // E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to rtart the machine.
 func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) ActionResult {
 	var r ActionResult
-
-	reqBody, err := opts.ToServerRebootMap()
+	b, err := opts.ToServerRebootMap()
 	if err != nil {
 		r.Err = err
 		return r
 	}
-
-	_, r.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
+	_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
 	return r
 }
 
@@ -435,97 +402,64 @@
 // RebuildOpts represents the configuration options used in a server rebuild
 // operation
 type RebuildOpts struct {
-	// Required. The ID of the image you want your server to be provisioned on
-	ImageID string
-
+	// The server's admin password
+	AdminPass string `json:"adminPass" required:"true"`
+	// The ID of the image you want your server to be provisioned on
+	ImageID   string `json:"imageRef"`
+	ImageName string `json:"-"`
+	//ImageName string `json:"-"`
 	// Name to set the server to
-	Name string
-
-	// Required. The server's admin password
-	AdminPass string
-
+	Name string `json:"name,omitempty"`
 	// AccessIPv4 [optional] provides a new IPv4 address for the instance.
-	AccessIPv4 string
-
+	AccessIPv4 string `json:"accessIPv4,omitempty"`
 	// AccessIPv6 [optional] provides a new IPv6 address for the instance.
-	AccessIPv6 string
-
+	AccessIPv6 string `json:"accessIPv6,omitempty"`
 	// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
-	Metadata map[string]string
-
+	Metadata map[string]string `json:"metadata,omitempty"`
 	// Personality [optional] includes files to inject into the server at launch.
 	// Rebuild will base64-encode file contents for you.
-	Personality Personality
+	Personality   Personality                `json:"personality,omitempty"`
+	ServiceClient *gophercloud.ServiceClient `json:"-"`
 }
 
 // ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON
 func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
-	var err error
-	server := make(map[string]interface{})
-
-	if opts.AdminPass == "" {
-		err := ErrNoAdminPassProvided{}
-		err.Function = "servers.ToServerRebuildMap"
-		err.Argument = "AdminPass"
-		return nil, err
-	}
-
-	if opts.ImageID == "" {
-		err := ErrNoImageIDProvided{}
-		err.Function = "servers.ToServerRebuildMap"
-		err.Argument = "ImageID"
-		return nil, err
-	}
-
+	b, err := gophercloud.BuildRequestBody(opts, "")
 	if err != nil {
-		return server, err
+		return nil, err
 	}
 
-	server["adminPass"] = opts.AdminPass
-	server["imageRef"] = opts.ImageID
-
-	if opts.Name != "" {
-		server["name"] = opts.Name
+	// If ImageRef isn't provided, use ImageName to ascertain the image ID.
+	if opts.ImageID == "" {
+		if opts.ImageName == "" {
+			err := ErrNeitherImageIDNorImageNameProvided{}
+			err.Argument = "ImageRef/ImageName"
+			return nil, err
+		}
+		if opts.ServiceClient == nil {
+			err := ErrNoClientProvidedForIDByName{}
+			err.Argument = "ServiceClient"
+			return nil, err
+		}
+		imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName)
+		if err != nil {
+			return nil, err
+		}
+		b["imageRef"] = imageID
 	}
 
-	if opts.AccessIPv4 != "" {
-		server["accessIPv4"] = opts.AccessIPv4
-	}
-
-	if opts.AccessIPv6 != "" {
-		server["accessIPv6"] = opts.AccessIPv6
-	}
-
-	if opts.Metadata != nil {
-		server["metadata"] = opts.Metadata
-	}
-
-	if len(opts.Personality) > 0 {
-		server["personality"] = opts.Personality
-	}
-
-	return map[string]interface{}{"rebuild": server}, nil
+	return map[string]interface{}{"rebuild": b}, nil
 }
 
 // Rebuild will reprovision the server according to the configuration options
 // provided in the RebuildOpts struct.
 func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) RebuildResult {
 	var r RebuildResult
-
-	if id == "" {
-		err := ErrNoIDProvided{}
-		err.Function = "servers.Rebuild"
-		err.Argument = "id"
-		r.Err = err
-		return r
-	}
-
 	b, err := opts.ToServerRebuildMap()
 	if err != nil {
 		r.Err = err
 		return r
 	}
-
 	_, r.Err = client.Post(actionURL(client, id), b, &r.Body, nil)
 	return r
 }
@@ -539,17 +473,13 @@
 // ResizeOpts represents the configuration options used to control a Resize operation.
 type ResizeOpts struct {
 	// FlavorRef is the ID of the flavor you wish your server to become.
-	FlavorRef string
+	FlavorRef string `json:"flavorRef" required:"true"`
 }
 
 // ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON request body for the
 // Resize request.
 func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) {
-	resize := map[string]interface{}{
-		"flavorRef": opts.FlavorRef,
-	}
-
-	return map[string]interface{}{"resize": resize}, nil
+	return gophercloud.BuildRequestBody(opts, "resize")
 }
 
 // Resize instructs the provider to change the flavor of the server.
@@ -561,21 +491,11 @@
 // Otherwise, call RevertResize() to rtore the old configuration.
 func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) ActionResult {
 	var r ActionResult
-
-	if id == "" {
-		err := ErrNoIDProvided{}
-		err.Function = "servers.Resize"
-		err.Argument = "id"
-		r.Err = err
-		return r
-	}
-
 	b, err := opts.ToServerResizeMap()
 	if err != nil {
 		r.Err = err
 		return r
 	}
-
 	_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
 	return r
 }
@@ -584,17 +504,7 @@
 // See Resize() for more details.
 func ConfirmResize(client *gophercloud.ServiceClient, id string) ActionResult {
 	var r ActionResult
-
-	if id == "" {
-		err := ErrNoIDProvided{}
-		err.Function = "servers.ConfirmResize"
-		err.Argument = "id"
-		r.Err = err
-		return r
-	}
-
-	b := map[string]interface{}{"confirmResize": nil}
-	_, r.Err = client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{
 		OkCodes: []int{201, 202, 204},
 	})
 	return r
@@ -604,17 +514,7 @@
 // See Resize() for more details.
 func RevertResize(client *gophercloud.ServiceClient, id string) ActionResult {
 	var r ActionResult
-
-	if id == "" {
-		err := ErrNoIDProvided{}
-		err.Function = "servers.RevertResize"
-		err.Argument = "id"
-		r.Err = err
-		return r
-	}
-
-	b := map[string]interface{}{"revertResize": nil}
-	_, r.Err = client.Post(actionURL(client, id), b, nil, nil)
+	_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil)
 	return r
 }
 
@@ -629,41 +529,26 @@
 type RescueOpts struct {
 	// AdminPass is the desired administrative password for the instance in
 	// RESCUE mode. If it's left blank, the server will generate a password.
-	AdminPass string
+	AdminPass string `json:"adminPass,omitempty"`
 }
 
 // ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON
 // request body for the Rescue request.
 func (opts RescueOpts) ToServerRescueMap() (map[string]interface{}, error) {
-	server := make(map[string]interface{})
-	if opts.AdminPass != "" {
-		server["adminPass"] = opts.AdminPass
-	}
-	return map[string]interface{}{"rescue": server}, nil
+	return gophercloud.BuildRequestBody(opts, "rescue")
 }
 
 // Rescue instructs the provider to place the server into RESCUE mode.
 func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) RescueResult {
 	var r RescueResult
-
-	if id == "" {
-		err := ErrNoIDProvided{}
-		err.Function = "servers.Rebuild"
-		err.Argument = "id"
-		r.Err = err
-		return r
-	}
-
 	b, err := opts.ToServerRescueMap()
 	if err != nil {
 		r.Err = err
 		return r
 	}
-
 	_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-
 	return r
 }
 
@@ -692,12 +577,12 @@
 // UpdateMetadatas or UpdateMetadata function.
 func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) ResetMetadataResult {
 	var r ResetMetadataResult
-	metadata, err := opts.ToMetadataResetMap()
+	b, err := opts.ToMetadataResetMap()
 	if err != nil {
 		r.Err = err
 		return r
 	}
-	_, r.Err = client.Put(metadataURL(client, id), metadata, &r.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
 	return r
@@ -721,12 +606,12 @@
 // by opts.
 func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) UpdateMetadataResult {
 	var r UpdateMetadataResult
-	metadata, err := opts.ToMetadataUpdateMap()
+	b, err := opts.ToMetadataUpdateMap()
 	if err != nil {
 		r.Err = err
 		return r
 	}
-	_, r.Err = client.Post(metadataURL(client, id), metadata, &r.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
 	return r
@@ -760,13 +645,12 @@
 // CreateMetadatum will create or update the key-value pair with the given key for the given server ID.
 func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) CreateMetadatumResult {
 	var r CreateMetadatumResult
-	metadatum, key, err := opts.ToMetadatumCreateMap()
+	b, key, err := opts.ToMetadatumCreateMap()
 	if err != nil {
 		r.Err = err
 		return r
 	}
-
-	_, r.Err = client.Put(metadatumURL(client, id, key), metadatum, &r.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
 	return r
@@ -775,9 +659,7 @@
 // Metadatum requests the key-value pair with the given key for the given server ID.
 func Metadatum(client *gophercloud.ServiceClient, id, key string) GetMetadatumResult {
 	var r GetMetadatumResult
-	_, r.Err = client.Request("GET", metadatumURL(client, id, key), &gophercloud.RequestOpts{
-		JSONResponse: &r.Body,
-	})
+	_, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil)
 	return r
 }
 
@@ -790,60 +672,47 @@
 
 // ListAddresses makes a request against the API to list the servers IP addresses.
 func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager {
-	createPageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page {
 		return AddressPage{pagination.SinglePageBase(r)}
-	}
-	return pagination.NewPager(client, listAddressesURL(client, id), createPageFn)
+	})
 }
 
 // ListAddressesByNetwork makes a request against the API to list the servers IP addresses
 // for the given network.
 func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager {
-	createPageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page {
 		return NetworkAddressPage{pagination.SinglePageBase(r)}
-	}
-	return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), createPageFn)
+	})
 }
 
-type CreateImageOpts struct {
-	// Name [required] of the image/snapshot
-	Name string
-	// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the created image.
-	Metadata map[string]string
-}
-
+// CreateImageOptsBuilder is the interface types must satisfy in order to be
+// used as CreateImage options
 type CreateImageOptsBuilder interface {
 	ToServerCreateImageMap() (map[string]interface{}, error)
 }
 
+// CreateImageOpts satisfies the CreateImageOptsBuilder
+type CreateImageOpts struct {
+	// Name of the image/snapshot
+	Name string `json:"name" required:"true"`
+	// Metadata contains key-value pairs (up to 255 bytes each) to attach to the created image.
+	Metadata map[string]string `json:"metadata,omitempty"`
+}
+
 // ToServerCreateImageMap formats a CreateImageOpts structure into a request body.
 func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) {
-	var err error
-	img := make(map[string]interface{})
-	if opts.Name == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "servers.CreateImageOpts.ToServerCreateImageMap"
-		err.Argument = "CreateImageOpts.Name"
-		return nil, err
-	}
-	img["name"] = opts.Name
-	if opts.Metadata != nil {
-		img["metadata"] = opts.Metadata
-	}
-	createImage := make(map[string]interface{})
-	createImage["createImage"] = img
-	return createImage, err
+	return gophercloud.BuildRequestBody(opts, "createImage")
 }
 
 // CreateImage makes a request against the nova API to schedule an image to be created of the server
-func CreateImage(client *gophercloud.ServiceClient, serverID string, opts CreateImageOptsBuilder) CreateImageResult {
+func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageOptsBuilder) CreateImageResult {
 	var r CreateImageResult
 	b, err := opts.ToServerCreateImageMap()
 	if err != nil {
 		r.Err = err
 		return r
 	}
-	resp, err := client.Post(actionURL(client, serverID), b, nil, &gophercloud.RequestOpts{
+	resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
 		OkCodes: []int{202},
 	})
 	r.Err = err
@@ -855,13 +724,6 @@
 func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
 	count := 0
 	id := ""
-	if name == "" {
-		err := &gophercloud.ErrMissingInput{}
-		err.Function = "servers.IDFromName"
-		err.Argument = "name"
-		return "", err
-	}
-
 	allPages, err := List(client, nil).AllPages()
 	if err != nil {
 		return "", err
@@ -881,19 +743,10 @@
 
 	switch count {
 	case 0:
-		err := &gophercloud.ErrResourceNotFound{}
-		err.Function = "servers.IDFromName"
-		err.ResourceType = "server"
-		err.Name = name
-		return "", err
+		return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "server"}
 	case 1:
 		return id, nil
 	default:
-		err := &gophercloud.ErrMultipleResourcesFound{}
-		err.Function = "servers.IDFromName"
-		err.ResourceType = "server"
-		err.Name = name
-		err.Count = count
-		return "", err
+		return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"}
 	}
 }
diff --git a/openstack/compute/v2/servers/requests_test.go b/openstack/compute/v2/servers/requests_test.go
index 73d1375..931ab36 100644
--- a/openstack/compute/v2/servers/requests_test.go
+++ b/openstack/compute/v2/servers/requests_test.go
@@ -69,6 +69,24 @@
 	th.CheckDeepEquals(t, ServerDerp, *actual)
 }
 
+func TestCreateServerWithCustomField(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleServerCreationWithCustomFieldSuccessfully(t, SingleServerBody)
+
+	actual, err := Create(client.ServiceClient(), CreateOptsWithCustomField{
+		CreateOpts: CreateOpts{
+			Name:      "derp",
+			ImageRef:  "f90f6034-2570-4974-8351-6b49732ef2eb",
+			FlavorRef: "1",
+		},
+		Foo: "bar",
+	}).Extract()
+	th.AssertNoErr(t, err)
+
+	th.CheckDeepEquals(t, ServerDerp, *actual)
+}
+
 func TestDeleteServer(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
diff --git a/openstack/db/v1/configurations/requests.go b/openstack/db/v1/configurations/requests.go
index abb0013..eb59cc2 100644
--- a/openstack/db/v1/configurations/requests.go
+++ b/openstack/db/v1/configurations/requests.go
@@ -8,11 +8,9 @@
 
 // List will list all of the available configurations.
 func List(client *gophercloud.ServiceClient) pagination.Pager {
-	pageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, baseURL(client), func(r pagination.PageResult) pagination.Page {
 		return ConfigPage{pagination.SinglePageBase(r)}
-	}
-
-	return pagination.NewPager(client, baseURL(client), pageFn)
+	})
 }
 
 // CreateOptsBuilder is a top-level interface which renders a JSON map.
@@ -23,109 +21,51 @@
 // DatastoreOpts is the primary options struct for creating and modifying
 // how configuration resources are associated with datastores.
 type DatastoreOpts struct {
-	// [OPTIONAL] The type of datastore. Defaults to "MySQL".
-	Type string
-
-	// [OPTIONAL] The specific version of a datastore. Defaults to "5.6".
-	Version string
-}
-
-// ToMap renders a JSON map for a datastore setting.
-func (opts DatastoreOpts) ToMap() (map[string]string, error) {
-	datastore := map[string]string{}
-
-	if opts.Type != "" {
-		datastore["type"] = opts.Type
-	}
-
-	if opts.Version != "" {
-		datastore["version"] = opts.Version
-	}
-
-	return datastore, nil
+	// The type of datastore. Defaults to "MySQL".
+	Type string `json:"type,omitempty"`
+	// The specific version of a datastore. Defaults to "5.6".
+	Version string `json:"version,omitempty"`
 }
 
 // CreateOpts is the struct responsible for configuring new configurations.
 type CreateOpts struct {
 	// [REQUIRED] The configuration group name
-	Name string
+	Name string `json:"name" required:"true"`
 
 	// [REQUIRED] A map of user-defined configuration settings that will define
 	// how each associated datastore works. Each key/value pair is specific to a
 	// datastore type.
-	Values map[string]interface{}
+	Values map[string]interface{} `json:"values" required:"true"`
 
-	// [OPTIONAL] Associates the configuration group with a particular datastore.
-	Datastore *DatastoreOpts
+	// Associates the configuration group with a particular datastore.
+	Datastore *DatastoreOpts `json:"datastore,omitempty"`
 
-	// [OPTIONAL] A human-readable explanation for the group.
-	Description string
+	// A human-readable explanation for the group.
+	Description string `json:"description,omitempty"`
 }
 
 // ToConfigCreateMap casts a CreateOpts struct into a JSON map.
 func (opts CreateOpts) ToConfigCreateMap() (map[string]interface{}, error) {
-	if opts.Name == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "configurations.ToConfigCreateMap"
-		err.Argument = "configurations.CreateOpts.Name"
-		return nil, err
-	}
-	if len(opts.Values) == 0 {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "configurations.ToConfigCreateMap"
-		err.Argument = "configurations.CreateOpts.Values"
-		return nil, err
-	}
-
-	config := map[string]interface{}{
-		"name":   opts.Name,
-		"values": opts.Values,
-	}
-
-	if opts.Datastore != nil {
-		ds, err := opts.Datastore.ToMap()
-		if err != nil {
-			return config, err
-		}
-		config["datastore"] = ds
-	}
-
-	if opts.Description != "" {
-		config["description"] = opts.Description
-	}
-
-	return map[string]interface{}{"configuration": config}, nil
+	return gophercloud.BuildRequestBody(opts, "configuration")
 }
 
 // Create will create a new configuration group.
 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToConfigCreateMap()
+	var r CreateResult
+	b, err := opts.ToConfigCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Request("POST", baseURL(client), &gophercloud.RequestOpts{
-		OkCodes:      []int{200},
-		JSONBody:     &reqBody,
-		JSONResponse: &res.Body,
-	})
-
-	return res
+	_, r.Err = client.Post(baseURL(client), &b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}})
+	return r
 }
 
 // Get will retrieve the details for a specified configuration group.
 func Get(client *gophercloud.ServiceClient, configID string) GetResult {
-	var res GetResult
-
-	_, res.Err = client.Request("GET", resourceURL(client, configID), &gophercloud.RequestOpts{
-		OkCodes:      []int{200},
-		JSONResponse: &res.Body,
-	})
-
-	return res
+	var r GetResult
+	_, r.Err = client.Get(resourceURL(client, configID), &r.Body, nil)
+	return r
 }
 
 // UpdateOptsBuilder is the top-level interface for casting update options into
@@ -136,108 +76,66 @@
 
 // UpdateOpts is the struct responsible for modifying existing configurations.
 type UpdateOpts struct {
-	// [OPTIONAL] The configuration group name
-	Name string
-
-	// [OPTIONAL] A map of user-defined configuration settings that will define
+	// The configuration group name
+	Name string `json:"name,omitempty"`
+	// A map of user-defined configuration settings that will define
 	// how each associated datastore works. Each key/value pair is specific to a
 	// datastore type.
-	Values map[string]interface{}
-
-	// [OPTIONAL] Associates the configuration group with a particular datastore.
-	Datastore *DatastoreOpts
-
-	// [OPTIONAL] A human-readable explanation for the group.
-	Description string
+	Values map[string]interface{} `json:"values,omitempty"`
+	// Associates the configuration group with a particular datastore.
+	Datastore *DatastoreOpts `json:"datastore,omitempty"`
+	// A human-readable explanation for the group.
+	Description string `json:"description,omitempty"`
 }
 
 // ToConfigUpdateMap will cast an UpdateOpts struct into a JSON map.
 func (opts UpdateOpts) ToConfigUpdateMap() (map[string]interface{}, error) {
-	config := map[string]interface{}{}
-
-	if opts.Name != "" {
-		config["name"] = opts.Name
-	}
-
-	if opts.Description != "" {
-		config["description"] = opts.Description
-	}
-
-	if opts.Datastore != nil {
-		ds, err := opts.Datastore.ToMap()
-		if err != nil {
-			return config, err
-		}
-		config["datastore"] = ds
-	}
-
-	if len(opts.Values) > 0 {
-		config["values"] = opts.Values
-	}
-
-	return map[string]interface{}{"configuration": config}, nil
+	return gophercloud.BuildRequestBody(opts, "configuration")
 }
 
 // Update will modify an existing configuration group by performing a merge
 // between new and existing values. If the key already exists, the new value
 // will overwrite. All other keys will remain unaffected.
 func Update(client *gophercloud.ServiceClient, configID string, opts UpdateOptsBuilder) UpdateResult {
-	var res UpdateResult
-
-	reqBody, err := opts.ToConfigUpdateMap()
+	var r UpdateResult
+	b, err := opts.ToConfigUpdateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Request("PATCH", resourceURL(client, configID), &gophercloud.RequestOpts{
-		OkCodes:  []int{200},
-		JSONBody: &reqBody,
-	})
-
-	return res
+	_, r.Err = client.Patch(resourceURL(client, configID), &b, nil, nil)
+	return r
 }
 
 // Replace will modify an existing configuration group by overwriting the
 // entire parameter group with the new values provided. Any existing keys not
 // included in UpdateOptsBuilder will be deleted.
 func Replace(client *gophercloud.ServiceClient, configID string, opts UpdateOptsBuilder) ReplaceResult {
-	var res ReplaceResult
-
-	reqBody, err := opts.ToConfigUpdateMap()
+	var r ReplaceResult
+	b, err := opts.ToConfigUpdateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Request("PUT", resourceURL(client, configID), &gophercloud.RequestOpts{
-		OkCodes:  []int{202},
-		JSONBody: &reqBody,
-	})
-
-	return res
+	_, r.Err = client.Put(resourceURL(client, configID), &b, nil, nil)
+	return r
 }
 
 // Delete will permanently delete a configuration group. Please note that
 // config groups cannot be deleted whilst still attached to running instances -
 // you must detach and then delete them.
 func Delete(client *gophercloud.ServiceClient, configID string) DeleteResult {
-	var res DeleteResult
-
-	_, res.Err = client.Request("DELETE", resourceURL(client, configID), &gophercloud.RequestOpts{
-		OkCodes: []int{202},
-	})
-
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(resourceURL(client, configID), nil)
+	return r
 }
 
 // ListInstances will list all the instances associated with a particular
 // configuration group.
 func ListInstances(client *gophercloud.ServiceClient, configID string) pagination.Pager {
-	pageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, instancesURL(client, configID), func(r pagination.PageResult) pagination.Page {
 		return instances.InstancePage{pagination.LinkedPageBase{PageResult: r}}
-	}
-	return pagination.NewPager(client, instancesURL(client, configID), pageFn)
+	})
 }
 
 // ListDatastoreParams will list all the available and supported parameters
@@ -246,10 +144,9 @@
 // you can use this operation (you will need to retrieve the MySQL datastore ID
 // by using the datastores API).
 func ListDatastoreParams(client *gophercloud.ServiceClient, datastoreID, versionID string) pagination.Pager {
-	pageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, listDSParamsURL(client, datastoreID, versionID), func(r pagination.PageResult) pagination.Page {
 		return ParamPage{pagination.SinglePageBase(r)}
-	}
-	return pagination.NewPager(client, listDSParamsURL(client, datastoreID, versionID), pageFn)
+	})
 }
 
 // GetDatastoreParam will retrieve information about a specific configuration
@@ -258,34 +155,23 @@
 // need the param's ID first, which can be attained by using the ListDatastoreParams
 // operation.
 func GetDatastoreParam(client *gophercloud.ServiceClient, datastoreID, versionID, paramID string) ParamResult {
-	var res ParamResult
-
-	_, res.Err = client.Request("GET", getDSParamURL(client, datastoreID, versionID, paramID), &gophercloud.RequestOpts{
-		OkCodes:      []int{200},
-		JSONResponse: &res.Body,
-	})
-
-	return res
+	var r ParamResult
+	_, r.Err = client.Get(getDSParamURL(client, datastoreID, versionID, paramID), &r.Body, nil)
+	return r
 }
 
 // ListGlobalParams is similar to ListDatastoreParams but does not require a
 // DatastoreID.
 func ListGlobalParams(client *gophercloud.ServiceClient, versionID string) pagination.Pager {
-	pageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, listGlobalParamsURL(client, versionID), func(r pagination.PageResult) pagination.Page {
 		return ParamPage{pagination.SinglePageBase(r)}
-	}
-	return pagination.NewPager(client, listGlobalParamsURL(client, versionID), pageFn)
+	})
 }
 
 // GetGlobalParam is similar to GetDatastoreParam but does not require a
 // DatastoreID.
 func GetGlobalParam(client *gophercloud.ServiceClient, versionID, paramID string) ParamResult {
-	var res ParamResult
-
-	_, res.Err = client.Request("GET", getGlobalParamURL(client, versionID, paramID), &gophercloud.RequestOpts{
-		OkCodes:      []int{200},
-		JSONResponse: &res.Body,
-	})
-
-	return res
+	var r ParamResult
+	_, r.Err = client.Get(getGlobalParamURL(client, versionID, paramID), &r.Body, nil)
+	return r
 }
diff --git a/openstack/db/v1/databases/requests.go b/openstack/db/v1/databases/requests.go
index 18e1af5..7338112 100644
--- a/openstack/db/v1/databases/requests.go
+++ b/openstack/db/v1/databases/requests.go
@@ -13,52 +13,35 @@
 // CreateOpts is the struct responsible for configuring a database; often in
 // the context of an instance.
 type CreateOpts struct {
-	// [REQUIRED] Specifies the name of the database. Valid names can be composed
+	// Specifies the name of the database. Valid names can be composed
 	// of the following characters: letters (either case); numbers; these
 	// characters '@', '?', '#', ' ' but NEVER beginning a name string; '_' is
 	// permitted anywhere. Prohibited characters that are forbidden include:
 	// single quotes, double quotes, back quotes, semicolons, commas, backslashes,
 	// and forward slashes.
-	Name string
-
-	// [OPTIONAL] Set of symbols and encodings. The default character set is
+	Name string `json:"name" required:"true"`
+	// Set of symbols and encodings. The default character set is
 	// "utf8". See http://dev.mysql.com/doc/refman/5.1/en/charset-mysql.html for
 	// supported character sets.
-	CharSet string
-
-	// [OPTIONAL] Set of rules for comparing characters in a character set. The
+	CharSet string `json:"character_set,omitempty"`
+	// Set of rules for comparing characters in a character set. The
 	// default value for collate is "utf8_general_ci". See
 	// http://dev.mysql.com/doc/refman/5.1/en/charset-mysql.html for supported
 	// collations.
-	Collate string
+	Collate string `json:"collate,omitempty"`
 }
 
 // ToMap is a helper function to convert individual DB create opt structures
 // into sub-maps.
-func (opts CreateOpts) ToMap() (map[string]string, error) {
-	if opts.Name == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "databases.ToMap"
-		err.Argument = "databases.CreateOpts.Name"
-	}
+func (opts CreateOpts) ToMap() (map[string]interface{}, error) {
 	if len(opts.Name) > 64 {
 		err := gophercloud.ErrInvalidInput{}
-		err.Function = "databases.ToMap"
 		err.Argument = "databases.CreateOpts.Name"
 		err.Value = opts.Name
 		err.Info = "Must be less than 64 chars long"
 		return nil, err
 	}
-
-	db := map[string]string{"name": opts.Name}
-
-	if opts.CharSet != "" {
-		db["character_set"] = opts.CharSet
-	}
-	if opts.Collate != "" {
-		db["collate"] = opts.Collate
-	}
-	return db, nil
+	return gophercloud.BuildRequestBody(opts, "")
 }
 
 // BatchCreateOpts allows for multiple databases to created and modified.
@@ -66,7 +49,7 @@
 
 // ToDBCreateMap renders a JSON map for creating DBs.
 func (opts BatchCreateOpts) ToDBCreateMap() (map[string]interface{}, error) {
-	dbs := make([]map[string]string, len(opts))
+	dbs := make([]map[string]interface{}, len(opts))
 	for i, db := range opts {
 		dbMap, err := db.ToMap()
 		if err != nil {
@@ -80,41 +63,29 @@
 // Create will create a new database within the specified instance. If the
 // specified instance does not exist, a 404 error will be returned.
 func Create(client *gophercloud.ServiceClient, instanceID string, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToDBCreateMap()
+	var r CreateResult
+	b, err := opts.ToDBCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Request("POST", baseURL(client, instanceID), &gophercloud.RequestOpts{
-		JSONBody: &reqBody,
-		OkCodes:  []int{202},
-	})
-
-	return res
+	_, r.Err = client.Post(baseURL(client, instanceID), &b, nil, nil)
+	return r
 }
 
 // List will list all of the databases for a specified instance. Note: this
 // operation will only return user-defined databases; it will exclude system
 // databases like "mysql", "information_schema", "lost+found" etc.
 func List(client *gophercloud.ServiceClient, instanceID string) pagination.Pager {
-	createPageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, baseURL(client, instanceID), func(r pagination.PageResult) pagination.Page {
 		return DBPage{pagination.LinkedPageBase{PageResult: r}}
-	}
-
-	return pagination.NewPager(client, baseURL(client, instanceID), createPageFn)
+	})
 }
 
 // Delete will permanently delete the database within a specified instance.
 // All contained data inside the database will also be permanently deleted.
 func Delete(client *gophercloud.ServiceClient, instanceID, dbName string) DeleteResult {
-	var res DeleteResult
-
-	_, res.Err = client.Request("DELETE", dbURL(client, instanceID, dbName), &gophercloud.RequestOpts{
-		OkCodes: []int{202},
-	})
-
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(dbURL(client, instanceID, dbName), nil)
+	return r
 }
diff --git a/openstack/db/v1/datastores/requests.go b/openstack/db/v1/datastores/requests.go
index 277c797..d820915 100644
--- a/openstack/db/v1/datastores/requests.go
+++ b/openstack/db/v1/datastores/requests.go
@@ -7,41 +7,29 @@
 
 // List will list all available datastore types that instances can use.
 func List(client *gophercloud.ServiceClient) pagination.Pager {
-	pageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, baseURL(client), func(r pagination.PageResult) pagination.Page {
 		return DatastorePage{pagination.SinglePageBase(r)}
-	}
-	return pagination.NewPager(client, baseURL(client), pageFn)
+	})
 }
 
 // Get will retrieve the details of a specified datastore type.
 func Get(client *gophercloud.ServiceClient, datastoreID string) GetResult {
-	var res GetResult
-
-	_, res.Err = client.Request("GET", resourceURL(client, datastoreID), &gophercloud.RequestOpts{
-		OkCodes:      []int{200},
-		JSONResponse: &res.Body,
-	})
-
-	return res
+	var r GetResult
+	_, r.Err = client.Get(resourceURL(client, datastoreID), &r.Body, nil)
+	return r
 }
 
 // ListVersions will list all of the available versions for a specified
 // datastore type.
 func ListVersions(client *gophercloud.ServiceClient, datastoreID string) pagination.Pager {
-	pageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, versionsURL(client, datastoreID), func(r pagination.PageResult) pagination.Page {
 		return VersionPage{pagination.SinglePageBase(r)}
-	}
-	return pagination.NewPager(client, versionsURL(client, datastoreID), pageFn)
+	})
 }
 
 // GetVersion will retrieve the details of a specified datastore version.
 func GetVersion(client *gophercloud.ServiceClient, datastoreID, versionID string) GetVersionResult {
-	var res GetVersionResult
-
-	_, res.Err = client.Request("GET", versionURL(client, datastoreID, versionID), &gophercloud.RequestOpts{
-		OkCodes:      []int{200},
-		JSONResponse: &res.Body,
-	})
-
-	return res
+	var r GetVersionResult
+	_, r.Err = client.Get(versionURL(client, datastoreID, versionID), &r.Body, nil)
+	return r
 }
diff --git a/openstack/db/v1/flavors/requests.go b/openstack/db/v1/flavors/requests.go
index c767606..8b7797f 100644
--- a/openstack/db/v1/flavors/requests.go
+++ b/openstack/db/v1/flavors/requests.go
@@ -9,21 +9,14 @@
 // operation is identical to the one supported by the Nova API, but without the
 // "disk" property.
 func List(client *gophercloud.ServiceClient) pagination.Pager {
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
 		return FlavorPage{pagination.LinkedPageBase{PageResult: r}}
-	}
-
-	return pagination.NewPager(client, listURL(client), createPage)
+	})
 }
 
 // Get will retrieve information for a specified hardware flavor.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var gr GetResult
-
-	_, gr.Err = client.Request("GET", getURL(client, id), &gophercloud.RequestOpts{
-		JSONResponse: &gr.Body,
-		OkCodes:      []int{200},
-	})
-
-	return gr
+	var r GetResult
+	_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+	return r
 }
diff --git a/openstack/db/v1/instances/requests.go b/openstack/db/v1/instances/requests.go
index 8e33dfe..b8cae03 100644
--- a/openstack/db/v1/instances/requests.go
+++ b/openstack/db/v1/instances/requests.go
@@ -14,16 +14,13 @@
 
 // DatastoreOpts represents the configuration for how an instance stores data.
 type DatastoreOpts struct {
-	Version string
-	Type    string
+	Version string `json:"version"`
+	Type    string `json:"type"`
 }
 
 // ToMap converts a DatastoreOpts to a map[string]string (for a request body)
-func (opts DatastoreOpts) ToMap() (map[string]string, error) {
-	return map[string]string{
-		"version": opts.Version,
-		"type":    opts.Type,
-	}, nil
+func (opts DatastoreOpts) ToMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "")
 }
 
 // CreateOpts is the struct responsible for configuring a new database instance.
@@ -31,21 +28,16 @@
 	// Either the integer UUID (in string form) of the flavor, or its URI
 	// reference as specified in the response from the List() call. Required.
 	FlavorRef string
-
 	// Specifies the volume size in gigabytes (GB). The value must be between 1
 	// and 300. Required.
 	Size int
-
 	// Name of the instance to create. The length of the name is limited to
 	// 255 characters and any characters are permitted. Optional.
 	Name string
-
 	// A slice of database information options.
 	Databases db.CreateOptsBuilder
-
 	// A slice of user information options.
 	Users users.CreateOptsBuilder
-
 	// Options to configure the type of datastore the instance will use. This is
 	// optional, and if excluded will default to MySQL.
 	Datastore *DatastoreOpts
@@ -55,17 +47,14 @@
 func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) {
 	if opts.Size > 300 || opts.Size < 1 {
 		err := gophercloud.ErrInvalidInput{}
-		err.Function = "instances.ToInstanceCreateMap"
 		err.Argument = "instances.CreateOpts.Size"
 		err.Value = opts.Size
 		err.Info = "Size (GB) must be between 1-300"
 		return nil, err
 	}
+
 	if opts.FlavorRef == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "instances.ToInstanceCreateMap"
-		err.Argument = "instances.CreateOpts.FlavorRef"
-		return nil, err
+		return nil, gophercloud.ErrMissingInput{Argument: "instances.CreateOpts.FlavorRef"}
 	}
 
 	instance := map[string]interface{}{
@@ -110,143 +99,79 @@
 // can create an instance with multiple databases and users. The default
 // binding for a MySQL instance is port 3306.
 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToInstanceCreateMap()
+	var r CreateResult
+	b, err := opts.ToInstanceCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Request("POST", baseURL(client), &gophercloud.RequestOpts{
-		JSONBody:     &reqBody,
-		JSONResponse: &res.Body,
-		OkCodes:      []int{200},
-	})
-
-	return res
+	_, r.Err = client.Post(baseURL(client), &b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}})
+	return r
 }
 
 // List retrieves the status and information for all database instances.
 func List(client *gophercloud.ServiceClient) pagination.Pager {
-	createPageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, baseURL(client), func(r pagination.PageResult) pagination.Page {
 		return InstancePage{pagination.LinkedPageBase{PageResult: r}}
-	}
-
-	return pagination.NewPager(client, baseURL(client), createPageFn)
+	})
 }
 
 // Get retrieves the status and information for a specified database instance.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
-	var res GetResult
-
-	_, res.Err = client.Request("GET", resourceURL(client, id), &gophercloud.RequestOpts{
-		JSONResponse: &res.Body,
-		OkCodes:      []int{200},
-	})
-
-	return res
+	var r GetResult
+	_, r.Err = client.Get(resourceURL(client, id), &r.Body, nil)
+	return r
 }
 
 // Delete permanently destroys the database instance.
 func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
-	var res DeleteResult
-
-	_, res.Err = client.Request("DELETE", resourceURL(client, id), &gophercloud.RequestOpts{
-		OkCodes: []int{202},
-	})
-
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(resourceURL(client, id), nil)
+	return r
 }
 
 // EnableRootUser enables the login from any host for the root user and
 // provides the user with a generated root password.
-func EnableRootUser(client *gophercloud.ServiceClient, id string) UserRootResult {
-	var res UserRootResult
-
-	_, res.Err = client.Request("POST", userRootURL(client, id), &gophercloud.RequestOpts{
-		JSONResponse: &res.Body,
-		OkCodes:      []int{200},
-	})
-
-	return res
+func EnableRootUser(client *gophercloud.ServiceClient, id string) EnableRootUserResult {
+	var r EnableRootUserResult
+	_, r.Err = client.Post(userRootURL(client, id), nil, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}})
+	return r
 }
 
 // IsRootEnabled checks an instance to see if root access is enabled. It returns
 // True if root user is enabled for the specified database instance or False
 // otherwise.
-func IsRootEnabled(client *gophercloud.ServiceClient, id string) (bool, error) {
-	var res gophercloud.Result
-
-	_, err := client.Request("GET", userRootURL(client, id), &gophercloud.RequestOpts{
-		JSONResponse: &res.Body,
-		OkCodes:      []int{200},
-	})
-
-	return res.Body.(map[string]interface{})["rootEnabled"] == true, err
+func IsRootEnabled(client *gophercloud.ServiceClient, id string) IsRootEnabledResult {
+	var r IsRootEnabledResult
+	_, r.Err = client.Get(userRootURL(client, id), &r.Body, nil)
+	return r
 }
 
 // Restart will restart only the MySQL Instance. Restarting MySQL will
 // erase any dynamic configuration settings that you have made within MySQL.
 // The MySQL service will be unavailable until the instance restarts.
 func Restart(client *gophercloud.ServiceClient, id string) ActionResult {
-	var res ActionResult
-
-	_, res.Err = client.Request("POST", actionURL(client, id), &gophercloud.RequestOpts{
-		JSONBody: map[string]interface{}{"restart": struct{}{}},
-		OkCodes:  []int{202},
-	})
-
-	return res
+	var r ActionResult
+	b := map[string]interface{}{"restart": struct{}{}}
+	_, r.Err = client.Post(actionURL(client, id), &b, nil, nil)
+	return r
 }
 
 // Resize changes the memory size of the instance, assuming a valid
 // flavorRef is provided. It will also restart the MySQL service.
 func Resize(client *gophercloud.ServiceClient, id, flavorRef string) ActionResult {
-	var res ActionResult
-
-	type resize struct {
-		FlavorRef string `json:"flavorRef"`
-	}
-
-	type req struct {
-		Resize resize `json:"resize"`
-	}
-
-	reqBody := req{Resize: resize{FlavorRef: flavorRef}}
-
-	_, res.Err = client.Request("POST", actionURL(client, id), &gophercloud.RequestOpts{
-		JSONBody: reqBody,
-		OkCodes:  []int{202},
-	})
-
-	return res
+	var r ActionResult
+	b := map[string]interface{}{"resize": map[string]string{"flavorRef": flavorRef}}
+	_, r.Err = client.Post(actionURL(client, id), &b, nil, nil)
+	return r
 }
 
 // ResizeVolume will resize the attached volume for an instance. It supports
 // only increasing the volume size and does not support decreasing the size.
 // The volume size is in gigabytes (GB) and must be an integer.
 func ResizeVolume(client *gophercloud.ServiceClient, id string, size int) ActionResult {
-	var res ActionResult
-
-	type volume struct {
-		Size int `json:"size"`
-	}
-
-	type resize struct {
-		Volume volume `json:"volume"`
-	}
-
-	type req struct {
-		Resize resize `json:"resize"`
-	}
-
-	reqBody := req{Resize: resize{Volume: volume{Size: size}}}
-
-	_, res.Err = client.Request("POST", actionURL(client, id), &gophercloud.RequestOpts{
-		JSONBody: reqBody,
-		OkCodes:  []int{202},
-	})
-
-	return res
+	var r ActionResult
+	b := map[string]interface{}{"resize": map[string]interface{}{"volume": map[string]int{"size": size}}}
+	_, r.Err = client.Post(actionURL(client, id), &b, nil, nil)
+	return r
 }
diff --git a/openstack/db/v1/instances/requests_test.go b/openstack/db/v1/instances/requests_test.go
index b56165a..3caac23 100644
--- a/openstack/db/v1/instances/requests_test.go
+++ b/openstack/db/v1/instances/requests_test.go
@@ -99,7 +99,7 @@
 	defer th.TeardownHTTP()
 	HandleIsRootEnabled(t)
 
-	isEnabled, err := IsRootEnabled(fake.ServiceClient(), instanceID)
+	isEnabled, err := IsRootEnabled(fake.ServiceClient(), instanceID).Extract()
 
 	th.AssertNoErr(t, err)
 	th.AssertEquals(t, true, isEnabled)
diff --git a/openstack/db/v1/instances/results.go b/openstack/db/v1/instances/results.go
index f32632c..21900d7 100644
--- a/openstack/db/v1/instances/results.go
+++ b/openstack/db/v1/instances/results.go
@@ -117,13 +117,13 @@
 	return s.Instances, err
 }
 
-// UserRootResult represents the result of an operation to enable the root user.
-type UserRootResult struct {
+// EnableRootUserResult represents the result of an operation to enable the root user.
+type EnableRootUserResult struct {
 	gophercloud.Result
 }
 
 // Extract will extract root user information from a UserRootResult.
-func (r UserRootResult) Extract() (*users.User, error) {
+func (r EnableRootUserResult) Extract() (*users.User, error) {
 	var s struct {
 		User *users.User `json:"user"`
 	}
@@ -137,3 +137,14 @@
 type ActionResult struct {
 	gophercloud.ErrResult
 }
+
+// IsRootEnabledResult is the result of a call to IsRootEnabled. To see if
+// root is enabled, call the type's Extract method.
+type IsRootEnabledResult struct {
+	gophercloud.Result
+}
+
+// Extract is used to extract the data from a IsRootEnabledResult.
+func (r IsRootEnabledResult) Extract() (bool, error) {
+	return r.Body.(map[string]interface{})["rootEnabled"] == true, r.Err
+}
diff --git a/openstack/db/v1/users/requests.go b/openstack/db/v1/users/requests.go
index 6815c1c..910a615 100644
--- a/openstack/db/v1/users/requests.go
+++ b/openstack/db/v1/users/requests.go
@@ -14,71 +14,35 @@
 // CreateOpts is the struct responsible for configuring a new user; often in the
 // context of an instance.
 type CreateOpts struct {
-	// [REQUIRED] Specifies a name for the user. Valid names can be composed
+	// Specifies a name for the user. Valid names can be composed
 	// of the following characters: letters (either case); numbers; these
 	// characters '@', '?', '#', ' ' but NEVER beginning a name string; '_' is
 	// permitted anywhere. Prohibited characters that are forbidden include:
 	// single quotes, double quotes, back quotes, semicolons, commas, backslashes,
 	// and forward slashes. Spaces at the front or end of a user name are also
 	// not permitted.
-	Name string
-
-	// [REQUIRED] Specifies a password for the user.
-	Password string
-
-	// [OPTIONAL] An array of databases that this user will connect to. The
+	Name string `json:"name" required:"true"`
+	// Specifies a password for the user.
+	Password string `json:"password" required:"true"`
+	// An array of databases that this user will connect to. The
 	// "name" field is the only requirement for each option.
-	Databases db.BatchCreateOpts
-
-	// [OPTIONAL] Specifies the host from which a user is allowed to connect to
+	Databases db.BatchCreateOpts `json:"databases,omitempty"`
+	// Specifies the host from which a user is allowed to connect to
 	// the database. Possible values are a string containing an IPv4 address or
 	// "%" to allow connecting from any host. Optional; the default is "%".
-	Host string
+	Host string `json:"host,omitempty"`
 }
 
 // ToMap is a convenience function for creating sub-maps for individual users.
 func (opts CreateOpts) ToMap() (map[string]interface{}, error) {
-
 	if opts.Name == "root" {
 		err := gophercloud.ErrInvalidInput{}
-		err.Function = "users.ToUserCreateMap"
 		err.Argument = "users.CreateOpts.Name"
 		err.Value = "root"
 		err.Info = "root is a reserved user name and cannot be used"
 		return nil, err
 	}
-	if opts.Name == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "users.ToUserCreateMap"
-		err.Argument = "users.CreateOpts.Name"
-		return nil, err
-	}
-	if opts.Password == "" {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "users.ToUserCreateMap"
-		err.Argument = "users.CreateOpts.Password"
-		return nil, err
-	}
-
-	user := map[string]interface{}{
-		"name":     opts.Name,
-		"password": opts.Password,
-	}
-
-	if opts.Host != "" {
-		user["host"] = opts.Host
-	}
-
-	dbs := make([]map[string]string, len(opts.Databases))
-	for i, db := range opts.Databases {
-		dbs[i] = map[string]string{"name": db.Name}
-	}
-
-	if len(dbs) > 0 {
-		user["databases"] = dbs
-	}
-
-	return user, nil
+	return gophercloud.BuildRequestBody(opts, "")
 }
 
 // BatchCreateOpts allows multiple users to be created at once.
@@ -102,40 +66,28 @@
 // assigned for a particular user, the user will be granted all privileges
 // for those specified databases. "root" is a reserved name and cannot be used.
 func Create(client *gophercloud.ServiceClient, instanceID string, opts CreateOptsBuilder) CreateResult {
-	var res CreateResult
-
-	reqBody, err := opts.ToUserCreateMap()
+	var r CreateResult
+	b, err := opts.ToUserCreateMap()
 	if err != nil {
-		res.Err = err
-		return res
+		r.Err = err
+		return r
 	}
-
-	_, res.Err = client.Request("POST", baseURL(client, instanceID), &gophercloud.RequestOpts{
-		JSONBody: &reqBody,
-		OkCodes:  []int{202},
-	})
-
-	return res
+	_, r.Err = client.Post(baseURL(client, instanceID), &b, nil, nil)
+	return r
 }
 
 // List will list all the users associated with a specified database instance,
 // along with their associated databases. This operation will not return any
 // system users or administrators for a database.
 func List(client *gophercloud.ServiceClient, instanceID string) pagination.Pager {
-	createPageFn := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, baseURL(client, instanceID), func(r pagination.PageResult) pagination.Page {
 		return UserPage{pagination.LinkedPageBase{PageResult: r}}
-	}
-
-	return pagination.NewPager(client, baseURL(client, instanceID), createPageFn)
+	})
 }
 
 // Delete will permanently delete a user from a specified database instance.
 func Delete(client *gophercloud.ServiceClient, instanceID, userName string) DeleteResult {
-	var res DeleteResult
-
-	_, res.Err = client.Request("DELETE", userURL(client, instanceID, userName), &gophercloud.RequestOpts{
-		OkCodes: []int{202},
-	})
-
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(userURL(client, instanceID, userName), nil)
+	return r
 }
diff --git a/openstack/endpoint_location.go b/openstack/endpoint_location.go
index a14f989..fb6b84a 100644
--- a/openstack/endpoint_location.go
+++ b/openstack/endpoint_location.go
@@ -29,7 +29,6 @@
 	if len(endpoints) > 1 {
 		err := &ErrMultipleMatchingEndpointsV2{}
 		err.Endpoints = endpoints
-		err.Function = "openstack.V2EndpointURL"
 		return "", err
 	}
 
@@ -44,7 +43,6 @@
 			return gophercloud.NormalizeURL(endpoint.AdminURL), nil
 		default:
 			err := &ErrInvalidAvailabilityProvided{}
-			err.Function = "openstack.V2EndpointURL"
 			err.Argument = "Availability"
 			err.Value = opts.Availability
 			return "", err
@@ -74,7 +72,6 @@
 					opts.Availability != gophercloud.AvailabilityPublic &&
 					opts.Availability != gophercloud.AvailabilityInternal {
 					err := &ErrInvalidAvailabilityProvided{}
-					err.Function = "openstack.V3EndpointURL"
 					err.Argument = "Availability"
 					err.Value = opts.Availability
 					return "", err
@@ -89,10 +86,7 @@
 
 	// Report an error if the options were ambiguous.
 	if len(endpoints) > 1 {
-		err := &ErrMultipleMatchingEndpointsV3{}
-		err.Endpoints = endpoints
-		err.Function = "openstack.V3EndpointURL"
-		return "", err
+		return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints}
 	}
 
 	// Extract the URL from the matching Endpoint.
@@ -102,6 +96,5 @@
 
 	// Report an error if there were no matching endpoints.
 	err := &gophercloud.ErrEndpointNotFound{}
-	err.Function = "openstack.V3EndpointURL"
 	return "", err
 }
diff --git a/openstack/identity/v2/extensions/admin/roles/requests.go b/openstack/identity/v2/extensions/admin/roles/requests.go
index 891ab62..d80c53f 100644
--- a/openstack/identity/v2/extensions/admin/roles/requests.go
+++ b/openstack/identity/v2/extensions/admin/roles/requests.go
@@ -8,26 +8,25 @@
 // List is the operation responsible for listing all available global roles
 // that a user can adopt.
 func List(client *gophercloud.ServiceClient) pagination.Pager {
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, rootURL(client), func(r pagination.PageResult) pagination.Page {
 		return RolePage{pagination.SinglePageBase(r)}
-	}
-	return pagination.NewPager(client, rootURL(client), createPage)
+	})
 }
 
-// AddUserRole is the operation responsible for assigning a particular role to
+// AddUser is the operation responsible for assigning a particular role to
 // a user. This is confined to the scope of the user's tenant - so the tenant
 // ID is a required argument.
-func AddUserRole(client *gophercloud.ServiceClient, tenantID, userID, roleID string) UserRoleResult {
-	var result UserRoleResult
-	_, result.Err = client.Put(userRoleURL(client, tenantID, userID, roleID), nil, nil, nil)
-	return result
+func AddUser(client *gophercloud.ServiceClient, tenantID, userID, roleID string) UserRoleResult {
+	var r UserRoleResult
+	_, r.Err = client.Put(userRoleURL(client, tenantID, userID, roleID), nil, nil, nil)
+	return r
 }
 
-// DeleteUserRole is the operation responsible for deleting a particular role
+// DeleteUser is the operation responsible for deleting a particular role
 // from a user. This is confined to the scope of the user's tenant - so the
 // tenant ID is a required argument.
-func DeleteUserRole(client *gophercloud.ServiceClient, tenantID, userID, roleID string) UserRoleResult {
-	var result UserRoleResult
-	_, result.Err = client.Delete(userRoleURL(client, tenantID, userID, roleID), nil)
-	return result
+func DeleteUser(client *gophercloud.ServiceClient, tenantID, userID, roleID string) UserRoleResult {
+	var r UserRoleResult
+	_, r.Err = client.Delete(userRoleURL(client, tenantID, userID, roleID), nil)
+	return r
 }
diff --git a/openstack/identity/v2/extensions/admin/roles/requests_test.go b/openstack/identity/v2/extensions/admin/roles/requests_test.go
index af809a5..cf3402d 100644
--- a/openstack/identity/v2/extensions/admin/roles/requests_test.go
+++ b/openstack/identity/v2/extensions/admin/roles/requests_test.go
@@ -41,24 +41,24 @@
 	th.AssertEquals(t, 1, count)
 }
 
-func TestAddUserRole(t *testing.T) {
+func TestAddUser(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
 	MockAddUserRoleResponse(t)
 
-	err := AddUserRole(client.ServiceClient(), "{tenant_id}", "{user_id}", "{role_id}").ExtractErr()
+	err := AddUser(client.ServiceClient(), "{tenant_id}", "{user_id}", "{role_id}").ExtractErr()
 
 	th.AssertNoErr(t, err)
 }
 
-func TestDeleteUserRole(t *testing.T) {
+func TestDeleteUser(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
 	MockDeleteUserRoleResponse(t)
 
-	err := DeleteUserRole(client.ServiceClient(), "{tenant_id}", "{user_id}", "{role_id}").ExtractErr()
+	err := DeleteUser(client.ServiceClient(), "{tenant_id}", "{user_id}", "{role_id}").ExtractErr()
 
 	th.AssertNoErr(t, err)
 }
diff --git a/openstack/identity/v2/extensions/delegate.go b/openstack/identity/v2/extensions/delegate.go
index 4b2c6a7..cf6cc81 100644
--- a/openstack/identity/v2/extensions/delegate.go
+++ b/openstack/identity/v2/extensions/delegate.go
@@ -14,23 +14,19 @@
 // IsEmpty returns true if the current page contains at least one Extension.
 func (page ExtensionPage) IsEmpty() (bool, error) {
 	is, err := ExtractExtensions(page)
-	if err != nil {
-		return true, err
-	}
-	return len(is) == 0, nil
+	return len(is) == 0, err
 }
 
 // ExtractExtensions accepts a Page struct, specifically an ExtensionPage struct, and extracts the
 // elements into a slice of Extension structs.
 func ExtractExtensions(page pagination.Page) ([]common.Extension, error) {
-	r := page.(ExtensionPage)
 	// Identity v2 adds an intermediate "values" object.
 	var s struct {
 		Extensions struct {
 			Values []common.Extension `json:"values"`
 		} `json:"extensions"`
 	}
-	err := r.ExtractInto(&s)
+	err := page.(ExtensionPage).ExtractInto(&s)
 	return s.Extensions.Values, err
 }
 
diff --git a/openstack/identity/v2/tenants/requests.go b/openstack/identity/v2/tenants/requests.go
index d4ea632..b9d7de6 100644
--- a/openstack/identity/v2/tenants/requests.go
+++ b/openstack/identity/v2/tenants/requests.go
@@ -9,17 +9,12 @@
 type ListOpts struct {
 	// Marker is the ID of the last Tenant on the previous page.
 	Marker string `q:"marker"`
-
 	// Limit specifies the page size.
 	Limit int `q:"limit"`
 }
 
 // List enumerates the Tenants to which the current token has access.
 func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
-	createPage := func(r pagination.PageResult) pagination.Page {
-		return TenantPage{pagination.LinkedPageBase{PageResult: r}}
-	}
-
 	url := listURL(client)
 	if opts != nil {
 		q, err := gophercloud.BuildQueryString(opts)
@@ -28,6 +23,7 @@
 		}
 		url += q.String()
 	}
-
-	return pagination.NewPager(client, url, createPage)
+	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
+		return TenantPage{pagination.LinkedPageBase{PageResult: r}}
+	})
 }
diff --git a/openstack/identity/v2/tokens/errors.go b/openstack/identity/v2/tokens/errors.go
deleted file mode 100644
index 12570f5..0000000
--- a/openstack/identity/v2/tokens/errors.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package tokens
-
-import "fmt"
-
-var (
-	// ErrUserIDProvided is returned if you attempt to authenticate with a UserID.
-	ErrUserIDProvided = unacceptedAttributeErr("UserID")
-
-	// ErrAPIKeyProvided is returned if you attempt to authenticate with an APIKey.
-	ErrAPIKeyProvided = unacceptedAttributeErr("APIKey")
-
-	// ErrDomainIDProvided is returned if you attempt to authenticate with a DomainID.
-	ErrDomainIDProvided = unacceptedAttributeErr("DomainID")
-
-	// ErrDomainNameProvided is returned if you attempt to authenticate with a DomainName.
-	ErrDomainNameProvided = unacceptedAttributeErr("DomainName")
-)
-
-func unacceptedAttributeErr(attribute string) error {
-	return fmt.Errorf("The base Identity V2 API does not accept authentication by %s", attribute)
-}
diff --git a/openstack/identity/v2/tokens/requests.go b/openstack/identity/v2/tokens/requests.go
index 6064bc7..a2a0685 100644
--- a/openstack/identity/v2/tokens/requests.go
+++ b/openstack/identity/v2/tokens/requests.go
@@ -4,74 +4,9 @@
 
 // AuthOptionsBuilder describes any argument that may be passed to the Create call.
 type AuthOptionsBuilder interface {
-
 	// ToTokenCreateMap assembles the Create request body, returning an error if parameters are
 	// missing or inconsistent.
-	ToTokenCreateMap() (map[string]interface{}, error)
-}
-
-// AuthOptions wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
-// interface.
-type AuthOptions struct {
-	gophercloud.AuthOptions
-}
-
-// WrapOptions embeds a root AuthOptions struct in a package-specific one.
-func WrapOptions(original gophercloud.AuthOptions) AuthOptions {
-	return AuthOptions{AuthOptions: original}
-}
-
-// ToTokenCreateMap converts AuthOptions into nested maps that can be serialized into a JSON
-// request.
-func (auth AuthOptions) ToTokenCreateMap() (map[string]interface{}, error) {
-	// Error out if an unsupported auth option is present.
-	if auth.UserID != "" {
-		return nil, ErrUserIDProvided
-	}
-	if auth.APIKey != "" {
-		return nil, ErrAPIKeyProvided
-	}
-	if auth.DomainID != "" {
-		return nil, ErrDomainIDProvided
-	}
-	if auth.DomainName != "" {
-		return nil, ErrDomainNameProvided
-	}
-
-	// Populate the request map.
-	authMap := make(map[string]interface{})
-
-	if auth.Username != "" {
-		if auth.Password == "" {
-			err := gophercloud.ErrMissingInput{}
-			err.Function = "tokens.ToTokenCreateMap"
-			err.Argument = "tokens.AuthOptions.Password"
-			return nil, err
-		}
-		authMap["passwordCredentials"] = map[string]interface{}{
-			"username": auth.Username,
-			"password": auth.Password,
-		}
-	} else if auth.TokenID != "" {
-		authMap["token"] = map[string]interface{}{
-			"id": auth.TokenID,
-		}
-	} else {
-		err := gophercloud.ErrMissingInput{}
-		err.Function = "tokens.ToTokenCreateMap"
-		err.Argument = "tokens.AuthOptions.Username/tokens.AuthOptions.TokenID"
-		err.Info = "You must provide either username/password or tenantID/token values."
-		return nil, err
-	}
-
-	if auth.TenantID != "" {
-		authMap["tenantId"] = auth.TenantID
-	}
-	if auth.TenantName != "" {
-		authMap["tenantName"] = auth.TenantName
-	}
-
-	return map[string]interface{}{"auth": authMap}, nil
+	ToTokenV2CreateMap() (map[string]interface{}, error)
 }
 
 // Create authenticates to the identity service and attempts to acquire a Token.
@@ -79,23 +14,23 @@
 // Generally, rather than interact with this call directly, end users should call openstack.AuthenticatedClient(),
 // which abstracts all of the gory details about navigating service catalogs and such.
 func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) CreateResult {
-	request, err := auth.ToTokenCreateMap()
+	var r CreateResult
+	b, err := auth.ToTokenV2CreateMap()
 	if err != nil {
-		return CreateResult{gophercloud.Result{Err: err}}
+		r.Err = err
+		return r
 	}
-
-	var result CreateResult
-	_, result.Err = client.Post(CreateURL(client), request, &result.Body, &gophercloud.RequestOpts{
+	_, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200, 203},
 	})
-	return result
+	return r
 }
 
 // Get validates and retrieves information for user's token.
 func Get(client *gophercloud.ServiceClient, token string) GetResult {
-	var result GetResult
-	_, result.Err = client.Get(GetURL(client, token), &result.Body, &gophercloud.RequestOpts{
+	var r GetResult
+	_, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200, 203},
 	})
-	return result
+	return r
 }
diff --git a/openstack/identity/v2/tokens/requests_test.go b/openstack/identity/v2/tokens/requests_test.go
index 9b3273e..d25c2d7 100644
--- a/openstack/identity/v2/tokens/requests_test.go
+++ b/openstack/identity/v2/tokens/requests_test.go
@@ -1,6 +1,7 @@
 package tokens
 
 import (
+	"reflect"
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
@@ -12,8 +13,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 	HandleTokenPost(t, requestJSON)
-
-	return Create(client.ServiceClient(), AuthOptions{options})
+	return Create(client.ServiceClient(), options)
 }
 
 func tokenPostErr(t *testing.T, options gophercloud.AuthOptions, expectedErr error) {
@@ -21,15 +21,30 @@
 	defer th.TeardownHTTP()
 	HandleTokenPost(t, "")
 
-	actualErr := Create(client.ServiceClient(), AuthOptions{options}).Err
-	th.CheckDeepEquals(t, expectedErr, actualErr)
+	actualErr := Create(client.ServiceClient(), options).Err
+	th.CheckDeepEquals(t, reflect.TypeOf(expectedErr), reflect.TypeOf(actualErr))
+}
+
+func TestCreateWithToken(t *testing.T) {
+	options := gophercloud.AuthOptions{
+		TokenID: "cbc36478b0bd8e67e89469c7749d4127",
+	}
+
+	IsSuccessful(t, tokenPost(t, options, `
+    {
+      "auth": {
+        "token": {
+          "id": "cbc36478b0bd8e67e89469c7749d4127"
+        }
+      }
+    }
+  `))
 }
 
 func TestCreateWithPassword(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Username: "me",
-		Password: "swordfish",
-	}
+	options := gophercloud.AuthOptions{}
+	options.Username = "me"
+	options.Password = "swordfish"
 
 	IsSuccessful(t, tokenPost(t, options, `
     {
@@ -44,11 +59,10 @@
 }
 
 func TestCreateTokenWithTenantID(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Username: "me",
-		Password: "opensesame",
-		TenantID: "fc394f2ab2df4114bde39905f800dc57",
-	}
+	options := gophercloud.AuthOptions{}
+	options.Username = "me"
+	options.Password = "opensesame"
+	options.TenantID = "fc394f2ab2df4114bde39905f800dc57"
 
 	IsSuccessful(t, tokenPost(t, options, `
     {
@@ -64,11 +78,10 @@
 }
 
 func TestCreateTokenWithTenantName(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Username:   "me",
-		Password:   "opensesame",
-		TenantName: "demo",
-	}
+	options := gophercloud.AuthOptions{}
+	options.Username = "me"
+	options.Password = "opensesame"
+	options.TenantName = "demo"
 
 	IsSuccessful(t, tokenPost(t, options, `
     {
@@ -83,63 +96,21 @@
   `))
 }
 
-func TestProhibitUserID(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Username: "me",
-		UserID:   "1234",
-		Password: "thing",
-	}
-
-	tokenPostErr(t, options, ErrUserIDProvided)
-}
-
-func TestProhibitAPIKey(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Username: "me",
-		Password: "thing",
-		APIKey:   "123412341234",
-	}
-
-	tokenPostErr(t, options, ErrAPIKeyProvided)
-}
-
-func TestProhibitDomainID(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Username: "me",
-		Password: "thing",
-		DomainID: "1234",
-	}
-
-	tokenPostErr(t, options, ErrDomainIDProvided)
-}
-
-func TestProhibitDomainName(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Username:   "me",
-		Password:   "thing",
-		DomainName: "wat",
-	}
-
-	tokenPostErr(t, options, ErrDomainNameProvided)
-}
-
 func TestRequireUsername(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Password: "thing",
-	}
+	options := gophercloud.AuthOptions{}
+	options.Password = "thing"
+
 	expected := gophercloud.ErrMissingInput{}
-	expected.Function = "tokens.ToTokenCreateMap"
 	expected.Argument = "tokens.AuthOptions.Username/tokens.AuthOptions.TokenID"
 	expected.Info = "You must provide either username/password or tenantID/token values."
 	tokenPostErr(t, options, expected)
 }
 
 func TestRequirePassword(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Username: "me",
-	}
+	options := gophercloud.AuthOptions{}
+	options.Username = "me"
+
 	expected := gophercloud.ErrMissingInput{}
-	expected.Function = "tokens.ToTokenCreateMap"
 	expected.Argument = "tokens.AuthOptions.Password"
 	tokenPostErr(t, options, expected)
 }
diff --git a/openstack/identity/v2/users/requests.go b/openstack/identity/v2/users/requests.go
index 7fa5fc3..f62f979 100644
--- a/openstack/identity/v2/users/requests.go
+++ b/openstack/identity/v2/users/requests.go
@@ -7,41 +7,25 @@
 
 // List lists the existing users.
 func List(client *gophercloud.ServiceClient) pagination.Pager {
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, rootURL(client), func(r pagination.PageResult) pagination.Page {
 		return UserPage{pagination.SinglePageBase(r)}
-	}
-
-	return pagination.NewPager(client, rootURL(client), createPage)
+	})
 }
 
-// EnabledState represents whether the user is enabled or not.
-type EnabledState *bool
-
-// Useful variables to use when creating or updating users.
-var (
-	iTrue  = true
-	iFalse = false
-
-	Enabled  EnabledState = &iTrue
-	Disabled EnabledState = &iFalse
-)
-
 // CommonOpts are the parameters that are shared between CreateOpts and
 // UpdateOpts
 type CommonOpts struct {
 	// Either a name or username is required. When provided, the value must be
 	// unique or a 409 conflict error will be returned. If you provide a name but
 	// omit a username, the latter will be set to the former; and vice versa.
-	Name, Username string
-
+	Name     string `json:"name,omitempty"`
+	Username string `json:"username,omitempty"`
 	// The ID of the tenant to which you want to assign this user.
-	TenantID string
-
+	TenantID string `json:"tenant_id,omitempty"`
 	// Indicates whether this user is enabled or not.
-	Enabled EnabledState
-
+	Enabled *bool `json:"enabled,omitempty"`
 	// The email address of this user.
-	Email string
+	Email string `json:"email,omitempty"`
 }
 
 // CreateOpts represents the options needed when creating new users.
@@ -54,33 +38,13 @@
 
 // ToUserCreateMap assembles a request body based on the contents of a CreateOpts.
 func (opts CreateOpts) ToUserCreateMap() (map[string]interface{}, error) {
-	m := make(map[string]interface{})
-
 	if opts.Name == "" && opts.Username == "" {
 		err := gophercloud.ErrMissingInput{}
-		err.Function = "users.ToUserCreateMap"
 		err.Argument = "users.CreateOpts.Name/users.CreateOpts.Username"
 		err.Info = "Either a Name or Username must be provided"
-		return m, err
+		return nil, err
 	}
-
-	if opts.Name != "" {
-		m["name"] = opts.Name
-	}
-	if opts.Username != "" {
-		m["username"] = opts.Username
-	}
-	if opts.Enabled != nil {
-		m["enabled"] = &opts.Enabled
-	}
-	if opts.Email != "" {
-		m["email"] = opts.Email
-	}
-	if opts.TenantID != "" {
-		m["tenant_id"] = opts.TenantID
-	}
-
-	return map[string]interface{}{"user": m}, nil
+	return gophercloud.BuildRequestBody(opts, "user")
 }
 
 // Create is the operation responsible for creating new users.
@@ -109,57 +73,41 @@
 
 // UpdateOptsBuilder allows extensions to add additional attributes to the Update request.
 type UpdateOptsBuilder interface {
-	ToUserUpdateMap() map[string]interface{}
+	ToUserUpdateMap() (map[string]interface{}, error)
 }
 
 // UpdateOpts specifies the base attributes that may be updated on an existing server.
 type UpdateOpts CommonOpts
 
 // ToUserUpdateMap formats an UpdateOpts structure into a request body.
-func (opts UpdateOpts) ToUserUpdateMap() map[string]interface{} {
-	m := make(map[string]interface{})
-
-	if opts.Name != "" {
-		m["name"] = opts.Name
-	}
-	if opts.Username != "" {
-		m["username"] = opts.Username
-	}
-	if opts.Enabled != nil {
-		m["enabled"] = &opts.Enabled
-	}
-	if opts.Email != "" {
-		m["email"] = opts.Email
-	}
-	if opts.TenantID != "" {
-		m["tenant_id"] = opts.TenantID
-	}
-
-	return map[string]interface{}{"user": m}
+func (opts UpdateOpts) ToUserUpdateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "user")
 }
 
 // Update is the operation responsible for updating exist users by their UUID.
 func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
-	var result UpdateResult
-	reqBody := opts.ToUserUpdateMap()
-	_, result.Err = client.Put(ResourceURL(client, id), reqBody, &result.Body, &gophercloud.RequestOpts{
+	var r UpdateResult
+	b, err := opts.ToUserUpdateMap()
+	if err != nil {
+		r.Err = err
+		return r
+	}
+	_, r.Err = client.Put(ResourceURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
-	return result
+	return r
 }
 
 // Delete is the operation responsible for permanently deleting an API user.
 func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
-	var result DeleteResult
-	_, result.Err = client.Delete(ResourceURL(client, id), nil)
-	return result
+	var r DeleteResult
+	_, r.Err = client.Delete(ResourceURL(client, id), nil)
+	return r
 }
 
 // ListRoles lists the existing roles that can be assigned to users.
 func ListRoles(client *gophercloud.ServiceClient, tenantID, userID string) pagination.Pager {
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, listRolesURL(client, tenantID, userID), func(r pagination.PageResult) pagination.Page {
 		return RolePage{pagination.SinglePageBase(r)}
-	}
-
-	return pagination.NewPager(client, listRolesURL(client, tenantID, userID), createPage)
+	})
 }
diff --git a/openstack/identity/v2/users/requests_test.go b/openstack/identity/v2/users/requests_test.go
index 8604ab1..0e03653 100644
--- a/openstack/identity/v2/users/requests_test.go
+++ b/openstack/identity/v2/users/requests_test.go
@@ -6,6 +6,7 @@
 	"github.com/gophercloud/gophercloud/pagination"
 	th "github.com/gophercloud/gophercloud/testhelper"
 	"github.com/gophercloud/gophercloud/testhelper/client"
+	"github.com/jrperritt/gophercloud"
 )
 
 func TestList(t *testing.T) {
@@ -19,10 +20,7 @@
 	err := List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
 		count++
 		actual, err := ExtractUsers(page)
-		if err != nil {
-			t.Errorf("Failed to extract users: %v", err)
-			return false, err
-		}
+		th.AssertNoErr(t, err)
 
 		expected := []User{
 			User{
@@ -42,12 +40,9 @@
 				TenantID: "12345",
 			},
 		}
-
 		th.CheckDeepEquals(t, expected, actual)
-
 		return true, nil
 	})
-
 	th.AssertNoErr(t, err)
 	th.AssertEquals(t, 1, count)
 }
@@ -61,7 +56,7 @@
 	opts := CreateOpts{
 		Name:     "new_user",
 		TenantID: "12345",
-		Enabled:  Disabled,
+		Enabled:  gophercloud.Disabled,
 		Email:    "new_user@foo.com",
 	}
 
@@ -109,7 +104,7 @@
 	id := "c39e3de9be2d4c779f1dfd6abacc176d"
 	opts := UpdateOpts{
 		Name:    "new_name",
-		Enabled: Enabled,
+		Enabled: gophercloud.Enabled,
 		Email:   "new_email@foo.com",
 	}
 
diff --git a/openstack/identity/v3/endpoints/errors.go b/openstack/identity/v3/endpoints/errors.go
deleted file mode 100644
index 854957f..0000000
--- a/openstack/identity/v3/endpoints/errors.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package endpoints
-
-import "fmt"
-
-func requiredAttribute(attribute string) error {
-	return fmt.Errorf("You must specify %s for this endpoint.", attribute)
-}
-
-var (
-	// ErrAvailabilityRequired is reported if an Endpoint is created without an Availability.
-	ErrAvailabilityRequired = requiredAttribute("an availability")
-
-	// ErrNameRequired is reported if an Endpoint is created without a Name.
-	ErrNameRequired = requiredAttribute("a name")
-
-	// ErrURLRequired is reported if an Endpoint is created without a URL.
-	ErrURLRequired = requiredAttribute("a URL")
-
-	// ErrServiceIDRequired is reported if an Endpoint is created without a ServiceID.
-	ErrServiceIDRequired = requiredAttribute("a serviceID")
-)
diff --git a/openstack/identity/v3/endpoints/requests.go b/openstack/identity/v3/endpoints/requests.go
index c9b8cf5..7165fb4 100644
--- a/openstack/identity/v3/endpoints/requests.go
+++ b/openstack/identity/v3/endpoints/requests.go
@@ -5,59 +5,38 @@
 	"github.com/gophercloud/gophercloud/pagination"
 )
 
-// EndpointOpts contains the subset of Endpoint attributes that should be used to create or update an Endpoint.
-type EndpointOpts struct {
-	Availability gophercloud.Availability
-	Name         string
-	Region       string
-	URL          string
-	ServiceID    string
+type CreateOptsBuilder interface {
+	ToEndpointCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts contains the subset of Endpoint attributes that should be used to create an Endpoint.
+type CreateOpts struct {
+	Availability gophercloud.Availability `json:"interface" required:"true"`
+	Name         string                   `json:"name" required:"true"`
+	Region       string                   `json:"region,omitempty"`
+	URL          string                   `json:"url" required:"true"`
+	ServiceID    string                   `json:"service_id" required:"true"`
+}
+
+func (opts CreateOpts) ToEndpointCreateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "endpoint")
 }
 
 // Create inserts a new Endpoint into the service catalog.
 // Within EndpointOpts, Region may be omitted by being left as "", but all other fields are required.
-func Create(client *gophercloud.ServiceClient, opts EndpointOpts) CreateResult {
-	// Redefined so that Region can be re-typed as a *string, which can be omitted from the JSON output.
-	type endpoint struct {
-		Interface string  `json:"interface"`
-		Name      string  `json:"name"`
-		Region    *string `json:"region,omitempty"`
-		URL       string  `json:"url"`
-		ServiceID string  `json:"service_id"`
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
+	var r CreateResult
+	b, err := opts.ToEndpointCreateMap()
+	if err != nil {
+		r.Err = err
+		return r
 	}
+	_, r.Err = client.Post(listURL(client), &b, &r.Body, nil)
+	return r
+}
 
-	type request struct {
-		Endpoint endpoint `json:"endpoint"`
-	}
-
-	// Ensure that EndpointOpts is fully populated.
-	if opts.Availability == "" {
-		return createErr(ErrAvailabilityRequired)
-	}
-	if opts.Name == "" {
-		return createErr(ErrNameRequired)
-	}
-	if opts.URL == "" {
-		return createErr(ErrURLRequired)
-	}
-	if opts.ServiceID == "" {
-		return createErr(ErrServiceIDRequired)
-	}
-
-	// Populate the request body.
-	reqBody := request{
-		Endpoint: endpoint{
-			Interface: string(opts.Availability),
-			Name:      opts.Name,
-			URL:       opts.URL,
-			ServiceID: opts.ServiceID,
-		},
-	}
-	reqBody.Endpoint.Region = gophercloud.MaybeString(opts.Region)
-
-	var result CreateResult
-	_, result.Err = client.Post(listURL(client), reqBody, &result.Body, nil)
-	return result
+type ListOptsBuilder interface {
+	ToEndpointListParams() (string, error)
 }
 
 // ListOpts allows finer control over the endpoints returned by a List call.
@@ -69,55 +48,58 @@
 	PerPage      int                      `q:"per_page"`
 }
 
-// List enumerates endpoints in a paginated collection, optionally filtered by ListOpts criteria.
-func List(client *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
-	u := listURL(client)
+func (opts ListOpts) ToEndpointListParams() (string, error) {
 	q, err := gophercloud.BuildQueryString(opts)
-	if err != nil {
-		return pagination.Pager{Err: err}
-	}
-	u += q.String()
-	createPage := func(r pagination.PageResult) pagination.Page {
-		return EndpointPage{pagination.LinkedPageBase{PageResult: r}}
-	}
+	return q.String(), err
+}
 
-	return pagination.NewPager(client, u, createPage)
+// List enumerates endpoints in a paginated collection, optionally filtered by ListOpts criteria.
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	u := listURL(client)
+	if opts != nil {
+		q, err := gophercloud.BuildQueryString(opts)
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		u += q.String()
+	}
+	return pagination.NewPager(client, u, func(r pagination.PageResult) pagination.Page {
+		return EndpointPage{pagination.LinkedPageBase{PageResult: r}}
+	})
+}
+
+type UpdateOptsBuilder interface {
+	ToEndpointUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateOpts contains the subset of Endpoint attributes that should be used to update an Endpoint.
+type UpdateOpts struct {
+	Availability gophercloud.Availability `json:"interface,omitempty"`
+	Name         string                   `json:"name,omitempty"`
+	Region       string                   `json:"region,omitempty"`
+	URL          string                   `json:"url,omitempty"`
+	ServiceID    string                   `json:"service_id,omitempty"`
+}
+
+func (opts UpdateOpts) ToEndpointUpdateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "endpoint")
 }
 
 // Update changes an existing endpoint with new data.
-// All fields are optional in the provided EndpointOpts.
-func Update(client *gophercloud.ServiceClient, endpointID string, opts EndpointOpts) UpdateResult {
-	type endpoint struct {
-		Interface *string `json:"interface,omitempty"`
-		Name      *string `json:"name,omitempty"`
-		Region    *string `json:"region,omitempty"`
-		URL       *string `json:"url,omitempty"`
-		ServiceID *string `json:"service_id,omitempty"`
+func Update(client *gophercloud.ServiceClient, endpointID string, opts UpdateOptsBuilder) UpdateResult {
+	var r UpdateResult
+	b, err := opts.ToEndpointUpdateMap()
+	if err != nil {
+		r.Err = err
+		return r
 	}
-
-	type request struct {
-		Endpoint endpoint `json:"endpoint"`
-	}
-
-	reqBody := request{Endpoint: endpoint{}}
-	reqBody.Endpoint.Interface = gophercloud.MaybeString(string(opts.Availability))
-	reqBody.Endpoint.Name = gophercloud.MaybeString(opts.Name)
-	reqBody.Endpoint.Region = gophercloud.MaybeString(opts.Region)
-	reqBody.Endpoint.URL = gophercloud.MaybeString(opts.URL)
-	reqBody.Endpoint.ServiceID = gophercloud.MaybeString(opts.ServiceID)
-
-	var result UpdateResult
-	_, result.Err = client.Request("PATCH", endpointURL(client, endpointID), &gophercloud.RequestOpts{
-		JSONBody:     &reqBody,
-		JSONResponse: &result.Body,
-		OkCodes:      []int{200},
-	})
-	return result
+	_, r.Err = client.Patch(endpointURL(client, endpointID), &b, &r.Body, nil)
+	return r
 }
 
 // Delete removes an endpoint from the service catalog.
 func Delete(client *gophercloud.ServiceClient, endpointID string) DeleteResult {
-	var res DeleteResult
-	_, res.Err = client.Delete(endpointURL(client, endpointID), nil)
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(endpointURL(client, endpointID), nil)
+	return r
 }
diff --git a/openstack/identity/v3/endpoints/requests_test.go b/openstack/identity/v3/endpoints/requests_test.go
index ec17d17..14bbe6a 100644
--- a/openstack/identity/v3/endpoints/requests_test.go
+++ b/openstack/identity/v3/endpoints/requests_test.go
@@ -3,23 +3,22 @@
 import (
 	"fmt"
 	"net/http"
-	"reflect"
 	"testing"
 
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
-	"github.com/gophercloud/gophercloud/testhelper"
+	th "github.com/gophercloud/gophercloud/testhelper"
 	"github.com/gophercloud/gophercloud/testhelper/client"
 )
 
 func TestCreateSuccessful(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/endpoints", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "POST")
-		testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
-		testhelper.TestJSONRequest(t, r, `
+	th.Mux.HandleFunc("/endpoints", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+		th.TestJSONRequest(t, r, `
       {
         "endpoint": {
           "interface": "public",
@@ -49,16 +48,14 @@
     `)
 	})
 
-	actual, err := Create(client.ServiceClient(), EndpointOpts{
+	actual, err := Create(client.ServiceClient(), CreateOpts{
 		Availability: gophercloud.AvailabilityPublic,
 		Name:         "the-endiest-of-points",
 		Region:       "underground",
 		URL:          "https://1.2.3.4:9000/",
 		ServiceID:    "asdfasdfasdfasdf",
 	}).Extract()
-	if err != nil {
-		t.Fatalf("Unable to create an endpoint: %v", err)
-	}
+	th.AssertNoErr(t, err)
 
 	expected := &Endpoint{
 		ID:           "12",
@@ -69,18 +66,16 @@
 		URL:          "https://1.2.3.4:9000/",
 	}
 
-	if !reflect.DeepEqual(actual, expected) {
-		t.Errorf("Expected %#v, was %#v", expected, actual)
-	}
+	th.AssertDeepEquals(t, expected, actual)
 }
 
 func TestListEndpoints(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/endpoints", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+	th.Mux.HandleFunc("/endpoints", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
 
 		w.Header().Add("Content-Type", "application/json")
 		fmt.Fprintf(w, `
@@ -144,26 +139,20 @@
 				URL:          "https://1.2.3.4:9001/",
 			},
 		}
-
-		if !reflect.DeepEqual(expected, actual) {
-			t.Errorf("Expected %#v, got %#v", expected, actual)
-		}
-
+		th.AssertDeepEquals(t, expected, actual)
 		return true, nil
 	})
-	if count != 1 {
-		t.Errorf("Expected 1 page, got %d", count)
-	}
+	th.AssertEquals(t, 1, count)
 }
 
 func TestUpdateEndpoint(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/endpoints/12", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "PATCH")
-		testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
-		testhelper.TestJSONRequest(t, r, `
+	th.Mux.HandleFunc("/endpoints/12", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PATCH")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+		th.TestJSONRequest(t, r, `
 		{
 	    "endpoint": {
 	      "name": "renamed",
@@ -189,7 +178,7 @@
 	`)
 	})
 
-	actual, err := Update(client.ServiceClient(), "12", EndpointOpts{
+	actual, err := Update(client.ServiceClient(), "12", UpdateOpts{
 		Name:   "renamed",
 		Region: "somewhere-else",
 	}).Extract()
@@ -205,22 +194,20 @@
 		ServiceID:    "asdfasdfasdfasdf",
 		URL:          "https://1.2.3.4:9000/",
 	}
-	if !reflect.DeepEqual(expected, actual) {
-		t.Errorf("Expected %#v, was %#v", expected, actual)
-	}
+	th.AssertDeepEquals(t, expected, actual)
 }
 
 func TestDeleteEndpoint(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/endpoints/34", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "DELETE")
-		testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+	th.Mux.HandleFunc("/endpoints/34", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
 
 		w.WriteHeader(http.StatusNoContent)
 	})
 
 	res := Delete(client.ServiceClient(), "34")
-	testhelper.AssertNoErr(t, res.Err)
+	th.AssertNoErr(t, res.Err)
 }
diff --git a/openstack/identity/v3/roles/requests.go b/openstack/identity/v3/roles/requests.go
index 0264347..de65c51 100644
--- a/openstack/identity/v3/roles/requests.go
+++ b/openstack/identity/v3/roles/requests.go
@@ -17,34 +17,31 @@
 // Effective lists effective assignments at the user, project, and domain level,
 // allowing for the effects of group membership.
 type ListAssignmentsOpts struct {
-	GroupId        string `q:"group.id"`
-	RoleId         string `q:"role.id"`
-	ScopeDomainId  string `q:"scope.domain.id"`
-	ScopeProjectId string `q:"scope.project.id"`
-	UserId         string `q:"user.id"`
-	Effective      bool   `q:"effective"`
+	GroupID        string `q:"group.id"`
+	RoleID         string `q:"role.id"`
+	ScopeDomainID  string `q:"scope.domain.id"`
+	ScopeProjectID string `q:"scope.project.id"`
+	UserID         string `q:"user.id"`
+	Effective      *bool  `q:"effective"`
 }
 
 // ToRolesListAssignmentsQuery formats a ListAssignmentsOpts into a query string.
 func (opts ListAssignmentsOpts) ToRolesListAssignmentsQuery() (string, error) {
 	q, err := gophercloud.BuildQueryString(opts)
-	if err != nil {
-		return "", err
-	}
-	return q.String(), nil
+	return q.String(), err
 }
 
 // ListAssignments enumerates the roles assigned to a specified resource.
 func ListAssignments(client *gophercloud.ServiceClient, opts ListAssignmentsOptsBuilder) pagination.Pager {
 	url := listAssignmentsURL(client)
-	query, err := opts.ToRolesListAssignmentsQuery()
-	if err != nil {
-		return pagination.Pager{Err: err}
+	if opts != nil {
+		query, err := opts.ToRolesListAssignmentsQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
 	}
-	url += query
-	createPage := func(r pagination.PageResult) pagination.Page {
+	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
 		return RoleAssignmentPage{pagination.LinkedPageBase{PageResult: r}}
-	}
-
-	return pagination.NewPager(client, url, createPage)
+	})
 }
diff --git a/openstack/identity/v3/services/requests.go b/openstack/identity/v3/services/requests.go
index 484afab..34bf979 100644
--- a/openstack/identity/v3/services/requests.go
+++ b/openstack/identity/v3/services/requests.go
@@ -5,21 +5,16 @@
 	"github.com/gophercloud/gophercloud/pagination"
 )
 
-type response struct {
-	Service Service `json:"service"`
-}
-
 // Create adds a new service of the requested type to the catalog.
 func Create(client *gophercloud.ServiceClient, serviceType string) CreateResult {
-	type request struct {
-		Type string `json:"type"`
-	}
+	var r CreateResult
+	b := map[string]string{"type": serviceType}
+	_, r.Err = client.Post(listURL(client), b, &r.Body, nil)
+	return r
+}
 
-	req := request{Type: serviceType}
-
-	var result CreateResult
-	_, result.Err = client.Post(listURL(client), req, &result.Body, nil)
-	return result
+type ListOptsBuilder interface {
+	ToServiceListMap() (string, error)
 }
 
 // ListOpts allows you to query the List method.
@@ -29,49 +24,45 @@
 	Page        int    `q:"page"`
 }
 
-// List enumerates the services available to a specific user.
-func List(client *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
-	u := listURL(client)
+func (opts ListOpts) ToServiceListMap() (string, error) {
 	q, err := gophercloud.BuildQueryString(opts)
-	if err != nil {
-		return pagination.Pager{Err: err}
-	}
-	u += q.String()
-	createPage := func(r pagination.PageResult) pagination.Page {
-		return ServicePage{pagination.LinkedPageBase{PageResult: r}}
-	}
+	return q.String(), err
+}
 
-	return pagination.NewPager(client, u, createPage)
+// List enumerates the services available to a specific user.
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	u := listURL(client)
+	if opts != nil {
+		q, err := opts.ToServiceListMap()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		u += q
+	}
+	return pagination.NewPager(client, u, func(r pagination.PageResult) pagination.Page {
+		return ServicePage{pagination.LinkedPageBase{PageResult: r}}
+	})
 }
 
 // Get returns additional information about a service, given its ID.
 func Get(client *gophercloud.ServiceClient, serviceID string) GetResult {
-	var result GetResult
-	_, result.Err = client.Get(serviceURL(client, serviceID), &result.Body, nil)
-	return result
+	var r GetResult
+	_, r.Err = client.Get(serviceURL(client, serviceID), &r.Body, nil)
+	return r
 }
 
 // Update changes the service type of an existing service.
 func Update(client *gophercloud.ServiceClient, serviceID string, serviceType string) UpdateResult {
-	type request struct {
-		Type string `json:"type"`
-	}
-
-	req := request{Type: serviceType}
-
-	var result UpdateResult
-	_, result.Err = client.Request("PATCH", serviceURL(client, serviceID), &gophercloud.RequestOpts{
-		JSONBody:     &req,
-		JSONResponse: &result.Body,
-		OkCodes:      []int{200},
-	})
-	return result
+	var r UpdateResult
+	b := map[string]string{"type": serviceType}
+	_, r.Err = client.Patch(serviceURL(client, serviceID), &b, &r.Body, nil)
+	return r
 }
 
 // Delete removes an existing service.
 // It either deletes all associated endpoints, or fails until all endpoints are deleted.
 func Delete(client *gophercloud.ServiceClient, serviceID string) DeleteResult {
-	var res DeleteResult
-	_, res.Err = client.Delete(serviceURL(client, serviceID), nil)
-	return res
+	var r DeleteResult
+	_, r.Err = client.Delete(serviceURL(client, serviceID), nil)
+	return r
 }
diff --git a/openstack/identity/v3/services/requests_test.go b/openstack/identity/v3/services/requests_test.go
index ebe9e0f..aa19bcc 100644
--- a/openstack/identity/v3/services/requests_test.go
+++ b/openstack/identity/v3/services/requests_test.go
@@ -3,22 +3,21 @@
 import (
 	"fmt"
 	"net/http"
-	"reflect"
 	"testing"
 
 	"github.com/gophercloud/gophercloud/pagination"
-	"github.com/gophercloud/gophercloud/testhelper"
+	th "github.com/gophercloud/gophercloud/testhelper"
 	"github.com/gophercloud/gophercloud/testhelper/client"
 )
 
 func TestCreateSuccessful(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "POST")
-		testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
-		testhelper.TestJSONRequest(t, r, `{ "type": "compute" }`)
+	th.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+		th.TestJSONRequest(t, r, `{ "type": "compute" }`)
 
 		w.Header().Add("Content-Type", "application/json")
 		w.WriteHeader(http.StatusCreated)
@@ -32,32 +31,27 @@
     }`)
 	})
 
-	result, err := Create(client.ServiceClient(), "compute").Extract()
+	expected := &Service{
+		Description: "Here's your service",
+		ID:          "1234",
+		Name:        "InscrutableOpenStackProjectName",
+		Type:        "compute",
+	}
+
+	actual, err := Create(client.ServiceClient(), "compute").Extract()
 	if err != nil {
 		t.Fatalf("Unexpected error from Create: %v", err)
 	}
-
-	if result.Description == nil || *result.Description != "Here's your service" {
-		t.Errorf("Service description was unexpected [%s]", *result.Description)
-	}
-	if result.ID != "1234" {
-		t.Errorf("Service ID was unexpected [%s]", result.ID)
-	}
-	if result.Name != "InscrutableOpenStackProjectName" {
-		t.Errorf("Service name was unexpected [%s]", result.Name)
-	}
-	if result.Type != "compute" {
-		t.Errorf("Service type was unexpected [%s]", result.Type)
-	}
+	th.AssertDeepEquals(t, expected, actual)
 }
 
 func TestListSinglePage(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+	th.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
 
 		w.Header().Add("Content-Type", "application/json")
 		fmt.Fprintf(w, `
@@ -92,44 +86,34 @@
 			return false, err
 		}
 
-		desc0 := "Service One"
-		desc1 := "Service Two"
 		expected := []Service{
 			Service{
-				Description: &desc0,
+				Description: "Service One",
 				ID:          "1234",
 				Name:        "service-one",
 				Type:        "identity",
 			},
 			Service{
-				Description: &desc1,
+				Description: "Service Two",
 				ID:          "9876",
 				Name:        "service-two",
 				Type:        "compute",
 			},
 		}
-
-		if !reflect.DeepEqual(expected, actual) {
-			t.Errorf("Expected %#v, got %#v", expected, actual)
-		}
-
+		th.AssertDeepEquals(t, expected, actual)
 		return true, nil
 	})
-	if err != nil {
-		t.Errorf("Unexpected error while paging: %v", err)
-	}
-	if count != 1 {
-		t.Errorf("Expected 1 page, got %d", count)
-	}
+	th.AssertNoErr(t, err)
+	th.AssertEquals(t, 1, count)
 }
 
 func TestGetSuccessful(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/services/12345", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+	th.Mux.HandleFunc("/services/12345", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
 
 		w.Header().Add("Content-Type", "application/json")
 		fmt.Fprintf(w, `
@@ -144,33 +128,27 @@
 		`)
 	})
 
-	result, err := Get(client.ServiceClient(), "12345").Extract()
-	if err != nil {
-		t.Fatalf("Error fetching service information: %v", err)
+	actual, err := Get(client.ServiceClient(), "12345").Extract()
+	th.AssertNoErr(t, err)
+
+	expected := &Service{
+		ID:          "12345",
+		Description: "Service One",
+		Name:        "service-one",
+		Type:        "identity",
 	}
 
-	if result.ID != "12345" {
-		t.Errorf("Unexpected service ID: %s", result.ID)
-	}
-	if *result.Description != "Service One" {
-		t.Errorf("Unexpected service description: [%s]", *result.Description)
-	}
-	if result.Name != "service-one" {
-		t.Errorf("Unexpected service name: [%s]", result.Name)
-	}
-	if result.Type != "identity" {
-		t.Errorf("Unexpected service type: [%s]", result.Type)
-	}
+	th.AssertDeepEquals(t, expected, actual)
 }
 
 func TestUpdateSuccessful(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/services/12345", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "PATCH")
-		testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
-		testhelper.TestJSONRequest(t, r, `{ "type": "lasermagic" }`)
+	th.Mux.HandleFunc("/services/12345", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PATCH")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+		th.TestJSONRequest(t, r, `{ "type": "lasermagic" }`)
 
 		w.Header().Add("Content-Type", "application/json")
 		fmt.Fprintf(w, `
@@ -183,27 +161,26 @@
 		`)
 	})
 
-	result, err := Update(client.ServiceClient(), "12345", "lasermagic").Extract()
-	if err != nil {
-		t.Fatalf("Unable to update service: %v", err)
+	expected := &Service{
+		ID:   "12345",
+		Type: "lasermagic",
 	}
 
-	if result.ID != "12345" {
-		t.Fatalf("Expected ID 12345, was %s", result.ID)
-	}
+	actual, err := Update(client.ServiceClient(), "12345", "lasermagic").Extract()
+	th.AssertNoErr(t, err)
+	th.AssertDeepEquals(t, expected, actual)
 }
 
 func TestDeleteSuccessful(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/services/12345", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "DELETE")
-		testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
-
+	th.Mux.HandleFunc("/services/12345", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
 		w.WriteHeader(http.StatusNoContent)
 	})
 
 	res := Delete(client.ServiceClient(), "12345")
-	testhelper.AssertNoErr(t, res.Err)
+	th.AssertNoErr(t, res.Err)
 }
diff --git a/openstack/identity/v3/services/results.go b/openstack/identity/v3/services/results.go
index b0d36fb..9ebcc20 100644
--- a/openstack/identity/v3/services/results.go
+++ b/openstack/identity/v3/services/results.go
@@ -41,10 +41,10 @@
 
 // Service is the result of a list or information query.
 type Service struct {
-	Description *string `json:"description,omitempty"`
-	ID          string  `json:"id"`
-	Name        string  `json:"name"`
-	Type        string  `json:"type"`
+	Description string `json:"description`
+	ID          string `json:"id"`
+	Name        string `json:"name"`
+	Type        string `json:"type"`
 }
 
 // ServicePage is a single page of Service results.
diff --git a/openstack/identity/v3/tokens/requests.go b/openstack/identity/v3/tokens/requests.go
index 4b87311..e7320fd 100644
--- a/openstack/identity/v3/tokens/requests.go
+++ b/openstack/identity/v3/tokens/requests.go
@@ -6,12 +6,11 @@
 	"github.com/gophercloud/gophercloud"
 )
 
-// Scope allows a created token to be limited to a specific domain or project.
-type Scope struct {
-	ProjectID   string
-	ProjectName string
-	DomainID    string
-	DomainName  string
+// AuthOptionsBuilder describes any argument that may be passed to the Create call.
+type AuthOptionsBuilder interface {
+	// ToTokenV3CreateMap assembles the Create request body, returning an error if parameters are
+	// missing or inconsistent.
+	ToTokenV3CreateMap(*gophercloud.ScopeOptsV3) (map[string]interface{}, error)
 }
 
 func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
@@ -21,241 +20,33 @@
 }
 
 // Create authenticates and either generates a new token, or changes the Scope of an existing token.
-func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope *Scope) CreateResult {
-	type domainReq struct {
-		ID   *string `json:"id,omitempty"`
-		Name *string `json:"name,omitempty"`
+func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder, scopeOpts *gophercloud.ScopeOptsV3) CreateResult {
+	var r CreateResult
+	b, err := opts.ToTokenV3CreateMap(scopeOpts)
+	if err != nil {
+		r.Err = err
+		return r
 	}
-
-	type projectReq struct {
-		Domain *domainReq `json:"domain,omitempty"`
-		Name   *string    `json:"name,omitempty"`
-		ID     *string    `json:"id,omitempty"`
+	var resp *http.Response
+	resp, r.Err = c.Post(tokenURL(c), b, &r.Body, nil)
+	if resp != nil {
+		r.Header = resp.Header
 	}
-
-	type userReq struct {
-		ID       *string    `json:"id,omitempty"`
-		Name     *string    `json:"name,omitempty"`
-		Password string     `json:"password"`
-		Domain   *domainReq `json:"domain,omitempty"`
-	}
-
-	type passwordReq struct {
-		User userReq `json:"user"`
-	}
-
-	type tokenReq struct {
-		ID string `json:"id"`
-	}
-
-	type identityReq struct {
-		Methods  []string     `json:"methods"`
-		Password *passwordReq `json:"password,omitempty"`
-		Token    *tokenReq    `json:"token,omitempty"`
-	}
-
-	type scopeReq struct {
-		Domain  *domainReq  `json:"domain,omitempty"`
-		Project *projectReq `json:"project,omitempty"`
-	}
-
-	type authReq struct {
-		Identity identityReq `json:"identity"`
-		Scope    *scopeReq   `json:"scope,omitempty"`
-	}
-
-	type request struct {
-		Auth authReq `json:"auth"`
-	}
-
-	// Populate the request structure based on the provided arguments. Create and return an error
-	// if insufficient or incompatible information is present.
-	var req request
-
-	// Test first for unrecognized arguments.
-	if options.APIKey != "" {
-		return createErr(ErrAPIKeyProvided)
-	}
-	if options.TenantID != "" {
-		return createErr(ErrTenantIDProvided)
-	}
-	if options.TenantName != "" {
-		return createErr(ErrTenantNameProvided)
-	}
-
-	if options.Password == "" {
-		if c.TokenID != "" {
-			// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
-			// parameters.
-			if options.Username != "" {
-				return createErr(ErrUsernameWithToken)
-			}
-			if options.UserID != "" {
-				return createErr(ErrUserIDWithToken)
-			}
-			if options.DomainID != "" {
-				return createErr(ErrDomainIDWithToken)
-			}
-			if options.DomainName != "" {
-				return createErr(ErrDomainNameWithToken)
-			}
-
-			// Configure the request for Token authentication.
-			req.Auth.Identity.Methods = []string{"token"}
-			req.Auth.Identity.Token = &tokenReq{
-				ID: c.TokenID,
-			}
-		} else {
-			// If no password or token ID are available, authentication can't continue.
-			return createErr(ErrMissingPassword)
-		}
-	} else {
-		// Password authentication.
-		req.Auth.Identity.Methods = []string{"password"}
-
-		// At least one of Username and UserID must be specified.
-		if options.Username == "" && options.UserID == "" {
-			return createErr(ErrUsernameOrUserID)
-		}
-
-		if options.Username != "" {
-			// If Username is provided, UserID may not be provided.
-			if options.UserID != "" {
-				return createErr(ErrUsernameOrUserID)
-			}
-
-			// Either DomainID or DomainName must also be specified.
-			if options.DomainID == "" && options.DomainName == "" {
-				return createErr(ErrDomainIDOrDomainName)
-			}
-
-			if options.DomainID != "" {
-				if options.DomainName != "" {
-					return createErr(ErrDomainIDOrDomainName)
-				}
-
-				// Configure the request for Username and Password authentication with a DomainID.
-				req.Auth.Identity.Password = &passwordReq{
-					User: userReq{
-						Name:     &options.Username,
-						Password: options.Password,
-						Domain:   &domainReq{ID: &options.DomainID},
-					},
-				}
-			}
-
-			if options.DomainName != "" {
-				// Configure the request for Username and Password authentication with a DomainName.
-				req.Auth.Identity.Password = &passwordReq{
-					User: userReq{
-						Name:     &options.Username,
-						Password: options.Password,
-						Domain:   &domainReq{Name: &options.DomainName},
-					},
-				}
-			}
-		}
-
-		if options.UserID != "" {
-			// If UserID is specified, neither DomainID nor DomainName may be.
-			if options.DomainID != "" {
-				return createErr(ErrDomainIDWithUserID)
-			}
-			if options.DomainName != "" {
-				return createErr(ErrDomainNameWithUserID)
-			}
-
-			// Configure the request for UserID and Password authentication.
-			req.Auth.Identity.Password = &passwordReq{
-				User: userReq{ID: &options.UserID, Password: options.Password},
-			}
-		}
-	}
-
-	// Add a "scope" element if a Scope has been provided.
-	if scope != nil {
-		if scope.ProjectName != "" {
-			// ProjectName provided: either DomainID or DomainName must also be supplied.
-			// ProjectID may not be supplied.
-			if scope.DomainID == "" && scope.DomainName == "" {
-				return createErr(ErrScopeDomainIDOrDomainName)
-			}
-			if scope.ProjectID != "" {
-				return createErr(ErrScopeProjectIDOrProjectName)
-			}
-
-			if scope.DomainID != "" {
-				// ProjectName + DomainID
-				req.Auth.Scope = &scopeReq{
-					Project: &projectReq{
-						Name:   &scope.ProjectName,
-						Domain: &domainReq{ID: &scope.DomainID},
-					},
-				}
-			}
-
-			if scope.DomainName != "" {
-				// ProjectName + DomainName
-				req.Auth.Scope = &scopeReq{
-					Project: &projectReq{
-						Name:   &scope.ProjectName,
-						Domain: &domainReq{Name: &scope.DomainName},
-					},
-				}
-			}
-		} else if scope.ProjectID != "" {
-			// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
-			if scope.DomainID != "" {
-				return createErr(ErrScopeProjectIDAlone)
-			}
-			if scope.DomainName != "" {
-				return createErr(ErrScopeProjectIDAlone)
-			}
-
-			// ProjectID
-			req.Auth.Scope = &scopeReq{
-				Project: &projectReq{ID: &scope.ProjectID},
-			}
-		} else if scope.DomainID != "" {
-			// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
-			if scope.DomainName != "" {
-				return createErr(ErrScopeDomainIDOrDomainName)
-			}
-
-			// DomainID
-			req.Auth.Scope = &scopeReq{
-				Domain: &domainReq{ID: &scope.DomainID},
-			}
-		} else if scope.DomainName != "" {
-			return createErr(ErrScopeDomainName)
-		} else {
-			return createErr(ErrScopeEmpty)
-		}
-	}
-
-	var result CreateResult
-	var response *http.Response
-	response, result.Err = c.Post(tokenURL(c), req, &result.Body, nil)
-	if result.Err != nil {
-		return result
-	}
-	result.Header = response.Header
-	return result
+	return r
 }
 
 // Get validates and retrieves information about another token.
 func Get(c *gophercloud.ServiceClient, token string) GetResult {
-	var result GetResult
-	var response *http.Response
-	response, result.Err = c.Get(tokenURL(c), &result.Body, &gophercloud.RequestOpts{
+	var r GetResult
+	var resp *http.Response
+	resp, r.Err = c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
 		MoreHeaders: subjectTokenHeaders(c, token),
 		OkCodes:     []int{200, 203},
 	})
-	if result.Err != nil {
-		return result
+	if resp != nil {
+		r.Header = resp.Header
 	}
-	result.Header = response.Header
-	return result
+	return r
 }
 
 // Validate determines if a specified token is valid or not.
@@ -273,9 +64,9 @@
 
 // Revoke immediately makes specified token invalid.
 func Revoke(c *gophercloud.ServiceClient, token string) RevokeResult {
-	var res RevokeResult
-	_, res.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
+	var r RevokeResult
+	_, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
 		MoreHeaders: subjectTokenHeaders(c, token),
 	})
-	return res
+	return r
 }
diff --git a/openstack/identity/v3/tokens/requests_test.go b/openstack/identity/v3/tokens/requests_test.go
index 89d3b51..a39a6f4 100644
--- a/openstack/identity/v3/tokens/requests_test.go
+++ b/openstack/identity/v3/tokens/requests_test.go
@@ -11,7 +11,7 @@
 )
 
 // authTokenPost verifies that providing certain AuthOptions and Scope results in an expected JSON structure.
-func authTokenPost(t *testing.T, options gophercloud.AuthOptions, scope *Scope, requestJSON string) {
+func authTokenPost(t *testing.T, options AuthOptionsBuilder, scope *gophercloud.ScopeOptsV3, requestJSON string) {
 	testhelper.SetupHTTP()
 	defer testhelper.TeardownHTTP()
 
@@ -42,7 +42,7 @@
 	}
 }
 
-func authTokenPostErr(t *testing.T, options gophercloud.AuthOptions, scope *Scope, includeToken bool, expectedErr error) {
+func authTokenPostErr(t *testing.T, options AuthOptionsBuilder, scope *gophercloud.ScopeOptsV3, includeToken bool, expectedErr error) {
 	testhelper.SetupHTTP()
 	defer testhelper.TeardownHTTP()
 
@@ -64,7 +64,10 @@
 }
 
 func TestCreateUserIDAndPassword(t *testing.T) {
-	authTokenPost(t, gophercloud.AuthOptions{UserID: "me", Password: "squirrel!"}, nil, `
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "me"
+	ao.Password = "squirrel!"
+	authTokenPost(t, ao, nil, `
 		{
 			"auth": {
 				"identity": {
@@ -79,7 +82,10 @@
 }
 
 func TestCreateUsernameDomainIDPassword(t *testing.T) {
-	authTokenPost(t, gophercloud.AuthOptions{Username: "fakey", Password: "notpassword", DomainID: "abc123"}, nil, `
+	ao := gophercloud.AuthOptions{DomainID: "abc123"}
+	ao.Username = "fakey"
+	ao.Password = "notpassword"
+	authTokenPost(t, ao, nil, `
 		{
 			"auth": {
 				"identity": {
@@ -100,7 +106,10 @@
 }
 
 func TestCreateUsernameDomainNamePassword(t *testing.T) {
-	authTokenPost(t, gophercloud.AuthOptions{Username: "frank", Password: "swordfish", DomainName: "spork.net"}, nil, `
+	ao := gophercloud.AuthOptions{DomainName: "spork.net"}
+	ao.Username = "frank"
+	ao.Password = "swordfish"
+	authTokenPost(t, ao, nil, `
 		{
 			"auth": {
 				"identity": {
@@ -121,7 +130,7 @@
 }
 
 func TestCreateTokenID(t *testing.T) {
-	authTokenPost(t, gophercloud.AuthOptions{}, nil, `
+	authTokenPost(t, gophercloud.AuthOptions{TokenID: "12345abcdef"}, nil, `
 		{
 			"auth": {
 				"identity": {
@@ -136,9 +145,11 @@
 }
 
 func TestCreateProjectIDScope(t *testing.T) {
-	options := gophercloud.AuthOptions{UserID: "fenris", Password: "g0t0h311"}
-	scope := &Scope{ProjectID: "123456"}
-	authTokenPost(t, options, scope, `
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "fenris"
+	ao.Password = "g0t0h311"
+	scope := &gophercloud.ScopeOptsV3{ProjectID: "123456"}
+	authTokenPost(t, ao, scope, `
 		{
 			"auth": {
 				"identity": {
@@ -161,9 +172,11 @@
 }
 
 func TestCreateDomainIDScope(t *testing.T) {
-	options := gophercloud.AuthOptions{UserID: "fenris", Password: "g0t0h311"}
-	scope := &Scope{DomainID: "1000"}
-	authTokenPost(t, options, scope, `
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "fenris"
+	ao.Password = "g0t0h311"
+	scope := &gophercloud.ScopeOptsV3{DomainID: "1000"}
+	authTokenPost(t, ao, scope, `
 		{
 			"auth": {
 				"identity": {
@@ -186,9 +199,11 @@
 }
 
 func TestCreateProjectNameAndDomainIDScope(t *testing.T) {
-	options := gophercloud.AuthOptions{UserID: "fenris", Password: "g0t0h311"}
-	scope := &Scope{ProjectName: "world-domination", DomainID: "1000"}
-	authTokenPost(t, options, scope, `
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "fenris"
+	ao.Password = "g0t0h311"
+	scope := &gophercloud.ScopeOptsV3{ProjectName: "world-domination", DomainID: "1000"}
+	authTokenPost(t, ao, scope, `
 		{
 			"auth": {
 				"identity": {
@@ -214,9 +229,11 @@
 }
 
 func TestCreateProjectNameAndDomainNameScope(t *testing.T) {
-	options := gophercloud.AuthOptions{UserID: "fenris", Password: "g0t0h311"}
-	scope := &Scope{ProjectName: "world-domination", DomainName: "evil-plans"}
-	authTokenPost(t, options, scope, `
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "fenris"
+	ao.Password = "g0t0h311"
+	scope := &gophercloud.ScopeOptsV3{ProjectName: "world-domination", DomainName: "evil-plans"}
+	authTokenPost(t, ao, scope, `
 		{
 			"auth": {
 				"identity": {
@@ -261,8 +278,10 @@
 		}`)
 	})
 
-	options := gophercloud.AuthOptions{UserID: "me", Password: "shhh"}
-	token, err := Create(&client, options, nil).Extract()
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "me"
+	ao.Password = "shhh"
+	token, err := Create(&client, ao, nil).Extract()
 	if err != nil {
 		t.Fatalf("Create returned an error: %v", err)
 	}
@@ -276,20 +295,10 @@
 	authTokenPostErr(t, gophercloud.AuthOptions{}, nil, false, ErrMissingPassword)
 }
 
-func TestCreateFailureAPIKey(t *testing.T) {
-	authTokenPostErr(t, gophercloud.AuthOptions{APIKey: "something"}, nil, false, ErrAPIKeyProvided)
-}
-
-func TestCreateFailureTenantID(t *testing.T) {
-	authTokenPostErr(t, gophercloud.AuthOptions{TenantID: "something"}, nil, false, ErrTenantIDProvided)
-}
-
-func TestCreateFailureTenantName(t *testing.T) {
-	authTokenPostErr(t, gophercloud.AuthOptions{TenantName: "something"}, nil, false, ErrTenantNameProvided)
-}
-
 func TestCreateFailureTokenIDUsername(t *testing.T) {
-	authTokenPostErr(t, gophercloud.AuthOptions{Username: "something"}, nil, true, ErrUsernameWithToken)
+	ao := gophercloud.AuthOptions{}
+	ao.Username = "somthing"
+	authTokenPostErr(t, ao, nil, true, ErrUsernameWithToken)
 }
 
 func TestCreateFailureTokenIDUserID(t *testing.T) {
@@ -305,95 +314,105 @@
 }
 
 func TestCreateFailureMissingUser(t *testing.T) {
-	options := gophercloud.AuthOptions{Password: "supersecure"}
-	authTokenPostErr(t, options, nil, false, ErrUsernameOrUserID)
+	ao := gophercloud.AuthOptions{}
+	ao.Password = "supersecure"
+	authTokenPostErr(t, ao, nil, false, ErrUsernameOrUserID)
 }
 
 func TestCreateFailureBothUser(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Password: "supersecure",
-		Username: "oops",
-		UserID:   "redundancy",
-	}
-	authTokenPostErr(t, options, nil, false, ErrUsernameOrUserID)
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "redundancy"
+	ao.Username = "oops"
+	ao.Password = "supersecure"
+	authTokenPostErr(t, ao, nil, false, ErrUsernameOrUserID)
 }
 
 func TestCreateFailureMissingDomain(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Password: "supersecure",
-		Username: "notuniqueenough",
-	}
-	authTokenPostErr(t, options, nil, false, ErrDomainIDOrDomainName)
+	ao := gophercloud.AuthOptions{}
+	ao.Username = "notuniqueenough"
+	ao.Password = "supersecure"
+	authTokenPostErr(t, ao, nil, false, ErrDomainIDOrDomainName)
 }
 
 func TestCreateFailureBothDomain(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		Password:   "supersecure",
-		Username:   "someone",
-		DomainID:   "hurf",
-		DomainName: "durf",
-	}
-	authTokenPostErr(t, options, nil, false, ErrDomainIDOrDomainName)
+	ao := gophercloud.AuthOptions{}
+	ao.Username = "someone"
+	ao.Password = "supersecure"
+	ao.DomainID = "hurf"
+	ao.DomainName = "durf"
+	authTokenPostErr(t, ao, nil, false, ErrDomainIDOrDomainName)
 }
 
 func TestCreateFailureUserIDDomainID(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		UserID:   "100",
-		Password: "stuff",
-		DomainID: "oops",
-	}
-	authTokenPostErr(t, options, nil, false, ErrDomainIDWithUserID)
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "100"
+	ao.Password = "stuff"
+	ao.DomainID = "oops"
+	authTokenPostErr(t, ao, nil, false, ErrDomainIDWithUserID)
 }
 
 func TestCreateFailureUserIDDomainName(t *testing.T) {
-	options := gophercloud.AuthOptions{
-		UserID:     "100",
-		Password:   "sssh",
-		DomainName: "oops",
-	}
-	authTokenPostErr(t, options, nil, false, ErrDomainNameWithUserID)
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "100"
+	ao.Password = "sssh"
+	ao.DomainName = "oops"
+	authTokenPostErr(t, ao, nil, false, ErrDomainNameWithUserID)
 }
 
 func TestCreateFailureScopeProjectNameAlone(t *testing.T) {
-	options := gophercloud.AuthOptions{UserID: "myself", Password: "swordfish"}
-	scope := &Scope{ProjectName: "notenough"}
-	authTokenPostErr(t, options, scope, false, ErrScopeDomainIDOrDomainName)
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "myself"
+	ao.Password = "swordfish"
+	scope := &gophercloud.ScopeOptsV3{ProjectName: "notenough"}
+	authTokenPostErr(t, ao, scope, false, ErrScopeDomainIDOrDomainName)
 }
 
 func TestCreateFailureScopeProjectNameAndID(t *testing.T) {
-	options := gophercloud.AuthOptions{UserID: "myself", Password: "swordfish"}
-	scope := &Scope{ProjectName: "whoops", ProjectID: "toomuch", DomainID: "1234"}
-	authTokenPostErr(t, options, scope, false, ErrScopeProjectIDOrProjectName)
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "myself"
+	ao.Password = "swordfish"
+	scope := &gophercloud.ScopeOptsV3{ProjectName: "whoops", ProjectID: "toomuch", DomainID: "1234"}
+	authTokenPostErr(t, ao, scope, false, ErrScopeProjectIDOrProjectName)
 }
 
 func TestCreateFailureScopeProjectIDAndDomainID(t *testing.T) {
-	options := gophercloud.AuthOptions{UserID: "myself", Password: "swordfish"}
-	scope := &Scope{ProjectID: "toomuch", DomainID: "notneeded"}
-	authTokenPostErr(t, options, scope, false, ErrScopeProjectIDAlone)
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "myself"
+	ao.Password = "swordfish"
+	scope := &gophercloud.ScopeOptsV3{ProjectID: "toomuch", DomainID: "notneeded"}
+	authTokenPostErr(t, ao, scope, false, ErrScopeProjectIDAlone)
 }
 
 func TestCreateFailureScopeProjectIDAndDomainNAme(t *testing.T) {
-	options := gophercloud.AuthOptions{UserID: "myself", Password: "swordfish"}
-	scope := &Scope{ProjectID: "toomuch", DomainName: "notneeded"}
-	authTokenPostErr(t, options, scope, false, ErrScopeProjectIDAlone)
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "myself"
+	ao.Password = "swordfish"
+	scope := &gophercloud.ScopeOptsV3{ProjectID: "toomuch", DomainName: "notneeded"}
+	authTokenPostErr(t, ao, scope, false, ErrScopeProjectIDAlone)
 }
 
 func TestCreateFailureScopeDomainIDAndDomainName(t *testing.T) {
-	options := gophercloud.AuthOptions{UserID: "myself", Password: "swordfish"}
-	scope := &Scope{DomainID: "toomuch", DomainName: "notneeded"}
-	authTokenPostErr(t, options, scope, false, ErrScopeDomainIDOrDomainName)
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "myself"
+	ao.Password = "swordfish"
+	scope := &gophercloud.ScopeOptsV3{DomainID: "toomuch", DomainName: "notneeded"}
+	authTokenPostErr(t, ao, scope, false, ErrScopeDomainIDOrDomainName)
 }
 
 func TestCreateFailureScopeDomainNameAlone(t *testing.T) {
-	options := gophercloud.AuthOptions{UserID: "myself", Password: "swordfish"}
-	scope := &Scope{DomainName: "notenough"}
-	authTokenPostErr(t, options, scope, false, ErrScopeDomainName)
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "myself"
+	ao.Password = "swordfish"
+	scope := &gophercloud.ScopeOptsV3{DomainName: "notenough"}
+	authTokenPostErr(t, ao, scope, false, ErrScopeDomainName)
 }
 
 func TestCreateFailureEmptyScope(t *testing.T) {
-	options := gophercloud.AuthOptions{UserID: "myself", Password: "swordfish"}
-	scope := &Scope{}
-	authTokenPostErr(t, options, scope, false, ErrScopeEmpty)
+	ao := gophercloud.AuthOptions{}
+	ao.UserID = "myself"
+	ao.Password = "swordfish"
+	scope := &gophercloud.ScopeOptsV3{}
+	authTokenPostErr(t, ao, scope, false, ErrScopeEmpty)
 }
 
 func TestGetRequest(t *testing.T) {