diff --git a/README.md b/README.md
index d7e0b74..a6d6be5 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,8 @@ A Clojure library designed to manipulate sparse *arrays* - multi-dimensional spa
 
 Arbitrary numbers of dimensions are supported, up to limits imposed by the JVM stack.
 
+[![Clojars Project](https://img.shields.io/clojars/v/sparse-array.svg)](https://clojars.org/sparse-array)
+
 ## Conventions:
 
 ### Sparse arrays
@@ -32,7 +34,26 @@ Thus an array with a single value 'hello' at coordinates x = 3, y = 4, z = 5 wou
   }
 ```
 
-At the present stage of development, where the expectations of an operation are violated, `nil` is returned and no exception is thrown. However, it's probable that later there will be at least the option of thowing specific exceptions, as otherwise debugging could be tricky.
+### Errors and error-reporting
+
+A dynamic variable, `*safe-sparse-operations*`, is provided to handle behaviour in error conditions. If this is `false`, bad data will generally not cause an exception to be thrown, and corrupt structures may be returned, thus:
+
+```clojure
+(put (make-sparse-array :x :y :z) "hello" 3) ;; insufficient coordinates specified
+
+=> {:dimensions 3, :coord :x, :content (:y :z), 3 {:dimensions 2, :coord :y, :content (:z), nil {:dimensions 1, :coord :z, :content :data, nil nil}}}
+```
+
+However, if `*safe-sparse-operations*` is bound to `true`, exceptions will be thrown instead:
+
+```clojure
+(binding [*safe-sparse-operations* true]
+  (put (make-sparse-array :x :y :z) "hello" 3))
+
+ExceptionInfo Expected 3 coordinates; found 1  clojure.core/ex-info (core.clj:4617)
+```
+
+Sanity checking data is potentially expensive; for this reason `*safe-sparse-operations*` defaults to `false`, but you make wish to bind it to `true` especially while debugging.
 
 ### Dense arrays
 
diff --git a/project.clj b/project.clj
index d9c47b7..10fba9b 100644
--- a/project.clj
+++ b/project.clj
@@ -1,6 +1,27 @@
-(defproject sparse-array "0.1.0-SNAPSHOT"
+(defproject sparse-array "0.1.0"
   :description "A Clojure library designed to manipulate sparse *arrays* - multi-dimensional spaces accessed by indices, but containing arbitrary values rather than just numbers. For sparse spaces which contain numbers only, you're better to use a *sparse matrix* library, for example [clojure.core.matrix](https://mikera.github.io/core.matrix/)."
   :url "http://example.com/FIXME"
   :license {:name "Eclipse Public License"
             :url "http://www.eclipse.org/legal/epl-v10.html"}
-  :dependencies [[org.clojure/clojure "1.8.0"]])
+  :dependencies [[org.clojure/clojure "1.8.0"]]
+
+  :plugins [[lein-codox "0.10.4"]
+            [lein-release "1.0.5"]]
+
+
+  ;; `lein release` doesn't play nice with `git flow release`. Run `lein release` in the
+  ;; `develop` branch, then merge the release tag into the `master` branch.
+
+  :release-tasks [["vcs" "assert-committed"]
+                  ["clean"]
+                  ["test"]
+                  ["codox"]
+                  ["change" "version" "leiningen.release/bump-version" "release"]
+                  ["vcs" "commit"]
+                  ;; ["vcs" "tag"] -- not working, problems with secret key
+                  ["uberjar"]
+                  ["install"]
+                  ["deploy" "clojars"]
+                  ["change" "version" "leiningen.release/bump-version"]
+                  ["vcs" "commit"]])
+
diff --git a/src/sparse_array/core.clj b/src/sparse_array/core.clj
index b169df8..c984580 100644
--- a/src/sparse_array/core.clj
+++ b/src/sparse_array/core.clj
@@ -1,5 +1,18 @@
 (ns sparse-array.core)
 
+(declare put get)
+
+(def ^:dynamic *safe-sparse-operations*
+  "Whether spase array operations should be conducted safely, with careful
+  checking of data conventions and exceptions thrown if expectations are not
+  met. Normally `false`."
+  false)
+
+(defn- unsafe-sparse-operations?
+  "returns `true` if `*safe-sparse-operations*` is `false`, and vice versa."
+  []
+  (not (true? *safe-sparse-operations*)))
+
 (defn make-sparse-array
   "Make a sparse array with these `dimensions`. Every member of `dimensions`
   must be a keyword; otherwise, `nil` will be returned."
@@ -14,26 +27,63 @@
                 :data
                 (rest dimensions))}))
 
+(defn- safe-test-or-throw
+  "If `v` is truthy or `*safe-sparse-operations*` is false, return `v`;
+  otherwise, throw an `ExceptionInfo` with this `message` and the map `m`."
+  [v message m]
+  (if-not
+    v
+    (if
+      *safe-sparse-operations*
+      (throw (ex-info message m))
+      v)
+    v))
+
 (defn sparse-array?
   "`true` if `x` is a sparse array conforming to the conventions established
   by this library, else `false`."
   ([x]
-    (and
-      (map? x)
-      (pos? (:dimensions x))
-      (keyword? (:coord x))
-      (if
-        (coll? (:content x))
-        (every?
-          sparse-array?
-          (map #(x %) (filter integer? (keys x))))
-        (= (:content x) :data)))))
+   (apply
+     sparse-array?
+     (cons
+       x
+       (cons
+         (:coord x)
+         (if
+           (coll? (:content x))
+           (:content x))))))
+  ([x & axes]
+   (and
+     (safe-test-or-throw
+       (map? x)
+       "Array must be a map" {:array x})
+     (safe-test-or-throw
+       (and (integer? (:dimensions x)) (pos? (:dimensions x)))
+       (str "The value of `:dimensions` must be a positive integer, not " (:dimensions x))
+       {:array x})
+     (safe-test-or-throw
+       (keyword? (:coord x))
+       (str "The value of `:coord` must be a keyword, not " (:coord x))
+       {:array x})
+     (safe-test-or-throw
+       (= (:coord x) (first axes))
+       (str "The value of `:coord` must be " (first axes) ", not " (:coord x))
+       {:array x})
+     (if
+       (empty? (rest axes))
+       (safe-test-or-throw
+         (= (:content x) :data)
+         "If there are no further axes the value of `:content` must be `:data`"
+         {:array x})
+       (and
+         (= (:content x) (rest axes))
+         (every?
+           sparse-array?
+           (map #(x %) (filter integer? (keys x)))))))))
 
-(defn put
-  "Return a sparse array like this `array` but with this `value` at these
-  `coordinates`. Returns `nil` if any coordinate is invalid."
-  [array value & coordinates]
-  (if
+(defn- unsafe-put
+  [array value coordinates]
+  (cond
     (every?
       #(and (integer? %) (or (zero? %) (pos? %)))
       coordinates)
@@ -51,14 +101,75 @@
               (apply make-sparse-array (:content array)))
             (cons value (rest coordinates))))))))
 
+(defn put
+  "Return a sparse array like this `array` but with this `value` at these
+  `coordinates`. Returns `nil` if any coordinate is invalid."
+  [array value & coordinates]
+  (cond
+    (nil? value)
+    nil
+    (unsafe-sparse-operations?)
+    (unsafe-put array value coordinates)
+    (not (sparse-array? array))
+    (throw (ex-info "Sparse array expected" {:array array}))
+    (not= (:dimensions array) (count coordinates))
+    (throw
+      (ex-info
+        (str "Expected " (:dimensions array) " coordinates; found " (count coordinates))
+        {:array array
+         :coordinates coordinates}))
+    (not
+      (every?
+        #(and (integer? %) (or (zero? %) (pos? %)))
+        coordinates))
+    (throw
+      (ex-info
+        "Coordinates must be zero or positive integers"
+        {:array array
+         :coordinates coordinates
+         :invalid (remove #(and (pos? %) (integer? %)) coordinates)}))
+    :else
+    (unsafe-put array value coordinates)
+    value
+    *safe-sparse-operations*))
+
+(defn- unsafe-get
+  ;; TODO: I am CERTAIN there is a more elegant solution to this.
+  [array coordinates]
+  (let [v (array (first coordinates))]
+    (cond
+      (= :data (:content array))
+      v
+      (nil? v)
+      nil
+      :else
+      (apply get (cons v (rest coordinates))))))
+
 (defn get
   "Return the value in this sparse `array` at these `coordinates`."
-  ;; TODO: I am CERTAIN there is a more elegant solution to this.
   [array & coordinates]
-  (if
-    (= :data (:content array))
-    (array (first coordinates))
-    (apply get (cons (array (first coordinates)) (rest coordinates)))))
+  (cond
+    (unsafe-sparse-operations?)
+    (unsafe-get array coordinates)
+    (not (sparse-array? array))
+    (throw (ex-info "Sparse array expected" {:array array}))
+    (not (every?
+           #(and (integer? %) (or (zero? %) (pos? %)))
+           coordinates))
+    (throw
+      (ex-info
+        "Coordinates must be zero or positive integers"
+        {:array array
+         :coordinates coordinates
+         :invalid (remove #(and (pos? %) (integer? %)) coordinates)}))
+    (not (= (:dimensions array) (count coordinates)))
+    (throw
+      (ex-info
+        (str "Expected " (:dimensions array) " coordinates; found " (count coordinates))
+        {:array array
+         :coordinates coordinates}))
+    :else
+    (unsafe-get array coordinates)))
 
 (defn merge-sparse-arrays
   "Return a sparse array taking values from sparse arrays `a1` and `a2`,
@@ -74,7 +185,7 @@
     nil
     (= :data (:content a1))
     (merge a1 a2)
-    :else
+    (or (unsafe-sparse-operations?) (and (sparse-array? a1) (sparse-array? a2)))
     (reduce
       merge
       a2
diff --git a/test/sparse_array/core_test.clj b/test/sparse_array/core_test.clj
index 815618f..b06f2ad 100644
--- a/test/sparse_array/core_test.clj
+++ b/test/sparse_array/core_test.clj
@@ -4,7 +4,94 @@
 
 (deftest creation-and-testing
   (testing "Creation and testing."
-    (is (sparse-array? (make-sparse-array :x :y :z)))))
+    (is (sparse-array? (make-sparse-array :x :y :z)))
+    (is (sparse-array? {:dimensions 2,
+                        :coord :x,
+                        :content '(:y),
+                        3 {:dimensions 1,
+                           :coord :y,
+                           :content :data,
+                           4 "hello"},
+                        4 {:dimensions 1,
+                           :coord :y,
+                           :content :data,
+                           3 "goodbye"}}))
+    (is (= (sparse-array? []) false))
+    (is (= (sparse-array? "hello") false))
+    (is (=
+          (sparse-array?
+            (dissoc (make-sparse-array :x :y :z) :dimensions))
+          false)
+        "All mandatory keywords must be present: dimensions")
+    (is (=
+          (sparse-array?
+            (dissoc (make-sparse-array :x :y :z) :coord))
+          false)
+        "All mandatory keywords must be present: coord")
+    (is (=
+          (sparse-array?
+            (dissoc (make-sparse-array :x :y :z) :content))
+          false)
+        "All mandatory keywords must be present: content")
+    (is (=
+          (sparse-array? {:dimensions 2,
+                          :coord :x,
+                          :content '(:y),
+                          3 {:dimensions 1,
+                             :coord :y,
+                             :content :data,
+                             4 "hello"},
+                          4 {:dimensions 1,
+                             :coord :y,
+                             :content :data,
+                             3 "goodbye"}
+                          5 :foo})
+          false)
+          "Can't have data in a non-data layer")
+    ))
+
+(deftest testing-safe
+  (testing "Checking that correct exceptions are thrown when `*safe-sparse-operations*` is true"
+    (binding [*safe-sparse-operations* true]
+      (is
+        (thrown-with-msg?
+            clojure.lang.ExceptionInfo
+            #"Array must be a map"
+            (sparse-array? [])))
+      (is
+        (thrown-with-msg?
+          clojure.lang.ExceptionInfo
+          #"The value of `:dimensions` must be a positive integer, not .*"
+          (sparse-array?
+            (dissoc (make-sparse-array :x :y :z) :dimensions)))
+        "All mandatory keywords must be present: dimensions")
+    (is (thrown-with-msg?
+          clojure.lang.ExceptionInfo
+          #"The value of `:coord` must be a keyword, not .*"
+          (sparse-array?
+            (dissoc (make-sparse-array :x :y :z) :coord))))
+    (is (thrown-with-msg?
+          clojure.lang.ExceptionInfo
+          #"If there are no further axes the value of `:content` must be `:data`"
+          (sparse-array?
+            (dissoc (make-sparse-array :x :y :z) :content)))
+        "All mandatory keywords must be present: content")
+    (is (thrown-with-msg?
+            clojure.lang.ExceptionInfo
+            #"Array must be a map"
+          (sparse-array? {:dimensions 2,
+                          :coord :x,
+                          :content '(:y),
+                          3 {:dimensions 1,
+                             :coord :y,
+                             :content :data,
+                             4 "hello"},
+                          4 {:dimensions 1,
+                             :coord :y,
+                             :content :data,
+                             3 "goodbye"}
+                          5 :foo}))
+          "Can't have data in a non-data layer"))))
 
 (deftest put-and-get
   (testing "get"
@@ -14,6 +101,13 @@
                  :content '(:y)
                  3 {:dimensions 1 :coord :y :content :data 4 "hello"}}
           actual (get array 3 4)]
+      (is (= actual expected)))
+    (let [expected nil
+          array {:dimensions 2,
+                 :coord :x,
+                 :content '(:y)
+                 3 {:dimensions 1 :coord :y :content :data 4 "hello"}}
+          actual (get array 4 3)]
       (is (= actual expected))))
   (testing "put"
     (let [expected "hello"
@@ -26,12 +120,24 @@
     (let
       [expected "hello"
        actual (get (put (make-sparse-array :x) expected 3) 3)]
-      (is (= actual expected))))
+      (is (= actual expected)))
+    (binding [*safe-sparse-operations* true]
+      ;; enabling error handling shouldn't make any difference
+      (let
+        [expected "hello"
+         actual (get (put (make-sparse-array :x) expected 3) 3)]
+        (is (= actual expected)))))
   (testing "round trip, two dimensions"
     (let
       [expected "hello"
        actual (get (put (make-sparse-array :x :y) expected 3 4) 3 4)]
-      (is (= actual expected))))
+      (is (= actual expected)))
+    (binding [*safe-sparse-operations* true]
+      ;; enabling error handling shouldn't make any difference
+    (let
+      [expected "hello"
+       actual (get (put (make-sparse-array :x :y) expected 3 4) 3 4)]
+      (is (= actual expected)))))
     (testing "round trip, three dimensions"
     (let
       [expected "hello"
@@ -41,7 +147,29 @@
     (let
       [expected "hello"
        actual (get (put (make-sparse-array :p :q :r :s) expected 3 4 5 6) 3 4 5 6)]
-      (is (= actual expected)))))
+      (is (= actual expected))))
+  (testing "Error handling, number of dimensions"
+    (binding [*safe-sparse-operations* true]
+      (is
+        (thrown-with-msg?
+          clojure.lang.ExceptionInfo
+          #"Expected 3 coordinates; found 2"
+          (put (make-sparse-array :x :y :z) "hello" 3 4)))
+      (is
+        (thrown-with-msg?
+          clojure.lang.ExceptionInfo
+          #"Expected 3 coordinates; found 4"
+          (put (make-sparse-array :x :y :z) "hello" 3 4 5 6)))
+      (is
+        (thrown-with-msg?
+          clojure.lang.ExceptionInfo
+          #"Expected 3 coordinates; found 2"
+          (get (make-sparse-array :x :y :z) 3 4)))
+      (is
+        (thrown-with-msg?
+          clojure.lang.ExceptionInfo
+          #"Expected 3 coordinates; found 4"
+          (get (make-sparse-array :x :y :z) 3 4 5 6))))))
 
 (deftest merge-test
   (testing "merge, one dimension"
@@ -84,9 +212,17 @@
                     [nil nil nil nil nil]
                     [nil nil nil nil "hello"]
                     [nil nil nil "goodbye" nil]]
-          actual (sparse-to-dense (put
-                                    (put
-                                      (make-sparse-array :x :y)
-                                      "hello" 3 4)
-                                    "goodbye" 4 3))]
+          actual (sparse-to-dense {:dimensions 2,
+                                   :coord :x,
+                                   :content '(:y),
+                                   3 {:dimensions 1,
+                                      :coord :y,
+                                      :content :data,
+                                      4 "hello"},
+                                   4 {:dimensions 1,
+                                      :coord :y,
+                                      :content :data,
+                                      3 "goodbye"}})]
       (is (= actual expected)))))
+
+