Index: shorturl.module
===================================================================
--- shorturl.module	(revision 50)
+++ shorturl.module	(working copy)
@@ -39,7 +39,7 @@
 * hook_perm implementation
 */ 
 function shorturl_perm() {
-  return array('administer shorturl');
+  return array('administer shorturl', 'use shorturl');
 }
 
 /**
@@ -57,6 +57,13 @@
     'type' => MENU_NORMAL_ITEM,
   );
 
+  $items['v1/shorten'] = array(
+    'page callback' => 'shorturl_rest_shorten',
+    'access callback' => TRUE,
+    'file' => 'shorturl.rest.inc',
+    'type' => MENU_CALLBACK,
+  );
+
   return $items;
 }
 
Index: shorturl.rest.inc
===================================================================
--- shorturl.rest.inc	(revision 0)
+++ shorturl.rest.inc	(revision 0)
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * Menu callback
+ */
+function shorturl_rest_shorten() {
+  foreach (array('username', 'password', 'uri') as $varname) {
+    if (empty($_GET[$varname])) {
+      shorturl_rest_respond(500, "Missing $varname argument");
+    }
+  }
+  if (!valid_url($_GET['uri'], TRUE)) {
+    shorturl_rest_respond(500, 'Invalid URI');
+  }
+  if (!$account = user_load(array('name' => $_GET['username'], 'pass' => $_GET['password']))) {
+    shorturl_rest_respond(401, 'Invalid credentials');
+  }
+  if (!user_access('use shorturl', $account)) {
+    shorturl_rest_respond(403, 'Permission denied');
+  }
+  shorturl_rest_respond(200, 'OK', array(
+    'url' => shorturl_shorten($_REQUEST['uri'], TRUE),
+    'long_url' => $_REQUEST['uri'],
+  ));
+}
+
+/**
+ * Send REST response to the user
+ */
+function shorturl_rest_respond($code, $txt, $data = array()) {
+  switch ($code) {
+    case 500:
+      header('HTTP/1.0 500 Internal Server Error');
+      break;
+    case 401:
+      header('HTTP/1.0 401 Unauthorized');
+      break;
+    case 403:
+      header('HTTP/1.0 403 Forbidden');
+      break;
+  }
+  $format = empty($_GET['format']) ? 'json' : $_GET['format'];
+  switch ($format) {
+    case 'txt':
+      header('Content-Type: text/plain');
+      print $code == 200 ? $data['url'] : $txt;
+      break;
+    case 'xml':
+      header('Content-Type: text/xml');
+      print format_xml_elements(array(
+        'response' => array(
+          'status_code' => $code,
+          'status_txt' => $txt,
+          'data' => $data,
+        ),
+      ));
+      break;
+    default:
+      header('Content-Type: application/json');
+      print drupal_json(array(
+        'status_code' => $code,
+        'status_txt' => $txt,
+        'data' => $data,
+      ));
+      break;
+  }
+  module_invoke_all('exit');
+  exit;
+}
