diff --git a/api/.htaccess b/api/.htaccess
index e73e2eb3b9c3f1204223b4426274817c0200e279..f460420d6d2f55f362fb420788b6751afe38bbba 100644
--- a/api/.htaccess
+++ b/api/.htaccess
@@ -1,8 +1,11 @@
-RewriteEngine On
+<IfModule mod_rewrite.c>
 
-RewriteBase /api/
+RewriteEngine On
 
 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteCond %{REQUEST_FILENAME} !-d
+RewriteCond %{REQUEST_URI} (.*/api)
+
+RewriteRule ^(.*)$ %1/http.php/$1 [L]
 
-RewriteRule ^(.*)$ http.php/$1 [L]
+</IfModule>
diff --git a/include/api.ticket.php b/include/api.ticket.php
index 4fcae4b183925dfe32bb91c95275a986fc492ea6..51e129ad41e5cfe691517fbdb1a180326ecec4f7 100644
--- a/include/api.ticket.php
+++ b/include/api.ticket.php
@@ -15,7 +15,7 @@ class TicketController extends ApiController {
             "attachments" => array("*" => 
                 array("name", "type", "data", "encoding")
             ), 
-            "message", "ip"
+            "message", "ip", "priorityId"
         );
         if ($format == "xml") return array("ticket" => $supported);
         else return $supported;
@@ -43,7 +43,7 @@ class TicketController extends ApiController {
                 if (!($info["data"] = base64_decode($info["data"], true)))
                     Http::response(400, sprintf(
                         "%s: Poorly encoded base64 data",
-                        $filename));
+                        $info['name']));
             }
             $info['size'] = strlen($info['data']);
         }
diff --git a/include/class.api.php b/include/class.api.php
index 1bd25463484a4e2f01a1d72050dde4e92d89d967..f3d3094a5b695a1601cb0d91e5dafdfe6014918b 100644
--- a/include/class.api.php
+++ b/include/class.api.php
@@ -184,7 +184,7 @@ class ApiController {
     function validate($data, $structure, $prefix="") {
         foreach ($data as $key=>$info) {
             if (is_array($structure) and is_array($info)) {
-                $search = isset($structure[$key]) ? $key : "*"; 
+                $search = (isset($structure[$key]) && !is_numeric($key)) ? $key : "*"; 
                 if (isset($structure[$search])) {
                     $this->validate($info, $structure[$search], "$prefix$key/");
                     continue;
@@ -219,16 +219,21 @@ class ApiXmlDataParser extends XmlDataParser {
             } else if ($key == "autorespond") {
                 $value = (bool)$value;
             } else if ($key == "attachments") {
-                foreach ($value as &$info) { 
-                    $info["data"] = $info[":text"]; 
-                    unset($info[":text"]);
+                if(!isset($value['file'][':text']))
+                    $value = $value['file'];
+
+                if($value && is_array($value)) {
+                    foreach ($value as &$info) { 
+                        $info["data"] = $info[":text"]; 
+                        unset($info[":text"]);
+                    }
+                    unset($info);
                 }
-                unset($info);
-            }
-            if (is_array($value)) {
+            } else if(is_array($value)) {
                 $value = $this->fixup($value);
             }
         }
+
         return $current;
     }
 }
@@ -255,7 +260,7 @@ class ApiJsonDataParser extends JsonDataParser {
                     # PHP5: fopen("data://$data[5:]");
                     if (substr($data, 0, 5) != "data:") {
                         $info = array(
-                            "data" => $data, 
+                            "data" => $data,
                             "type" => "text/plain",
                             "name" => key($info));
                     } else {
@@ -264,11 +269,17 @@ class ApiJsonDataParser extends JsonDataParser {
                         list($type, $extra) = explode(";", $meta);
                         $info = array(
                             "data" => $contents,
-                            "type" => $type,
+                            "type" => ($type) ? $type : "text/plain",
                             "name" => key($info));
                         if (substr($extra, -6) == "base64")
                             $info["encoding"] = "base64";
-                        # TODO: Handle 'charset' hint in $extra
+                        # Handle 'charset' hint in $extra, such as
+                        # data:text/plain;charset=iso-8859-1,Blah
+                        # Convert to utf-8 since it's the encoding scheme
+                        # for the database. Otherwise, assume utf-8
+                        list($param,$charset) = explode('=', $extra);
+                        if ($param == 'charset' && function_exists('iconv'))
+                            $contents = iconv($charset, "UTF-8", $contents);
                     }
                 }
                 unset($value);
diff --git a/include/class.xml.php b/include/class.xml.php
index 854f182372c94f57338b8b45a94d155f69f5bd33..56baf4fbccaf65985921e081afd8e5f475c95c07 100644
--- a/include/class.xml.php
+++ b/include/class.xml.php
@@ -77,10 +77,14 @@ class XmlDataParser {
         $this->content = array_pop($this->stack);
         $i = 1;
         if (array_key_exists($name, $this->content)) {
-            while (array_key_exists("$name$i", $this->content)) $i++;
-            $name = "$name$i";
-        }
-        $this->content[$name] = $prev;
+            if(!isset($this->content[$name][0])) {
+                $current = $this->content[$name];
+                unset($this->content[$name]);
+                $this->content[$name][0] = $current;
+            }
+            $this->content[$name][] = $prev;
+        } else
+            $this->content[$name] = $prev;
     }
 
     function content($parser, $data) {
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 59964d4e61561ba78bc704e3e6ddb2d1a73af964..67e6212512c0bf617d934fbf1feabd369784fd80 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -311,6 +311,7 @@ a.Icon:hover {
 .Icon.webTicket { background:url(../images/icons/ticket_source_web.gif) 0 0 no-repeat; }
 .Icon.emailTicket { background:url(../images/icons/ticket_source_email.gif) 0 0 no-repeat; }
 .Icon.phoneTicket { background:url(../images/icons/ticket_source_phone.gif) 0 0 no-repeat; }
+.Icon.apiTicket { background:url(../images/icons/ticket_source_other.gif) 0 0 no-repeat; }
 .Icon.otherTicket { background:url(../images/icons/ticket_source_other.gif) 0 0 no-repeat; }
 .Icon.overdueTicket { background:url(../images/icons/overdue_ticket.gif) 0 0 no-repeat; }
 .Icon.assignedTicket { background:url(../images/icons/assigned_ticket.gif) 0 0 no-repeat; }
diff --git a/setup/doc/api.md b/setup/doc/api.md
new file mode 100644
index 0000000000000000000000000000000000000000..7509616aa2406b842f56cacd7a6c0693b23415ca
--- /dev/null
+++ b/setup/doc/api.md
@@ -0,0 +1,29 @@
+osTicket API
+============
+
+The osTicket API is implemented as (somewhat) simple XML or JSON over HTTP.
+For now, only ticket creation is supported, but eventually, all resources
+inside osTicket will be accessible and modifiable via the API.
+
+Authentication
+--------------
+
+Authentication via the API is done via API keys configured inside the
+osTicket admin panel. API keys are created and tied to a source IP address,
+which will be checked against the source IP of requests to the HTTP API.
+
+API keys can be created and managed via the admin panel. Navigate to Manage
+-> API keys. Use *Add New API Key* to create a new API key. Currently, no
+special configuration is required to allow the API key to be used for the
+HTTP API. All API keys are valid for the HTTP API.
+
+Wrappers
+--------
+
+Currently, there are no wrappers for the API. If you've written one and
+would like it on the list, submit a pull request to add your wrapper.
+
+Resources
+---------
+
+- [Tickets](api/tickets.md)
diff --git a/setup/doc/api/tickets.md b/setup/doc/api/tickets.md
new file mode 100644
index 0000000000000000000000000000000000000000..fc79b1aead833a9f4cf9fd3a214635dcba9e52fc
--- /dev/null
+++ b/setup/doc/api/tickets.md
@@ -0,0 +1,128 @@
+Tickets
+=======
+The API supports ticket creation via the HTTP API (as well as via email,
+etc.). Currently, the API support creation of tickets only -- so no
+modifications and deletions of existing tickets is possible via the API for
+now.
+
+Create a Ticket
+---------------
+
+Tickets can be created in the osTicket system by sending an HTTP POST to
+`api/tickets.xml` or `api/tickets.json` depending on the format of the
+request content.
+
+### Fields ######
+
+*   __email__:   *required* Email address of the submitter
+*   __name__:    *required* Name of the submitter
+*   __subject__: *required* Subject of the ticket
+*   __message__: *required* Initial message for the ticket thread
+*   __alert__:       If unset, disable alerts to staff. Default is `true`
+*   __autorespond__: If unset, disable autoresponses. Default is `true`
+*   __ip__:          IP address of the submitter
+*   __phone__:       Phone number of the submitter
+*   __phone_ext__:   Phone number extension -- can also be embedded in *phone*
+*   __priorityId__:  Priority *id* for the new ticket to assume
+*   __source__:      Source of the ticket, default is `API`
+*   __topicId__:     Help topic *id* associated with the ticket
+*   __attachments__: An array of files to attach to the initial message.
+                     Each attachment must have some content and also the
+                     following fields:
+    *   __name__:     *required* name of the file to be attached. Multiple files
+                      with the same name are allowable
+    *   __type__:     Mime type of the file. Default is `text/plain`
+    *   __encoding__: Set to `base64` if content is base64 encoded 
+
+### XML Payload Example ######
+
+* `POST /api/tickets.xml`
+
+The XML data format is extremely lax. Content can be either sent as an
+attribute or a named element if it has no sub-content.
+
+In the example below, the simple element could also be replaced as
+attributes on the root `<ticket>` element; however, if a `CDATA` element is
+necessary to hold special content, or difficult content such as double
+quotes is to be embedded, simple sub-elements are also supported.
+
+Notice that the phone extension can be sent as the `@ext` attribute of the
+`phone` sub-element.
+
+``` xml
+<?xml version="1.0" encoding="UTF-8"?>
+<ticket alert="true" autorespond="true" source="API">
+    <name>Angry User</name>
+    <email>api@osticket.com</email>
+    <subject>Testing API</subject>
+    <phone ext="123">318-555-8634</phone>
+    <message><![CDATA[Message content here]]></message>
+    <attachments>
+        <file name="file.txt" type="text/plain"><![CDATA[
+            File content is here and is automatically trimmed
+        ]]></file>
+        <file name="image.gif" type="image/gif" encoding="base64">
+            R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwy
+            GHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ
+            +5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/TPg7JpJHxyendzWTBfX0cxOnK
+            PjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5iJ90m6
+            mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3u
+            Nna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7
+        </file>
+    </attachments>
+    <ip>123.211.233.122</ip>
+</ticket>
+```
+
+### JSON Payload Example ###
+
+* `POST /api/tickets.json`
+
+Attachment data for the JSON content uses the [RFC 2397][] data URL format.
+As described above, the content-type and base64 encoding hints are optional.
+Furthermore, a character set can be optionally declared for each attachment
+and will be automatically converted to UTF-8 for database storage.
+
+Notice that the phone number extension can be embedded in the `phone` value
+denoted with a capital `X`
+
+Do also note that the JSON format forbids a comma after the last element in
+an object or array definition, and newlines are not allowed inside strings.
+
+``` json
+{
+    "alert": true,
+    "autorespond": true,
+    "source": "API",
+    "name": "Angry User",
+    "email": "api@osticket.com",
+    "phone": "3185558634X123",
+    "subject": "Testing API",
+    "ip": "123.211.233.122",
+    "message": "MESSAGE HERE",
+    "attachments": [
+        {"file.txt": "data:text/plain;charset=utf-8,content"},
+        {"image.png": "data:image/png;base64,R0lGODdhMAA..."},
+    ]
+}
+```
+
+[rfc 2397]:     http://www.ietf.org/rfc/rfc2397.txt     "Data URLs"
+
+### Response ######
+
+If successful, the server will send `HTTP/201 Created`. Otherwise, it will
+send an appropriate HTTP status with the content being the error
+description. Most likely offenders are
+
+* Required field not included
+* Data type mismatch (text send for numeric field)
+* Incorrectly encoded base64 data
+* Unsupported field sent
+* Incorrectly formatted content (bad JSON or XML)
+
+Upon success, the content of the response will be the external ticket id of
+the newly-created ticket.
+
+    Status: 201 Created
+    123456