diff --git a/authentication.go b/authentication.go
new file mode 100644
index 0000000..e8ba0db
--- /dev/null
+++ b/authentication.go
@@ -0,0 +1,66 @@
+package jira
+
+import (
+	"fmt"
+)
+
+// AuthenticationService handles authentication for the JIRA instance / API.
+//
+// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#authentication
+type AuthenticationService struct {
+	client *Client
+}
+
+// Session represents a Session JSON response by the JIRA API.
+type Session struct {
+	Self    string `json:"self,omitempty"`
+	Name    string `json:"name,omitempty"`
+	Session struct {
+		Name  string `json:"name"`
+		Value string `json:"value"`
+	} `json:"session,omitempty"`
+	LoginInfo struct {
+		FailedLoginCount    int    `json:"failedLoginCount"`
+		LoginCount          int    `json:"loginCount"`
+		LastFailedLoginTime string `json:"lastFailedLoginTime"`
+		PreviousLoginTime   string `json:"previousLoginTime"`
+	} `json:"loginInfo"`
+}
+
+// AcquireSessionCookie creates a new session for a user in JIRA.
+// Once a session has been successfully created it can be used to access any of JIRA's remote APIs and also the web UI by passing the appropriate HTTP Cookie header.
+// The header will by automatically applied to every API request.
+// Note that it is generally preferrable to use HTTP BASIC authentication with the REST API.
+// However, this resource may be used to mimic the behaviour of JIRA's log-in page (e.g. to display log-in errors to a user).
+//
+// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#d2e459
+func (s *AuthenticationService) AcquireSessionCookie(username, password string) (bool, error) {
+	apiEndpoint := "rest/auth/1/session"
+	body := struct {
+		Username string `json:"username"`
+		Password string `json:"password"`
+	}{
+		username,
+		password,
+	}
+
+	req, err := s.client.NewRequest("POST", apiEndpoint, body)
+	if err != nil {
+		return false, err
+	}
+
+	session := new(Session)
+	resp, err := s.client.Do(req, session)
+	if resp.StatusCode != 200 || err != nil {
+		return false, fmt.Errorf("Auth at JIRA instance failed (HTTP(S) request). %s", err)
+	}
+
+	s.client.session = session
+
+	return true, nil
+}
+
+// TODO Missing API Call GET (Returns information about the currently authenticated user's session)
+// See https://docs.atlassian.com/jira/REST/latest/#d2e456
+// TODO Missing API Call DELETE (Logs the current user out of JIRA, destroying the existing session, if any.)
+// https://docs.atlassian.com/jira/REST/latest/#d2e456
diff --git a/errors.go b/errors.go
new file mode 100644
index 0000000..5c5a316
--- /dev/null
+++ b/errors.go
@@ -0,0 +1,19 @@
+package jira
+
+import (
+	"fmt"
+	"net/http"
+)
+
+// ErrorResponse reports one or more errors caused by an API request.
+type ErrorResponse struct {
+	Response      *http.Response    // HTTP response that caused this error
+	ErrorMessages []string          `json:"errorMessages,omitempty"`
+	Errors        map[string]string `json:"errors,omitempty"`
+}
+
+func (r *ErrorResponse) Error() string {
+	return fmt.Sprintf("%v %v: %d %v %+v",
+		r.Response.Request.Method, r.Response.Request.URL,
+		r.Response.StatusCode, r.ErrorMessages, r.Errors)
+}
diff --git a/issue.go b/issue.go
new file mode 100644
index 0000000..dad4a40
--- /dev/null
+++ b/issue.go
@@ -0,0 +1,250 @@
+package jira
+
+import (
+	"fmt"
+	"net/http"
+)
+
+const (
+	// AssigneeAutomatic represents the value of the "Assignee: Automatic" of JIRA
+	AssigneeAutomatic = "-1"
+)
+
+// IssueService handles Issues for the JIRA instance / API.
+//
+// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#d2e2279
+type IssueService struct {
+	client *Client
+}
+
+// Issue represents a JIRA issue.
+type Issue struct {
+	Expand string       `json:"expand,omitempty"`
+	ID     string       `json:"id,omitempty"`
+	Self   string       `json:"self,omitempty"`
+	Key    string       `json:"key,omitempty"`
+	Fields *IssueFields `json:"fields,omitempty"`
+}
+
+// IssueFields represents single fields of a JIRA issue.
+// Every JIRA issue has several fields attached.
+type IssueFields struct {
+	// TODO Missing fields
+	//	* "timespent": null,
+	//	* "fixVersions": [],
+	//	* "aggregatetimespent": null,
+	//	* "workratio": -1,
+	//	* "lastViewed": null,
+	//	* "labels": [],
+	//	* "timeestimate": null,
+	//	* "aggregatetimeoriginalestimate": null,
+	//	* "timeoriginalestimate": null,
+	//	* "timetracking": {},
+	//	* "attachment": [],
+	//	* "aggregatetimeestimate": null,
+	//	* "subtasks": [],
+	//	* "environment": null,
+	//	* "duedate": null,
+	IssueType         IssueType    `json:"issuetype"`
+	Project           Project      `json:"project"`
+	Resolution        *Resolution  `json:"resolution,omitempty"`
+	Priority          *Priority    `json:"priority,omitempty"`
+	Resolutiondate    string       `json:"resolutiondate,omitempty"`
+	Created           string       `json:"created,omitempty"`
+	Watches           *Watches     `json:"watches,omitempty"`
+	Assignee          *Assignee    `json:"assignee"`
+	Updated           string       `json:"updated,omitempty"`
+	Description       string       `json:"description"`
+	Summary           string       `json:"summary"`
+	Creator           *Assignee    `json:"Creator,omitempty"`
+	Reporter          *Assignee    `json:"reporter,omitempty"`
+	Components        []*Component `json:"components,omitempty"`
+	Status            *Status      `json:"status,omitempty"`
+	Progress          *Progress    `json:"progress,omitempty"`
+	AggregateProgress *Progress    `json:"aggregateprogress,omitempty"`
+	Worklog           *Worklog     `json:"worklog,omitempty"`
+	IssueLinks        []*IssueLink `json:"issuelinks,omitempty"`
+	Comments          *CommentList `json:"comment,omitempty"`
+}
+
+// IssueType represents a type of a JIRA issue.
+// Typical types are "Request", "Bug", "Story", ...
+type IssueType struct {
+	Self        string `json:"self,omitempty"`
+	ID          string `json:"id,omitempty"`
+	Description string `json:"description,omitempty"`
+	IconURL     string `json:"iconUrl,omitempty"`
+	Name        string `json:"name,omitempty"`
+	Subtask     bool   `json:"subtask,omitempty"`
+}
+
+// Project represents a JIRA Project.
+type Project struct {
+	Self       string            `json:"self,omitempty"`
+	ID         string            `json:"id,omitempty"`
+	Key        string            `json:"key,omitempty"`
+	Name       string            `json:"name,omitempty"`
+	AvatarURLs map[string]string `json:"avatarUrls,omitempty"`
+}
+
+// Resolution represents a resolution of a JIRA issue.
+// Typical types are "Fixed", "Suspended", "Won't Fix", ...
+type Resolution struct {
+	Self        string `json:"self"`
+	ID          string `json:"id"`
+	Description string `json:"description"`
+	Name        string `json:"name"`
+}
+
+// Priority represents a priority of a JIRA issue.
+// Typical types are "Normal", "Moderate", "Urgent", ...
+type Priority struct {
+	Self    string `json:"self,omitempty"`
+	IconURL string `json:"iconUrl,omitempty"`
+	Name    string `json:"name,omitempty"`
+	ID      string `json:"id,omitempty"`
+}
+
+// Watches represents a type of how many user are "observing" a JIRA issue to track the status / updates.
+type Watches struct {
+	Self       string `json:"self,omitempty"`
+	WatchCount int    `json:"watchCount,omitempty"`
+	IsWatching bool   `json:"isWatching,omitempty"`
+}
+
+// Assignee represents a user who is this JIRA issue assigned to.
+type Assignee struct {
+	Self         string            `json:"self,omitempty"`
+	Name         string            `json:"name,omitempty"`
+	EmailAddress string            `json:"emailAddress,omitempty"`
+	AvatarURLs   map[string]string `json:"avatarUrls,omitempty"`
+	DisplayName  string            `json:"displayName,omitempty"`
+	Active       bool              `json:"active,omitempty"`
+}
+
+// Component represents a "component" of a JIRA issue.
+// Components can be user defined in every JIRA instance.
+type Component struct {
+	Self string `json:"self,omitempty"`
+	ID   string `json:"id,omitempty"`
+	Name string `json:"name,omitempty"`
+}
+
+// Status represents the current status of a JIRA issue.
+// Typical status are "Open", "In Progress", "Closed", ...
+// Status can be user defined in every JIRA instance.
+type Status struct {
+	Self           string         `json:"self"`
+	Description    string         `json:"description"`
+	IconURL        string         `json:"iconUrl"`
+	Name           string         `json:"name"`
+	ID             string         `json:"id"`
+	StatusCategory StatusCategory `json:"statusCategory"`
+}
+
+// StatusCategory represents the category a status belongs to.
+// Those categories can be user defined in every JIRA instance.
+type StatusCategory struct {
+	Self      string `json:"self"`
+	ID        int    `json:"id"`
+	Name      string `json:"name"`
+	Key       string `json:"key"`
+	ColorName string `json:"colorName"`
+}
+
+// Progress represents the progress of a JIRA issue.
+type Progress struct {
+	Progress int `json:"progress"`
+	Total    int `json:"total"`
+}
+
+// Worklog represents the work log of a JIRA issue.
+// JIRA Wiki: https://confluence.atlassian.com/jira/logging-work-on-an-issue-185729605.html
+type Worklog struct {
+	// Missing fields
+	//	* "worklogs": []
+	StartAt    int `json:"startAt"`
+	MaxResults int `json:"maxResults"`
+	Total      int `json:"total"`
+}
+
+// IssueLink represents a link between two issues in JIRA.
+type IssueLink struct {
+	ID           string        `json:"id"`
+	Self         string        `json:"self"`
+	Type         IssueLinkType `json:"type"`
+	OutwardIssue Issue         `json:"outwardIssue"`
+	InwardIssue  Issue         `json:"inwardIssue"`
+}
+
+// IssueLinkType represents a type of a link between to issues in JIRA.
+// Typical issue link types are "Related to", "Duplicate", "Is blocked by", etc.
+type IssueLinkType struct {
+	ID      string `json:"id"`
+	Self    string `json:"self"`
+	Name    string `json:"name"`
+	Inward  string `json:"inward"`
+	Outward string `json:"outward"`
+}
+
+// Comment represents a comment by a person to an issue in JIRA.
+type Comment struct {
+	Self         string   `json:"self"`
+	Name         string   `json:"name"`
+	Author       Assignee `json:"author"`
+	Body         string   `json:"body"`
+	UpdateAuthor Assignee `json:"updateAuthor"`
+	Updated      string   `json:"updated"`
+	Created      string   `json:"created"`
+}
+
+// CommentList represents a list of comments by various persons of an issue in JIRA.
+type CommentList struct {
+	StartAt    int       `json:"startAt"`
+	MaxResults int       `json:"maxResults"`
+	Total      int       `json:"total"`
+	Comments   []Comment `json:"comments"`
+}
+
+// Get returns a full representation of the issue for the given issue key.
+// JIRA will attempt to identify the issue by the issueIdOrKey path parameter.
+// This can be an issue id, or an issue key.
+// If the issue cannot be found via an exact match, JIRA will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved.
+//
+// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#d2e2609
+func (s *IssueService) Get(issueID string) (*Issue, *http.Response, error) {
+	apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID)
+	req, err := s.client.NewRequest("GET", apiEndpoint, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	issue := new(Issue)
+	resp, err := s.client.Do(req, issue)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return issue, resp, nil
+}
+
+// Create creates an issue or a sub-task from a JSON representation.
+// Creating a sub-task is similar to creating a regular issue, with two important differences:
+// The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue.
+//
+// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#d2e2280
+func (s *IssueService) Create(issue *Issue) (*Issue, *http.Response, error) {
+	apiEndpoint := "rest/api/2/issue/"
+	req, err := s.client.NewRequest("POST", apiEndpoint, issue)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	responseIssue := new(Issue)
+	resp, err := s.client.Do(req, responseIssue)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return responseIssue, resp, nil
+}
diff --git a/jira.go b/jira.go
new file mode 100644
index 0000000..5d7ddb8
--- /dev/null
+++ b/jira.go
@@ -0,0 +1,131 @@
+package jira
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+)
+
+// A Client manages communication with the JIRA API.
+type Client struct {
+	// HTTP client used to communicate with the API.
+	client *http.Client
+
+	// Base URL for API requests.
+	baseURL *url.URL
+
+	// Session storage if the user authentificate with a Session cookie
+	session *Session
+
+	// Services used for talking to different parts of the JIRA API.
+	Authentication *AuthenticationService
+	Issue          *IssueService
+}
+
+// NewClient returns a new JIRA API client.
+// If a nil httpClient is provided, http.DefaultClient will be used.
+// To use API methods which require authentication you can follow the prefered solution and
+// provide an http.Client that will perform the authentication for you with OAuth and HTTP Basic (such as that provided by the golang.org/x/oauth2 library).
+// As an alternative you can use Session Cookie based authentication provided by this package as well.
+// See https://docs.atlassian.com/jira/REST/latest/#authentication
+// baseURL is the HTTP endpoint of your JIRA instance and should always be specified with a trailing slash.
+func NewClient(httpClient *http.Client, baseURL string) (*Client, error) {
+	if httpClient == nil {
+		httpClient = http.DefaultClient
+	}
+
+	parsedBaseURL, err := url.Parse(baseURL)
+	if err != nil {
+		return nil, err
+	}
+
+	c := &Client{
+		client:  httpClient,
+		baseURL: parsedBaseURL,
+	}
+	c.Authentication = &AuthenticationService{client: c}
+	c.Issue = &IssueService{client: c}
+
+	return c, nil
+}
+
+// NewRequest creates an API request.
+// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client.
+// Relative URLs should always be specified without a preceding slash.
+// If specified, the value pointed to by body is JSON encoded and included as the request body.
+func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
+	rel, err := url.Parse(urlStr)
+	if err != nil {
+		return nil, err
+	}
+
+	u := c.baseURL.ResolveReference(rel)
+
+	var buf io.ReadWriter
+	if body != nil {
+		buf = new(bytes.Buffer)
+		err := json.NewEncoder(buf).Encode(body)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	req, err := http.NewRequest(method, u.String(), buf)
+	if err != nil {
+		return nil, err
+	}
+
+	req.Header.Set("Content-Type", "application/json")
+
+	// Set session cookie if there is one
+	if c.session != nil {
+		req.Header.Set("Cookie", fmt.Sprintf("%s=%s", c.session.Session.Name, c.session.Session.Value))
+	}
+
+	return req, nil
+}
+
+// Do sends an API request and returns the API response.
+// The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred.
+func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
+	resp, err := c.client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	defer resp.Body.Close()
+
+	err = CheckResponse(resp)
+	if err != nil {
+		// Even though there was an error, we still return the response
+		// in case the caller wants to inspect it further
+		return resp, err
+	}
+
+	if v != nil {
+		err = json.NewDecoder(resp.Body).Decode(v)
+	}
+
+	return resp, err
+}
+
+// CheckResponse checks the API response for errors, and returns them if present.
+// A response is considered an error if it has a status code outside the 200 range.
+// API error responses are expected to have either no response body, or a JSON response body that maps to ErrorResponse.
+// Any other response body will be silently ignored.
+func CheckResponse(r *http.Response) error {
+	if c := r.StatusCode; 200 <= c && c <= 299 {
+		return nil
+	}
+
+	errorResponse := &ErrorResponse{Response: r}
+	data, err := ioutil.ReadAll(r.Body)
+	if err == nil && data != nil {
+		json.Unmarshal(data, errorResponse)
+	}
+	return errorResponse
+}