diff --git a/lib/RedMiner/API.pm b/lib/RedMiner/API.pm
index 11cfdaad2c4c204faa0d594b8ea7b98bf9b0e41b..13fac40211353715aea84fc11554a72fe1d87382 100644
--- a/lib/RedMiner/API.pm
+++ b/lib/RedMiner/API.pm
@@ -4,7 +4,7 @@ use 5.010;
 use strict;
 use warnings;
 
-our $VERSION = '0.02';
+our $VERSION = '0.03';
 
 use URI;
 use URI::QueryParam;
@@ -155,7 +155,7 @@ sub _dispatch_name
 	my $name = shift // return $self->_set_client_error('Undefined method name');
 	my @args = @_;
 
-	my ($action, $objects) = ($name =~ /^(get|read|create|update|delete)?(.+?)$/);
+	my ($action, $objects) = ($name =~ /^(get|read|create|update|delete)?([A-Za-z]+?)$/);
 	
 	if (!$action || $action eq 'read') {
 		$action = 'get';
@@ -164,7 +164,6 @@ sub _dispatch_name
 		return $self->_set_client_error("Malformed method name '$name'");
 	}
 
-	$objects   = ucfirst $objects;
 	my %METHOD = (
 		get    => 'GET'   ,
 		create => 'POST'  ,
@@ -188,45 +187,43 @@ sub _dispatch_name
 		# If last argument is an array/hash reference, treat it as a request body:
 		if (ref $args[-1] ne 'ARRAY' && ref $args[-1] ne 'HASH') {
 			return $self->_set_client_error(
-				'No data provided for create/update query'
+				'No data provided for a create/update method'
 			);
 		}
 		$data->{content} = pop @args;
 	}
 
+	$objects = $self->_normalize_objects($objects);
+
 	my $i = 0;
 	my @objects;
 	while ($objects =~ /([A-Z][a-z]+)/g) {
-		my $object   = lc $1;
-		my $category = $object;
-		
-		# If an object is singular, pluralize to make its category name: user -> users
-		if ($object !~ /s$/) {
-			$category .= 's';
-		}
+		my $object   = $self->_object($1);
+		my $category = $self->_category($object);
 		
 		push @objects, $category;
 
+		next if $object eq $category;
+
 		# We need to attach an object ID to the path if an object is singular and
 		# we either perform anything but creation or we create a new object inside
 		# another object (createProjectMembership)
-		if ($object !~ /s$/) {
-			if ($action ne 'create' || pos($objects) != length($objects)) {
-				my $object_id = $args[$i++];
+		if ($action ne 'create' || pos($objects) != length($objects)) {
+			my $object_id = $args[$i++];
 
-				return $self->_set_client_error(
-					sprintf 'Incorrect object ID for %s in query %s', $object, $name
-				) if !defined $object_id || ref \$object_id ne 'SCALAR';
+			return $self->_set_client_error(
+				sprintf 'Incorrect object ID for %s in query %s', $object, $name
+			) if !defined $object_id || ref \$object_id ne 'SCALAR';
 
-				push @objects, $object_id;
-			}
-			if (defined $data->{content} && pos($objects) == length($objects)) {
-				# Add wrapping object, if necessary:
-				if (!exists $data->{content}{$object}) {
-					$data->{content} = {
-						$object => $data->{content}
-					};
-				}
+			push @objects, $object_id;
+		}
+
+		# Add wrapping object, if necessary:
+		if (defined $data->{content} && pos($objects) == length($objects)) {
+			if (!exists $data->{content}{$object}) {
+				$data->{content} = {
+					$object => $data->{content}
+				};
 			}
 		}
 	}
@@ -236,6 +233,58 @@ sub _dispatch_name
 	return $data;
 }
 
+sub _normalize_objects
+{
+	my $self    = shift;
+	my $objects = shift;
+
+	$objects = ucfirst $objects;
+	# These are token that for a *single* entry in the resulting request path,
+	# e.g.: PUT /time_entries/1.json
+	# But it is natural to spell them like this:
+	# $api->updateTimeEntry(1, { ... });
+	$objects =~ s/TimeEntr/Timeentr/g;
+	$objects =~ s/IssueCategor/Issuecategor/g;
+	$objects =~ s/IssueStatus/Issuestatus/g;
+	$objects =~ s/CustomField/Customfield/g;
+
+	return $objects;
+}
+
+sub _object
+{
+	my $self   = shift;
+	my $object = lc(shift);
+	
+	# Process compound words:
+	$object =~ s/timeentr/time_entr/ig;
+	$object =~ s/issue(categor|status)/issue_$1/ig;
+	$object =~ s/customfield/custom_field/ig;
+	
+	return $object;
+}
+
+# If an object is singular, pluralize to make its category name: user -> users
+sub _category
+{
+	my $self   = shift;
+	my $object = shift;
+
+	my $category = $object;
+
+	if ($category !~ /s$/ || $category =~ /us$/) {
+		if ($object =~ /y$/) {
+			$category =~ s/y$/ies/;
+		} elsif ($category =~ /us$/) {
+			$category .= 'es';
+		} else {
+			$category .= 's';
+		}
+	}
+
+	return $category;
+}
+
 =head1 SEE ALSO
 
 RedMine::API: http://search.cpan.org/~celogeek/Redmine-API-0.04/
diff --git a/t/01-dispatching.t b/t/01-dispatching.t
index 1da677c74adadaec32ee844c9ef825f2396ff42a..0b6907b7e82c5ff28ac99cc1ffabfc86609ab9ea 100644
--- a/t/01-dispatching.t
+++ b/t/01-dispatching.t
@@ -1,12 +1,12 @@
 use strict;
 use warnings;
 
-use Test::More;
+use Test::More tests => 30;
 
 BEGIN { use_ok('RedMiner::API') };
 
 #
-# Tests for internal dispatching mechanizm
+# Tests for internal name dispatching
 #
 
 my $redminer = RedMiner::API->new(
@@ -154,6 +154,84 @@ is_deeply($r, {
 	query   => undef,
 }, 'createProjectMembership');
 
-done_testing;
+$r = $redminer->_dispatch_name('createIssueWatcher', 1, { user_id => 1 });
+is_deeply($r, {
+	method => 'POST',
+	path   => 'issues/1/watchers',
+	content => { watcher => { user_id => 1 } },
+	query   => undef,
+}, 'createIssueWatcher');
+
+$r = $redminer->_dispatch_name('deleteIssueWatcher', 1, 42);
+is_deeply($r, {
+	method => 'DELETE',
+	path   => 'issues/1/watchers/42',
+	content => undef,
+	query   => undef,
+}, 'deleteIssueWatcher');
+
+#
+# Dispatching methods with compound object names
+#
+
+$r = $redminer->_dispatch_name('timeEntries', { limit => 10, offset => 9 });
+is_deeply($r, {
+	method => 'GET',
+	path   => 'time_entries',
+	content => undef,
+	query   => { limit => 10, offset => 9 },
+}, 'timeEntries');
+
+$r = $redminer->_dispatch_name('timeEntry', 1);
+is_deeply($r, {
+	method => 'GET',
+	path   => 'time_entries/1',
+	content => undef,
+	query   => undef,
+}, 'timeEntry');
+
+$r = $redminer->_dispatch_name('createTimeEntry', { issue_id => 42, hours => 1 });
+is_deeply($r, {
+	method => 'POST',
+	path   => 'time_entries',
+	content => { time_entry => { issue_id => 42, hours => 1 } },
+	query   => undef,
+}, 'createTimeEntry');
+
+$r = $redminer->_dispatch_name('updateTimeEntry', 1, { issue_id => 42, hours => 1 });
+is_deeply($r, {
+	method => 'PUT',
+	path   => 'time_entries/1',
+	content => { time_entry => { issue_id => 42, hours => 1 } },
+	query   => undef,
+}, 'updateTimeEntry');
+
+$r = $redminer->_dispatch_name('deleteTimeEntry', 1);
+is_deeply($r, {
+	method => 'DELETE',
+	path   => 'time_entries/1',
+	content => undef,
+	query   => undef,
+}, 'deleteTimeEntry');
+
+#
+# Dispatching methods with more than 1 identifying object *and* compound object names:
+#
+
+$r = $redminer->_dispatch_name('projectIssueCategories', 1, { limit => 10, offset => 9 });
+is_deeply($r, {
+	method => 'GET',
+	path   => 'projects/1/issue_categories',
+	content => undef,
+	query   => { limit => 10, offset => 9 },
+}, 'projectIssueCategories');
+
+$r = $redminer->_dispatch_name('createProjectIssueCategory', 1, { name => 'My Category', assign_to_id => 1 });
+is_deeply($r, {
+	method => 'POST',
+	path   => 'projects/1/issue_categories',
+	content => { issue_category => { name => 'My Category', assign_to_id => 1 } },
+	query   => undef,
+}, 'projectIssueCategories');
 
 exit;