The first identity v3 request.
diff --git a/openstack/identity/v3/client.go b/openstack/identity/v3/client.go
new file mode 100644
index 0000000..164958f
--- /dev/null
+++ b/openstack/identity/v3/client.go
@@ -0,0 +1,24 @@
+package v3
+
+import "github.com/rackspace/gophercloud"
+
+// Client abstracts the connection information necessary to make API calls to Identity v3
+// resources.
+type Client struct {
+	gophercloud.ServiceClient
+
+	// TokenID is redudant storage for an active token.
+	// The Identity service occasionally needs to access the assigned token directly, but I don't want to export it from all
+	// service clients unless we absolutely need to.
+	TokenID string
+}
+
+var (
+	nilClient = Client{}
+)
+
+// NewClient attempts to authenticate to the v3 identity endpoint. Returns a populated
+// IdentityV3Client on success or an error on failure.
+func NewClient(authOptions gophercloud.AuthOptions) (*Client, error) {
+	return &nilClient, nil
+}
diff --git a/openstack/identity/v3/doc.go b/openstack/identity/v3/doc.go
new file mode 100644
index 0000000..8a9335a
--- /dev/null
+++ b/openstack/identity/v3/doc.go
@@ -0,0 +1,4 @@
+/*
+Package v3 implements v3 of the OpenStack identity API.
+*/
+package v3
diff --git a/openstack/identity/v3/tokens/requests.go b/openstack/identity/v3/tokens/requests.go
new file mode 100644
index 0000000..625f8d7
--- /dev/null
+++ b/openstack/identity/v3/tokens/requests.go
@@ -0,0 +1,317 @@
+package tokens
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/racker/perigee"
+	"github.com/rackspace/gophercloud"
+	identity "github.com/rackspace/gophercloud/openstack/identity/v3"
+)
+
+func unacceptedAttributeErr(attribute string) error {
+	return fmt.Errorf("The base Identity V3 API does not accept authentication by %s", attribute)
+}
+
+func redundantWithTokenErr(attribute string) error {
+	return fmt.Errorf("%s may not be provided when authenticating with a TokenID", attribute)
+}
+
+func redundantWithUserID(attribute string) error {
+	return fmt.Errorf("%s may not be provided when authenticating with a UserID", attribute)
+}
+
+var (
+	// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
+	ErrAPIKeyProvided = unacceptedAttributeErr("APIKey")
+
+	// ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
+	ErrTenantIDProvided = unacceptedAttributeErr("TenantID")
+
+	// ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
+	ErrTenantNameProvided = unacceptedAttributeErr("TenantName")
+
+	// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
+	ErrUsernameWithToken = redundantWithTokenErr("Username")
+
+	// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
+	ErrUserIDWithToken = redundantWithTokenErr("UserID")
+
+	// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
+	ErrDomainIDWithToken = redundantWithTokenErr("DomainID")
+
+	// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
+	ErrDomainNameWithToken = redundantWithTokenErr("DomainName")
+
+	// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
+	ErrUsernameOrUserID = errors.New("Exactly one of Username and UserID must be provided for password authentication")
+
+	// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used.
+	ErrDomainIDWithUserID = redundantWithUserID("DomainID")
+
+	// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
+	ErrDomainNameWithUserID = redundantWithUserID("DomainName")
+
+	// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it.
+	// It may also indicate that both a DomainID and a DomainName were provided at once.
+	ErrDomainIDOrDomainName = errors.New("You must provide exactly one of DomainID or DomainName to authenticate by Username")
+
+	// ErrMissingPassword indicates that no password was provided and no token is available.
+	ErrMissingPassword = errors.New("You must provide a password to authenticate")
+
+	// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
+	ErrScopeDomainIDOrDomainName = errors.New("You must provide exactly one of DomainID or DomainName in a Scope with ProjectName")
+
+	// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope.
+	ErrScopeProjectIDOrProjectName = errors.New("You must provide at most one of ProjectID or ProjectName in a Scope")
+
+	// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope.
+	ErrScopeProjectIDAlone = errors.New("ProjectID must be supplied alone in a Scope")
+
+	// ErrScopeDomainName indicates that a DomainName was provided alone in a Scope.
+	ErrScopeDomainName = errors.New("DomainName must be supplied with a ProjectName or ProjectID in a Scope.")
+
+	// ErrScopeEmpty indicates that no credentials were provided in a Scope.
+	ErrScopeEmpty = errors.New("You must provide either a Project or Domain in a Scope")
+)
+
+// TokenCreateResult contains the document structure returned from a Create call.
+type TokenCreateResult map[string]interface{}
+
+// GetTokenID retrieves a token generated by a Create call from an token creation response.
+func (r TokenCreateResult) GetTokenID() (string, error) {
+	return "", nil
+}
+
+// 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
+}
+
+// Create authenticates and generates a new token.
+func Create(c *identity.Client, ao gophercloud.AuthOptions, scope *Scope) (gophercloud.AuthResults, error) {
+	type domainReq struct {
+		ID   *string `json:"id,omitempty"`
+		Name *string `json:"id,omitempty"`
+	}
+
+	type projectReq struct {
+		Domain *domainReq `json:"domain,omitempty"`
+		Name   *string    `json:"name,omitempty"`
+		ID     *string    `json:"id,omitempty"`
+	}
+
+	type userReq struct {
+		ID       *string    `json:"id,omitempty"`
+		Name     *string    `json:"name,omitempty"`
+		Password string     `json:"password"`
+		Domain   *domainReq `json:"domain"`
+	}
+
+	type passwordReq struct {
+		User userReq `json:"user"`
+	}
+
+	type tokenReq struct {
+		ID string `json:"id"`
+	}
+
+	type identityReq struct {
+		Methods  []string     `json:"methods"`
+		Password *passwordReq `json:"token,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"`
+	}
+
+	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 ao.APIKey != "" {
+		return nil, ErrAPIKeyProvided
+	}
+	if ao.TenantID != "" {
+		return nil, ErrTenantIDProvided
+	}
+	if ao.TenantName != "" {
+		return nil, ErrTenantNameProvided
+	}
+
+	if ao.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 ao.Username != "" {
+				return nil, ErrUsernameWithToken
+			}
+			if ao.UserID != "" {
+				return nil, ErrUserIDWithToken
+			}
+			if ao.DomainID != "" {
+				return nil, ErrDomainIDWithToken
+			}
+			if ao.DomainName != "" {
+				return nil, 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 nil, ErrMissingPassword
+		}
+	} else {
+		// Password authentication.
+		req.Auth.Identity.Methods = []string{"password"}
+
+		// At least one of Username and UserID must be specified.
+		if ao.Username == "" && ao.UserID == "" {
+			return nil, ErrUsernameOrUserID
+		}
+
+		if ao.Username != "" {
+			// If Username is provided, UserID may not be provided.
+			if ao.UserID != "" {
+				return nil, ErrUsernameOrUserID
+			}
+
+			// Either DomainID or DomainName must also be specified.
+			if ao.DomainID == "" && ao.DomainName == "" {
+				return nil, ErrDomainIDOrDomainName
+			}
+
+			if ao.DomainID != "" {
+				if ao.DomainName != "" {
+					return nil, ErrDomainIDOrDomainName
+				}
+
+				// Configure the request for Username and Password authentication with a DomainID.
+				req.Auth.Identity.Password = &passwordReq{
+					User: userReq{
+						Name:     &ao.Username,
+						Password: ao.Password,
+						Domain:   &domainReq{ID: &ao.DomainID},
+					},
+				}
+			}
+
+			if ao.DomainName != "" {
+				// Configure the request for Username and Password authentication with a DomainName.
+				req.Auth.Identity.Password = &passwordReq{
+					User: userReq{
+						Name:     &ao.Username,
+						Password: ao.Password,
+						Domain:   &domainReq{Name: &ao.DomainName},
+					},
+				}
+			}
+		}
+
+		if ao.UserID != "" {
+			// If UserID is specified, neither DomainID nor DomainName may be.
+			if ao.DomainID != "" {
+				return nil, ErrDomainIDWithUserID
+			}
+			if ao.DomainName != "" {
+				return nil, ErrDomainNameWithUserID
+			}
+
+			// Configure the request for UserID and Password authentication.
+			req.Auth.Identity.Password = &passwordReq{
+				User: userReq{ID: &ao.UserID},
+			}
+		}
+	}
+
+	// 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 nil, ErrScopeDomainIDOrDomainName
+			}
+			if scope.ProjectID != "" {
+				return nil, 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.ProjectName != "" {
+				return nil, ErrScopeProjectIDOrProjectName
+			}
+			if scope.DomainID != "" {
+				return nil, ErrScopeProjectIDAlone
+			}
+			if scope.DomainName != "" {
+				return nil, 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 nil, ErrScopeDomainIDOrDomainName
+			}
+
+			// DomainID
+			req.Auth.Scope = &scopeReq{
+				Domain: &domainReq{ID: &scope.DomainID},
+			}
+		} else if scope.DomainName != "" {
+			return nil, ErrScopeDomainName
+		} else {
+			return nil, ErrScopeEmpty
+		}
+	}
+
+	var resp TokenCreateResult
+	perigee.Post(getTokenURL(c), perigee.Options{
+		ReqBody: &req,
+		Results: &resp,
+		OkCodes: []int{201},
+	})
+	return &resp, nil
+}
diff --git a/openstack/identity/v3/tokens/urls.go b/openstack/identity/v3/tokens/urls.go
new file mode 100644
index 0000000..2c100ab
--- /dev/null
+++ b/openstack/identity/v3/tokens/urls.go
@@ -0,0 +1,7 @@
+package tokens
+
+import identity "github.com/rackspace/gophercloud/openstack/identity/v3"
+
+func getTokenURL(c *identity.Client) string {
+	return c.ServiceURL("auth", "tokens")
+}