Moving calls to client helper while I'm at it
diff --git a/_site/openstack/identity/v3/services/doc.go b/_site/openstack/identity/v3/services/doc.go
new file mode 100644
index 0000000..c4772c0
--- /dev/null
+++ b/_site/openstack/identity/v3/services/doc.go
@@ -0,0 +1,4 @@
+/*
+Package services queries and manages the service catalog.
+*/
+package services
diff --git a/_site/openstack/identity/v3/services/requests.go b/_site/openstack/identity/v3/services/requests.go
new file mode 100644
index 0000000..7816aca
--- /dev/null
+++ b/_site/openstack/identity/v3/services/requests.go
@@ -0,0 +1,99 @@
+package services
+
+import (
+	"strconv"
+
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/openstack/utils"
+	"github.com/rackspace/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"`
+	}
+
+	req := request{Type: serviceType}
+
+	var result CreateResult
+	_, result.Err = perigee.Request("POST", listURL(client), perigee.Options{
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		ReqBody:     &req,
+		Results:     &result.Resp,
+		OkCodes:     []int{201},
+	})
+	return result
+}
+
+// ListOpts allows you to query the List method.
+type ListOpts struct {
+	ServiceType string
+	PerPage     int
+	Page        int
+}
+
+// List enumerates the services available to a specific user.
+func List(client *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
+	q := make(map[string]string)
+	if opts.ServiceType != "" {
+		q["type"] = opts.ServiceType
+	}
+	if opts.Page != 0 {
+		q["page"] = strconv.Itoa(opts.Page)
+	}
+	if opts.PerPage != 0 {
+		q["perPage"] = strconv.Itoa(opts.PerPage)
+	}
+	u := listURL(client) + utils.BuildQuery(q)
+
+	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
+		return ServicePage{pagination.LinkedPageBase{LastHTTPResponse: r}}
+	}
+
+	return pagination.NewPager(client, u, createPage)
+}
+
+// Get returns additional information about a service, given its ID.
+func Get(client *gophercloud.ServiceClient, serviceID string) GetResult {
+	var result GetResult
+	_, result.Err = perigee.Request("GET", serviceURL(client, serviceID), perigee.Options{
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		Results:     &result.Resp,
+		OkCodes:     []int{200},
+	})
+	return result
+}
+
+// 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 = perigee.Request("PATCH", serviceURL(client, serviceID), perigee.Options{
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		ReqBody:     &req,
+		Results:     &result.Resp,
+		OkCodes:     []int{200},
+	})
+	return result
+}
+
+// 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) error {
+	_, err := perigee.Request("DELETE", serviceURL(client, serviceID), perigee.Options{
+		MoreHeaders: client.Provider.AuthenticatedHeaders(),
+		OkCodes:     []int{204},
+	})
+	return err
+}
diff --git a/_site/openstack/identity/v3/services/requests_test.go b/_site/openstack/identity/v3/services/requests_test.go
new file mode 100644
index 0000000..a3d345b
--- /dev/null
+++ b/_site/openstack/identity/v3/services/requests_test.go
@@ -0,0 +1,232 @@
+package services
+
+import (
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+	"github.com/rackspace/gophercloud/testhelper"
+)
+
+const tokenID = "111111"
+
+func serviceClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{
+		Provider: &gophercloud.ProviderClient{
+			TokenID: tokenID,
+		},
+		Endpoint: testhelper.Endpoint(),
+	}
+}
+
+func TestCreateSuccessful(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "POST")
+		testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+		testhelper.TestJSONRequest(t, r, `{ "type": "compute" }`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusCreated)
+		fmt.Fprintf(w, `{
+        "service": {
+          "description": "Here's your service",
+          "id": "1234",
+          "name": "InscrutableOpenStackProjectName",
+          "type": "compute"
+        }
+    }`)
+	})
+
+	client := serviceClient()
+
+	result, err := Create(client, "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)
+	}
+}
+
+func TestListSinglePage(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.TeardownHTTP()
+
+	testhelper.Mux.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
+		testhelper.TestMethod(t, r, "GET")
+		testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		fmt.Fprintf(w, `
+			{
+				"links": {
+					"next": null,
+					"previous": null
+				},
+				"services": [
+					{
+						"description": "Service One",
+						"id": "1234",
+						"name": "service-one",
+						"type": "identity"
+					},
+					{
+						"description": "Service Two",
+						"id": "9876",
+						"name": "service-two",
+						"type": "compute"
+					}
+				]
+			}
+		`)
+	})
+
+	client := serviceClient()
+
+	count := 0
+	err := List(client, ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+		count++
+		actual, err := ExtractServices(page)
+		if err != nil {
+			return false, err
+		}
+
+		desc0 := "Service One"
+		desc1 := "Service Two"
+		expected := []Service{
+			Service{
+				Description: &desc0,
+				ID:          "1234",
+				Name:        "service-one",
+				Type:        "identity",
+			},
+			Service{
+				Description: &desc1,
+				ID:          "9876",
+				Name:        "service-two",
+				Type:        "compute",
+			},
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Expected %#v, got %#v", 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)
+	}
+}
+
+func TestGetSuccessful(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.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", tokenID)
+
+		w.Header().Add("Content-Type", "application/json")
+		fmt.Fprintf(w, `
+			{
+				"service": {
+						"description": "Service One",
+						"id": "12345",
+						"name": "service-one",
+						"type": "identity"
+				}
+			}
+		`)
+	})
+
+	client := serviceClient()
+
+	result, err := Get(client, "12345").Extract()
+	if err != nil {
+		t.Fatalf("Error fetching service information: %v", err)
+	}
+
+	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)
+	}
+}
+
+func TestUpdateSuccessful(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.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", tokenID)
+		testhelper.TestJSONRequest(t, r, `{ "type": "lasermagic" }`)
+
+		w.Header().Add("Content-Type", "application/json")
+		fmt.Fprintf(w, `
+			{
+				"service": {
+						"id": "12345",
+						"type": "lasermagic"
+				}
+			}
+		`)
+	})
+
+	client := serviceClient()
+
+	result, err := Update(client, "12345", "lasermagic").Extract()
+	if err != nil {
+		t.Fatalf("Unable to update service: %v", err)
+	}
+
+	if result.ID != "12345" {
+		t.Fatalf("Expected ID 12345, was %s", result.ID)
+	}
+}
+
+func TestDeleteSuccessful(t *testing.T) {
+	testhelper.SetupHTTP()
+	defer testhelper.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", tokenID)
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	client := serviceClient()
+
+	err := Delete(client, "12345")
+	if err != nil {
+		t.Fatalf("Unable to delete service: %v", err)
+	}
+}
diff --git a/_site/openstack/identity/v3/services/results.go b/_site/openstack/identity/v3/services/results.go
new file mode 100644
index 0000000..e4e068b
--- /dev/null
+++ b/_site/openstack/identity/v3/services/results.go
@@ -0,0 +1,75 @@
+package services
+
+import (
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/pagination"
+
+	"github.com/mitchellh/mapstructure"
+)
+
+type commonResult struct {
+	gophercloud.CommonResult
+}
+
+// Extract interprets a GetResult, CreateResult or UpdateResult as a concrete Service.
+// An error is returned if the original call or the extraction failed.
+func (r commonResult) Extract() (*Service, error) {
+	if r.Err != nil {
+		return nil, r.Err
+	}
+
+	var res struct {
+		Service `json:"service"`
+	}
+
+	err := mapstructure.Decode(r.Resp, &res)
+
+	return &res.Service, err
+}
+
+// CreateResult is the deferred result of a Create call.
+type CreateResult struct {
+	commonResult
+}
+
+// GetResult is the deferred result of a Get call.
+type GetResult struct {
+	commonResult
+}
+
+// UpdateResult is the deferred result of an Update call.
+type UpdateResult struct {
+	commonResult
+}
+
+// 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"`
+}
+
+// ServicePage is a single page of Service results.
+type ServicePage struct {
+	pagination.LinkedPageBase
+}
+
+// IsEmpty returns true if the page contains no results.
+func (p ServicePage) IsEmpty() (bool, error) {
+	services, err := ExtractServices(p)
+	if err != nil {
+		return true, err
+	}
+	return len(services) == 0, nil
+}
+
+// ExtractServices extracts a slice of Services from a Collection acquired from List.
+func ExtractServices(page pagination.Page) ([]Service, error) {
+	var response struct {
+		Services []Service `mapstructure:"services"`
+	}
+
+	err := mapstructure.Decode(page.(ServicePage).Body, &response)
+	return response.Services, err
+}
diff --git a/_site/openstack/identity/v3/services/urls.go b/_site/openstack/identity/v3/services/urls.go
new file mode 100644
index 0000000..85443a4
--- /dev/null
+++ b/_site/openstack/identity/v3/services/urls.go
@@ -0,0 +1,11 @@
+package services
+
+import "github.com/rackspace/gophercloud"
+
+func listURL(client *gophercloud.ServiceClient) string {
+	return client.ServiceURL("services")
+}
+
+func serviceURL(client *gophercloud.ServiceClient, serviceID string) string {
+	return client.ServiceURL("services", serviceID)
+}
diff --git a/_site/openstack/identity/v3/services/urls_test.go b/_site/openstack/identity/v3/services/urls_test.go
new file mode 100644
index 0000000..5a31b32
--- /dev/null
+++ b/_site/openstack/identity/v3/services/urls_test.go
@@ -0,0 +1,23 @@
+package services
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+)
+
+func TestListURL(t *testing.T) {
+	client := gophercloud.ServiceClient{Endpoint: "http://localhost:5000/v3/"}
+	url := listURL(&client)
+	if url != "http://localhost:5000/v3/services" {
+		t.Errorf("Unexpected list URL generated: [%s]", url)
+	}
+}
+
+func TestServiceURL(t *testing.T) {
+	client := gophercloud.ServiceClient{Endpoint: "http://localhost:5000/v3/"}
+	url := serviceURL(&client, "1234")
+	if url != "http://localhost:5000/v3/services/1234" {
+		t.Errorf("Unexpected service URL generated: [%s]", url)
+	}
+}