diff --git a/redminer.pl b/redminer.pl
index 41c001c2754f1114bd9832fcbd7192db41bfcfc2..15a9b74afd7fa3405fd5007a1a8fb7830e18c820 100644
--- a/redminer.pl
+++ b/redminer.pl
@@ -4,9 +4,14 @@ use 5.010;
 use strict;
 use warnings;
 
+use FindBin;
+use lib "$FindBin::Bin/lib";
+
 use Getopt::Long;
 use Config::IniFiles;
 
+use RedMiner::API;
+
 my $conf_fname   = 'redminer.conf';
 my $layout_fname = 'layout.conf';
 my $project_id   = '';
@@ -42,8 +47,8 @@ if (!$layout) {
 
 my $redminer = RedMiner::API->new(
 	host => $conf->val('redmine', 'host') // '',
-#	user => $conf->val('redmine', 'user') // '',
-#	pass => $conf->val('redmine', 'pass') // '',
+	user => $conf->val('redmine', 'user') // '',
+	pass => $conf->val('redmine', 'pass') // '',
 	key  => $conf->val('redmine',  'key') // '',
 );
 
@@ -96,7 +101,7 @@ if ($perm_source) {
 			}
 
 			if ($new_membership->{user_id} && @{ $new_membership->{role_ids} }) {
-				$redminer->updateProjectMembership(
+				$redminer->createProjectMembership(
 					$project->{project}{id}, $new_membership
 				);
 			}
@@ -105,211 +110,3 @@ if ($perm_source) {
 }
 
 exit;
-
-#
-# Inline wrapper package for RedMine REST API
-#
-
-package RedMiner::API;
-
-# 2DO: fully implement project API
-# 2DO: fully implement issues API
-# 2DO: fully implement membership API
-
-use 5.010;
-use strict;
-use warnings;
-
-use LWP::UserAgent;
-use JSON::XS qw/encode_json decode_json/;
-use Encode   qw/decode/;
-
-=pod
-
-=encoding UTF-8
-
-=head1 RedMiner::API
-
-Wrapper package for RedMine REST API (http://www.redmine.org/projects/redmine/wiki/Rest_api).
-
-=cut
-
-sub new
-{
-	my $class = shift;
-	my %arg   = @_;
-	
-	my $self  = {
-		error    => '',
-		protocol => $arg{protocol} // 'http',
-		ua       => LWP::UserAgent->new,
-	};
-
-	foreach my $param (qw/host user pass key/) {
-		$self->{$param} = $arg{$param} // '';
-	}
-
-	if (length $self->{host} && $self->{host} =~ m|^(https?)://|i) {
-		$self->{protocol} = lc $1;
-		$self->{host}     =~ s/^https?://i;
-	} else {
-		$self->{protocol} = 'http' if $self->{protocol} !~ /^https?$/i;
-	}
-
-	my $auth = '';
-	if (!length $self->{key} && length $self->{user}) {
-		$auth = $self->{user};
-		if (length $self->{pass}) {
-			$auth .= ':' . $self->{pass};
-		}
-		$auth .= '@';
-	}
-	$self->{uri} = "$self->{protocol}://$auth$self->{host}";
-
-	$self->{ua}->default_header('Content-Type' => 'application/json');
-	
-	if (length $self->{key}) {
-		$self->{ua}->default_header('X-Redmine-API-Key' => $self->{key});
-	}
-
-	bless $self, $class;
-}
-
-sub error       { $_[0]->{error} }
-sub _set_error  { $_[0]->{error} = $_[1] // ''; return; }
-
-sub _set_arg_error
-{
-	my $self  = shift;
-	my $error = shift;
-
-	$self->{raw_response} = '';
-	$self->{raw_content}  = '';
-
-	return $self->_set_error($error);
-}
-
-sub rawResponse { $_[0]->{raw_response} // '' }
-sub rawContent  { $_[0]->{raw_content}  // '' }
-
-sub _request
-{
-	my $self   = shift;
-	my $method = shift // return $self->_set_arg_error('Request method missing');
-	my $path   = shift // return $self->_set_arg_error('Request path missing');
-	my $data   = shift;
-
-	if ($method !~ /^(?:GET|POST|PUT|DELETE)$/) {
-		$method = 'GET';
-	}
-
-	$self->_set_error;
-
-	my $request = HTTP::Request->new(
-		$method, sprintf('%s/%s.json', $self->{uri}, $path)
-	);
-
-	if ($method ne 'GET' && defined $data) {
-		my $json = eval { Encode::decode('UTF-8', JSON::XS::encode_json($data)) };
-		if ($@) {
-			return $self->_set_arg_error('Malformed input data:' . $@);
-		}
-		$request->header('Content-Length' => length $json);
-		$request->content($json);
-	}
-
-	return $request;
-}
-
-sub _response
-{
-	my $self     = shift;
-	my $request  = shift // return;
-	my $response = $self->{ua}->request($request);
-
-	$self->{raw_response} = $response->as_string;
-	$self->{raw_content}  = $response->content;
-
-	if (!$response->is_success) {
-		# FIXME: decode into error object
-		return $self->_set_error($response->status_line);
-	}
-
-	return eval {
-		JSON::XS::decode_json($response->decoded_content)
-	} // $self->_set_error($@);
-}
-
-sub createProject
-{
-	my $self = shift;
-	my $data = shift;
-	$self->_response(
-		$self->_request('POST', 'projects', { project => $data })
-	);
-}
-
-# TESTME
-sub project
-{
-	my $self       = shift;
-	my $project_id = shift // return $self->_set_arg_error('Incorrect project ID');
-	$self->_response(
-		$self->_request('GET', 'projects/' . $project_id)
-	);
-}
-
-# FIXME: implement handling of limit+offset+total_count parameters
-# TESTME
-sub projects
-{
-	my $self = shift;
-	$self->_response(
-		$self->_request('GET', 'projects')
-	);
-}
-
-# Undocumented: parent_id, inherit_members
-sub updateProject
-{
-	my $self       = shift;
-	my $project_id = shift // return $self->_set_arg_error('Incorrect project ID');
-	my $data       = shift;
-	$self->_response(
-		$self->_request('PUT', 'projects/' . $project_id, { project => $data })
-	);
-}
-
-# TESTME
-sub deleteProject
-{
-	my $self       = shift;
-	my $project_id = shift // return $self->_set_arg_error('Incorrect project ID');
-	$self->_response(
-		$self->_request('DELETE', 'projects/' . $project_id)
-	);
-}
-
-# FIXME: implement handling of limit+offset+total_count parameters
-sub projectMemberships
-{
-	my $self       = shift;
-	my $project_id = shift // return $self->_set_arg_error('Incorrect project ID');
-	$self->_response(
-		$self->_request('GET', 'projects/' . $project_id . '/memberships')
-	);
-}
-
-# FIXME: set*, not update*
-# Setting membership for a group: group_id
-sub updateProjectMembership
-{
-	my $self       = shift;
-	my $project_id = shift // return $self->_set_arg_error('Incorrect project ID');
-	my $data       = shift;
-	$self->_response(
-		$self->_request('POST', 'projects/' . $project_id . '/memberships', { membership => $data })
-	);
-}
-
-1;