diff --git a/bin/build-driver.sh b/bin/build-driver.sh
index 688d93f5eb341f7fd90e9166fdacbbe5ed9d6017..445e1bf832dfe5314d5248e2d780d04e67ad10aa 100755
--- a/bin/build-driver.sh
+++ b/bin/build-driver.sh
@@ -48,23 +48,26 @@ fi
 # Calculate a checksum of all the driver source files. If we've already built the driver and the checksum is the same
 # there's no need to build the driver a second time
 calculate_checksum() {
-    find "$driver_project_dir" -name '*.clj' -or -name '*.yaml' | sort | cat | $md5_command
+    find "$driver_project_dir" -name '*.clj' -or -name '*.yaml' | sort | xargs cat | $md5_command
 }
 
 # Check whether the saved checksum for the driver sources from the last build is the same as the current one. If so,
 # we don't need to build again.
 checksum_is_same() {
-    result=""
     if [ -f "$checksum_file" ]; then
         old_checksum=`cat "$checksum_file"`
-        if [ "$(calculate_checksum)" == "$old_checksum" ]; then
+        current_checksum=`calculate_checksum`
+        echo "Checksum of source files for previous build: $old_checksum"
+        echo "Current checksum of source files: $current_checksum"
+        if [  "$current_checksum" == "$old_checksum" ]; then
             # Make sure the target driver JAR actually exists as well!
             if [ -f "$target_jar" ]; then
-                result="$driver driver source unchanged since last build. Skipping re-build."
+                echo "$driver driver source unchanged since last build. Skipping re-build."
+                return 0
             fi
         fi
     fi
-    echo "$result"
+    return 1
 }
 
 ######################################## BUILDING THE DRIVER ########################################
@@ -228,9 +231,11 @@ mkdir -p resources/modules
 if [ $# -eq 2 ]; then
     $2
 # Build driver if checksum has changed
-elif [ ! "$(checksum_is_same)" ]; then
+elif ! checksum_is_same; then
+    echo "Checksum has changed."
     build_driver || retry_clean_build
 # Either way, always copy the target uberjar to the dest location
 else
+    echo "Checksum is unchanged."
     (copy_target_to_dest && verify_build) || retry_clean_build
 fi
diff --git a/docs/api-documentation.md b/docs/api-documentation.md
index 0c459fa33d57b798b5ed6742a6a7ea1ec5c861f2..327e5b1fc63967830904949d1acb5ee7abbc6eba 100644
--- a/docs/api-documentation.md
+++ b/docs/api-documentation.md
@@ -1,4 +1,4 @@
-# API Documentation for Metabase v0.30.0-snapshot
+# API Documentation for Metabase v0.32.2
 
 ## `GET /api/activity/`
 
@@ -16,7 +16,7 @@ Delete an Alert. (DEPRECATED -- don't delete a Alert anymore -- archive it inste
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/alert/`
@@ -34,7 +34,7 @@ Fetch all questions for the given question (`Card`) id
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/alert/`
@@ -53,7 +53,7 @@ Create a new Alert.
 
 *  **`alert_above_goal`** value may be nil, or if non-nil, value must be a boolean.
 
-*  **`new-alert-request-body`**
+*  **`new-alert-request-body`** 
 
 
 ## `PUT /api/alert/:id`
@@ -62,7 +62,7 @@ Update a `Alert` with ID.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`alert_condition`** value may be nil, or if non-nil, value must be one of: `goal`, `rows`.
 
@@ -76,7 +76,7 @@ Update a `Alert` with ID.
 
 *  **`archived`** value may be nil, or if non-nil, value must be a boolean.
 
-*  **`alert-updates`**
+*  **`alert-updates`** 
 
 
 ## `PUT /api/alert/:id/unsubscribe`
@@ -85,7 +85,7 @@ Unsubscribes a user from the given alert
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/automagic-dashboards/:entity/:entity-id-or-query`
@@ -96,7 +96,7 @@ Return an automagic dashboard for entity `entity` with id `ìd`.
 
 *  **`entity`** Invalid entity type
 
-*  **`entity-id-or-query`**
+*  **`entity-id-or-query`** 
 
 *  **`show`** invalid show value
 
@@ -111,7 +111,7 @@ Return an automagic dashboard analyzing cell in  automagic dashboard for entity
 
 *  **`entity`** Invalid entity type
 
-*  **`entity-id-or-query`**
+*  **`entity-id-or-query`** 
 
 *  **`cell-query`** value couldn't be parsed as base64 encoded JSON
 
@@ -128,7 +128,7 @@ Return an automagic comparison dashboard for cell in automagic dashboard for ent
 
 *  **`entity`** Invalid entity type
 
-*  **`entity-id-or-query`**
+*  **`entity-id-or-query`** 
 
 *  **`cell-query`** value couldn't be parsed as base64 encoded JSON
 
@@ -136,7 +136,7 @@ Return an automagic comparison dashboard for cell in automagic dashboard for ent
 
 *  **`comparison-entity`** Invalid comparison entity type. Can only be one of "table", "segment", or "adhoc"
 
-*  **`comparison-entity-id-or-query`**
+*  **`comparison-entity-id-or-query`** 
 
 
 ## `GET /api/automagic-dashboards/:entity/:entity-id-or-query/cell/:cell-query/rule/:prefix/:rule`
@@ -148,7 +148,7 @@ Return an automagic dashboard analyzing cell in question  with id `id` defined b
 
 *  **`entity`** Invalid entity type
 
-*  **`entity-id-or-query`**
+*  **`entity-id-or-query`** 
 
 *  **`cell-query`** value couldn't be parsed as base64 encoded JSON
 
@@ -169,7 +169,7 @@ Return an automagic comparison dashboard for cell in automagic dashboard for ent
 
 *  **`entity`** Invalid entity type
 
-*  **`entity-id-or-query`**
+*  **`entity-id-or-query`** 
 
 *  **`cell-query`** value couldn't be parsed as base64 encoded JSON
 
@@ -181,7 +181,7 @@ Return an automagic comparison dashboard for cell in automagic dashboard for ent
 
 *  **`comparison-entity`** Invalid comparison entity type. Can only be one of "table", "segment", or "adhoc"
 
-*  **`comparison-entity-id-or-query`**
+*  **`comparison-entity-id-or-query`** 
 
 
 ## `GET /api/automagic-dashboards/:entity/:entity-id-or-query/compare/:comparison-entity/:comparison-entity-id-or-query`
@@ -193,13 +193,13 @@ Return an automagic comparison dashboard for entity `entity` with id `ìd` compa
 
 *  **`entity`** Invalid entity type
 
-*  **`entity-id-or-query`**
+*  **`entity-id-or-query`** 
 
 *  **`show`** invalid show value
 
 *  **`comparison-entity`** Invalid comparison entity type. Can only be one of "table", "segment", or "adhoc"
 
-*  **`comparison-entity-id-or-query`**
+*  **`comparison-entity-id-or-query`** 
 
 
 ## `GET /api/automagic-dashboards/:entity/:entity-id-or-query/rule/:prefix/:rule`
@@ -210,7 +210,7 @@ Return an automagic dashboard for entity `entity` with id `ìd` using rule `rule
 
 *  **`entity`** Invalid entity type
 
-*  **`entity-id-or-query`**
+*  **`entity-id-or-query`** 
 
 *  **`prefix`** invalid value for prefix
 
@@ -228,7 +228,7 @@ Return an automagic comparison dashboard for entity `entity` with id `ìd` using
 
 *  **`entity`** Invalid entity type
 
-*  **`entity-id-or-query`**
+*  **`entity-id-or-query`** 
 
 *  **`prefix`** invalid value for prefix
 
@@ -238,7 +238,7 @@ Return an automagic comparison dashboard for entity `entity` with id `ìd` using
 
 *  **`comparison-entity`** Invalid comparison entity type. Can only be one of "table", "segment", or "adhoc"
 
-*  **`comparison-entity-id-or-query`**
+*  **`comparison-entity-id-or-query`** 
 
 
 ## `GET /api/automagic-dashboards/database/:id/candidates`
@@ -247,7 +247,7 @@ Return a list of candidates for automagic dashboards orderd by interestingness.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `DELETE /api/card/:card-id/favorite`
@@ -256,7 +256,7 @@ Unfavorite a Card.
 
 ##### PARAMS:
 
-*  **`card-id`**
+*  **`card-id`** 
 
 
 ## `DELETE /api/card/:card-id/public_link`
@@ -267,7 +267,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`card-id`**
+*  **`card-id`** 
 
 
 ## `DELETE /api/card/:id`
@@ -276,7 +276,7 @@ Delete a Card. (DEPRECATED -- don't delete a Card anymore -- archive it instead.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/card/`
@@ -298,7 +298,7 @@ Get `Card` with ID.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/card/:id/related`
@@ -307,7 +307,7 @@ Return related entities.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/card/embeddable`
@@ -345,7 +345,7 @@ Create a new `Card`.
 
 *  **`name`** value must be a non-blank string.
 
-*  **`dataset_query`**
+*  **`dataset_query`** 
 
 *  **`display`** value must be a non-blank string.
 
@@ -356,7 +356,7 @@ Favorite a Card.
 
 ##### PARAMS:
 
-*  **`card-id`**
+*  **`card-id`** 
 
 
 ## `POST /api/card/:card-id/public_link`
@@ -369,7 +369,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`card-id`**
+*  **`card-id`** 
 
 
 ## `POST /api/card/:card-id/query`
@@ -378,9 +378,9 @@ Run the query associated with a Card.
 
 ##### PARAMS:
 
-*  **`card-id`**
+*  **`card-id`** 
 
-*  **`parameters`**
+*  **`parameters`** 
 
 *  **`ignore_cache`** value may be nil, or if non-nil, value must be a boolean.
 
@@ -392,12 +392,16 @@ Run the query associated with a Card, and return its results as a file in the sp
 
 ##### PARAMS:
 
-*  **`card-id`**
+*  **`card-id`** 
 
 *  **`export-format`** value must be one of: `csv`, `json`, `xlsx`.
 
 *  **`parameters`** value may be nil, or if non-nil, value must be a valid JSON string.
 
+*  **`respond`** 
+
+*  **`raise`** 
+
 
 ## `POST /api/card/collections`
 
@@ -417,7 +421,7 @@ Return related entities for an ad-hoc query.
 
 ##### PARAMS:
 
-*  **`query`**
+*  **`query`** 
 
 
 ## `PUT /api/card/:id`
@@ -442,7 +446,7 @@ Update a `Card`.
 
 *  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
-*  **`card-updates`**
+*  **`card-updates`** 
 
 *  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
 
@@ -450,7 +454,7 @@ Update a `Card`.
 
 *  **`dataset_query`** value may be nil, or if non-nil, value must be a map.
 
-*  **`id`**
+*  **`id`** 
 
 *  **`display`** value may be nil, or if non-nil, value must be a non-blank string.
 
@@ -474,7 +478,7 @@ Fetch a specific Collection with standard details added
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/collection/:id/items`
@@ -486,7 +490,7 @@ Fetch a specific Collection's items with the following options:
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`model`** value may be nil, or if non-nil, value must be one of: `card`, `collection`, `dashboard`, `pulse`.
 
@@ -546,7 +550,7 @@ Modify an existing Collection, including archiving or unarchiving it, or moving
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
 
@@ -558,7 +562,7 @@ Modify an existing Collection, including archiving or unarchiving it, or moving
 
 *  **`parent_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
-*  **`collection-updates`**
+*  **`collection-updates`** 
 
 
 ## `PUT /api/collection/graph`
@@ -580,25 +584,25 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`dashboard-id`**
+*  **`dashboard-id`** 
 
 
 ## `DELETE /api/dashboard/:id`
 
-Delete a `Dashboard`.
+Delete a Dashboard.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `DELETE /api/dashboard/:id/cards`
 
-Remove a `DashboardCard` from a `Dashboard`.
+Remove a DashboardCard from a Dashboard.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`dashcardId`** value must be a valid integer greater than zero.
 
@@ -609,7 +613,7 @@ Unfavorite a Dashboard.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/dashboard/`
@@ -627,11 +631,11 @@ Get `Dashboards`. With filter option `f` (default `all`), restrict results as fo
 
 ## `GET /api/dashboard/:id`
 
-Get `Dashboard` with ID.
+Get Dashboard with `id`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/dashboard/:id/related`
@@ -640,16 +644,16 @@ Return related entities.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/dashboard/:id/revisions`
 
-Fetch `Revisions` for `Dashboard` with ID.
+Fetch Revisions for Dashboard with `id`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/dashboard/embeddable`
@@ -670,7 +674,7 @@ You must be a superuser to do this.
 
 ## `POST /api/dashboard/`
 
-Create a new `Dashboard`.
+Create a new Dashboard.
 
 ##### PARAMS:
 
@@ -684,7 +688,7 @@ Create a new `Dashboard`.
 
 *  **`collection_position`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
-*  **`dashboard`**
+*  **`dashboard`** 
 
 
 ## `POST /api/dashboard/:dashboard-id/public_link`
@@ -697,24 +701,43 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`dashboard-id`**
+*  **`dashboard-id`** 
+
+
+## `POST /api/dashboard/:from-dashboard-id/copy`
+
+Copy a Dashboard.
+
+##### PARAMS:
+
+*  **`from-dashboard-id`** 
+
+*  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
+
+*  **`description`** value may be nil, or if non-nil, value must be a string.
+
+*  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
+
+*  **`collection_position`** value may be nil, or if non-nil, value must be an integer greater than zero.
+
+*  **`dashboard`** 
 
 
 ## `POST /api/dashboard/:id/cards`
 
-Add a `Card` to a `Dashboard`.
+Add a Card to a Dashboard.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`cardId`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
 *  **`parameter_mappings`** value must be an array. Each value must be a map.
 
-*  **`series`**
+*  **`series`** 
 
-*  **`dashboard-card`**
+*  **`dashboard-card`** 
 
 
 ## `POST /api/dashboard/:id/favorite`
@@ -723,16 +746,16 @@ Favorite a Dashboard.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/dashboard/:id/revert`
 
-Revert a `Dashboard` to a prior `Revision`.
+Revert a Dashboard to a prior Revision.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`revision_id`** value must be an integer greater than zero.
 
@@ -743,7 +766,7 @@ Save a denormalized description of dashboard.
 
 ##### PARAMS:
 
-*  **`dashboard`**
+*  **`dashboard`** 
 
 
 ## `POST /api/dashboard/save/collection/:parent-collection-id`
@@ -752,14 +775,14 @@ Save a denormalized description of dashboard into collection with ID `:parent-co
 
 ##### PARAMS:
 
-*  **`parent-collection-id`**
+*  **`parent-collection-id`** 
 
-*  **`dashboard`**
+*  **`dashboard`** 
 
 
 ## `PUT /api/dashboard/:id`
 
-Update a `Dashboard`.
+Update a Dashboard.
 
   Usually, you just need write permissions for this Dashboard to do this (which means you have appropriate
   permissions for the Cards belonging to this Dashboard), but to change the value of `enable_embedding` you must be a
@@ -783,7 +806,7 @@ Update a `Dashboard`.
 
 *  **`collection_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
-*  **`dash-updates`**
+*  **`dash-updates`** 
 
 *  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
 
@@ -791,14 +814,14 @@ Update a `Dashboard`.
 
 *  **`embedding_params`** value may be nil, or if non-nil, value must be a valid embedding params map.
 
-*  **`id`**
+*  **`id`** 
 
 *  **`position`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
 
 ## `PUT /api/dashboard/:id/cards`
 
-Update `Cards` on a `Dashboard`. Request body should have the form:
+Update Cards on a Dashboard. Request body should have the form:
 
     {:cards [{:id     ...
               :sizeX  ...
@@ -810,9 +833,9 @@ Update `Cards` on a `Dashboard`. Request body should have the form:
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
-*  **`cards`**
+*  **`cards`** 
 
 
 ## `DELETE /api/database/:id`
@@ -821,7 +844,7 @@ Delete a `Database`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/database/`
@@ -843,7 +866,7 @@ Get `Database` with ID.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/database/:id/autocomplete_suggestions`
@@ -857,7 +880,7 @@ Return a list of autocomplete suggestions for a given PREFIX.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`prefix`** value must be a non-blank string.
 
@@ -868,7 +891,7 @@ Get a list of all `Fields` in `Database`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/database/:id/idfields`
@@ -877,7 +900,7 @@ Get a list of all primary key `Fields` for `Database`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/database/:id/metadata`
@@ -887,7 +910,7 @@ Get metadata about a `Database`, including all of its `Tables` and `Fields`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/database/:id/schema/:schema`
@@ -896,9 +919,9 @@ Returns a list of tables for the given database `id` and `schema`
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
-*  **`schema`**
+*  **`schema`** 
 
 
 ## `GET /api/database/:id/schemas`
@@ -907,7 +930,7 @@ Returns a list of all the schemas found for the database `id`
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/database/:virtual-db/metadata`
@@ -945,7 +968,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/database/:id/rescan_values`
@@ -956,7 +979,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/database/:id/sync`
@@ -965,7 +988,7 @@ Update the metadata for this `Database`. This happens asynchronously.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/database/:id/sync_schema`
@@ -976,7 +999,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/database/sample_dataset`
@@ -1019,13 +1042,13 @@ You must be a superuser to do this.
 
 *  **`caveats`** value may be nil, or if non-nil, value must be a string.
 
-*  **`is_full_sync`**
+*  **`is_full_sync`** 
 
 *  **`details`** value may be nil, or if non-nil, value must be a map.
 
-*  **`id`**
+*  **`id`** 
 
-*  **`is_on_demand`**
+*  **`is_on_demand`** 
 
 
 ## `POST /api/dataset/`
@@ -1036,7 +1059,7 @@ Execute a query and retrieve the results in the usual format.
 
 *  **`database`** value must be an integer.
 
-*  **`query`**
+*  **`query`** 
 
 
 ## `POST /api/dataset/:export-format`
@@ -1049,6 +1072,10 @@ Execute a query and download the result data as a file in the specified format.
 
 *  **`query`** value must be a valid JSON string.
 
+*  **`respond`** 
+
+*  **`raise`** 
+
 
 ## `POST /api/dataset/duration`
 
@@ -1056,9 +1083,9 @@ Get historical query execution duration.
 
 ##### PARAMS:
 
-*  **`database`**
+*  **`database`** 
 
-*  **`query`**
+*  **`query`** 
 
 
 ## `DELETE /api/email/`
@@ -1077,7 +1104,7 @@ You must be a superuser to do this.
 
 ## `PUT /api/email/`
 
-Update multiple `Settings` values.  You must be a superuser to do this.
+Update multiple email Settings. You must be a superuser to do this.
 
 You must be a superuser to do this.
 
@@ -1096,7 +1123,7 @@ Fetch a Card via a JSON Web Token signed with the `embedding-secret-key`.
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
 
 ## `GET /api/embed/card/:token/field/:field-id/remapping/:remapped-id`
@@ -1106,11 +1133,11 @@ Fetch remapped Field values. This is the same as `GET /api/field/:id/remapping/:
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
-*  **`remapped-id`**
+*  **`remapped-id`** 
 
 *  **`value`** value must be a non-blank string.
 
@@ -1121,11 +1148,11 @@ Search for values of a Field that is referenced by an embedded Card.
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
-*  **`search-field-id`**
+*  **`search-field-id`** 
 
 *  **`value`** value must be a non-blank string.
 
@@ -1138,9 +1165,9 @@ Fetch FieldValues for a Field that is referenced by an embedded Card.
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
 
 ## `GET /api/embed/card/:token/query`
@@ -1154,11 +1181,11 @@ Fetch the results of running a Card using a JSON Web Token signed with the `embe
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
-*  **`&`**
+*  **`&`** 
 
-*  **`query-params`**
+*  **`query-params`** 
 
 
 ## `GET /api/embed/card/:token/query/:export-format`
@@ -1167,13 +1194,15 @@ Like `GET /api/embed/card/query`, but returns the results as a file in the speci
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
 *  **`export-format`** value must be one of: `csv`, `json`, `xlsx`.
 
-*  **`&`**
+*  **`query-params`** 
+
+*  **`respond`** 
 
-*  **`query-params`**
+*  **`raise`** 
 
 
 ## `GET /api/embed/dashboard/:token`
@@ -1186,44 +1215,47 @@ Fetch a Dashboard via a JSON Web Token signed with the `embedding-secret-key`.
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
 
 ## `GET /api/embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id`
 
-Fetch the results of running a Card belonging to a Dashboard using a JSON Web Token signed with the `embedding-secret-key`
+Fetch the results of running a Card belonging to a Dashboard using a JSON Web Token signed with the
+  `embedding-secret-key`
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
-*  **`dashcard-id`**
+*  **`dashcard-id`** 
 
-*  **`card-id`**
+*  **`card-id`** 
 
-*  **`&`**
+*  **`&`** 
 
-*  **`query-params`**
+*  **`query-params`** 
 
 
 ## `GET /api/embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id/:export-format`
 
-Fetch the results of running a Card belonging to a Dashboard using a JSON Web Token signed with the `embedding-secret-key`
-   return the data in one of the export formats
+Fetch the results of running a Card belonging to a Dashboard using a JSON Web Token signed with the
+  `embedding-secret-key` return the data in one of the export formats
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
 *  **`export-format`** value must be one of: `csv`, `json`, `xlsx`.
 
-*  **`dashcard-id`**
+*  **`dashcard-id`** 
 
-*  **`card-id`**
+*  **`card-id`** 
 
-*  **`&`**
+*  **`query-params`** 
 
-*  **`query-params`**
+*  **`respond`** 
+
+*  **`raise`** 
 
 
 ## `GET /api/embed/dashboard/:token/field/:field-id/remapping/:remapped-id`
@@ -1233,11 +1265,11 @@ Fetch remapped Field values. This is the same as `GET /api/field/:id/remapping/:
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
-*  **`remapped-id`**
+*  **`remapped-id`** 
 
 *  **`value`** value must be a non-blank string.
 
@@ -1248,11 +1280,11 @@ Search for values of a Field that is referenced by a Card in an embedded Dashboa
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
-*  **`search-field-id`**
+*  **`search-field-id`** 
 
 *  **`value`** value must be a non-blank string.
 
@@ -1265,9 +1297,9 @@ Fetch FieldValues for a Field that is used as a param in an embedded Dashboard.
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
 
 ## `DELETE /api/field/:id/dimension`
@@ -1276,7 +1308,7 @@ Remove the dimension associated to field at ID
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/field/:id`
@@ -1285,7 +1317,7 @@ Get `Field` with ID.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/field/:id/related`
@@ -1294,7 +1326,7 @@ Return related entities.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/field/:id/remapping/:remapped-id`
@@ -1303,22 +1335,23 @@ Fetch remapped Field values.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
-*  **`remapped-id`**
+*  **`remapped-id`** 
 
-*  **`value`**
+*  **`value`** 
 
 
 ## `GET /api/field/:id/search/:search-id`
 
-Search for values of a Field that match values of another Field when breaking out by the
+Search for values of a Field with `search-id` that start with `value`. See docstring for
+  `metabase.api.field/search-values` for a more detailed explanation.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
-*  **`search-id`**
+*  **`search-id`** 
 
 *  **`value`** value must be a non-blank string.
 
@@ -1331,7 +1364,7 @@ Get the count and distinct count of `Field` with ID.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/field/:id/values`
@@ -1341,7 +1374,7 @@ If a Field's value of `has_field_values` is `list`, return a list of all the dis
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/field/field-literal%2C:field-name%2Ctype%2F:field-type/values`
@@ -1351,7 +1384,7 @@ Implementation of the field values endpoint for fields in the Saved Questions 'v
 
 ##### PARAMS:
 
-*  **`_`**
+*  **`_`** 
 
 
 ## `POST /api/field/:id/dimension`
@@ -1360,7 +1393,7 @@ Sets the dimension for the given field at ID
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`type`** value must be one of: `external`, `internal`.
 
@@ -1378,7 +1411,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/field/:id/rescan_values`
@@ -1390,7 +1423,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/field/:id/values`
@@ -1400,7 +1433,7 @@ Update the fields values and human-readable values for a `Field` whose special t
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`value-pairs`** value must be an array. Each value must be an array.
 
@@ -1423,11 +1456,13 @@ Update `Field` with ID.
 
 *  **`has_field_values`** value may be nil, or if non-nil, value must be one of: `auto-list`, `list`, `none`, `search`.
 
+*  **`settings`** value may be nil, or if non-nil, value must be a map.
+
 *  **`caveats`** value may be nil, or if non-nil, value must be a non-blank string.
 
 *  **`fk_target_field_id`** value may be nil, or if non-nil, value must be an integer greater than zero.
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/geojson/:key`
@@ -1453,13 +1488,11 @@ You must be a superuser to do this.
 
 ## `DELETE /api/metric/:id`
 
-Delete a `Metric`.
-
-You must be a superuser to do this.
+Archive a Metric. (DEPRECATED -- Just pass updated value of `:archived` to the `PUT` endpoint instead.)
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`revision_message`** value must be a non-blank string.
 
@@ -1470,18 +1503,16 @@ Fetch *all* `Metrics`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/metric/:id`
 
 Fetch `Metric` with ID.
 
-You must be a superuser to do this.
-
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/metric/:id/related`
@@ -1490,31 +1521,27 @@ Return related entities.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/metric/:id/revisions`
 
 Fetch `Revisions` for `Metric` with ID.
 
-You must be a superuser to do this.
-
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/metric/`
 
 Create a new `Metric`.
 
-You must be a superuser to do this.
-
 ##### PARAMS:
 
 *  **`name`** value must be a non-blank string.
 
-*  **`description`**
+*  **`description`** value may be nil, or if non-nil, value must be a string.
 
 *  **`table_id`** value must be an integer greater than zero.
 
@@ -1525,11 +1552,9 @@ You must be a superuser to do this.
 
 Revert a `Metric` to a prior `Revision`.
 
-You must be a superuser to do this.
-
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`revision_id`** value must be an integer greater than zero.
 
@@ -1538,18 +1563,28 @@ You must be a superuser to do this.
 
 Update a `Metric` with ID.
 
-You must be a superuser to do this.
-
 ##### PARAMS:
 
-*  **`id`**
+*  **`points_of_interest`** value may be nil, or if non-nil, value must be a string.
 
-*  **`definition`** value must be a map.
+*  **`description`** value may be nil, or if non-nil, value must be a string.
 
-*  **`name`** value must be a non-blank string.
+*  **`archived`** value may be nil, or if non-nil, value must be a boolean.
+
+*  **`definition`** value may be nil, or if non-nil, value must be a map.
 
 *  **`revision_message`** value must be a non-blank string.
 
+*  **`show_in_getting_started`** value may be nil, or if non-nil, value must be a boolean.
+
+*  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
+
+*  **`caveats`** value may be nil, or if non-nil, value must be a string.
+
+*  **`id`** 
+
+*  **`how_is_this_calculated`** value may be nil, or if non-nil, value must be a string.
+
 
 ## `PUT /api/metric/:id/important_fields`
 
@@ -1560,7 +1595,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`important_field_ids`** value must be an array. Each value must be an integer greater than zero.
 
@@ -1572,11 +1607,11 @@ Notification about a potential schema change to one of our `Databases`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
-*  **`table_id`**
+*  **`table_id`** 
 
-*  **`table_name`**
+*  **`table_name`** 
 
 
 ## `DELETE /api/permissions/group/:group-id`
@@ -1587,7 +1622,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`group-id`**
+*  **`group-id`** 
 
 
 ## `DELETE /api/permissions/membership/:id`
@@ -1598,7 +1633,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/permissions/graph`
@@ -1623,7 +1658,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/permissions/membership`
@@ -1687,7 +1722,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`group-id`**
+*  **`group-id`** 
 
 *  **`name`** value must be a non-blank string.
 
@@ -1698,7 +1733,7 @@ Fetch a Card you're considering embedding by passing a JWT TOKEN.
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
 
 ## `GET /api/preview-embed/card/:token/query`
@@ -1707,20 +1742,20 @@ Fetch the query results for a Card you're considering embedding by passing a JWT
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
-*  **`&`**
+*  **`&`** 
 
-*  **`query-params`**
+*  **`query-params`** 
 
 
 ## `GET /api/preview-embed/dashboard/:token`
 
-Fetch a Dashboard you're considering embedding by passing a JWT TOKEN.
+Fetch a Dashboard you're considering embedding by passing a JWT TOKEN. 
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
 
 ## `GET /api/preview-embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id`
@@ -1729,15 +1764,15 @@ Fetch the results of running a Card belonging to a Dashboard you're considering
 
 ##### PARAMS:
 
-*  **`token`**
+*  **`token`** 
 
-*  **`dashcard-id`**
+*  **`dashcard-id`** 
 
-*  **`card-id`**
+*  **`card-id`** 
 
-*  **`&`**
+*  **`&`** 
 
-*  **`query-params`**
+*  **`query-params`** 
 
 
 ## `GET /api/public/card/:uuid`
@@ -1747,7 +1782,7 @@ Fetch a publicly-accessible Card an return query results as well as `:card` info
 
 ##### PARAMS:
 
-*  **`uuid`**
+*  **`uuid`** 
 
 
 ## `GET /api/public/card/:uuid/field/:field-id/remapping/:remapped-id`
@@ -1757,11 +1792,11 @@ Fetch remapped Field values. This is the same as `GET /api/field/:id/remapping/:
 
 ##### PARAMS:
 
-*  **`uuid`**
+*  **`uuid`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
-*  **`remapped-id`**
+*  **`remapped-id`** 
 
 *  **`value`** value must be a non-blank string.
 
@@ -1772,11 +1807,11 @@ Search for values of a Field that is referenced by a public Card.
 
 ##### PARAMS:
 
-*  **`uuid`**
+*  **`uuid`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
-*  **`search-field-id`**
+*  **`search-field-id`** 
 
 *  **`value`** value must be a non-blank string.
 
@@ -1789,9 +1824,9 @@ Fetch FieldValues for a Field that is referenced by a public Card.
 
 ##### PARAMS:
 
-*  **`uuid`**
+*  **`uuid`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
 
 ## `GET /api/public/card/:uuid/query`
@@ -1801,7 +1836,7 @@ Fetch a publicly-accessible Card an return query results as well as `:card` info
 
 ##### PARAMS:
 
-*  **`uuid`**
+*  **`uuid`** 
 
 *  **`parameters`** value may be nil, or if non-nil, value must be a valid JSON string.
 
@@ -1813,12 +1848,16 @@ Fetch a publicly-accessible Card and return query results in the specified forma
 
 ##### PARAMS:
 
-*  **`uuid`**
+*  **`uuid`** 
 
 *  **`export-format`** value must be one of: `csv`, `json`, `xlsx`.
 
 *  **`parameters`** value may be nil, or if non-nil, value must be a valid JSON string.
 
+*  **`respond`** 
+
+*  **`raise`** 
+
 
 ## `GET /api/public/dashboard/:uuid`
 
@@ -1826,7 +1865,7 @@ Fetch a publicly-accessible Dashboard. Does not require auth credentials. Public
 
 ##### PARAMS:
 
-*  **`uuid`**
+*  **`uuid`** 
 
 
 ## `GET /api/public/dashboard/:uuid/card/:card-id`
@@ -1836,9 +1875,9 @@ Fetch the results for a Card in a publicly-accessible Dashboard. Does not requir
 
 ##### PARAMS:
 
-*  **`uuid`**
+*  **`uuid`** 
 
-*  **`card-id`**
+*  **`card-id`** 
 
 *  **`parameters`** value may be nil, or if non-nil, value must be a valid JSON string.
 
@@ -1850,11 +1889,11 @@ Fetch remapped Field values. This is the same as `GET /api/field/:id/remapping/:
 
 ##### PARAMS:
 
-*  **`uuid`**
+*  **`uuid`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
-*  **`remapped-id`**
+*  **`remapped-id`** 
 
 *  **`value`** value must be a non-blank string.
 
@@ -1865,11 +1904,11 @@ Search for values of a Field that is referenced by a Card in a public Dashboard.
 
 ##### PARAMS:
 
-*  **`uuid`**
+*  **`uuid`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
-*  **`search-field-id`**
+*  **`search-field-id`** 
 
 *  **`value`** value must be a non-blank string.
 
@@ -1882,9 +1921,9 @@ Fetch FieldValues for a Field that is referenced by a Card in a public Dashboard
 
 ##### PARAMS:
 
-*  **`uuid`**
+*  **`uuid`** 
 
-*  **`field-id`**
+*  **`field-id`** 
 
 
 ## `GET /api/public/oembed`
@@ -1908,7 +1947,7 @@ Delete a Pulse. (DEPRECATED -- don't delete a Pulse anymore -- archive it instea
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/pulse/`
@@ -1926,7 +1965,7 @@ Fetch `Pulse` with ID.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/pulse/form_input`
@@ -1940,7 +1979,7 @@ Get HTML rendering of a Card with `id`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/pulse/preview_card_info/:id`
@@ -1949,7 +1988,7 @@ Get JSON object containing HTML rendering of a Card with `id` and other informat
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/pulse/preview_card_png/:id`
@@ -1958,7 +1997,7 @@ Get PNG rendering of a Card with `id`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/pulse/`
@@ -2005,7 +2044,7 @@ Update a Pulse with `id`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
 
@@ -2019,7 +2058,7 @@ Update a Pulse with `id`.
 
 *  **`archived`** value may be nil, or if non-nil, value must be a boolean.
 
-*  **`pulse-updates`**
+*  **`pulse-updates`** 
 
 
 ## `GET /api/revision/`
@@ -2059,13 +2098,11 @@ Search Cards, Dashboards, Collections and Pulses for the substring `q`.
 
 ## `DELETE /api/segment/:id`
 
-Delete a `Segment`.
-
-You must be a superuser to do this.
+Archive a Segment. (DEPRECATED -- Just pass updated value of `:archived` to the `PUT` endpoint instead.)
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`revision_message`** value must be a non-blank string.
 
@@ -2079,11 +2116,9 @@ Fetch *all* `Segments`.
 
 Fetch `Segment` with ID.
 
-You must be a superuser to do this.
-
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/segment/:id/related`
@@ -2092,31 +2127,27 @@ Return related entities.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/segment/:id/revisions`
 
 Fetch `Revisions` for `Segment` with ID.
 
-You must be a superuser to do this.
-
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/segment/`
 
 Create a new `Segment`.
 
-You must be a superuser to do this.
-
 ##### PARAMS:
 
 *  **`name`** value must be a non-blank string.
 
-*  **`description`**
+*  **`description`** value may be nil, or if non-nil, value must be a string.
 
 *  **`table_id`** value must be an integer greater than zero.
 
@@ -2127,11 +2158,9 @@ You must be a superuser to do this.
 
 Revert a `Segement` to a prior `Revision`.
 
-You must be a superuser to do this.
-
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`revision_id`** value must be an integer greater than zero.
 
@@ -2140,18 +2169,26 @@ You must be a superuser to do this.
 
 Update a `Segment` with ID.
 
-You must be a superuser to do this.
-
 ##### PARAMS:
 
-*  **`id`**
+*  **`points_of_interest`** value may be nil, or if non-nil, value must be a string.
 
-*  **`name`** value must be a non-blank string.
+*  **`description`** value may be nil, or if non-nil, value must be a string.
 
-*  **`definition`** value must be a map.
+*  **`archived`** value may be nil, or if non-nil, value must be a boolean.
+
+*  **`definition`** value may be nil, or if non-nil, value must be a map.
 
 *  **`revision_message`** value must be a non-blank string.
 
+*  **`show_in_getting_started`** value may be nil, or if non-nil, value must be a boolean.
+
+*  **`name`** value may be nil, or if non-nil, value must be a non-blank string.
+
+*  **`caveats`** value may be nil, or if non-nil, value must be a string.
+
+*  **`id`** 
+
 
 ## `DELETE /api/session/`
 
@@ -2159,7 +2196,7 @@ Logout.
 
 ##### PARAMS:
 
-*  **`session_id`** value must be a non-blank string.
+*  **`metabase-session-id`** 
 
 
 ## `GET /api/session/password_reset_token_valid`
@@ -2186,7 +2223,9 @@ Login.
 
 *  **`password`** value must be a non-blank string.
 
-*  **`remote-address`**
+*  **`remote-address`** 
+
+*  **`request`** 
 
 
 ## `POST /api/session/forgot_password`
@@ -2195,11 +2234,11 @@ Send a reset email when user has forgotten their password.
 
 ##### PARAMS:
 
-*  **`server-name`**
+*  **`server-name`** 
 
 *  **`email`** value must be a valid email address.
 
-*  **`remote-address`**
+*  **`remote-address`** 
 
 
 ## `POST /api/session/google_auth`
@@ -2210,7 +2249,9 @@ Login with Google Auth.
 
 *  **`token`** value must be a non-blank string.
 
-*  **`remote-address`**
+*  **`remote-address`** 
+
+*  **`request`** 
 
 
 ## `POST /api/session/reset_password`
@@ -2223,6 +2264,8 @@ Reset password with a reset token.
 
 *  **`password`** Insufficient password strength
 
+*  **`request`** 
+
 
 ## `GET /api/setting/`
 
@@ -2250,7 +2293,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`settings`**
+*  **`settings`** 
 
 
 ## `PUT /api/setting/:key`
@@ -2264,7 +2307,7 @@ You must be a superuser to do this.
 
 *  **`key`** value must be a non-blank string.
 
-*  **`value`**
+*  **`value`** 
 
 
 ## `GET /api/setup/admin_checklist`
@@ -2281,7 +2324,7 @@ Special endpoint for creating the first user during setup.
 
 ##### PARAMS:
 
-*  **`engine`**
+*  **`engine`** 
 
 *  **`schedules`** value may be nil, or if non-nil, value must be a valid map of schedule maps for a DB.
 
@@ -2291,19 +2334,21 @@ Special endpoint for creating the first user during setup.
 
 *  **`first_name`** value must be a non-blank string.
 
+*  **`request`** 
+
 *  **`password`** Insufficient password strength
 
-*  **`name`**
+*  **`name`** 
 
-*  **`is_full_sync`**
+*  **`is_full_sync`** 
 
 *  **`site_name`** value must be a non-blank string.
 
 *  **`token`** Token does not match the setup token.
 
-*  **`details`**
+*  **`details`** 
 
-*  **`is_on_demand`**
+*  **`is_on_demand`** 
 
 *  **`last_name`** value must be a non-blank string.
 
@@ -2316,7 +2361,7 @@ Validate that we can connect to a database given a set of details.
 
 *  **`engine`** value must be a valid database engine.
 
-*  **`details`**
+*  **`details`** 
 
 *  **`token`** Token does not match the setup token.
 
@@ -2333,7 +2378,7 @@ You must be a superuser to do this.
 
 *  **`metabot-enabled`** value must be a boolean.
 
-*  **`slack-settings`**
+*  **`slack-settings`** 
 
 
 ## `GET /api/table/`
@@ -2347,7 +2392,7 @@ Get `Table` with ID.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/table/:id/fks`
@@ -2356,7 +2401,7 @@ Get all foreign keys whose destination is a `Field` that belongs to this `Table`
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/table/:id/query_metadata`
@@ -2369,7 +2414,7 @@ Get metadata about a `Table` useful for running queries.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`include_sensitive_fields`** value may be nil, or if non-nil, value must be a valid boolean string ('true' or 'false').
 
@@ -2380,7 +2425,7 @@ Return related entities.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/table/card__:id/fks`
@@ -2395,7 +2440,7 @@ Return metadata for the 'virtual' table for a Card.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/table/:id/discard_values`
@@ -2407,7 +2452,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `POST /api/table/:id/rescan_values`
@@ -2419,7 +2464,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `PUT /api/table/:id`
@@ -2428,7 +2473,7 @@ Update `Table` with ID.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`display_name`** value may be nil, or if non-nil, value must be a non-blank string.
 
@@ -2445,6 +2490,35 @@ Update `Table` with ID.
 *  **`show_in_getting_started`** value may be nil, or if non-nil, value must be a boolean.
 
 
+## `GET /api/task/`
+
+Fetch a list of recent tasks stored as Task History
+
+You must be a superuser to do this.
+
+##### PARAMS:
+
+*  **`limit`** value may be nil, or if non-nil, value must be a valid integer greater than zero.
+
+*  **`offset`** value may be nil, or if non-nil, value must be a valid integer greater than or equal to zero.
+
+
+## `GET /api/task/:id`
+
+Get `TaskHistory` entry with ID.
+
+##### PARAMS:
+
+*  **`id`** 
+
+
+## `GET /api/task/info`
+
+Return raw data about all scheduled tasks (i.e., Quartz Jobs and Triggers).
+
+You must be a superuser to do this.
+
+
 ## `GET /api/tiles/:zoom/:x/:y/:lat-field-id/:lon-field-id/:lat-col-idx/:lon-col-idx/`
 
 This endpoints provides an image with the appropriate pins rendered given a MBQL QUERY (passed as a GET query
@@ -2479,7 +2553,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/user/`
@@ -2499,7 +2573,7 @@ Fetch a `User`. You must be fetching yourself *or* be a superuser.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/user/current`
@@ -2521,7 +2595,9 @@ You must be a superuser to do this.
 
 *  **`email`** value must be a valid email address.
 
-*  **`password`**
+*  **`password`** 
+
+*  **`group_ids`** value may be nil, or if non-nil, value must be an array. Each value must be an integer greater than zero.
 
 *  **`login_attributes`** value may be nil, or if non-nil, value must be a map with each value either a string or number.
 
@@ -2534,7 +2610,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `PUT /api/user/:id`
@@ -2543,7 +2619,7 @@ Update an existing, active `User`.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`email`** value may be nil, or if non-nil, value must be a valid email address.
 
@@ -2551,7 +2627,9 @@ Update an existing, active `User`.
 
 *  **`last_name`** value may be nil, or if non-nil, value must be a non-blank string.
 
-*  **`is_superuser`**
+*  **`group_ids`** value may be nil, or if non-nil, value must be an array. Each value must be an integer greater than zero.
+
+*  **`is_superuser`** value may be nil, or if non-nil, value must be a boolean.
 
 *  **`login_attributes`** value may be nil, or if non-nil, value must be a map with each value either a string or number.
 
@@ -2562,11 +2640,11 @@ Update a user's password.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 *  **`password`** Insufficient password strength
 
-*  **`old_password`**
+*  **`old_password`** 
 
 
 ## `PUT /api/user/:id/qbnewb`
@@ -2575,7 +2653,7 @@ Indicate that a user has been informed about the vast intricacies of 'the' Query
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `PUT /api/user/:id/reactivate`
@@ -2586,7 +2664,7 @@ You must be a superuser to do this.
 
 ##### PARAMS:
 
-*  **`id`**
+*  **`id`** 
 
 
 ## `GET /api/util/logs`
@@ -2598,7 +2676,7 @@ You must be a superuser to do this.
 
 ## `GET /api/util/random_token`
 
-Return a cryptographically secure random 32-byte token, encoded as a hexidecimal string.
+Return a cryptographically secure random 32-byte token, encoded as a hexadecimal string.
    Intended for use when creating a value for `embedding-secret-key`.
 
 
@@ -2616,4 +2694,4 @@ Endpoint that checks if the supplied password meets the currently configured pas
 
 ##### PARAMS:
 
-*  **`password`** Insufficient password strength
+*  **`password`** Insufficient password strength
\ No newline at end of file
diff --git a/modules/drivers/snowflake/project.clj b/modules/drivers/snowflake/project.clj
index 4dfb565206bd2439b89e50eea9d8679e5adeac5d..47a23e171bbd0c3a87cdfec8e19b21d839f81b32 100644
--- a/modules/drivers/snowflake/project.clj
+++ b/modules/drivers/snowflake/project.clj
@@ -1,8 +1,8 @@
-(defproject metabase/snowflake-driver "1.0.0-SNAPSHOT-3.6.20"
+(defproject metabase/snowflake-driver "1.0.0-SNAPSHOT-3.6.27"
   :min-lein-version "2.5.0"
 
   :dependencies
-  [[net.snowflake/snowflake-jdbc "3.6.21"]]
+  [[net.snowflake/snowflake-jdbc "3.6.27"]]
 
   :profiles
   {:provided
diff --git a/modules/drivers/snowflake/resources/metabase-plugin.yaml b/modules/drivers/snowflake/resources/metabase-plugin.yaml
index bbfdd6e483de07569b76e0101f860bdd144104db..2d9b314b873a482445cd7517f0c041d72d1a68d1 100644
--- a/modules/drivers/snowflake/resources/metabase-plugin.yaml
+++ b/modules/drivers/snowflake/resources/metabase-plugin.yaml
@@ -1,6 +1,6 @@
 info:
   name: Metabase Snowflake Driver
-  version: 1.0.0-SNAPSHOT-3.6.20
+  version: 1.0.0-SNAPSHOT-3.6.27
   description: Allows Metabase to connect to Snowflake databases.
 driver:
   name: snowflake
@@ -33,6 +33,7 @@ driver:
     - name: role
       display-name: Role
       placeholder: my_role
+    - additional-options
   connection-properties-include-tunnel-config: true
 init:
   - step: load-namespace
diff --git a/modules/drivers/snowflake/src/metabase/driver/snowflake.clj b/modules/drivers/snowflake/src/metabase/driver/snowflake.clj
index 6917bb01b601f8aa838ceab4316c219c69a6f363..8133fce538f2e8542dbf3041d3f396550b05f75f 100644
--- a/modules/drivers/snowflake/src/metabase/driver/snowflake.clj
+++ b/modules/drivers/snowflake/src/metabase/driver/snowflake.clj
@@ -13,6 +13,7 @@
              [common :as driver.common]
              [sql-jdbc :as sql-jdbc]]
             [metabase.driver.sql-jdbc
+             [common :as sql-jdbc.common]
              [connection :as sql-jdbc.conn]
              [execute :as sql-jdbc.execute]
              [sync :as sql-jdbc.sync]]
@@ -39,22 +40,26 @@
                account)]
     ;; it appears to be the case that their JDBC driver ignores `db` -- see my bug report at
     ;; https://support.snowflake.net/s/question/0D50Z00008WTOMCSA5/
-    (merge {:classname                                  "net.snowflake.client.jdbc.SnowflakeDriver"
-            :subprotocol                                "snowflake"
-            :subname                                    (str "//" host ".snowflakecomputing.com/")
-            :client_metadata_request_use_connection_ctx true
-            :ssl                                        true
-            ;; other SESSION parameters
-            ;; use the same week start we use for all the other drivers
-            :week_start                                 7
-            ;; not 100% sure why we need to do this but if we don't set the connection to UTC our report timezone
-            ;; stuff doesn't work, even though we ultimately override this when we set the session timezone
-            :timezone                                   "UTC"}
-           (-> opts
-               ;; original version of the Snowflake driver incorrectly used `dbname` in the details fields instead of
-               ;; `db`. If we run across `dbname`, correct our behavior
-               (set/rename-keys {:dbname :db})
-               (dissoc :host :port :timezone)))))
+    (-> (merge {:classname                                  "net.snowflake.client.jdbc.SnowflakeDriver"
+                :subprotocol                                "snowflake"
+                :subname                                    (str "//" host ".snowflakecomputing.com/")
+                :client_metadata_request_use_connection_ctx true
+                :ssl                                        true
+                ;; keep open connections open indefinitely instead of closing them. See #9674 and
+                ;; https://docs.snowflake.net/manuals/sql-reference/parameters.html#client-session-keep-alive
+                :client_session_keep_alive                  true
+                ;; other SESSION parameters
+                ;; use the same week start we use for all the other drivers
+                :week_start                                 7
+                ;; not 100% sure why we need to do this but if we don't set the connection to UTC our report timezone
+                ;; stuff doesn't work, even though we ultimately override this when we set the session timezone
+                :timezone                                   "UTC"}
+               (-> opts
+                   ;; original version of the Snowflake driver incorrectly used `dbname` in the details fields instead of
+                   ;; `db`. If we run across `dbname`, correct our behavior
+                   (set/rename-keys {:dbname :db})
+                   (dissoc :host :port :timezone)))
+        (sql-jdbc.common/handle-additional-options opts))))
 
 (defmethod sql-jdbc.sync/database-type->base-type :snowflake [_ base-type]
   ({:NUMBER                     :type/Number
diff --git a/resources/log4j.properties b/resources/log4j.properties
index 2ba0837e28f20c90d51b51f835bac05bc2b04fdf..ca4192baa2fe05391a98311d8c2e128a32fa04c8 100644
--- a/resources/log4j.properties
+++ b/resources/log4j.properties
@@ -15,6 +15,7 @@ log4j.appender.file.layout=org.apache.log4j.PatternLayout
 log4j.appender.file.layout.ConversionPattern=%d [%t] %-5p%c - %m%n
 
 # customizations to logging by package
+
 log4j.logger.metabase.driver=INFO
 log4j.logger.metabase.plugins=DEBUG
 log4j.logger.metabase.middleware=DEBUG
@@ -23,12 +24,15 @@ log4j.logger.metabase.query-processor.permissions=INFO
 log4j.logger.metabase.query-processor=INFO
 log4j.logger.metabase.sync=DEBUG
 log4j.logger.metabase.models.field-values=INFO
-# NOCOMMIT
+
+# TODO - we can dial these back a bit once we are satisfied the async stuff isn't so new (0.33.0+)
+log4j.logger.metabase.async.api-response=DEBUG
 log4j.logger.metabase.async.semaphore-channel=DEBUG
 log4j.logger.metabase.async.util=DEBUG
 log4j.logger.metabase.middleware.async=DEBUG
 log4j.logger.metabase.query-processor.async=DEBUG
-log4j.logger.metabase.async.api-response=DEBUG
+
 log4j.logger.metabase=INFO
+
 # c3p0 connection pools tend to log useless warnings way too often; only log actual errors
 log4j.logger.com.mchange=ERROR
diff --git a/src/metabase/api/dashboard.clj b/src/metabase/api/dashboard.clj
index bb6f2b99d1c240289819e12cbcb54a9106ba266a..ff60886538d06e2ba3dafd33dd95eb66040ed8be 100644
--- a/src/metabase/api/dashboard.clj
+++ b/src/metabase/api/dashboard.clj
@@ -217,14 +217,14 @@
                         :collection_id       collection_id
                         :collection_position collection_position}
         dashboard      (db/transaction
-                            ;; Adding a new dashboard at `collection_position` could cause other dashboards in this collection to change
-                            ;; position, check that and fix up if needed
-                            (api/maybe-reconcile-collection-position! dashboard-data)
-                            ;; Ok, now save the Dashboard
-                            (u/prog1 (db/insert! Dashboard dashboard-data)
-                                    ;; Get cards from existing dashboard and associate to copied dashboard
-                                    (doseq [card (:ordered_cards existing-dashboard)]
-                                      (api/check-500 (dashboard/add-dashcard! <> (:card_id card) card)))))]
+                         ;; Adding a new dashboard at `collection_position` could cause other dashboards in this
+                         ;; collection to change position, check that and fix up if needed
+                         (api/maybe-reconcile-collection-position! dashboard-data)
+                         ;; Ok, now save the Dashboard
+                         (u/prog1 (db/insert! Dashboard dashboard-data)
+                           ;; Get cards from existing dashboard and associate to copied dashboard
+                           (doseq [card (:ordered_cards existing-dashboard)]
+                             (api/check-500 (dashboard/add-dashcard! <> (:card_id card) card)))))]
     (events/publish-event! :dashboard-create dashboard)))
 
 
@@ -233,13 +233,7 @@
 (api/defendpoint GET "/:id"
   "Get `Dashboard` with ID."
   [id]
-  (u/prog1 (-> (Dashboard id)
-               api/check-404
-               (hydrate [:ordered_cards :card :series] :can_write)
-               api/read-check
-               api/check-not-archived
-               hide-unreadable-cards
-               add-query-average-durations)
+  (u/prog1 (get-dashboard id)
     (events/publish-event! :dashboard-read (assoc <> :actor_id api/*current-user-id*))))
 
 
diff --git a/src/metabase/api/dataset.clj b/src/metabase/api/dataset.clj
index 28024af1e50772a08a9c0a8599544788bab5092c..4bfa5f7e343e268bce132facc48187926ba86fe0 100644
--- a/src/metabase/api/dataset.clj
+++ b/src/metabase/api/dataset.clj
@@ -47,7 +47,7 @@
   (let [source-card-id (query->source-card-id query)
         options        {:executed-by api/*current-user-id*, :context :ad-hoc,
                         :card-id     source-card-id,        :nested? (boolean source-card-id)}]
-    (qp.async/process-query-and-save-with-max! query options)))
+    (qp.async/process-query-and-save-with-max-results-constraints! query options)))
 
 
 ;;; ----------------------------------- Downloading Query Results in Other Formats -----------------------------------
diff --git a/src/metabase/async/api_response.clj b/src/metabase/async/api_response.clj
index 0f6b1340ef375554eda7e23bff8311a15c777579..881d192a9ff3f1b743e203247dd59821a2b34db4 100644
--- a/src/metabase/async/api_response.clj
+++ b/src/metabase/async/api_response.clj
@@ -1,4 +1,11 @@
 (ns metabase.async.api-response
+  "Handle ring response maps that contain a core.async chan in the :body key:
+
+    {:status 200
+     :body (a/chan)}
+
+  and send strings (presumibly \n) as heartbeats to the client until the real results (a seq) is received, then stream
+  that to the client."
   (:require [cheshire.core :as json]
             [clojure.core.async :as a]
             [clojure.java.io :as io]
@@ -19,6 +26,7 @@
 (def ^:private keepalive-interval-ms
   "Interval between sending newline characters to keep Heroku from terminating requests like queries that take a long
   time to complete."
+  ;; 1 second
   (* 1 1000))
 
 (def ^:private absolute-max-keepalive-ms
@@ -29,18 +37,16 @@
   ;; 4 hours
   (* 4 60 60 1000))
 
-;; Handle ring response maps that contain a core.async chan in the :body key:
-;;
-;; {:status 200
-;;  :body (a/chan)}
-;;
-;; and send strings (presumibly \n) as heartbeats to the client until the real results (a seq) is received, then
-;; stream that to the client
-(defn- write-keepalive-character [^Writer out]
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                  Writing Results of Async Keep-alive Channel                                   |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(defn- write-keepalive-character! [^Writer out]
   (try
-    ;; a newline padding character as it's harmless and will allow us to check if the client
-    ;; is connected. If sending this character fails because the connection is closed, the
-    ;; chan will then close. Newlines are no-ops when reading JSON which this depends upon.
+    ;; a newline padding character as it's harmless and will allow us to check if the client is connected. If sending
+    ;; this character fails because the connection is closed, the chan will then close. Newlines are no-ops when
+    ;; reading JSON which this depends upon.
     (.write out (str \newline))
     (.flush out)
     true
@@ -52,7 +58,7 @@
       false)))
 
 ;; `chunkk` named as such to avoid conflict with `clojure.core/chunk`
-(defn- write-response-chunk [chunkk, ^Writer out]
+(defn- write-response-chunk! [chunkk, ^Writer out]
   (cond
     ;; An error has occurred, let the user know
     (instance? Throwable chunkk)
@@ -65,12 +71,16 @@
     :else
     (log/error (trs "Unexpected output in async API response") (class chunkk))))
 
-(defn- write-channel-to-output-stream [chan, ^Writer out]
+(defn- write-chan-vals-to-writer!
+  "Write whatever val(s) come into `chan` onto the Writer wrapping our OutputStream. Vals should be either
+  `::keepalive`, meaning we should write a keepalive newline character to the Writer, or some other value, which is
+  the actual response we've been waiting for (at this point we can close both the Writer and the channel)."
+  [chan, ^Writer out]
   (a/go-loop [chunkk (a/<! chan)]
     (cond
-      (= chunkk ::keepalive)
       ;; keepalive chunkk
-      (if (write-keepalive-character out)
+      (= chunkk ::keepalive)
+      (if (write-keepalive-character! out)
         (recur (a/<! chan))
         (do
           (a/close! chan)
@@ -86,7 +96,7 @@
       (future
         (try
           ;; chunkk *might* be `nil` if the channel already go closed.
-          (write-response-chunk chunkk out)
+          (write-response-chunk! chunkk out)
           (finally
             ;; should already be closed, but just to be safe
             (a/close! chan)
@@ -94,15 +104,12 @@
             (.close out))))))
   nil)
 
-
-(extend-protocol ring.protocols/StreamableResponseBody
-  ManyToManyChannel
-  (write-body-to-stream [chan _ ^OutputStream output-stream]
-    (log/debug (u/format-color 'green (trs "starting streaming response")))
-    (write-channel-to-output-stream chan (io/writer output-stream))))
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                            Async Keep-alive Channel                                            |
+;;; +----------------------------------------------------------------------------------------------------------------+
 
 
-(defn- start-async-keepalive-loop
+(defn- start-async-keepalive-loop!
   "Starts a go-loop that will send `::keepalive` messages to `output-chan` every second until `input-chan` either
   produces a response or one of the two channels is closed. If `output-chan` is closed (because there's no longer
   anywhere to write to -- the connection was canceled), closes `input-chan`; this can and is used by producers such as
@@ -155,19 +162,32 @@
               (a/close! output-chan)
               (a/close! input-chan))))))))
 
-(defn- async-keepalive-chan [input-chan]
+(defn- async-keepalive-channel
+  "Given a core.async channel `input-chan` which will (presumably) eventually receive an asynchronous result, return a
+  new channel 'wrapping' the original that will write keepalive bytes until the actual result is obtained."
+  [input-chan]
   ;; Output chan only needs to hold on to the last message it got, for example no point in writing multiple `\n`
   ;; characters if the consumer didn't get a chance to consume them, and no point writing `\n` before writing the
   ;; actual response
-  (let [output-chan (a/chan (a/sliding-buffer 1))]
-    (start-async-keepalive-loop input-chan output-chan)
-    output-chan))
+  (u/prog1 (a/chan (a/sliding-buffer 1))
+    (start-async-keepalive-loop! input-chan <>)))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                    Telling Ring & Compojure how to handle core.async channel API responses                     |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+;; Synchronous Compojure endpoint (e.g. `defendpoint`) responses go directly to here. Async endpoint
+;; (`defendpoint-async`) responses go to Sendable and then to here. So technically this affects both sync & async.
 
-(defn- async-keepalive-response [input-chan]
-  (assoc (response/response (async-keepalive-chan input-chan))
-    :content-type "applicaton/json; charset=utf-8"))
+(extend-protocol ring.protocols/StreamableResponseBody
+  ManyToManyChannel
+  (write-body-to-stream [chan _ ^OutputStream output-stream]
+    (log/debug (u/format-color 'green (trs "starting streaming response")))
+    (write-chan-vals-to-writer! (async-keepalive-channel chan) (io/writer output-stream))))
 
 (extend-protocol Sendable
   ManyToManyChannel
   (send* [input-chan _ respond _]
-    (respond (async-keepalive-response input-chan))))
+    (respond (assoc (response/response input-chan)
+               :content-type "applicaton/json; charset=utf-8"))))
diff --git a/src/metabase/mbql/schema.clj b/src/metabase/mbql/schema.clj
index bdb8c9e4f37254859052b53f1b34f9a2008a317f..c7284b06701f09c44b345357c76f8e8a4672f260 100644
--- a/src/metabase/mbql/schema.clj
+++ b/src/metabase/mbql/schema.clj
@@ -658,7 +658,7 @@
           :question
           :xlsx-download))
 
-;; TODO - this schema is somewhat misleading because if you use a function like `qp/process-query-and-save-with-max!`
+;; TODO - this schema is somewhat misleading because if you use a function like `qp/process-query-and-save-with-max-results-constraints!`
 ;; some of these keys (e.g. `:context`) are in fact required
 (def Info
   "Schema for query `:info` dictionary, which is used for informational purposes to record information about how a query
diff --git a/src/metabase/middleware/json.clj b/src/metabase/middleware/json.clj
index 34b067d88eb5ad76db9e8af85900a5440e732289..e032187c790bcaf6119bce6497005bd36bb5a661 100644
--- a/src/metabase/middleware/json.clj
+++ b/src/metabase/middleware/json.clj
@@ -62,29 +62,6 @@
         (respond ring.json/default-malformed-response))
       (handler request respond raise))))
 
-#_(defn check-application-type-headers
-  "We don't support API requests with any type of content encoding other than JSON so let's be nice and make that
-  explicit. Added benefit is that it reduces CSRF surface because POSTing a form with JSON content encoding isn't so
-  easy to do."
-  [handler]
-  (fn
-    [{:keys [request-method body], {:strs [content-type]} :headers, :as request} respond raise]
-    ;; GET or DELETE requests with no body we can go ahead and proceed without Content-Type headers, since they
-    ;; generally don't have bodies.
-    ;;
-    ;; POST/PUT requests always require Content-Type: application/json. GET/DELETE requests that specify any other
-    ;; content type aren't allowed.
-    (if (or (and (#{:get :delete} request-method)
-                 (nil? content-type))
-            (#'ring.json/json-request? request))
-      (handler request respond raise)
-      (respond
-       {:status  400
-        :headers {"Content-Type" "text/plain"}
-        :body    (str (tru "Metabase only supports JSON requests.")
-                      " "
-                      (tru "Make sure you set a 'Content-Type: application/json' header."))}))))
-
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                            Streaming JSON Responses                                            |
diff --git a/src/metabase/middleware/session.clj b/src/metabase/middleware/session.clj
index 9ccb9fc5bde409e83bac86a33368400a76e84688..640f41499a6812a98a6864286c6a3563e0558ae8 100644
--- a/src/metabase/middleware/session.clj
+++ b/src/metabase/middleware/session.clj
@@ -83,7 +83,6 @@
        (merge
         {:same-site :lax
          :http-only true
-         :path      "/api"
          :max-age   (config/config-int :max-session-age)}
         ;; If the authentication request request was made over HTTPS (hopefully always except for local dev instances)
         ;; add `Secure` attribute so the cookie is only sent over HTTPS.
diff --git a/src/metabase/models/task_history.clj b/src/metabase/models/task_history.clj
index 7fe870220b19271775b2473490e7817db4f723ce..0e058eccb492207df056c5ce257054c2f3de488a 100644
--- a/src/metabase/models/task_history.clj
+++ b/src/metabase/models/task_history.clj
@@ -1,8 +1,10 @@
 (ns metabase.models.task-history
-  (:require [metabase.models.interface :as i]
+  (:require [clojure.tools.logging :as log]
+            [metabase.models.interface :as i]
             [metabase.util :as u]
             [metabase.util
              [date :as du]
+             [i18n :refer [trs]]
              [schema :as su]]
             [schema.core :as s]
             [toucan
@@ -20,9 +22,9 @@
   ;; the date that task finished, it deletes everything after that. As we continue to add TaskHistory entries, this
   ;; ensures we'll have a good amount of history for debugging/troubleshooting, but not grow too large and fill the
   ;; disk.
-  (when-let  [clean-before-date (db/select-one-field :ended_at TaskHistory {:limit    1
-                                                                            :offset   num-rows-to-keep
-                                                                            :order-by [[:ended_at :desc]]})]
+  (when-let [clean-before-date (db/select-one-field :ended_at TaskHistory {:limit    1
+                                                                           :offset   num-rows-to-keep
+                                                                           :order-by [[:ended_at :desc]]})]
     (db/simple-delete! TaskHistory :ended_at [:<= clean-before-date])))
 
 (u/strict-extend (class TaskHistory)
@@ -36,7 +38,7 @@
 
 (s/defn all
   "Return all TaskHistory entries, applying `limit` and `offset` if not nil"
-  [limit :- (s/maybe su/IntGreaterThanZero)
+  [limit  :- (s/maybe su/IntGreaterThanZero)
    offset :- (s/maybe su/IntGreaterThanOrEqualToZero)]
   (db/select TaskHistory (merge {:order-by [[:ended_at :desc]]}
                                 (when limit
@@ -58,11 +60,14 @@
 (defn- save-task-history! [start-time-ms info]
   (let [end-time-ms (System/currentTimeMillis)
         duration-ms (- end-time-ms start-time-ms)]
-    (db/insert! TaskHistory
-      (assoc info
-        :started_at (du/->Timestamp start-time-ms)
-        :ended_at   (du/->Timestamp end-time-ms)
-        :duration   duration-ms))))
+    (try
+      (db/insert! TaskHistory
+        (assoc info
+          :started_at (du/->Timestamp start-time-ms)
+          :ended_at   (du/->Timestamp end-time-ms)
+          :duration   duration-ms))
+      (catch Throwable e
+        (log/warn e (trs "Error saving task history"))))))
 
 (s/defn do-with-task-history
   "Impl for `with-task-history` macro; see documentation below."
@@ -85,6 +90,7 @@
   "Execute `body`, recording a TaskHistory entry when the task completes; if it failed to complete, records an entry
   containing information about the Exception. `info` should contain at least a name for the task (conventionally
   lisp-cased) as `:task`; see the `TaskHistoryInfo` schema in this namespace for other optional keys.
+
     (with-task-history {:task \"send-pulses\"}
       ...)"
   {:style/indent 1}
diff --git a/src/metabase/plugins.clj b/src/metabase/plugins.clj
index c1d0cd099511921cd15f04d45c731d99bf1f03ad..6ad8c4e30e2f864b3d423efcebeb748eedbcee78 100644
--- a/src/metabase/plugins.clj
+++ b/src/metabase/plugins.clj
@@ -54,7 +54,7 @@
   (when (io/resource "modules")
     (let [plugins-path (plugins-dir)]
       (files/with-open-path-to-resource [modules-path "modules"]
-        (files/copy-files-if-not-exists! modules-path plugins-path)))))
+        (files/copy-files! modules-path plugins-path)))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
diff --git a/src/metabase/plugins/files.clj b/src/metabase/plugins/files.clj
index 20e070519c1e163d17b188231eac47bf789ebc28..dace8e0a9e688c63dcd186946ed1054933aeba7c 100644
--- a/src/metabase/plugins/files.clj
+++ b/src/metabase/plugins/files.clj
@@ -7,14 +7,15 @@
   *file-manipulation* functions for the sorts of operations the plugin system needs to perform."
   (:require [clojure.java.io :as io]
             [clojure.string :as str]
+            [clojure.tools.logging :as log]
             [metabase.util :as u]
             [metabase.util
              [date :as du]
              [i18n :refer [trs]]])
   (:import java.io.FileNotFoundException
            java.net.URL
-           [java.nio.file CopyOption Files FileSystem FileSystems LinkOption OpenOption Path]
-           java.nio.file.attribute.FileAttribute
+           [java.nio.file CopyOption Files FileSystem FileSystems LinkOption OpenOption Path StandardCopyOption]
+           [java.nio.file.attribute FileAttribute FileTime]
            java.util.Collections))
 
 ;;; --------------------------------------------------- Path Utils ---------------------------------------------------
@@ -69,20 +70,25 @@
 
 ;;; ------------------------------------------------- Copying Stuff --------------------------------------------------
 
-(defn- copy! [^Path source, ^Path dest]
-  (du/profile (trs "Extract file {0} -> {1}" source dest)
-    (Files/copy source dest (u/varargs CopyOption))))
+(defn- last-modified-time ^FileTime [^Path path]
+  (Files/getLastModifiedTime path (u/varargs LinkOption)))
 
-(defn- copy-if-not-exists! [^Path source, ^Path dest]
-  (when-not (exists? dest)
-    (copy! source dest)))
+(defn- copy-file! [^Path source, ^Path dest]
+  (when (or (not (exists? dest))
+            (pos? (.compareTo (last-modified-time source) (last-modified-time dest))))
+    (du/profile (trs "Extract file {0} -> {1}" source dest)
+      (Files/copy source dest (u/varargs CopyOption [StandardCopyOption/REPLACE_EXISTING])))))
 
-(defn copy-files-if-not-exists!
-  "Copy all files in `source-dir` to `dest-dir`; skip files if a file of the same name already exists in `dest-dir`."
+(defn copy-files!
+  "Copy all files in `source-dir` to `dest-dir`. Overwrites existing files if last modified date is older than that of
+  the source file."
   [^Path source-dir, ^Path dest-dir]
   (doseq [^Path source (files-seq source-dir)
           :let [target (append-to-path dest-dir (str (.getFileName source)))]]
-    (copy-if-not-exists! source target)))
+    (try
+      (copy-file! source target)
+      (catch Throwable e
+        (log/error e (trs "Failed to copy file"))))))
 
 
 ;;; ------------------------------------------ Opening filesystems for URLs ------------------------------------------
diff --git a/src/metabase/pulse.clj b/src/metabase/pulse.clj
index 03972a4f517a6ba904437da73bc406e9a19b7114..1e4331c3ff70d8472ff76783cd3b0c5495981829 100644
--- a/src/metabase/pulse.clj
+++ b/src/metabase/pulse.clj
@@ -33,10 +33,11 @@
     (when-let [card (Card :id card-id, :archived false)]
       (let [{:keys [creator_id dataset_query]} card]
         {:card   card
-         :result (qp/process-query-and-save-with-max! dataset_query (merge {:executed-by creator_id,
-                                                                            :context     :pulse,
-                                                                            :card-id     card-id}
-                                                                           options))}))
+         :result (qp/process-query-and-save-with-max-results-constraints! dataset_query
+                   (merge {:executed-by creator_id,
+                           :context     :pulse,
+                           :card-id     card-id}
+                          options))}))
     (catch Throwable t
       (log/warn t (trs "Error running query for Card {0}" card-id)))))
 
diff --git a/src/metabase/query_processor.clj b/src/metabase/query_processor.clj
index fd64e52000b294e59ee98689760fe28332641ace..4794a3fbead20f13003a7fa24845a68537be8ba6 100644
--- a/src/metabase/query_processor.clj
+++ b/src/metabase/query_processor.clj
@@ -395,7 +395,7 @@
      {:max-results-bare-rows max-results})
    m))
 
-(s/defn process-query-and-save-with-max!
+(s/defn process-query-and-save-with-max-results-constraints!
   "Same as `process-query-and-save-execution!` but will include the default max rows returned as a constraint. (This
   function is ulitmately what powers most API endpoints that run queries, including `POST /api/dataset`.)"
   {:style/indent 1}
diff --git a/src/metabase/query_processor/async.clj b/src/metabase/query_processor/async.clj
index b829c6c3d7c29eb6d145b7d78139daab8c0dcb27..48ae943491025b9d24beec4979e573231807c856 100644
--- a/src/metabase/query_processor/async.clj
+++ b/src/metabase/query_processor/async.clj
@@ -70,12 +70,12 @@
   [query options]
   (do-async (:database query) qp/process-query-and-save-execution! query options))
 
-(defn process-query-and-save-with-max!
-  "Async version of `metabase.query-processor/process-query-and-save-with-max!`. Runs query asynchronously, and returns
-  a `core.async` channel that can be used to fetch the results once the query finishes running. Closing the channel
-  will cancel the query."
+(defn process-query-and-save-with-max-results-constraints!
+  "Async version of `metabase.query-processor/process-query-and-save-with-max-results-constraints!`. Runs query
+  asynchronously, and returns a `core.async` channel that can be used to fetch the results once the query finishes
+  running. Closing the channel will cancel the query."
   [query options]
-  (do-async (:database query) qp/process-query-and-save-with-max! query options))
+  (do-async (:database query) qp/process-query-and-save-with-max-results-constraints! query options))
 
 (defn process-query-without-save!
   "Async version of `metabase.query-processor/process-query-without-save!`. Runs query asynchronously, and returns a
diff --git a/src/metabase/server.clj b/src/metabase/server.clj
index 03dfdc75a0adc8f493d352279143f2e80682120b..71c19a5442f6465a6d87e7a46f7482b2b5d62636 100644
--- a/src/metabase/server.clj
+++ b/src/metabase/server.clj
@@ -56,9 +56,13 @@
     (.setHandler (#'ring-jetty/async-proxy-handler
                   handler
                   ;; if any API endpoint functions aren't at the very least returning a channel to fetch the results
-                  ;; later after 30 seconds we're in serious trouble. Kill the request.
+                  ;; later after 10 minutes we're in serious trouble. (Almost everything 'slow' should be returning a
+                  ;; channel before then, but some things like CSV downloads don't currently return channels at this
+                  ;; time)
+                  ;;
+                  ;; TODO - I suppose the default value should be moved to the `metabase.config` namespace?
                   (or (config/config-int :mb-jetty-async-response-timeout)
-                      (* 30 1000))))))
+                      (* 10 60 1000))))))
 
 (defn start-web-server!
   "Start the embedded Jetty web server. Returns `:started` if a new server was started; `nil` if there was already a
diff --git a/src/metabase/sync/util.clj b/src/metabase/sync/util.clj
index 89260d16608eb99ff5664a49b2b3cfa63b13f27c..12a561fe521e0c2c622684d4e0cb8171480179c5 100644
--- a/src/metabase/sync/util.clj
+++ b/src/metabase/sync/util.clj
@@ -411,23 +411,26 @@
   [task-name :- su/NonBlankString
    database  :- i/DatabaseInstance
    {:keys [start-time end-time]} :- SyncOperationOrStepRunMetadata]
-  {:task task-name
-   :db_id (u/get-id database)
+  {:task       task-name
+   :db_id      (u/get-id database)
    :started_at (du/->Timestamp start-time)
-   :ended_at (du/->Timestamp end-time)
-   :duration (du/calculate-duration start-time end-time)})
+   :ended_at   (du/->Timestamp end-time)
+   :duration   (du/calculate-duration start-time end-time)})
 
 (s/defn ^:private store-sync-summary!
   [operation :- s/Str
    database  :- i/DatabaseInstance
    {:keys [steps] :as sync-md} :- SyncOperationMetadata]
-  (db/insert-many! TaskHistory
-    (cons (create-task-history operation database sync-md)
-          (for [[step-name step-info] steps
-                :let [task-details (dissoc step-info :start-time :end-time :log-summary-fn)]]
-            (assoc (create-task-history step-name database step-info)
-              :task_details (when (seq task-details)
-                              task-details))))))
+  (try
+    (db/insert-many! TaskHistory
+      (cons (create-task-history operation database sync-md)
+            (for [[step-name step-info] steps
+                  :let                  [task-details (dissoc step-info :start-time :end-time :log-summary-fn)]]
+              (assoc (create-task-history step-name database step-info)
+                :task_details (when (seq task-details)
+                                task-details)))))
+    (catch Throwable e
+      (log/warn e (trs "Error saving task history")))))
 
 (s/defn run-sync-operation
   "Run `sync-steps` and log a summary message"
diff --git a/src/metabase/task.clj b/src/metabase/task.clj
index 485cad0e734e695f9d4d38e05b4e89f6362c8db6..0bf425489f5cd0e767f02624d81393f4f1065c4c 100644
--- a/src/metabase/task.clj
+++ b/src/metabase/task.clj
@@ -53,9 +53,11 @@
   {:arglists '([job-name-string])}
   keyword)
 
-(defn- find-and-load-tasks!
+(defn- find-and-load-task-namespaces!
   "Search Classpath for namespaces that start with `metabase.tasks.`, then `require` them so initialization can happen."
   []
+  ;; make sure current thread is using canonical MB classloader
+  (classloader/the-classloader)
   ;; first, load all the task namespaces
   (doseq [ns-symb @u/metabase-namespace-symbols
           :when   (.startsWith (name ns-symb) "metabase.task.")]
@@ -63,8 +65,11 @@
       (log/debug (trs "Loading tasks namespace:") (u/format-color 'blue ns-symb))
       (require ns-symb)
       (catch Throwable e
-        (log/error e (trs "Error loading tasks namespace {0}" ns-symb)))))
-  ;; next, call all implementations of `init!`
+        (log/error e (trs "Error loading tasks namespace {0}" ns-symb))))))
+
+(defn- init-tasks!
+  "Call all implementations of `init!`"
+  []
   (doseq [[k f] (methods init!)]
     (try
       ;; don't bother logging namespace for now, maybe in the future if there's tasks of the same name in multiple
@@ -97,33 +102,8 @@
 ;;; |                                       Quartz Scheduler Class Load Helper                                       |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-;; Custom `ClassLoadHelper` implementation that makes sure to require the namespaces that tasks live in (to make sure
-;; record types are loaded) and that uses our canonical ClassLoader.
-
-(defn- task-class-name->namespace-str
-  "Determine the namespace we need to load for one of our tasks.
-
-    (task-class-name->namespace-str \"metabase.task.upgrade_checks.CheckForNewVersions\")
-    ;; -> \"metabase.task.upgrade-checks\""
-  [class-name]
-  (-> class-name
-      (str/replace \_ \-)
-      (str/replace #"\.\w+$" "")))
-
-(defn- require-task-namespace
-  "Since Metabase tasks are defined in Clojure-land we need to make sure we `require` the namespaces where they are
-  defined before we try to load the task classes."
-  [class-name]
-  ;; call `the-classloader` to force side-effects of making it the current thread context classloader
-  (classloader/the-classloader)
-  ;; only try to `require` metabase.task classes; don't do this for other stuff that gets shuffled thru here like
-  ;; Quartz classes
-  (when (str/starts-with? class-name "metabase.task.")
-    (require (symbol (task-class-name->namespace-str class-name)))))
-
 (defn- load-class ^Class [^String class-name]
-  (require-task-namespace class-name)
-  (.loadClass (classloader/the-classloader) class-name))
+  (Class/forName class-name true (classloader/the-classloader)))
 
 (defrecord ^:private ClassLoadHelper []
   org.quartz.spi.ClassLoadHelper
@@ -158,8 +138,9 @@
     (set-jdbc-backend-properties!)
     (let [new-scheduler (qs/initialize)]
       (when (compare-and-set! quartz-scheduler nil new-scheduler)
+        (find-and-load-task-namespaces!)
         (qs/start new-scheduler)
-        (find-and-load-tasks!)))))
+        (init-tasks!)))))
 
 (defn stop-scheduler!
   "Stop our Quartzite scheduler and shutdown any running executions."
diff --git a/src/metabase/task/task_history_cleanup.clj b/src/metabase/task/task_history_cleanup.clj
index 45a5f6dfb77041185b4ce432e3de60d1e7993927..53762c570292f0fa84e5bf76a537e69182b52205 100644
--- a/src/metabase/task/task_history_cleanup.clj
+++ b/src/metabase/task/task_history_cleanup.clj
@@ -9,7 +9,7 @@
             [metabase.util.i18n :refer [trs]]))
 
 (def ^:private history-rows-to-keep
-  "Maximum number of TaskHistory rows. This is not a `const` so that we can redef it in tests"
+  "Maximum number of TaskHistory rows."
   100000)
 
 (defn- task-history-cleanup! []
diff --git a/test/metabase/async/api_response_test.clj b/test/metabase/async/api_response_test.clj
index fa4415fcf9adcff3d5247765a5d80daae377d370..d62b3228ecdcb28f4790ae33417c5d7dbb650373 100644
--- a/test/metabase/async/api_response_test.clj
+++ b/test/metabase/async/api_response_test.clj
@@ -1,7 +1,12 @@
 (ns metabase.async.api-response-test
   (:require [cheshire.core :as json]
+            [clj-http.client :as client]
             [clojure.core.async :as a]
+            [compojure.core :as compojure]
             [expectations :refer [expect]]
+            [metabase
+             [server :as server]
+             [util :as u]]
             [metabase.async.api-response :as async-response]
             [metabase.test.util.async :as tu.async]
             [ring.core.protocols :as ring.protocols])
@@ -13,7 +18,7 @@
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
-;;; |                                                   New Tests                                                    |
+;;; |                                 Tests to make sure channels do the right thing                                 |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (defn- do-with-response [input-chan f]
@@ -25,8 +30,16 @@
                          (a/close! os-closed-chan)
                          (let [^Closeable this this]
                            (proxy-super close))))]
-        (let [{output-chan :body, :as response} (#'async-response/async-keepalive-response input-chan)]
-          (ring.protocols/write-body-to-stream output-chan response os)
+        ;; normally `write-body-to-stream` will create the `output-chan`, however we want to do it ourselves so we can
+        ;; truly enjoy the magical output channel slash see when it gets closed. Create it now...
+        (let [output-chan (#'async-response/async-keepalive-channel input-chan)
+              response    {:status       200
+                           :headers      {}
+                           :body         input-chan
+                           :content-type "applicaton/json; charset=utf-8"}]
+          ;; and keep it from getting [re]created.
+          (with-redefs [async-response/async-keepalive-channel identity]
+            (ring.protocols/write-body-to-stream output-chan response os))
           (try
             (f {:os os, :output-chan output-chan, :os-closed-chan os-closed-chan})
             (finally
@@ -198,3 +211,59 @@
       (with-response [{:keys [output-chan os-closed-chan]} input-chan]
         (wait-for-close os-closed-chan)
         (wait-for-close output-chan)))))
+
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                            Tests to make sure keepalive bytes actually get written                             |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(defn- do-with-temp-server [handler f]
+  (let [port   (+ 60000 (rand-int 5000))
+        server (server/create-server handler {:port port})]
+    (try
+      (.start server)
+      (f port)
+      (finally
+        (.stop server)))))
+
+(defmacro ^:private with-temp-server
+  "Spin up a Jetty server with `handler` with a random port between 60000 and 65000; bind the random port to `port`, and
+  execute body. Shuts down server when finished."
+  [[port-binding handler] & body]
+  `(do-with-temp-server ~handler (fn [~port-binding] ~@body)))
+
+(defn- num-keepalive-chars-in-response
+  "Make a request to `handler` and count the number of newline keepalive chars in the response."
+  [handler]
+  (with-redefs [async-response/keepalive-interval-ms 50]
+    (with-temp-server [port handler]
+      (let [{response :body} (client/get (format "http://localhost:%d/" port))]
+        (count (re-seq #"\n" response))))))
+
+(defn- output-chan-with-delayed-result
+  "Returns an output channel that receives a 'DONE' value after 400ms. "
+  []
+  (u/prog1 (a/chan 1)
+    (a/go
+      (a/<! (a/timeout 400))
+      (a/>! <> "DONE"))))
+
+;; confirm that some newlines were written as part of the response for an async API response
+(defn- async-handler [_ respond _]
+  (respond {:status 200, :headers {"Content-Type" "text/plain"}, :body (output-chan-with-delayed-result)}))
+
+(expect pos? (num-keepalive-chars-in-response async-handler))
+
+;; make sure newlines are written for sync-style compojure endpoints (e.g. `defendpoint`)
+(def ^:private compojure-sync-handler
+  (compojure/routes
+   (compojure/GET "/" [_] (output-chan-with-delayed-result))))
+
+(expect pos? (num-keepalive-chars-in-response compojure-sync-handler))
+
+;; ...and for true async compojure endpoints (e.g. `defendpoint-async`)
+(def ^:private compojure-async-handler
+  (compojure/routes
+   (compojure/GET "/" [] (fn [_ respond _] (respond (output-chan-with-delayed-result))))))
+
+(expect pos? (num-keepalive-chars-in-response compojure-async-handler))
diff --git a/test/metabase/query_processor_test/constraints_test.clj b/test/metabase/query_processor_test/constraints_test.clj
index 02978fc1c17af92a3c4d9ab41dda943fe1e90a85..a25d8922a3a85f0b98242c0efe508a6f2369495b 100644
--- a/test/metabase/query_processor_test/constraints_test.clj
+++ b/test/metabase/query_processor_test/constraints_test.clj
@@ -29,8 +29,8 @@
        :native      (native-query)
        :constraints {:max-results 5}})))
 
-;; does it also work when running via `process-query-and-save-with-max!`, the function that powers endpoints like
-;; `POST /api/dataset`?
+;; does it also work when running via `process-query-and-save-with-max-results-constraints!`, the function that powers
+;; endpoints like `POST /api/dataset`?
 (qp.test/expect-with-non-timeseries-dbs
   [["Red Medicine"]
    ["Stout Burgers & Beers"]
@@ -38,7 +38,7 @@
    ["Wurstküche"]
    ["Brite Spot Family Restaurant"]]
   (qp.test/rows
-    (qp/process-query-and-save-with-max!
+    (qp/process-query-and-save-with-max-results-constraints!
         {:database    (data/id)
          :type        :native
          :native      (native-query)
diff --git a/test/metabase/task_test.clj b/test/metabase/task_test.clj
deleted file mode 100644
index e7f97fd0486c6bc1b9095ec5d930c3b71024eed9..0000000000000000000000000000000000000000
--- a/test/metabase/task_test.clj
+++ /dev/null
@@ -1,7 +0,0 @@
-(ns metabase.task-test
-  (:require [expectations :refer [expect]]
-            [metabase.task :as task]))
-
-(expect
-  "metabase.task.upgrade-checks"
-  (#'task/task-class-name->namespace-str "metabase.task.upgrade_checks.CheckForNewVersions"))