diff --git a/Gemfile b/Gemfile
index d4b3ce302f0ac71fa0e5098046d68901700e96bf..530f06b159d122c6c1833001f05e0cddf6e119b7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -18,6 +18,7 @@ gem 'yaml_db',       :git => "https://github.com/gitlabhq/yaml_db.git"
 gem 'grack',         :git => "https://github.com/gitlabhq/grack.git"
 gem "linguist", "~> 1.0.0", :git => "https://github.com/gitlabhq/linguist.git"
 
+gem "grape"
 gem "stamp"
 gem "kaminari"
 gem "haml-rails"
diff --git a/Gemfile.lock b/Gemfile.lock
index 6287b2d4d11efb35062488da297cbeedbe46446f..c3a45b59b6e6578c5026db5aee9437f1cf15ad0a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -169,6 +169,12 @@ GEM
     gherkin (2.11.0)
       json (>= 1.4.6)
     git (1.2.5)
+    grape (0.2.0)
+      hashie (~> 1.2)
+      multi_json
+      multi_xml
+      rack
+      rack-mount
     haml (3.1.6)
     haml-rails (0.3.4)
       actionpack (~> 3.0)
@@ -230,6 +236,8 @@ GEM
     rack (1.4.1)
     rack-cache (1.2)
       rack (>= 0.4)
+    rack-mount (0.8.3)
+      rack (>= 1.0.0)
     rack-protection (1.2.0)
       rack
     rack-ssl (1.3.2)
@@ -381,6 +389,7 @@ DEPENDENCIES
   git
   gitolite!
   grack!
+  grape
   grit!
   haml-rails
   httparty
diff --git a/config/routes.rb b/config/routes.rb
index b1bd5a7b2d67c2f8c9364d092c7524b62fd84875..454b3cd290f0bdfa546de8bebf6ad53576c52804 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -4,6 +4,10 @@ Gitlab::Application.routes.draw do
   #
   get 'search' => "search#show"
 
+  # API
+  require 'api'
+  mount Gitlab::API => '/api'
+
   # Optionally, enable Resque here
   require 'resque/server'
   mount Resque::Server.new, at: '/info/resque'
diff --git a/lib/api.rb b/lib/api.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4fdc3273a0e319fd6cf02e1abca5b46507b1b939
--- /dev/null
+++ b/lib/api.rb
@@ -0,0 +1,61 @@
+require 'api/entities'
+require 'api/helpers'
+
+module Gitlab
+  class API < Grape::API
+    format :json
+    helpers APIHelpers
+
+    # Users API
+    resource :users do
+      before { authenticate! }
+
+      # GET /users
+      get do
+        @users = User.all
+        present @users, :with => Entities::User
+      end
+
+      # GET /users/:id
+      get ":id" do
+        @user = User.find(params[:id])
+        present @user, :with => Entities::User
+      end
+    end
+
+    # GET /user
+    get "/user" do
+      authenticate!
+      present @current_user, :with => Entities::User
+    end
+
+    # Projects API
+    resource :projects do
+      before { authenticate! }
+
+      # GET /projects
+      get do
+        @projects = current_user.projects
+        present @projects, :with => Entities::Project
+      end
+
+      # GET /projects/:id
+      get ":id" do
+        @project = current_user.projects.find_by_code(params[:id])
+        present @project, :with => Entities::Project
+      end
+
+      # GET /projects/:id/repository/branches
+      get ":id/repository/branches" do
+        @project = current_user.projects.find_by_code(params[:id])
+        present @project.repo.heads.sort_by(&:name), :with => Entities::ProjectRepositoryBranches
+      end
+
+      # GET /projects/:id/repository/tags
+      get ":id/repository/tags" do
+        @project = current_user.projects.find_by_code(params[:id])
+        present @project.repo.tags.sort_by(&:name).reverse, :with => Entities::ProjectRepositoryTags
+      end
+    end
+  end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
new file mode 100644
index 0000000000000000000000000000000000000000..44a439856020b31765484721dccb220b50835edc
--- /dev/null
+++ b/lib/api/entities.rb
@@ -0,0 +1,23 @@
+module Gitlab
+  module Entities
+    class User < Grape::Entity
+      expose :id, :email, :name, :bio, :skype, :linkedin, :twitter,
+             :dark_scheme, :theme_id, :blocked, :created_at
+    end
+
+    class Project < Grape::Entity
+      expose :id, :code, :name, :description, :path, :default_branch
+      expose :owner, :using => Entities::User
+      expose :private_flag, :as => :private
+      expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at
+    end
+
+    class ProjectRepositoryBranches < Grape::Entity
+      expose :name, :commit
+    end
+
+    class ProjectRepositoryTags < Grape::Entity
+      expose :name, :commit
+    end
+  end
+end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cd2e39bf3a714306ff4d9b43d306ac70677d8624
--- /dev/null
+++ b/lib/api/helpers.rb
@@ -0,0 +1,11 @@
+module Gitlab
+  module APIHelpers
+    def current_user
+      @current_user ||= User.find_by_authentication_token(params[:private_token])
+    end
+
+    def authenticate!
+      error!('401 Unauthorized', 401) unless current_user
+    end
+  end
+end
diff --git a/spec/api/projects_spec.rb b/spec/api/projects_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e4835736b8cd5338e7571a0f76a34517750ffa4b
--- /dev/null
+++ b/spec/api/projects_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Gitlab::API do
+  let(:user) { Factory :user }
+  let!(:project) { Factory :project, :owner => user }
+  before { project.add_access(user, :read) }
+
+  describe "GET /projects" do
+    it "should return authentication error" do
+      get "/api/projects"
+      response.status.should == 401
+    end
+
+    describe "authenticated GET /projects" do
+      it "should return an array of projects" do
+        get "/api/projects?private_token=#{user.private_token}"
+        response.status.should == 200
+        json = JSON.parse(response.body)
+        json.should be_an Array
+        json.first['name'].should == project.name
+        json.first['owner']['email'].should == user.email
+      end
+    end
+  end
+
+  describe "GET /projects/:id" do
+    it "should return a project by id" do
+      get "/api/projects/#{project.code}?private_token=#{user.private_token}"
+      response.status.should == 200
+      json = JSON.parse(response.body)
+      json['name'].should == project.name
+      json['owner']['email'].should == user.email
+    end
+  end
+
+  describe "GET /projects/:id/repository/branches" do
+    it "should return an array of project branches" do
+      get "/api/projects/#{project.code}/repository/branches?private_token=#{user.private_token}"
+      response.status.should == 200
+      json = JSON.parse(response.body)
+      json.should be_an Array
+      json.first['name'].should == project.repo.heads.sort_by(&:name).first.name
+    end
+  end
+
+  describe "GET /projects/:id/repository/tags" do
+    it "should return an array of project tags" do
+      get "/api/projects/#{project.code}/repository/tags?private_token=#{user.private_token}"
+      response.status.should == 200
+      json = JSON.parse(response.body)
+      json.should be_an Array
+      json.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name
+    end
+  end
+end
diff --git a/spec/api/users_spec.rb b/spec/api/users_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f142ac637ec84fc638abebb0dd7c3832289ff4b0
--- /dev/null
+++ b/spec/api/users_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Gitlab::API do
+  let(:user) { Factory :user }
+
+  describe "GET /users" do
+    it "should return authentication error" do
+      get "/api/users"
+      response.status.should == 401
+    end
+
+    describe "authenticated GET /users" do
+      it "should return an array of users" do
+        get "/api/users?private_token=#{user.private_token}"
+        response.status.should == 200
+        json = JSON.parse(response.body)
+        json.should be_an Array
+        json.first['email'].should == user.email
+      end
+    end
+  end
+
+  describe "GET /users/:id" do
+    it "should return a user by id" do
+      get "/api/users/#{user.id}?private_token=#{user.private_token}"
+      response.status.should == 200
+      JSON.parse(response.body)['email'].should == user.email
+    end
+  end
+
+  describe "GET /user" do
+    it "should return current user" do
+      get "/api/user?private_token=#{user.private_token}"
+      response.status.should == 200
+      JSON.parse(response.body)['email'].should == user.email
+    end
+  end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 5556798f5111a357dc60bf1b3472c86d766c4110..65e7e529a5bc2f459934ac25e02b7982f00f07c6 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -58,4 +58,8 @@ RSpec.configure do |config|
   config.after do
     DatabaseCleaner.clean
   end
+
+  config.include RSpec::Rails::RequestExampleGroup, :type => :request, :example_group => {
+    :file_path => /spec\/api/
+  }
 end