diff --git a/src/applications/conduit/application/PhabricatorConduitApplication.php b/src/applications/conduit/application/PhabricatorConduitApplication.php
index 2632648109..7a0ca0e4b9 100644
--- a/src/applications/conduit/application/PhabricatorConduitApplication.php
+++ b/src/applications/conduit/application/PhabricatorConduitApplication.php
@@ -1,65 +1,65 @@
 <?php
 
 final class PhabricatorConduitApplication extends PhabricatorApplication {
 
   public function getBaseURI() {
     return '/conduit/';
   }
 
   public function getFontIcon() {
     return 'fa-tty';
   }
 
   public function canUninstall() {
     return false;
   }
 
   public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
     return array(
       array(
-        'name' => pht('Conduit Technical Documentation'),
-        'href' => PhabricatorEnv::getDoclink('Conduit Technical Documentation'),
+        'name' => pht('Conduit API Overview'),
+        'href' => PhabricatorEnv::getDoclink('Conduit API Overview'),
       ),
     );
   }
 
   public function getName() {
     return pht('Conduit');
   }
 
   public function getShortDescription() {
     return pht('Developer API');
   }
 
   public function getTitleGlyph() {
     return "\xE2\x87\xB5";
   }
 
   public function getApplicationGroup() {
     return self::GROUP_DEVELOPER;
   }
 
   public function getApplicationOrder() {
     return 0.100;
   }
 
   public function getRoutes() {
     return array(
       '/conduit/' => array(
         '(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorConduitListController',
         'method/(?P<method>[^/]+)/' => 'PhabricatorConduitConsoleController',
         'log/(?:query/(?P<queryKey>[^/]+)/)?' =>
           'PhabricatorConduitLogController',
         'log/view/(?P<view>[^/]+)/' => 'PhabricatorConduitLogController',
         'token/' => 'PhabricatorConduitTokenController',
         'token/edit/(?:(?P<id>\d+)/)?' =>
           'PhabricatorConduitTokenEditController',
         'token/terminate/(?:(?P<id>\d+)/)?' =>
           'PhabricatorConduitTokenTerminateController',
         'login/' => 'PhabricatorConduitTokenHandshakeController',
       ),
       '/api/(?P<method>[^/]+)' => 'PhabricatorConduitAPIController',
     );
   }
 
 }
diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
index effe57e47d..a299d90018 100644
--- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
+++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
@@ -1,572 +1,574 @@
 <?php
 
 abstract class PhabricatorSearchEngineAPIMethod
   extends ConduitAPIMethod {
 
   abstract public function newSearchEngine();
 
   public function getApplication() {
     $engine = $this->newSearchEngine();
     $class = $engine->getApplicationClassName();
     return PhabricatorApplication::getByClass($class);
   }
 
   public function getMethodStatus() {
     return self::METHOD_STATUS_UNSTABLE;
   }
 
   public function getMethodStatusDescription() {
     return pht('ApplicationSearch methods are highly unstable.');
   }
 
   final protected function defineParamTypes() {
     return array(
       'queryKey' => 'optional string',
       'constraints' => 'optional map<string, wild>',
       'attachments' => 'optional map<string, bool>',
       'order' => 'optional order',
     ) + $this->getPagerParamTypes();
   }
 
   final protected function defineReturnType() {
     return 'map<string, wild>';
   }
 
   final protected function execute(ConduitAPIRequest $request) {
     $engine = $this->newSearchEngine()
       ->setViewer($request->getUser());
 
     return $engine->buildConduitResponse($request);
   }
 
   final public function getMethodDescription() {
     return pht(
       'This is a standard **ApplicationSearch** method which will let you '.
-      'list, query, or search for objects.');
+      'list, query, or search for objects. For documentation on these '.
+      'endpoints, see **[[ %s | Conduit API: Using Search Endpoints ]]**.',
+      PhabricatorEnv::getDoclink('Conduit API: Using Edit Endpoints'));
   }
 
   final public function getMethodDocumentation() {
     $viewer = $this->getViewer();
 
     $engine = $this->newSearchEngine()
       ->setViewer($viewer);
 
     $query = $engine->newQuery();
 
     $out = array();
 
     $out[] = $this->buildQueriesBox($engine);
     $out[] = $this->buildConstraintsBox($engine);
     $out[] = $this->buildOrderBox($engine, $query);
     $out[] = $this->buildFieldsBox($engine);
     $out[] = $this->buildAttachmentsBox($engine);
     $out[] = $this->buildPagingBox($engine);
 
     return $out;
   }
 
   private function buildQueriesBox(
     PhabricatorApplicationSearchEngine $engine) {
     $viewer = $this->getViewer();
 
     $info = pht(<<<EOTEXT
 You can choose a builtin or saved query as a starting point for filtering
 results by selecting it with `queryKey`. If you don't specify a `queryKey`,
 the query will start with no constraints.
 
 For example, many applications have builtin queries like `"active"` or
 `"open"` to find only active or enabled results. To use a `queryKey`, specify
 it like this:
 
 ```lang=json, name="Selecting a Builtin Query"
 {
   ...
   "queryKey": "active",
   ...
 }
 ```
 
 The table below shows the keys to use to select builtin queries and your
 saved queries, but you can also use **any** query you run via the web UI as a
 starting point. You can find the key for a query by examining the URI after
 running a normal search.
 
 You can use these keys to select builtin queries and your configured saved
 queries:
 EOTEXT
       );
 
     $named_queries = $engine->loadAllNamedQueries();
 
     $rows = array();
     foreach ($named_queries as $named_query) {
       $builtin = $named_query->getIsBuiltin()
         ? pht('Builtin')
         : pht('Custom');
 
       $rows[] = array(
         $named_query->getQueryKey(),
         $named_query->getQueryName(),
         $builtin,
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Query Key'),
           pht('Name'),
           pht('Builtin'),
         ))
       ->setColumnClasses(
         array(
           'prewrap',
           'pri wide',
           null,
         ));
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Builtin and Saved Queries'))
       ->setCollapsed(true)
       ->appendChild($this->buildRemarkup($info))
       ->appendChild($table);
   }
 
   private function buildConstraintsBox(
     PhabricatorApplicationSearchEngine $engine) {
 
     $info = pht(<<<EOTEXT
 You can apply custom constraints by passing a dictionary in `constraints`.
 This will let you search for specific sets of results (for example, you may
 want show only results with a certain state, status, or owner).
 
 
 If you specify both a `queryKey` and `constraints`, the builtin or saved query
 will be applied first as a starting point, then any additional values in
 `constraints` will be applied, overwriting the defaults from the original query.
 
 Specify constraints like this:
 
 ```lang=json, name="Example Custom Constraints"
 {
   ...
   "constraints": {
     "authors": ["PHID-USER-1111", "PHID-USER-2222"],
     "statuses": ["open", "closed"],
     ...
   },
   ...
 }
 ```
 
 This API endpoint supports these constraints:
 EOTEXT
       );
 
     $fields = $engine->getSearchFieldsForConduit();
 
     // As a convenience, put these fields at the very top, even if the engine
     // specifies and alternate display order for the web UI. These fields are
     // very important in the API and nearly useless in the web UI.
     $fields = array_select_keys(
       $fields,
       array('ids', 'phids')) + $fields;
 
     $rows = array();
     foreach ($fields as $field) {
       $key = $field->getConduitKey();
       $label = $field->getLabel();
 
       $type_object = $field->getConduitParameterType();
       if ($type_object) {
         $type = $type_object->getTypeName();
         $description = $field->getDescription();
       } else {
         $type = null;
         $description = phutil_tag('em', array(), pht('Not supported.'));
       }
 
       $rows[] = array(
         $key,
         $label,
         $type,
         $description,
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Label'),
           pht('Type'),
           pht('Description'),
         ))
       ->setColumnClasses(
         array(
           'prewrap',
           'pri',
           'prewrap',
           'wide',
         ));
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Custom Query Constraints'))
       ->setCollapsed(true)
       ->appendChild($this->buildRemarkup($info))
       ->appendChild($table);
   }
 
   private function buildOrderBox(
     PhabricatorApplicationSearchEngine $engine,
     $query) {
 
     $orders_info = pht(<<<EOTEXT
 Use `order` to choose an ordering for the results.
 
 Either specify a single key from the builtin orders (these are a set of
 meaningful, high-level, human-readable orders) or specify a custom list of
 low-level columns.
 
 To use a high-level order, choose a builtin order from the table below
 and specify it like this:
 
 ```lang=json, name="Choosing a Result Order"
 {
   ...
   "order": "newest",
   ...
 }
 ```
 
 These builtin orders are available:
 EOTEXT
       );
 
     $orders = $query->getBuiltinOrders();
 
     $rows = array();
     foreach ($orders as $key => $order) {
       $rows[] = array(
         $key,
         $order['name'],
         implode(', ', $order['vector']),
       );
     }
 
     $orders_table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Description'),
           pht('Columns'),
         ))
       ->setColumnClasses(
         array(
           'pri',
           '',
           'wide',
         ));
 
     $columns_info = pht(<<<EOTEXT
 You can choose a low-level column order instead. To do this, provide a list
 of columns instead of a single key. This is an advanced feature.
 
 In a custom column order:
 
   - each column may only be specified once;
   - each column may be prefixed with `-` to invert the order;
   - the last column must be a unique column, usually `id`; and
   - no column other than the last may be unique.
 
 To use a low-level order, choose a sequence of columns and specify them like
 this:
 
 ```lang=json, name="Using a Custom Order"
 {
   ...
   "order": ["color", "-name", "id"],
   ...
 }
 ```
 
 These low-level columns are available:
 EOTEXT
       );
 
     $columns = $query->getOrderableColumns();
     $rows = array();
     foreach ($columns as $key => $column) {
       $rows[] = array(
         $key,
         idx($column, 'unique') ? pht('Yes') : pht('No'),
       );
     }
 
     $columns_table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Unique'),
         ))
       ->setColumnClasses(
         array(
           'pri',
           'wide',
         ));
 
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Result Ordering'))
       ->setCollapsed(true)
       ->appendChild($this->buildRemarkup($orders_info))
       ->appendChild($orders_table)
       ->appendChild($this->buildRemarkup($columns_info))
       ->appendChild($columns_table);
   }
 
   private function buildFieldsBox(
     PhabricatorApplicationSearchEngine $engine) {
 
     $info = pht(<<<EOTEXT
 Objects matching your query are returned as a list of dictionaries in the
 `data` property of the results. Each dictionary has some metadata and a
 `fields` key, which contains the information abou the object that most callers
 will be interested in.
 
 For example, the results may look something like this:
 
 ```lang=json, name="Example Results"
 {
   ...
   "data": [
     {
       "id": 123,
       "phid": "PHID-WXYZ-1111",
       "fields": {
         "name": "First Example Object",
         "authorPHID": "PHID-USER-2222"
       }
     },
     {
       "id": 124,
       "phid": "PHID-WXYZ-3333",
       "fields": {
         "name": "Second Example Object",
         "authorPHID": "PHID-USER-4444"
       }
     },
     ...
   ]
   ...
 }
 ```
 
 This result structure is standardized across all search methods, but the
 available fields differ from application to application.
 
 These are the fields available on this object type:
 EOTEXT
       );
 
     $specs = $engine->getAllConduitFieldSpecifications();
 
     $rows = array();
     foreach ($specs as $key => $spec) {
       $type = $spec->getType();
       $description = $spec->getDescription();
       $rows[] = array(
         $key,
         $type,
         $description,
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Type'),
           pht('Description'),
         ))
       ->setColumnClasses(
         array(
           'pri',
           'mono',
           'wide',
         ));
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Object Fields'))
       ->setCollapsed(true)
       ->appendChild($this->buildRemarkup($info))
       ->appendChild($table);
   }
 
   private function buildAttachmentsBox(
     PhabricatorApplicationSearchEngine $engine) {
 
     $info = pht(<<<EOTEXT
 By default, only basic information about objects is returned. If you want
 more extensive information, you can use available `attachments` to get more
 information in the results (like subscribers and projects).
 
 Generally, requesting more information means the query executes more slowly
 and returns more data (in some cases, much more data). You should normally
 request only the data you need.
 
 To request extra data, specify which attachments you want in the `attachments`
 parameter:
 
 ```lang=json, name="Example Attachments Request"
 {
   ...
   "attachments": {
     "subscribers": true
   },
   ...
 }
 ```
 
 This example specifies that results should include information about
 subscribers. In the return value, each object will now have this information
 filled out in the corresponding `attachments` value:
 
 ```lang=json, name="Example Attachments Result"
 {
   ...
   "data": [
     {
       ...
       "attachments": {
         "subscribers": {
           "subscriberPHIDs": [
             "PHID-WXYZ-2222",
           ],
           "subscriberCount": 1,
           "viewerIsSubscribed": false
         }
       },
       ...
     },
     ...
   ],
   ...
 }
 ```
 
 These attachments are available:
 EOTEXT
       );
 
     $attachments = $engine->getConduitSearchAttachments();
 
     $rows = array();
     foreach ($attachments as $key => $attachment) {
       $rows[] = array(
         $key,
         $attachment->getAttachmentName(),
         $attachment->getAttachmentDescription(),
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
           pht('Key'),
           pht('Name'),
           pht('Description'),
         ))
       ->setColumnClasses(
         array(
           'prewrap',
           'pri',
           'wide',
         ));
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Attachments'))
       ->setCollapsed(true)
       ->appendChild($this->buildRemarkup($info))
       ->appendChild($table);
   }
 
   private function buildPagingBox(
     PhabricatorApplicationSearchEngine $engine) {
 
     $info = pht(<<<EOTEXT
 Queries are limited to returning 100 results at a time. If you want fewer
 results than this, you can use `limit` to specify a smaller limit.
 
 If you want more results, you'll need to make additional queries to retrieve
 more pages of results.
 
 The result structure contains a `cursor` key with information you'll need in
 order to fetch the next page of results. After an initial query, it will
 usually look something like this:
 
 ```lang=json, name="Example Cursor Result"
 {
   ...
   "cursor": {
     "limit": 100,
     "after": "1234",
     "before": null,
     "order": null
   }
   ...
 }
 ```
 
 The `limit` and `order` fields are describing the effective limit and order the
 query was executed with, and are usually not of much interest. The `after` and
 `before` fields give you cursors which you can pass when making another API
 call in order to get the next (or previous) page of results.
 
 To get the next page of results, repeat your API call with all the same
 parameters as the original call, but pass the `after` cursor you received from
 the first call in the `after` parameter when making the second call.
 
 If you do things correctly, you should get the second page of results, and
 a cursor structure like this:
 
 ```lang=json, name="Second Result Page"
 {
   ...
   "cursor": {
     "limit": 5,
     "after": "4567",
     "before": "7890",
     "order": null
   }
   ...
 }
 ```
 
 You can now continue to the third page of results by passing the new `after`
 cursor to the `after` parameter in your third call, or return to the previous
 page of results by passing the `before` cursor to the `before` parameter. This
 might be useful if you are rendering a web UI for a user and want to provide
 "Next Page" and "Previous Page" links.
 
 If `after` is `null`, there is no next page of results available. Likewise,
 if `before` is `null`, there are no previous results available.
 EOTEXT
       );
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Paging and Limits'))
       ->setCollapsed(true)
       ->appendChild($this->buildRemarkup($info));
   }
 
   private function buildRemarkup($remarkup) {
     $viewer = $this->getViewer();
 
     $view = new PHUIRemarkupView($viewer, $remarkup);
 
     return id(new PHUIBoxView())
       ->appendChild($view)
       ->addPadding(PHUI::PADDING_LARGE);
   }
 }
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
index d29ec1c4ac..3cf04aeca8 100644
--- a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
@@ -1,202 +1,161 @@
 <?php
 
 abstract class PhabricatorEditEngineAPIMethod
   extends ConduitAPIMethod {
 
   abstract public function newEditEngine();
 
   public function getApplication() {
     $engine = $this->newEditEngine();
     $class = $engine->getEngineApplicationClass();
     return PhabricatorApplication::getByClass($class);
   }
 
   public function getMethodStatus() {
     return self::METHOD_STATUS_UNSTABLE;
   }
 
   public function getMethodStatusDescription() {
     return pht('ApplicationEditor methods are highly unstable.');
   }
 
   final protected function defineParamTypes() {
     return array(
       'transactions' => 'list<map<string, wild>>',
       'objectIdentifier' => 'optional id|phid|string',
     );
   }
 
   final protected function defineReturnType() {
     return 'map<string, wild>';
   }
 
   final protected function execute(ConduitAPIRequest $request) {
     $engine = $this->newEditEngine()
       ->setViewer($request->getUser());
 
     return $engine->buildConduitResponse($request);
   }
 
   final public function getMethodDescription() {
+    return pht(
+      'This is a standard **ApplicationEditor** method which allows you to '.
+      'create and modify objects by applying transactions. For documentation '.
+      'on these endpoints, see '.
+      '**[[ %s | Conduit API: Using Edit Endpoints ]]**.',
+      PhabricatorEnv::getDoclink('Conduit API: Using Edit Endpoints'));
+  }
+
+  final public function getMethodDocumentation() {
     $viewer = $this->getViewer();
 
     $engine = $this->newEditEngine()
       ->setViewer($viewer);
 
     $types = $engine->getConduitEditTypes();
 
     $out = array();
 
-    $out[] = pht(<<<EOTEXT
-This is a standard **ApplicationEditor** method which allows you to create and
-modify objects by applying transactions.
-
-Each transaction applies one change to the object. For example, to create an
-object with a specific title or change the title of an existing object you might
-start by building a transaction like this:
+    $out[] = $this->buildEditTypesBoxes($engine, $types);
 
-```lang=json, name=Example Single Transaction
-{
-  "type": "title",
-  "value": "New Object Title"
-}
-```
-
-By passing a list of transactions in the `transactions` parameter, you can
-apply a sequence of edits. For example, you'll often pass a value like this to
-create an object with several field values or apply changes to multiple fields:
-
-```lang=json, name=Example Transaction List
-[
-  {
-    "type": "title",
-    "value": "New Object Title"
-  },
-  {
-    "type": "body",
-    "value": "New body text for the object."
-  },
-  {
-    "type": "projects.add",
-    "value": ["PHID-PROJ-1111", "PHID-PROJ-2222"]
+    return $out;
   }
-]
-```
-
-Exactly which types of edits are available depends on the object you're editing.
-
-
-Creating Objects
-----------------
-
-To create an object, pass a list of `transactions` but leave `objectIdentifier`
-empty. This will create a new object with the initial field values you
-specify.
-
-
-Editing Objects
----------------
-
-To edit an object, pass a list of `transactions` and specify an object to
-apply them to with `objectIdentifier`. This will apply the changes to the
-object.
-
-You may pass an ID (like `123`), PHID (like `PHID-WXYZ-abcdef...`), or
-monogram (like `T123`, for objects which have monograms).
-
-
-Return Type
------------
-
-WARNING: The structure of the return value from these methods is likely to
-change as ApplicationEditor evolves.
-
-Return values look something like this for now:
-
-```lang=json, name=Example Return Value
-{
-  "object": {
-    "phid": "PHID-XXXX-1111"
-  },
-  "transactions": [
-    {
-      "phid": "PHID-YYYY-1111",
-    },
-    {
-      "phid": "PHID-YYYY-2222",
-    }
-  ]
-}
-```
-
-The `object` key contains information about the object which was created or
-edited.
 
-The `transactions` key contains information about the transactions which were
-actually applied. For many reasons, the transactions which actually apply may
-be greater or fewer in number than the transactions you provided, or may differ
-in their nature in other ways.
+  private function buildEditTypesBoxes(
+    PhabricatorEditEngine $engine,
+    array $types) {
 
+    $boxes = array();
 
-Edit Types
-==========
-
-This API method supports these edit types:
-EOTEXT
-      );
+    $summary_info = pht(
+      'This endpoint supports these types of transactions. See below for '.
+      'detailed information about each transaction type.');
 
-    $key = pht('Key');
-    $description = pht('Description');
-    $head_type = pht('Type');
-
-    $table = array();
-    $table[] = "| {$key} | {$description} |";
-    $table[] = '|--------|----------------|';
+    $rows = array();
     foreach ($types as $type) {
-      $edit_type = $type->getEditType();
-      $edit_description = $type->getConduitDescription();
-      $table[] = "| `{$edit_type}` | {$edit_description} |";
+      $rows[] = array(
+        $type->getEditType(),
+        $type->getConduitDescription(),
+      );
     }
 
-    $out[] = implode("\n", $table);
+    $summary_table = id(new AphrontTableView($rows))
+      ->setHeaders(
+        array(
+          pht('Key'),
+          pht('Description'),
+        ))
+      ->setColumnClasses(
+        array(
+          'prewrap',
+          'wide',
+        ));
+
+    $boxes[] = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Transaction Types'))
+      ->setCollapsed(true)
+      ->appendChild($this->buildRemarkup($summary_info))
+      ->appendChild($summary_table);
 
     foreach ($types as $type) {
       $section = array();
-      $section[] = pht('Edit Type: %s', $type->getEditType());
-      $section[] = '---------';
-      $section[] = null;
+
       $section[] = $type->getConduitDescription();
-      $section[] = null;
-      $section[] = pht(
-        'This edit generates transactions of type `%s` internally.',
-        $type->getTransactionType());
-      $section[] = null;
 
       $type_documentation = $type->getConduitDocumentation();
-      if ($type_documentation) {
+      if (strlen($type_documentation)) {
         $section[] = $type_documentation;
-        $section[] = null;
       }
 
-      $type_description = pht(
-        'Use `%s` to select this edit type.',
-        $type->getEditType());
+      $section = implode("\n\n", $section);
+
+      $rows = array();
 
-      $value_type = $type->getConduitType();
-      $value_description = $type->getConduitTypeDescription();
+      $rows[] = array(
+        'type',
+        'const',
+        $type->getEditType(),
+      );
 
-      $table = array();
-      $table[] = "| {$key} | {$head_type} | {$description} |";
-      $table[] = '|--------|--------------|----------------|';
-      $table[] = "| `type` | `const` | {$type_description} |";
-      $table[] = "| `value` | `{$value_type}` | {$value_description} |";
-      $section[] = implode("\n", $table);
+      $rows[] = array(
+        'value',
+        $type->getConduitType(),
+        $type->getConduitTypeDescription(),
+      );
 
-      $out[] = implode("\n", $section);
+      $type_table = id(new AphrontTableView($rows))
+        ->setHeaders(
+          array(
+            pht('Key'),
+            pht('Type'),
+            pht('Description'),
+          ))
+        ->setColumnClasses(
+          array(
+            'prewrap',
+            'prewrap',
+            'wide',
+          ));
+
+      $boxes[] = id(new PHUIObjectBoxView())
+      ->setHeaderText(pht('Transaction Type: %s', $type->getEditType()))
+      ->setCollapsed(true)
+      ->appendChild($this->buildRemarkup($section))
+      ->appendChild($type_table);
     }
 
-    $out = implode("\n\n", $out);
-    return $out;
+    return $boxes;
+  }
+
+
+  private function buildRemarkup($remarkup) {
+    $viewer = $this->getViewer();
+
+    $view = new PHUIRemarkupView($viewer, $remarkup);
+
+    return id(new PHUIBoxView())
+      ->appendChild($view)
+      ->addPadding(PHUI::PADDING_LARGE);
   }
 
 }
diff --git a/src/docs/book/user.book b/src/docs/book/user.book
index 2312bf65cd..20a72698be 100644
--- a/src/docs/book/user.book
+++ b/src/docs/book/user.book
@@ -1,39 +1,42 @@
 {
   "name": "phabricator",
   "title": "Phabricator User Documentation",
   "short": "Phabricator User Docs",
   "preface": "Instructions for installing, configuring, and using Phabricator.",
   "root": "../../../",
   "uri.source":
     "https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",
   "rules": {
     "(\\.diviner$)": "DivinerArticleAtomizer"
   },
   "exclude": [
     "(^externals/)",
     "(^resources/)",
     "(^scripts/)",
     "(^src/docs/contributor/)",
     "(^src/docs/flavor/)",
     "(^src/docs/tech/)",
     "(^support/)",
     "(^webroot/rsrc/externals/)"
   ],
   "groups": {
     "intro": {
       "name": "Introduction"
     },
     "config": {
       "name": "Configuration"
     },
     "userguide": {
       "name": "Application User Guides"
     },
+    "conduit": {
+      "name": "API Documentation"
+    },
     "fieldmanual": {
       "name": "Field Manuals"
     },
     "cellar": {
       "name": "Musty Cellar"
     }
   }
 }
diff --git a/src/docs/tech/conduit.diviner b/src/docs/tech/conduit.diviner
deleted file mode 100644
index c31473b684..0000000000
--- a/src/docs/tech/conduit.diviner
+++ /dev/null
@@ -1,28 +0,0 @@
-@title Conduit Technical Documentation
-@group conduit
-
-Technical overview of the Conduit API.
-
-Overview
-========
-
-Conduit is the HTTP API for Phabricator. It is roughly JSON-RPC: you usually
-pass a JSON blob, and usually get a JSON blob back, although both call and
-result formats are flexible in some cases.
-
-The easiest way to begin exploring Conduit is by visiting {nav Conduit} in the
-web UI. The application provides an API console which you can use to explore
-available methods, make calls, read documentation, and see examples.
-
-The API console has details about how to construct calls and generate API
-tokens for authentication.
-
-The three primary ways to make Conduit calls are:
-
-  - `arc call-conduit`: You can use this `arc` command to execute low-level
-    Conduit calls.
-  - `curl`: You can format a call with basic HTTP parameters and cURL.
-  - `ConduitClient`:
-
-There are also clients available in other languages. The Arcanist CLI client
-for Phabricator is implemented over Conduit.
diff --git a/src/docs/user/field/conduit_changes.diviner b/src/docs/user/field/conduit_changes.diviner
index 3b78490546..e77a582251 100644
--- a/src/docs/user/field/conduit_changes.diviner
+++ b/src/docs/user/field/conduit_changes.diviner
@@ -1,51 +1,59 @@
 @title Managing Conduit Changes
 @group fieldmanual
 
 Help with managing Conduit API changes.
 
 Overview
 ========
 
 Many parts of the Conduit API are stable, but some parts are subject to change.
 For example, when we write a new application, it usually adds several new API
 methods and may update older methods.
 
 This document discusses API stability and how to minimize disruption when
 transitionig between API versions.
 
 
 Method Statuses
 ===============
 
 Methods have one of three statuses:
 
   - **Unstable**: This is a new or experimental method which is subject to
     change. You may call these methods to get access to recently released
     features, but should expect that you may need to adjust your usage of
     them before they stabilize.
   - **Stable**: This is an established method which generally will not change.
   - **Deprecated**: This method will be removed in a future version of
     Phabricator and callers should cease using it.
 
 Normally, a method is deprecated only when it is obsolete or a new, more
 powerful method is available to replace it.
 
 
 Finding Deprecated Calls
 ========================
 
 You can identify calls to deprecated methods in {nav Conduit > Call Logs}.
 Use {nav My Deprecated Calls} to find calls to deprecated methods you have
 made, and {nav Deprecated Call Logs} to find deprecated calls by all users.
 
 You can also search for calls by specific users. For example, it may be useful
 to serach for any bot accounts you run to make sure they aren't calling
 outdated APIs.
 
 The most common cause of calls to deprecated methods is users running very
 old versions of Arcanist. They can normally upgrade by running `arc upgrade`.
 
 When the changelogs mention a method deprecation, you can use the call logs
 to identify callers and notify them to upgrade or switch away. When the
 changelogs mention a method removal, you can use the call logs to verify that
 you will not be impacted.
+
+
+Next Steps
+==========
+
+Continue by:
+
+  - returning to @{article:Conduit API Overview}.
diff --git a/src/docs/user/userguide/conduit.diviner b/src/docs/user/userguide/conduit.diviner
new file mode 100644
index 0000000000..5292736f30
--- /dev/null
+++ b/src/docs/user/userguide/conduit.diviner
@@ -0,0 +1,68 @@
+@title Conduit API Overview
+@group conduit
+
+Overview of the Conduit API.
+
+Overview
+========
+
+Conduit is the HTTP API for Phabricator. It is roughly JSON-RPC: you usually
+pass a JSON blob, and usually get a JSON blob back, although both call and
+result formats are flexible in some cases.
+
+API Clients
+===========
+
+The primary ways to make Conduit calls are:
+
+**Web Console**: The {nav Conduit} application provides a web UI for exploring
+the API and making calls. This is the best starting point for learning about
+the API. See the next section for details.
+
+`ConduitClient`: This is the official client available in `libphutil`, and
+the one used by `arc`.
+
+`arc call-conduit`: You can use this `arc` command to execute low-level
+Conduit calls by piping JSON in to stdin. This can provide a simple way
+to explore the API, or a quick way to get API access from a script written
+in another language without needing a real client.
+
+`curl`: You can format a call with basic HTTP parameters and cURL. The console
+includes examples which show how to format calls.
+
+**Other Clients**: There are also clients available in other languages. You
+can check the [[ https://secure.phabricator.com/w/community_resources/ |
+Community Resources ]] page for links.
+
+API Console
+===========
+
+The easiest way to begin exploring Conduit is by visiting {nav Conduit} in the
+web UI. The application provides an API console which you can use to explore
+available methods, make calls, read documentation, and see examples.
+
+The API console has details about how to construct calls and generate API
+tokens for authentication.
+
+
+Querying and Reading Objects
+============================
+
+For information on searching for objects and reading their properties and
+information, see @{article:Conduit API: Using Search Endpoints}.
+
+
+Creating and Editing Objects
+============================
+
+For information on creating, editing and updating objects, see
+@{article:Conduit API: Using Search Endpoints}.
+
+
+Next Steps
+==========
+
+Continue by:
+
+  - reading recommendations on responding to API changes in
+    @{article:Managing Conduit Changes}.
diff --git a/src/docs/user/userguide/conduit_edit.diviner b/src/docs/user/userguide/conduit_edit.diviner
new file mode 100644
index 0000000000..01d15873a8
--- /dev/null
+++ b/src/docs/user/userguide/conduit_edit.diviner
@@ -0,0 +1,115 @@
+@title Conduit API: Using Edit Endpoints
+@group conduit
+
+Describes how to use edit endpoints to create and update objects.
+
+Overview
+========
+
+Many applications provide `edit` endpoints, which are the primary way to
+create and update objects (like tasks) using the API.
+
+To create or edit an object, you'll build a list of //transactions// and pass
+them to the endpoint. Each transaction applies a change to a field or property
+on the object.
+
+For example, a transaction might change the title of an object or add
+subscribers.
+
+When creating an object, transactions will be applied to an empty object. When
+editing an object, transactions will be applied to an existing object.
+
+The best reference for a particular `edit` endpoint is the Conduit API console.
+For example, you can find the console page for `maniphest.edit` by navigating
+to {nav Conduit > maniphest.edit} in the web UI. This page contains detailed
+information about the endpoint and how it can be used.
+
+Creating Objects
+================
+
+To create objects, pass a list of transactions but leave `objectIdentfier`
+blank. This tells the endpoint that you want to create a new, empty object and
+then apply the transactions to it.
+
+
+Editing Objects
+===============
+
+To edit objects, pass a list of transactions and use `objectIdentifier` to
+specify which object to apply them to. You can normally pass an ID or PHID,
+and many applicaitons also allow you to pass a monogram (for example, you can
+edit a task by passing `T123`).
+
+
+Building Transactions
+=====================
+
+When creating or editing objects, you'll build a list of transactions to
+apply. This transaction list will look something like this:
+
+```lang=json, name="Example Transaction List"
+[
+  {
+    "type": "title",
+    "value": "Assemble in the barnyard"
+  },
+  {
+    "type": "description",
+    "value": "All animals should assemble in the barnyard promptly."
+  },
+  {
+    "type": "subscribers.add",
+    "value": ["dog", "cat", "mouse"]
+  }
+]
+```
+
+Applied to an empty object (say, a task), these transactions would create a new
+task with the specified title, description and subscribers.
+
+Applied to an existing object, they would retitle the task, change its
+description, and add new subscribers.
+
+The particular transactions available on each object are documented on the
+Conduit API console page for that object.
+
+
+Return Type
+===========
+
+WARNING: The structure of the return value from these methods is likely to
+change as ApplicationEditor evolves.
+
+Return values look something like this for now:
+
+```lang=json, name=Example Return Value
+{
+  "object": {
+    "phid": "PHID-XXXX-1111"
+  },
+  "transactions": [
+    {
+      "phid": "PHID-YYYY-1111",
+    },
+    {
+      "phid": "PHID-YYYY-2222",
+    }
+  ]
+}
+```
+
+The `object` key contains information about the object which was created or
+edited.
+
+The `transactions` key contains information about the transactions which were
+actually applied. For many reasons, the transactions which actually apply may
+be greater or fewer in number than the transactions you provided, or may differ
+in their nature in other ways.
+
+
+Next Steps
+==========
+
+Continue by:
+
+  - returning to the @{article:Conduit API Overview}.
diff --git a/src/docs/user/userguide/conduit_search.diviner b/src/docs/user/userguide/conduit_search.diviner
new file mode 100644
index 0000000000..2253831c65
--- /dev/null
+++ b/src/docs/user/userguide/conduit_search.diviner
@@ -0,0 +1,74 @@
+@title Conduit API: Using Search Endpoints
+@group conduit
+
+Describes how to use search endpoints to find objects and read information.
+
+Overview
+========
+
+Many applications provide `search` endpoints, which are the primary way to
+get information about objects (like tasks) using the API.
+
+To read information about objects, you'll specify a //query// which describes
+which objects you want to retrieve. You can query for specific objects by
+ID, or for a list of objects satisfying certain constraints (for example, open
+tasks in a particular project).
+
+The best reference for a particular `search` endpoint is the Conduit API
+console. For example, you can find the console page for `maniphest.search` by
+navigating to {nav Conduit > maniphest.search} in the web UI. This page
+contains detailed information about the endpoint and how it can be used.
+
+
+Specifying a Query
+==================
+
+The simplest query you can use is no query at all: just make a request with
+no parameters. This will return the first page of visible objects. Most
+applications sort objects by creation date by default, so usually this is
+the 100 most recent objects.
+
+The easiest way to constrain results is to use a builtin query or a custom
+query that you build using the web UI. To do this, first issue the query in
+the web UI (for example, by clicking the builtin link on the left nav of the
+list view, or by submitting the query form).
+
+The results page will include a //query key// in the URL. For builtin queries,
+this is usually a human-readable term like `all` or `active`. For custom
+queries, it is a hash value which looks something like `MT0Rh0fB2x4I`.
+
+You can submit this key in the `queryKey` parameter to issue the exact same
+query via the Conduit API. This provides a simple way to build complex queries:
+just build the via the web UI, then reuse the same query in the API.
+
+If you need more control or want to build dynamic queries, use the
+`constraints` parameter to set constraints for individual query fields.
+
+For more details, consult the Conduit API console documentation for the
+method you're using. It includes documentation on all available constraints
+and lists builtin and saved query keys.
+
+
+Attachments
+===========
+
+By default, queries return basic information about objects. If you want more
+detailed information, most applications offer //attachments// which can let
+you retrieve more information.
+
+For example, subscribers and projects are not returned by default, but you
+can use subscribers to query them if you need this data.
+
+Asking for more data means a slower query and a larger result, so usually you
+should only ask for data you need.
+
+The Conduit API console page for each query method has detailed information
+on which attachments it supports.
+
+
+Next Steps
+==========
+
+Continue by:
+
+  - returning to the @{article:Conduit API Overview}.